{"version":3,"sources":["../src/kvStore.ts","../src/duration.ts"],"sourcesContent":["/**\n * Indexed DB key-value store with support for auto-expirations.\n *\n * Why use this?\n * 1. Extremely simple interface to use indexed DBs.\n * 2. Auto-expirations with GC frees you from worrying about data clean-up.\n * 3. Any serializable data type can be stored (except undefined).\n *\n * How to use?\n * Just use the `kvStore` global constant like the local storage, but with\n * async interface functions as required by indexed DB.\n *\n * Why not use the indexed DB directly?\n * It will require you to write a lot of code to reinvent the wheel.\n */\n\nimport { Duration, durationOrMsToMs } from \"./duration\";\nimport {\n  FullStorageAdapter,\n  StorageAdapter,\n  StoredObject,\n} from \"./storageAdapter\";\nimport { MS_PER_DAY } from \"./timeConstants\";\n\n/** Global defaults can be updated directly. */\nexport const KvStoreConfig = {\n  /**\n   * Name of the DB in the indexed DB.\n   * Updating the DB name will cause all old entries to be gone.\n   */\n  dbName: \"KVStore\",\n\n  /**\n   * Version of the DB schema. Most likely you will never want to change this.\n   * Updating the version will cause all old entries to be gone.\n   */\n  dbVersion: 1,\n\n  /**\n   * Name of the store within the indexed DB. Each DB can have multiple stores.\n   * In practice, it doesn't matter what you name this to be.\n   */\n  storeName: \"kvStore\",\n\n  /** 30 days in ms. */\n  expiryMs: MS_PER_DAY * 30,\n\n  /** Do GC once per day. */\n  gcIntervalMs: MS_PER_DAY,\n};\n\nexport type KvStoreConfig = typeof KvStoreConfig;\n\n/** Convenience function to update global defaults. */\nexport function configureKvStore(config: Partial<KvStoreConfig>) {\n  Object.assign(KvStoreConfig, config);\n}\n\n/** Type to represent a full object with metadata stored in the store. */\nexport type KvStoredObject<T> = StoredObject<T> & {\n  // The key is required by the ObjectStore.\n  key: string;\n};\n\n/**\n * Parse a stored value string. Returns undefined if invalid or expired.\n * Throws an error if the string cannot be parsed as JSON.\n */\nfunction validateStoredObject<T>(\n  obj: KvStoredObject<T>,\n): KvStoredObject<T> | undefined {\n  if (\n    !obj ||\n    typeof obj !== \"object\" ||\n    typeof obj.key !== \"string\" ||\n    obj.value === undefined ||\n    typeof obj.storedMs !== \"number\" ||\n    typeof obj.expiryMs !== \"number\" ||\n    Date.now() >= obj.expiryMs\n  ) {\n    return undefined;\n  }\n\n  return obj;\n}\n\n/** Add an `onerror` handler to the request. */\nfunction withOnError<T extends IDBRequest | IDBTransaction>(\n  request: T,\n  reject: (reason?: unknown) => void,\n): T {\n  request.onerror = (event) => {\n    reject(event);\n  };\n\n  return request;\n}\n\n/**\n * You can create multiple KvStores if you want, but most likely you will only\n * need to use the default `kvStore` instance.\n */\nexport function createKvStore(\n  dbName: string,\n  options?: {\n    dbVersion?: number;\n    storeName?: string;\n    defaultExpiryMs?: number | Duration;\n    gcIntervalMs?: number | Duration;\n  },\n) {\n  /** We'll init the DB only on first use. */\n  let db: IDBDatabase | undefined;\n\n  const dbVersion = options?.dbVersion ?? KvStoreConfig.dbVersion;\n  const storeName = options?.storeName ?? KvStoreConfig.storeName;\n\n  const defaultExpiryMs = options?.defaultExpiryMs\n    ? durationOrMsToMs(options.defaultExpiryMs)\n    : KvStoreConfig.expiryMs;\n\n  const gcIntervalMs = options?.gcIntervalMs\n    ? durationOrMsToMs(options.gcIntervalMs)\n    : KvStoreConfig.gcIntervalMs;\n\n  const gcMsStorageKey = `__kvStore:lastGcMs:${dbName}:v${dbVersion}:${storeName}`;\n\n  async function getOrCreateDb() {\n    if (!db) {\n      db = await new Promise<IDBDatabase>((resolve, reject) => {\n        const request = withOnError(indexedDB.open(dbName, dbVersion), reject);\n\n        request.onupgradeneeded = (event) => {\n          const db = (event.target as unknown as { result: IDBDatabase })\n            .result;\n\n          // Create the store on DB init.\n          const objectStore = db.createObjectStore(storeName, {\n            keyPath: \"key\",\n          });\n\n          objectStore.createIndex(\"key\", \"key\", {\n            unique: true,\n          });\n        };\n\n        request.onsuccess = (event) => {\n          const db = (event.target as unknown as { result: IDBDatabase })\n            .result;\n          resolve(db);\n        };\n      });\n    }\n\n    return db;\n  }\n\n  async function transact<T>(\n    mode: IDBTransactionMode,\n    callback: (\n      objectStore: IDBObjectStore,\n      resolve: (t: T) => void,\n      reject: (reason?: unknown) => void,\n    ) => void,\n  ): Promise<T> {\n    const db = await getOrCreateDb();\n\n    return await new Promise<T>((resolve, reject) => {\n      const transaction = withOnError(db.transaction(storeName, mode), reject);\n\n      transaction.onabort = (event) => {\n        reject(event);\n      };\n\n      const objectStore = transaction.objectStore(storeName);\n\n      callback(objectStore, resolve, reject);\n    });\n  }\n\n  const obj = {\n    /** Input name for the DB. */\n    dbName,\n\n    /** Input version for the DB. */\n    dbVersion,\n\n    /** Input name for the DB store. */\n    storeName,\n\n    /** Default expiry to use if not specified in set(). */\n    defaultExpiryMs,\n\n    /** Time interval for when GC's occur. */\n    gcIntervalMs,\n\n    /** Local storage key name for the last GC completed timestamp. */\n    gcMsStorageKey,\n\n    /** Set a value in the store. */\n    async set<T>(\n      key: string,\n      value: T,\n      expiryDeltaMs?: number | Duration,\n    ): Promise<T> {\n      const nowMs = Date.now();\n      const stored: KvStoredObject<T> = {\n        key,\n        value,\n        storedMs: nowMs,\n        expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs ?? defaultExpiryMs),\n      };\n\n      return await transact<T>(\"readwrite\", (objectStore, resolve, reject) => {\n        const request = withOnError(objectStore.put(stored), reject);\n\n        request.onsuccess = () => {\n          resolve(value);\n\n          obj.gc(); // check GC on every write\n        };\n      });\n    },\n\n    /** Delete one or multiple keys. */\n    async delete(key: string | string[]): Promise<void> {\n      return await transact<void>(\n        \"readwrite\",\n        (objectStore, resolve, reject) => {\n          objectStore.transaction.oncomplete = () => {\n            resolve();\n          };\n\n          if (typeof key === \"string\") {\n            withOnError(objectStore.delete(key), reject);\n          } else {\n            for (const k of key) {\n              withOnError(objectStore.delete(k), reject);\n            }\n          }\n        },\n      );\n    },\n\n    /** Mainly used to get the expiration timestamp of an object. */\n    async getStoredObject<T>(\n      key: string,\n    ): Promise<KvStoredObject<T> | undefined> {\n      const stored = await transact<KvStoredObject<T> | undefined>(\n        \"readonly\",\n        (objectStore, resolve, reject) => {\n          const request = withOnError(objectStore.get(key), reject);\n\n          request.onsuccess = () => {\n            resolve(request.result);\n          };\n        },\n      );\n\n      if (!stored) {\n        return undefined;\n      }\n\n      try {\n        const valid = validateStoredObject(stored);\n        if (!valid) {\n          await obj.delete(key);\n\n          obj.gc(); // check GC on every read of an expired key\n\n          return undefined;\n        }\n\n        return valid;\n      } catch (e) {\n        console.error(`Invalid kv value: ${key}=${JSON.stringify(stored)}:`, e);\n        await obj.delete(key);\n\n        obj.gc(); // check GC on every read of an invalid key\n\n        return undefined;\n      }\n    },\n\n    /** Get a value by key, or undefined if it does not exist. */\n    async get<T>(key: string): Promise<T | undefined> {\n      const stored = await obj.getStoredObject<T>(key);\n\n      return stored?.value;\n    },\n\n    /** Generic way to iterate through all entries. */\n    async forEach<T>(\n      callback: (\n        key: string,\n        value: T,\n        expiryMs: number,\n        storedMs: number,\n      ) => void | Promise<void>,\n    ): Promise<void> {\n      await transact<void>(\"readonly\", (objectStore, resolve, reject) => {\n        const request = withOnError(objectStore.openCursor(), reject);\n\n        request.onsuccess = async (event) => {\n          const cursor = (\n            event.target as unknown as { result: IDBCursorWithValue }\n          ).result;\n\n          if (cursor) {\n            if (cursor.key) {\n              const valid = validateStoredObject(cursor.value);\n              if (valid !== undefined) {\n                await callback(\n                  String(cursor.key),\n                  valid.value as T,\n                  valid.expiryMs,\n                  valid.storedMs,\n                );\n              }\n            }\n            cursor.continue();\n          } else {\n            resolve();\n          }\n        };\n      });\n    },\n\n    /**\n     * Returns the number of items in the store. Note that getting the size\n     * requires iterating through the entire store because the items could expire\n     * at any time, and hence the size is a dynamic number.\n     */\n    async size(): Promise<number> {\n      let count = 0;\n      await obj.forEach(() => {\n        count++;\n      });\n      return count;\n    },\n\n    /** Remove all items from the store. */\n    async clear(): Promise<void> {\n      await transact<void>(\"readwrite\", (objectStore, resolve, reject) => {\n        const request = withOnError(objectStore.clear(), reject);\n\n        request.onsuccess = () => {\n          resolve();\n        };\n      });\n    },\n\n    /**\n     * Returns all items as map of key to value, mainly used for debugging dumps.\n     * The type T is applied to all values, even though they might not be of type\n     * T (in the case when you store different data types in the same store).\n     */\n    async asMap<T>(): Promise<Map<string, StoredObject<T>>> {\n      const map = new Map<string, StoredObject<T>>();\n      await obj.forEach((key, value, expiryMs, storedMs) => {\n        map.set(key, { value: value as T, expiryMs, storedMs });\n      });\n      return map;\n    },\n\n    /** Returns the ms timestamp for the last GC (garbage collection). */\n    getLastGcMs(): number {\n      const lastGcMsStr = localStorage.getItem(gcMsStorageKey);\n      if (!lastGcMsStr) return 0;\n\n      const ms = Number(lastGcMsStr);\n      return isNaN(ms) ? 0 : ms;\n    },\n\n    /** Set the ms timestamp for the last GC (garbage collection). */\n    setLastGcMs(ms: number) {\n      localStorage.setItem(gcMsStorageKey, String(ms));\n    },\n\n    /** Perform garbage-collection if due, else do nothing. */\n    async gc(): Promise<void> {\n      const lastGcMs = obj.getLastGcMs();\n\n      // Set initial timestamp - no need GC now.\n      if (!lastGcMs) {\n        obj.setLastGcMs(Date.now());\n        return;\n      }\n\n      if (Date.now() < lastGcMs + gcIntervalMs) {\n        return; // not due for next GC yet\n      }\n\n      // GC is due now, so run it.\n      await obj.gcNow();\n    },\n\n    /**\n     * Perform garbage collection immediately without checking whether we are\n     * due for the next GC or not.\n     */\n    async gcNow(): Promise<void> {\n      console.log(`Starting kvStore GC on ${dbName} v${dbVersion}...`);\n\n      // Prevent concurrent GC runs.\n      obj.setLastGcMs(Date.now());\n\n      const keysToDelete: string[] = [];\n      await obj.forEach(\n        async (key: string, value: unknown, expiryMs: number) => {\n          if (value === undefined || Date.now() >= expiryMs) {\n            keysToDelete.push(key);\n          }\n        },\n      );\n\n      if (keysToDelete.length) {\n        await obj.delete(keysToDelete);\n      }\n\n      console.log(\n        `Finished kvStore GC on ${dbName} v${dbVersion} ` +\n          `- deleted ${keysToDelete.length} keys`,\n      );\n\n      // Mark the end time as last GC time.\n      obj.setLastGcMs(Date.now());\n    },\n\n    /** Returns `this` casted into a StorageAdapter<T>. */\n    asStorageAdapter<T>(): StorageAdapter<T> {\n      return obj as StorageAdapter<T>;\n    },\n  } as const;\n\n  // Using `any` because the store could store any type of data for each key,\n  // but the caller can specify a more specific type when calling each of the\n  // methods.\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  return obj satisfies FullStorageAdapter<any>;\n}\n\nexport type KvStore = ReturnType<typeof createKvStore>;\n\n/**\n * Default KV store ready for immediate use. You can create new instances if\n * you want, but most likely you will only need one store instance.\n */\nexport const kvStore = createKvStore(KvStoreConfig.dbName);\n\n/** Create a KV store item with a key and a default expiration. */\nexport function kvStoreItem<T>(\n  key: string,\n  expiryMs?: number | Duration,\n  store: KvStore = kvStore,\n) {\n  const defaultExpiryMs = expiryMs && durationOrMsToMs(expiryMs);\n\n  const obj = {\n    key,\n    defaultExpiryMs,\n    store,\n\n    /** Set a value in the store. */\n    async set(value: T, expiryDeltaMs?: number | undefined): Promise<void> {\n      await store.set(key, value, expiryDeltaMs ?? defaultExpiryMs);\n    },\n\n    /**\n     * Example usage:\n     *\n     *   const { value, storedMs, expiryMs, storedMs } =\n     *     await myKvItem.getStoredObject();\n     */\n    async getStoredObject(): Promise<KvStoredObject<T> | undefined> {\n      return await store.getStoredObject(key);\n    },\n\n    /** Get a value by key, or undefined if it does not exist. */\n    async get(): Promise<T | undefined> {\n      return await store.get(key);\n    },\n\n    /** Delete this key from the store. */\n    async delete(): Promise<void> {\n      await store.delete(key);\n    },\n  } as const;\n\n  return obj;\n}\n\n/** Class to represent one key in the store with a default expiration. */\nexport type KvStoreItem<T> = ReturnType<typeof kvStoreItem<T>>;\n","/**\n * Bunch of miscellaneous constants and utility functions related to handling\n * date and time durations.\n *\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file. Weeks have fixed durations, but are excluded because we\n * use days as the max duration supported.\n */\n\nimport {\n  HOURS_PER_DAY,\n  MINUTES_PER_HOUR,\n  MS_PER_DAY,\n  MS_PER_HOUR,\n  MS_PER_MINUTE,\n  MS_PER_SECOND,\n  SECONDS_PER_MINUTE,\n} from \"./timeConstants\";\n\nexport type Duration = {\n  days?: number;\n  hours?: number;\n  minutes?: number;\n  seconds?: number;\n  milliseconds?: number;\n};\n\n/**\n * One of: days, hours, minutes, seconds, milliseconds\n */\nexport type DurationType = keyof Duration;\n\n/**\n * Order in which the duration type appears in the duration string.\n */\nexport const DURATION_TYPE_SEQUENCE: DurationType[] = [\n  \"days\",\n  \"hours\",\n  \"minutes\",\n  \"seconds\",\n  \"milliseconds\",\n];\n\n/**\n * Follows the same format as Intl.DurationFormat.prototype.format().\n *\n * Short: 1 yr, 2 mths, 3 wks, 3 days, 4 hr, 5 min, 6 sec, 7 ms, 8 μs, 9 ns\n * Long: 1 year, 2 months, 3 weeks, 3 days, 4 hours, 5 minutes, 6 seconds,\n *       7 milliseconds, 8 microseconds, 9 nanoseconds\n * Narrow: 1y 2mo 3w 3d 4h 5m 6s 7ms 8μs 9ns\n */\nexport type DurationStyle = \"short\" | \"long\" | \"narrow\";\n\nexport type DurationSuffixMap = {\n  short: string;\n  shorts: string;\n  long: string;\n  longs: string;\n  narrow: string;\n};\n\nexport type DurationSuffixType = keyof DurationSuffixMap;\n\nexport const DURATION_STYLE_SUFFIX_MAP: Record<\n  DurationType,\n  DurationSuffixMap\n> = {\n  days: {\n    short: \"day\",\n    shorts: \"days\",\n    long: \"day\",\n    longs: \"days\",\n    narrow: \"d\",\n  },\n  hours: {\n    short: \"hr\",\n    shorts: \"hrs\",\n    long: \"hour\",\n    longs: \"hours\",\n    narrow: \"h\",\n  },\n  minutes: {\n    short: \"min\",\n    shorts: \"mins\",\n    long: \"minute\",\n    longs: \"minutes\",\n    narrow: \"m\",\n  },\n  seconds: {\n    short: \"sec\",\n    shorts: \"secs\",\n    long: \"second\",\n    longs: \"seconds\",\n    narrow: \"s\",\n  },\n  milliseconds: {\n    short: \"ms\",\n    shorts: \"ms\",\n    long: \"millisecond\",\n    longs: \"milliseconds\",\n    narrow: \"ms\",\n  },\n};\n\nfunction getDurationStyleForPlural(style: DurationStyle): DurationSuffixType {\n  return style == \"short\" ? \"shorts\" : style === \"long\" ? \"longs\" : style;\n}\n\nfunction getValueAndUnitSeparator(style: DurationStyle): string {\n  return style === \"narrow\" ? \"\" : \" \";\n}\n\nfunction getDurationTypeSeparator(style: DurationStyle): string {\n  return style === \"narrow\" ? \" \" : \", \";\n}\n\n/**\n * Convert a milliseconds duration into a Duration object. If the given ms is\n * zero, then return an object with a single field of zero with duration type\n * of durationTypeForZero.\n *\n * @param durationTypeForZero Defaults to 'milliseconds'\n */\nexport function msToDuration(\n  ms: number,\n  durationTypeForZero?: DurationType,\n): Duration {\n  if (ms === 0) {\n    durationTypeForZero = durationTypeForZero ?? \"milliseconds\";\n    return { [durationTypeForZero]: 0 };\n  }\n\n  const duration: Duration = {};\n\n  for (let i = 0; i < 1; i++) {\n    let seconds = Math.floor(ms / MS_PER_SECOND);\n    const millis = ms - seconds * MS_PER_SECOND;\n\n    if (millis > 0) {\n      duration[\"milliseconds\"] = millis;\n    }\n\n    if (seconds === 0) {\n      break;\n    }\n\n    let minutes = Math.floor(seconds / SECONDS_PER_MINUTE);\n    seconds -= minutes * SECONDS_PER_MINUTE;\n\n    if (seconds > 0) {\n      duration[\"seconds\"] = seconds;\n    }\n\n    if (minutes === 0) {\n      break;\n    }\n\n    let hours = Math.floor(minutes / MINUTES_PER_HOUR);\n    minutes -= hours * MINUTES_PER_HOUR;\n\n    if (minutes > 0) {\n      duration[\"minutes\"] = minutes;\n    }\n\n    if (hours === 0) {\n      break;\n    }\n\n    const days = Math.floor(hours / HOURS_PER_DAY);\n    hours -= days * HOURS_PER_DAY;\n\n    if (hours > 0) {\n      duration[\"hours\"] = hours;\n    }\n\n    if (days > 0) {\n      duration[\"days\"] = days;\n    }\n  }\n\n  return duration;\n}\n\n/**\n * Returns the number of milliseconds for the given duration.\n */\nexport function durationToMs(duration: Duration): number {\n  const daysMs = (duration.days ?? 0) * MS_PER_DAY;\n  const hoursMs = (duration.hours ?? 0) * MS_PER_HOUR;\n  const minsMs = (duration.minutes ?? 0) * MS_PER_MINUTE;\n  const secsMs = (duration.seconds ?? 0) * MS_PER_SECOND;\n  const msMs = duration.milliseconds ?? 0;\n\n  return daysMs + hoursMs + minsMs + secsMs + msMs;\n}\n\n/**\n * Convenience function to return a duration given an ms or Duration.\n */\nexport function durationOrMsToMs(duration: number | Duration): number {\n  return typeof duration === \"number\" ? duration : durationToMs(duration);\n}\n\n/**\n * Format a given Duration object into a string. If the object has no fields,\n * then returns an empty string.\n *\n * @param style Defaults to 'short'\n */\nexport function formatDuration(duration: Duration, style?: DurationStyle) {\n  style = style ?? \"short\";\n  const stylePlural = getDurationStyleForPlural(style);\n\n  const space = getValueAndUnitSeparator(style);\n\n  const a: string[] = [];\n\n  for (const unit of DURATION_TYPE_SEQUENCE) {\n    const value = duration[unit];\n    if (value === undefined) continue;\n\n    const suffixMap = DURATION_STYLE_SUFFIX_MAP[unit];\n    const suffix = value === 1 ? suffixMap[style] : suffixMap[stylePlural];\n    a.push(value + space + suffix);\n  }\n\n  const separator = getDurationTypeSeparator(style);\n  return a.join(separator);\n}\n\n/**\n * Convert a millisecond duration into a human-readable duration string.\n *\n * @param options.durationTypeForZero - Defaults to 'milliseconds'\n * @param options.style - Defaults to 'short'\n */\nexport function readableDuration(\n  ms: number,\n  options?: { durationTypeForZero?: DurationType; style?: DurationStyle },\n): string {\n  const duration = msToDuration(ms, options?.durationTypeForZero);\n\n  return formatDuration(duration, options?.style);\n}\n\n/** A shortened duration string useful for logging timings. */\nexport function elapsed(ms: number): string {\n  // Use long format for 1 minute or over.\n  if (ms > MS_PER_MINUTE) {\n    return readableDuration(ms);\n  }\n\n  // Use seconds format for over 100ms.\n  if (ms > 100) {\n    return `${(ms / 1000).toFixed(3)}s`;\n  }\n\n  // Use milliseconds format.\n  return ms + \"ms\";\n}\n"],"mappings":"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","names":["kvStore_exports","__export","KvStoreConfig","configureKvStore","createKvStore","kvStore","kvStoreItem","__toCommonJS","durationToMs","duration","daysMs","hoursMs","minsMs","secsMs","msMs","durationOrMsToMs","KvStoreConfig","configureKvStore","config","validateStoredObject","obj","withOnError","request","reject","event","createKvStore","dbName","options","db","dbVersion","storeName","defaultExpiryMs","durationOrMsToMs","gcIntervalMs","gcMsStorageKey","getOrCreateDb","resolve","transact","mode","callback","transaction","objectStore","key","value","expiryDeltaMs","nowMs","stored","k","valid","e","cursor","count","map","expiryMs","storedMs","lastGcMsStr","ms","lastGcMs","keysToDelete","kvStore","kvStoreItem","store"]}