{"mappings":"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","sources":["src/index.ts","src/redisCache.ts","src/luaScripts.ts"],"sourcesContent":["export { RedisCache } from \"./redisCache\";\nexport type { RedisOptions } from \"./types/redisCache\";\n","import type { SortKeyCache, PruneStats, BatchDBOp, CacheOptions } from \"warp-contracts\";\nimport type { ChainableCommander } from \"ioredis\";\nimport { CacheKey, genesisSortKey, LoggerFactory, SortKeyCacheResult, lastPossibleSortKey } from \"warp-contracts\";\nimport { Redis } from \"ioredis\";\nimport { stringify } from \"safe-stable-stringify\";\nimport { luaScripts } from \"./luaScripts\";\nimport type { RedisOptions } from \"./types/redisCache\";\n\ninterface SortKeyCacheRangeOptions {\n  gte?: string;\n  lt?: string;\n  reverse?: boolean | undefined;\n  limit?: number | undefined;\n}\n\n/**\n * A deleted value placeholder is to differentiate a `null` result\n * that may belong to a deleted key or a non-existent key.\n * This is required due to SortKeyCache logic.\n */\nconst DELETED_VALUE_PLACEHOLDER = \"\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class RedisCache<V = any> implements SortKeyCache<V> {\n  /** Warp Logger */\n  private readonly logger = LoggerFactory.INST.create(\"RedisCache\");\n  /** Sub-level separator, usually `|` */\n  private readonly sls: string;\n  /** Prefix of all keys written by this cache */\n  private readonly prefix: string;\n  /**\n   * Maximum number of `key|sortKey` pairs for each key to be kept. If count goes beyond this\n   * number, older `key|sortKey`'s will be deleted.\n   */\n  maxEntriesPerContract: number;\n  /**\n   * Minimum number of `key|sortKey` pairs for each key to be kept upon pruning.\n   */\n  minEntriesPerContract: number;\n  /**\n   * If `true`, then it means `this.client` has been created outside, and this will further\n   * disable `open` and `close` functions. As such, `this.isOpen` will also not be touched.\n   */\n  isManaged: boolean;\n  /** Underlying Redis client (from `ioredis`) */\n  client: Redis;\n  /**\n   * A transaction object, returned by `MULTI`.\n   * @see {@link asAtomic}\n   */\n  transaction: ChainableCommander | null = null;\n\n  constructor(cacheOptions: CacheOptions, redisOptions: RedisOptions) {\n    // create client\n    if (redisOptions.client) {\n      // client is managed from outside\n      this.client = redisOptions.client;\n      this.isManaged = true;\n    } else if (redisOptions.url) {\n      // client is managed from inside\n      this.client = new Redis(redisOptions.url, {\n        lazyConnect: true, // disables auto-connect on client instantiation\n      });\n      this.isManaged = false;\n    } else {\n      throw new Error(\"You must provide either connection info or a client.\");\n    }\n\n    // cache options\n    this.prefix = cacheOptions.dbLocation;\n    this.sls = cacheOptions.subLevelSeparator || \"|\";\n\n    // redis specific options\n    this.maxEntriesPerContract = redisOptions.maxEntriesPerContract || 10;\n    this.minEntriesPerContract = redisOptions.minEntriesPerContract || 10;\n    if (this.minEntriesPerContract > this.maxEntriesPerContract) {\n      throw new Error(\"minEntries > maxEntries\");\n    }\n\n    // define client-side lua scripts\n    this.logger.debug(\"Defining Lua scripts.\");\n    Object.entries(luaScripts).forEach(([name, definition]) => this.client.defineCommand(name, definition));\n\n    if (this.isManaged) {\n      this.logger.warn(\"Client is managed by user, skipping configurations.\");\n    } else {\n      // configure no-persistance\n      if (cacheOptions.inMemory) {\n        this.open().then(() => {\n          this.logger.info(\"Configuring the redis for no-persistance mode.\");\n          RedisCache.setConfigForInMemory(this.client).then(() => this.logger.info(\"Configurations done.\"));\n        });\n      }\n    }\n  }\n\n  /**\n   * Updates the Redis client configs with respect to `inMemory: true` cache option.\n   * This is done by the following:\n   *\n   * - `SET appendonly no`\n   * - `SET save \"\"`\n   *\n   *  See here: https://stackoverflow.com/a/34736871/21699616\n   *\n   * @param client redis client that we are connected to\n   */\n  static async setConfigForInMemory(client: Redis) {\n    await Promise.all([\n      // https://redis.io/docs/management/persistence/#append-only-file\n      client.config(\"SET\", \"appendonly\", \"no\"),\n      // https://redis.io/docs/management/persistence/#snapshotting\n      client.config(\"SET\", \"save\", \"\"),\n    ]);\n  }\n\n  //////////////////// TRANSACTION LOGIC ////////////////////\n  /**\n   * Begin a transaction, where all operations will be atomic\n   * upon calling `commit`.\n   * @see {@link commit} and {@link rollback}\n   */\n  async begin(): Promise<void> {\n    this.logger.debug(\"BEGIN called.\");\n    if (this.transaction != null) {\n      throw new Error(\"Already begun\");\n    }\n    this.transaction = this.client.multi();\n  }\n\n  /**\n   * Abort a transaction, preferably after `begin` is called.\n   * @see {@link begin}\n   */\n  async rollback(): Promise<void> {\n    this.logger.debug(\"ROLLBACK called.\");\n    if (this.transaction === null) {\n      throw new Error(\"No transaction\");\n    }\n    this.transaction.discard();\n    this.transaction = null;\n  }\n\n  /**\n   * Commit to a transaction, preferably after `begin` is called.\n   * @see {@link begin}\n   */\n  async commit(): Promise<void> {\n    this.logger.debug(\"COMMIT called.\");\n    if (this.transaction === null) {\n      throw new Error(\"No transaction\");\n    }\n    await this.transaction.exec();\n    this.transaction = null;\n  }\n\n  /**\n   * If a transaction is going on, this function will return the transaction object; otherwise\n   * the underlying client is returned (which makes this call equivalent of `this.client`).\n   * @returns client or transaction\n   */\n  private asAtomic(): Redis | ChainableCommander {\n    return this.transaction || this.client;\n  }\n\n  /**\n   * Executes a list of operations in batch.\n   * @param opStack a `BatchDBOp` object with `key` and operation `type`\n   */\n  async batch(opStack: BatchDBOp<V>[]) {\n    this.logger.debug(\"BATCH called.\");\n    for (const op of opStack) {\n      if (op.type === \"put\") {\n        await this.put(op.key, op.value);\n      } else if (op.type === \"del\") {\n        await this.delete(op.key);\n      }\n    }\n  }\n\n  //////////////////// KEYS & KVMAP ////////////////////\n  /**\n   * Returns all `cacheKey`s. A `SortKeyCacheRange` can be given, where specific keys can be filtered.\n   * Note that the range option applies to `keys` themselves, not the `sortKey` part of it.\n   * @param sortKey maximum sortKey\n   * @param options a set of range options for the query\n   */\n  private async cacheKeys(sortKey: string, options?: SortKeyCacheRangeOptions): Promise<string[]> {\n    this.logger.debug(\"CACHE KEYS called.\", { sortKey, options });\n\n    // prepare range arguments\n    let limit: number | undefined = undefined;\n    let isReverse = false;\n    let lowerBound = \"-\"; // equals `-inf` in lex ordering\n    let upperBound = \"+\"; // equals `+inf` in lex ordering\n    if (options) {\n      // limit option does not apply to the cacheKey query, but to the final list of keys instead\n      if (options.limit) {\n        limit = options.limit;\n      }\n      // reverse option does not apply to the cacheKey query, but to the final list of keys instead\n      if (options.reverse) {\n        isReverse = options.reverse;\n      }\n      // pick keys that are lexicographically less than this key (exclusive)\n      if (options.lt) {\n        upperBound = `(${options.lt}${this.sls}${genesisSortKey}`;\n      }\n      // pick keys that are lexicographically greater-equal to this key (inclusive)\n      if (options.gte) {\n        lowerBound = `(${options.gte}${this.sls}${genesisSortKey}`;\n      }\n    }\n\n    // get the range of keys in reverse (reverse ordering is required for the next step)\n    const cacheKeys = await this.client.zrevrangebylex(`${this.prefix}.keys`, upperBound, lowerBound);\n\n    // reduce keys to obtain latest sortKey (with respect to maxSortKey)\n    const latestCacheKeys = this.reduceCacheKeys(cacheKeys, sortKey);\n\n    // the query above is reversed already, so if we need the query to be not reversed,\n    // we need to reverse it again\n    if (!isReverse) {\n      latestCacheKeys.reverse();\n    }\n\n    // return with limit; returns the entire array if `limit >= length`.\n    return latestCacheKeys.slice(0, limit);\n  }\n\n  /**\n   * Returns all `key`s. A `SortKeyCacheRange` can be given, where specific keys can be filtered.\n   * Note that the range option applies to `keys` themselves, not the `sortKey` part of it.\n   * @param sortKey maximum sortKey\n   * @param options a set of range options for the query\n   */\n  async keys(sortKey: string, options?: SortKeyCacheRangeOptions): Promise<string[]> {\n    this.logger.debug(\"KEYS called.\", { sortKey, options });\n    const cacheKeys = await this.cacheKeys(sortKey, options);\n    return cacheKeys.map((cacheKey) => cacheKey.split(this.sls)[0]);\n  }\n\n  /**\n   * Returns a key value map for a specified `sortKey` range.\n   * @see keys function that retrieves the latest keys and their sortKeys\n   * @param sortKey sortKey\n   * @param options and object with reference keys `lt` and `gte` for comparison, as well as `limit` and `reverse` options.\n   */\n  async kvMap(sortKey: string, options?: SortKeyCacheRangeOptions): Promise<Map<string, V>> {\n    this.logger.debug(\"KVMAP called.\", { sortKey, options });\n\n    const cacheKeys = await this.cacheKeys(sortKey, options);\n    const values = await this.client.mget(cacheKeys.map((cacheKey) => `${this.prefix}.${cacheKey}`));\n\n    const map: Map<string, V> = new Map();\n    for (let i = 0; i < cacheKeys.length; ++i) {\n      // not checking for `null` here because interface\n      // expects V only; perhaps this is understanble, as we are getting\n      // existing keys instead of querying a user key.\n      // however, a deleted key may be a problem?\n      map.set(cacheKeys[i].split(this.sls)[0], JSON.parse(values[i]) as V);\n    }\n\n    return map;\n  }\n\n  //////////////////// GETTER FUNCTIONS ////////////////////\n\n  /**\n   * Given a key, returns the latest sortKey.\n   * @param key a key\n   * @param maxSortKey optional upper bound, defaults to `lastPossibleSortKey`\n   * @returns the latest `sortKey` of this `key`\n   */\n  private async getLatestSortKey(key: string, maxSortKey?: string): Promise<string | null> {\n    // find the latest sortKey of this key\n    const result = await this.client.zrevrangebylex(\n      `${this.prefix}.keys`,\n      `[${key}${this.sls}${maxSortKey || lastPossibleSortKey}`,\n      `(${key}${this.sls}${genesisSortKey}`,\n      \"LIMIT\",\n      0,\n      1\n    );\n\n    if (result.length) {\n      // we expect result[0] to be in form of a cacheKey\n      const resultSplit = result[0].split(this.sls);\n      if (resultSplit.length !== 2) {\n        throw new Error(\"Result is not CacheKey\");\n      }\n      return resultSplit[1];\n    }\n    return null;\n  }\n\n  /**\n   * Returns the value at the given key with respect to the `sortKey`.\n   * @param cacheKey a key and sortKey\n   * @returns value, `null` if it does not exist in cache\n   */\n  async get(cacheKey: CacheKey): Promise<SortKeyCacheResult<V> | null> {\n    this.logger.debug(`GET called.`, cacheKey);\n    const res = await this.client.get(`${this.prefix}.${cacheKey.key}${this.sls}${cacheKey.sortKey}`);\n\n    // key not existent\n    if (res == null) {\n      return null;\n    }\n    // key exists & was deleted\n    else if (res == DELETED_VALUE_PLACEHOLDER) {\n      return {\n        sortKey: cacheKey.sortKey,\n        cachedValue: null,\n      };\n    }\n    // key exists & has a value\n    else {\n      return {\n        sortKey: cacheKey.sortKey,\n        cachedValue: JSON.parse(res) as V,\n      };\n    }\n  }\n\n  /**\n   * Returns the latest value at the given key.\n   * Internally calls `getLessOrEqual(key, lastSortKey)`.\n   * @param key key of the value\n   * @returns value and it's sortKey, or null if it does not exist\n   */\n  async getLast(key: string): Promise<SortKeyCacheResult<V> | null> {\n    this.logger.debug(`GET LAST called.`, { key });\n    return this.getLessOrEqual(key, lastPossibleSortKey);\n  }\n\n  /**\n   * Returns the first value less than the given sortKey.\n   * @param key key of the value\n   * @param sortKey sortKey to be compared against\n   * @returns value and it's sortKey, or null if it does not exist\n   */\n  async getLessOrEqual(key: string, sortKey: string): Promise<SortKeyCacheResult<V> | null> {\n    this.logger.debug(`GET LESS OR EQUAL called.`, {\n      key,\n      sortKey,\n    });\n    const latestSortKey = await this.getLatestSortKey(key, sortKey);\n\n    // no sortKey for this key\n    if (latestSortKey == null) {\n      return null;\n    }\n\n    return await this.get({ key, sortKey: latestSortKey });\n  }\n\n  /**\n   * Get the last `sortKey`\n   * @returns last `sortKey`, `null` if there is none\n   */\n  async getLastSortKey(): Promise<string | null> {\n    this.logger.debug(`GET LAST SORT KEY called.`);\n\n    // get all cache keys\n    const cacheKeys = await this.client.zrevrangebylex(`${this.prefix}.keys`, \"+\", \"-\");\n    if (!cacheKeys.length) {\n      // no keys!\n      return null;\n    }\n\n    const latestCacheKeys = this.reduceCacheKeys(cacheKeys);\n\n    // map `key|sortKey` to `sortKey` only\n    const sortKeys = latestCacheKeys.map((v) => v.split(this.sls)[1]);\n\n    // get the last one after sorting by `sortKey`s alone\n    // this extra sort is needed because the first ordering respected key name too\n    return sortKeys.sort().at(-1);\n  }\n\n  //////////////////// SET FUNCTIONS ////////////////////\n  /**\n   * Puts a new value in cache under given `CacheKey`.\n   * The respective key is updated with new value, and the string `key|sortKey`\n   * is added to a sorted list also stored in cache.\n   * @param cacheKey an object with `key` and `sortKey`\n   * @param value new value\n   */\n  async put(cacheKey: CacheKey, value: V): Promise<void> {\n    this.logger.debug(\"PUT called.\", cacheKey);\n\n    await this.asAtomic().sortkeycache_atomic_put(\n      cacheKey.key,\n      cacheKey.sortKey,\n      stringify(value),\n      this.minEntriesPerContract,\n      this.maxEntriesPerContract,\n      this.prefix,\n      this.sls\n    );\n  }\n\n  //////////////////// DEL FUNCTIONS ////////////////////\n  /**\n   * Deletes keys are not below a given `sortKey` for some `key`.\n   * Values below that `sortKey` should still be accessible.\n   *\n   * This is achieved by setting the key to be deleted with empty string `\"\"`.\n   * It can be checked via `MEMORY USAGE key` in Redis that this is equal\n   * in size to setting `null`. The only more optimized variant is setting 0,\n   * but that is dangerous as it might occur in a normally set value too.\n   *\n   * Note that this does not prevent a user to set empty string to some key,\n   * because all values are stringified during a SET, and the stringification\n   * of empty string is `\"\\\"\\\"\"` instead of `\"\"` alone!\n   *\n   * @param cacheKey a key and sortKey\n   */\n  async del(cacheKey: CacheKey): Promise<void> {\n    this.logger.debug(\"DEL called.\", cacheKey);\n    await this.asAtomic().sortkeycache_atomic_put(\n      cacheKey.key,\n      cacheKey.sortKey,\n      DELETED_VALUE_PLACEHOLDER, // instead of null, we put an empty string\n      this.minEntriesPerContract,\n      this.maxEntriesPerContract,\n      this.prefix,\n      this.sls\n    );\n  }\n\n  /**\n   * Removes all data at the given key.\n   *\n   * This means finding all `sortKey`s associated with this data and removing all of them.\n   * Internally calls `del` with the `genesisSortKey` as the argument.\n   * @see {@link del}\n   * @param key key\n   */\n  async delete(key: string): Promise<void> {\n    this.logger.debug(\"DELETE called.\", { key });\n    await this.del({ key, sortKey: genesisSortKey });\n  }\n\n  /**\n   * Prunes the cache so that only `entriesStored` latest sortKey's are left for each cached key\n   * @param entriesStored how many latest entries should be left for each cached key\n   * @returns `null`\n   */\n  async prune(entriesStored = 1): Promise<PruneStats | null> {\n    if (!entriesStored || entriesStored <= 0) {\n      entriesStored = 1;\n    }\n\n    this.logger.debug(\"PRUNE called.\", { entriesStored });\n    await this.asAtomic().sortkeycache_atomic_prune(entriesStored, this.prefix, this.sls);\n    return null;\n  }\n\n  //////////////////// UTILITY FUNCTIONS ///////////////////\n  /**\n   * Reduces the cache keys so that only the latest sortKey is left for\n   * each key. The input is assumed to be have the cacheKeys in reverse\n   * lexicographical ordering.\n   *\n   * ```ts\n   * // input\n   * ['c|3', 'c|2', 'c|1', 'b|5', 'b|2', 'a|6', 'a|5', 'a|2']\n   *\n   * // output\n   * ['c|3', 'b|5', 'a|6']\n   * ```\n   *\n   * With an optional `maxSortKey`, the allowed maximum sortKey is limited.\n   * For the example array above, if we provide `maxSortKey = 3` then we get:\n   *\n   * ```ts\n   * ['c|3', 'b|2', 'a|2']\n   * ```\n   *\n   * @param cacheKeys an array of cacheKeys in the form `key|sortKey`.\n   * @param maxSortKey optional maxSortKey, where larger sortKey's will be ignored\n   * @returns a reduced array with the latest sortKey per key.\n   */\n  private reduceCacheKeys(cacheKeys: string[], maxSortKey?: string): string[] {\n    return cacheKeys.reduce<{\n      result: string[]; // accumulation of cacheKeys\n      prevKey: string; // the last read key\n    }>(\n      (acc, cacheKey) => {\n        const [key, sortKey] = cacheKey.split(this.sls);\n        if (maxSortKey === undefined || sortKey <= maxSortKey) {\n          if (acc.prevKey !== key) {\n            acc.result.push(cacheKey);\n            acc.prevKey = key;\n          }\n        }\n        return acc;\n      },\n      {\n        result: [],\n        prevKey: \"\",\n      }\n    ).result;\n  }\n  //////////////////// CLIENT FUNCTIONS ////////////////////\n  /**\n   * Calls `connect` function of Redis client.\n   */\n  async open(): Promise<void> {\n    this.logger.debug(\"OPEN called.\");\n    if (this.isManaged) return; // client wants to close themselves\n    try {\n      if (\n        !(this.client.status === \"connecting\" || this.client.status === \"connect\" || this.client.status === \"ready\")\n      ) {\n        await this.client.connect();\n        this.logger.info(\"Connected.\");\n      }\n    } catch (err) {\n      this.logger.error(\"Could not open Redis.\", err);\n    }\n  }\n\n  /**\n   * Calls `quit` function of Redis client, which is more\n   * graceful than `disconnect`.\n   */\n  async close(): Promise<void> {\n    this.logger.debug(\"CLOSE called.\");\n    if (this.isManaged) return; // client wants to close themselves\n    try {\n      if (this.client.status === \"ready\") {\n        // abort a transaction if its not committed so far\n        if (this.transaction != null) {\n          await this.rollback();\n        }\n        await this.client.quit();\n        this.logger.info(\"Disconnected.\");\n      }\n    } catch (err) {\n      this.logger.error(\"Could not quit client.\", err);\n    }\n  }\n\n  /**\n   * Dumps the cache to server's active directory as `dump.rdb`.\n   * Note that this is a blocking operation, and you should not\n   * do this in production.\n   */\n  async dump() {\n    this.logger.warn(\"Saving to disk...\");\n    const response = await this.client.save();\n    // https://redis.io/commands/save/\n    if (response !== \"OK\") {\n      this.logger.error(\"Dump failed with:\", response);\n    }\n  }\n\n  storage<S = Redis>() {\n    return this.client as S;\n  }\n}\n","/**\n * A Lua Command defintion, as defined by `ioredis` in `Redis.defineCommand` function.\n *\n * - `lua` is the script to run\n * - `numberOfKeys` is the number of keys to be given to the command, where it is accessed\n * via `KEYS`. If omitted, the number of keys is treated dynamically and `ioredis` will have\n * the first argument determine the number of keys. We do not omit this in our scripts.\n * - `readOnly` is an additional option, where if true, the script is treated as read-only. See\n * the relevant section here: {@link https://redis.io/docs/manual/programmability/#read-only-scripts}\n */\nexport const luaScripts: { [name: string]: { lua: string; numberOfKeys: number; readOnly?: boolean } } = {\n  /** {@see [definition](./lua/prune.lua)} */\n  sortkeycache_atomic_prune: {\n    lua: `\nlocal entriesStored = KEYS[1]\nlocal prefix = ARGV[1]\nlocal sls = ARGV[2]\n\nlocal allCacheKeys = redis.call(\n  \"ZRANGE\",\n  prefix .. \".keys\",\n  \"-\",\n  \"+\",\n  \"BYLEX\"\n)\n\nlocal keys = {}\nfor _, cacheKey in pairs(allCacheKeys) do\n  keys[string.gmatch(cacheKey, \"([^\" .. sls .. \"]+)\")()] = true\nend\n\nfor key, _ in pairs(keys) do\n  local cacheKeysToRemove = redis.call(\n    \"ZRANGE\",\n    prefix .. \".keys\",\n    \"(\" .. key .. sls .. \"999999999999,9999999999999,zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\",\n    \"(\" .. key .. sls .. \"000000000000,0000000000000,0000000000000000000000000000000000000000000000000000000000000000\",\n    \"BYLEX\",\n    \"REV\",\n    \"LIMIT\",\n    entriesStored,\n    -1\n  )\n\n  redis.call(\"ZREM\", prefix .. \".keys\", unpack(cacheKeysToRemove))\n  for _, cacheKey in pairs(cacheKeysToRemove) do\n    redis.call(\"DEL\", prefix .. \".\" .. cacheKey)\n  end\nend\n`,\n    numberOfKeys: 1,\n  },\n  /** {@see [definition](./lua/put.lua)} */\n  sortkeycache_atomic_put: {\n    lua: `\nlocal key = KEYS[1]\nlocal sortKey = KEYS[2]\nlocal value = ARGV[1]\nlocal minCount = tonumber(ARGV[2])\nlocal maxCount = tonumber(ARGV[3])\nlocal prefix = ARGV[4]\nlocal sls = ARGV[5]\n\nredis.call(\"SET\", prefix .. \".\" .. key .. sls .. sortKey, value)\nredis.call(\"ZADD\", prefix .. \".keys\", 0, key .. sls .. sortKey)\n\nlocal count = redis.call(\n  \"ZLEXCOUNT\",\n  prefix .. \".keys\",\n  \"(\" .. key .. sls .. \"000000000000,0000000000000,0000000000000000000000000000000000000000000000000000000000000000\",\n  \"[\" .. key .. sls .. sortKey\n)\n\nif count > maxCount then\n  local cacheKeysToRemove = redis.call(\n    \"ZRANGE\",\n    prefix .. \".keys\",\n    \"(\" .. key .. sls .. sortKey,\n    \"(\" .. key .. sls .. \"000000000000,0000000000000,0000000000000000000000000000000000000000000000000000000000000000\",\n    \"BYLEX\",\n    \"REV\",\n    \"LIMIT\",\n    minCount,\n    -1\n  )\n\n  if #cacheKeysToRemove ~= 0 then\n    redis.call(\"ZREM\", prefix .. \".keys\", unpack(cacheKeysToRemove))\n    for _, cacheKey in pairs(cacheKeysToRemove) do\n      redis.call(\"DEL\", prefix .. \".\" .. cacheKey)\n    end\n  end\nend\n`,\n    numberOfKeys: 2,\n  },\n};\n"],"names":[],"version":3,"file":"index.mjs.map","sourceRoot":"../"}