import type { RecursivePartial } from '../interfaces'; import type { IOptions, ISetOptions, IDelOptions } from './jsonCache.types'; interface IJSONCache { set(key: string, obj: T, options: ISetOptions): Promise; get(key: string): Promise; get(key: string, ...fields: string[]): Promise | undefined>; rewrite(key: string, obj: T, options?: ISetOptions): Promise; clearAll(): Promise; del(key: string, options?: IDelOptions): Promise; incr(key: string, obj: RecursivePartial, options?: IDelOptions): Promise; } /** * JSONCache eases the difficulties in storing a JSON in redis. * * It stores the JSON in hashset for simpler get and set of required * fields. It also allows you to override/set specific fields in * the JSON without rewriting the whole JSON tree. Which means that it * is literally possible to `Object.deepAssign()`. * * Everytime you store an object, JSONCache would store two hashset * in Redis, one for data and the other for type information. This helps * during retrieval of data, to restore the type of data which was originally * provided. All these workaround are needed because Redis DOES NOT support * any other data type apart from String. * * Well the easiest way is to store an object in Redis is * JSON.stringify(obj) and store the stringified result. * But this can cause issue when the obj is * too huge or when you would want to retrieve only specific fields * from the JSON but do not want to parse the whole JSON. * Also note that this method would end up in returing all the * fields as strings and you would have no clue to identify the type of * field. */ export default class JSONCache implements IJSONCache { private options; private redisClientInt; private flattener; /** * Intializes JSONCache instance * @param redisClient RedisClient instance(Preferred ioredis - cient). * It supports any redisClient instance that has * `'hmset' | 'hmget' | 'hgetall' | 'expire' | 'del' | 'keys'` * methods implemented * @param options Options for controlling the prefix */ constructor(redisClient: any, options?: IOptions); /** * Flattens the given json object and * stores it in Redis hashset * * @param key Redis key * @param obj JSON object to be stored * @param options */ set(key: string, obj: T, options?: ISetOptions): Promise; /** * Retrieves the hashset from redis and * unflattens it back to the original Object * * @param key Redis key * @param fields List of fields to be retreived from redis. * This helps reduce network latency incase only a few fields are * needed. * * @returns request object from the cache */ get(key: string): Promise; get(key: string, ...fields: string[]): Promise | undefined>; /** * Replace the entire hashset for the given key * * @param key Redis key * @param obj JSON Object of type T */ rewrite(key: string, obj: T, options?: ISetOptions): Promise; /** * Removes/deletes all the keys in the JSON Cache, * having the prefix. */ clearAll(): Promise; /** * Removes the given key from Redis * * Please use this method instead of * directly using `redis.del` as this method * ensures that even the corresponding type info * is removed. It also ensures that prefix is * added to key, ensuring no other key is * removed unintentionally * * @param key Redis key */ del(key: string, options?: IDelOptions): Promise; /** * Increments the value of a variable in the JSON * Note: You can increment multiple variables in the * same command (Internally it will split it into multiple * commands on the RedisDB) * * @example * ```JS * await jsonCache.incr(key, {messages: 10, profile: {age: 1}}) * ``` * * @param key Redis Cache key * @param obj Partial object specifying the path to the required * variable along with value */ incr(key: string, obj: RecursivePartial, options?: IDelOptions): Promise; /****************** * PRIVATE METHODS ******************/ private getKeysToBeRemoved; /** * Returns the redis storage key for storing data * by prefixing custom string, such that it * doesn't collide with other keys in usage * * @param key Storage key */ private getKey; /** * Returns the redis storage key for storing * corresponding types by prefixing custom string, * such that it doesn't collide with other keys * in usage * * @param key Storage key */ private getTypeKey; /** * Will add Set commands to the given array * This logic was separated to remove code duplication * in set & rewrite methods * * @param key Storage key * @param flattened Flattened object containing data & typeInfo * @param commands List of commands to which set commands has to be appended * @param expire Redis Key expiry */ private addSetCommands; private execTransactionCommands; private execCommand; } export {};