@choksheak/ts-utils 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +9 -3
  2. package/asNumber.cjs +6 -6
  3. package/asNumber.d.mts +1 -1
  4. package/asNumber.d.ts +1 -1
  5. package/asNumber.min.cjs +1 -1
  6. package/asNumber.min.cjs.map +1 -1
  7. package/asNumber.min.mjs +1 -1
  8. package/asNumber.min.mjs.map +1 -1
  9. package/asNumber.mjs +6 -6
  10. package/capLength.cjs +80 -0
  11. package/capLength.d.mts +3 -0
  12. package/capLength.d.ts +3 -0
  13. package/capLength.min.cjs +6 -0
  14. package/capLength.min.cjs.map +1 -0
  15. package/capLength.min.mjs +6 -0
  16. package/capLength.min.mjs.map +1 -0
  17. package/capLength.mjs +53 -0
  18. package/{iterators.cjs → concatIterators.cjs} +4 -4
  19. package/{iterators.min.cjs → concatIterators.min.cjs} +1 -1
  20. package/concatIterators.min.cjs.map +1 -0
  21. package/{iterators.min.mjs → concatIterators.min.mjs} +1 -1
  22. package/concatIterators.min.mjs.map +1 -0
  23. package/{iterators.mjs → concatIterators.mjs} +1 -1
  24. package/duration.min.cjs +1 -1
  25. package/duration.min.mjs +1 -1
  26. package/kvStore.cjs +230 -239
  27. package/kvStore.d.mts +142 -44
  28. package/kvStore.d.ts +142 -44
  29. package/kvStore.min.cjs +1 -1
  30. package/kvStore.min.cjs.map +1 -1
  31. package/kvStore.min.mjs +1 -1
  32. package/kvStore.min.mjs.map +1 -1
  33. package/kvStore.mjs +229 -237
  34. package/localStore.cjs +187 -186
  35. package/localStore.d.mts +135 -34
  36. package/localStore.d.ts +135 -34
  37. package/localStore.min.cjs +1 -1
  38. package/localStore.min.cjs.map +1 -1
  39. package/localStore.min.mjs +1 -1
  40. package/localStore.min.mjs.map +1 -1
  41. package/localStore.mjs +186 -184
  42. package/mean.cjs +6 -6
  43. package/mean.min.cjs +1 -1
  44. package/mean.min.cjs.map +1 -1
  45. package/mean.min.mjs +1 -1
  46. package/mean.min.mjs.map +1 -1
  47. package/mean.mjs +6 -6
  48. package/median.cjs +7 -7
  49. package/median.min.cjs +1 -1
  50. package/median.min.cjs.map +1 -1
  51. package/median.min.mjs +1 -1
  52. package/median.min.mjs.map +1 -1
  53. package/median.mjs +7 -7
  54. package/package.json +67 -82
  55. package/round.cjs +2 -10
  56. package/round.d.mts +2 -8
  57. package/round.d.ts +2 -8
  58. package/round.min.cjs +1 -1
  59. package/round.min.cjs.map +1 -1
  60. package/round.min.mjs +1 -1
  61. package/round.min.mjs.map +1 -1
  62. package/round.mjs +1 -8
  63. package/{safeParseInt.cjs → roundToString.cjs} +19 -9
  64. package/roundToString.d.mts +8 -0
  65. package/roundToString.d.ts +8 -0
  66. package/roundToString.min.cjs +2 -0
  67. package/roundToString.min.cjs.map +1 -0
  68. package/roundToString.min.mjs +2 -0
  69. package/roundToString.min.mjs.map +1 -0
  70. package/roundToString.mjs +16 -0
  71. package/safeBtoa.d.mts +7 -0
  72. package/safeBtoa.d.ts +7 -0
  73. package/safeBtoa.min.cjs.map +1 -1
  74. package/safeBtoa.min.mjs.map +1 -1
  75. package/sum.cjs +6 -6
  76. package/sum.min.cjs +1 -1
  77. package/sum.min.cjs.map +1 -1
  78. package/sum.min.mjs +1 -1
  79. package/sum.min.mjs.map +1 -1
  80. package/sum.mjs +6 -6
  81. package/timer.cjs +19 -24
  82. package/timer.d.mts +9 -6
  83. package/timer.d.ts +9 -6
  84. package/timer.min.cjs +1 -1
  85. package/timer.min.cjs.map +1 -1
  86. package/timer.min.mjs +1 -1
  87. package/timer.min.mjs.map +1 -1
  88. package/timer.mjs +19 -23
  89. package/toReadableString.cjs +69 -0
  90. package/toReadableString.d.mts +10 -0
  91. package/toReadableString.d.ts +10 -0
  92. package/toReadableString.min.cjs +6 -0
  93. package/toReadableString.min.cjs.map +1 -0
  94. package/toReadableString.min.mjs +6 -0
  95. package/toReadableString.min.mjs.map +1 -0
  96. package/toReadableString.mjs +44 -0
  97. package/average.cjs +0 -55
  98. package/average.d.mts +0 -7
  99. package/average.d.ts +0 -7
  100. package/average.min.cjs +0 -2
  101. package/average.min.cjs.map +0 -1
  102. package/average.min.mjs +0 -2
  103. package/average.min.mjs.map +0 -1
  104. package/average.mjs +0 -28
  105. package/iterators.min.cjs.map +0 -1
  106. package/iterators.min.mjs.map +0 -1
  107. package/logging.cjs +0 -47
  108. package/logging.d.mts +0 -4
  109. package/logging.d.ts +0 -4
  110. package/logging.min.cjs +0 -2
  111. package/logging.min.cjs.map +0 -1
  112. package/logging.min.mjs +0 -2
  113. package/logging.min.mjs.map +0 -1
  114. package/logging.mjs +0 -21
  115. package/safeParseFloat.cjs +0 -33
  116. package/safeParseFloat.d.mts +0 -6
  117. package/safeParseFloat.d.ts +0 -6
  118. package/safeParseFloat.min.cjs +0 -2
  119. package/safeParseFloat.min.cjs.map +0 -1
  120. package/safeParseFloat.min.mjs +0 -2
  121. package/safeParseFloat.min.mjs.map +0 -1
  122. package/safeParseFloat.mjs +0 -8
  123. package/safeParseInt.d.mts +0 -6
  124. package/safeParseInt.d.ts +0 -6
  125. package/safeParseInt.min.cjs +0 -2
  126. package/safeParseInt.min.cjs.map +0 -1
  127. package/safeParseInt.min.mjs +0 -2
  128. package/safeParseInt.min.mjs.map +0 -1
  129. package/safeParseInt.mjs +0 -8
  130. /package/{iterators.d.mts → concatIterators.d.mts} +0 -0
  131. /package/{iterators.d.ts → concatIterators.d.ts} +0 -0
@@ -1 +1 @@
1
- {"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 */\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\nexport class KvStore implements FullStorageAdapter<any> {\n /** We'll init the DB only on first use. */\n private db: IDBDatabase | undefined;\n\n /** Local storage key name for the last GC completed timestamp. */\n public readonly gcMsStorageKey: string;\n\n public readonly dbVersion: number;\n public readonly storeName: string;\n public readonly defaultExpiryMs: number;\n public readonly gcIntervalMs: number;\n\n public constructor(\n public readonly dbName: string,\n options?: {\n dbVersion?: number;\n storeName?: string;\n defaultExpiryMs?: number | Duration;\n gcIntervalMs?: number | Duration;\n },\n ) {\n this.dbVersion = options?.dbVersion ?? KvStoreConfig.dbVersion;\n this.storeName = options?.storeName ?? KvStoreConfig.storeName;\n\n this.defaultExpiryMs = options?.defaultExpiryMs\n ? durationOrMsToMs(options.defaultExpiryMs)\n : KvStoreConfig.expiryMs;\n\n this.gcIntervalMs = options?.gcIntervalMs\n ? durationOrMsToMs(options.gcIntervalMs)\n : KvStoreConfig.gcIntervalMs;\n\n this.gcMsStorageKey = `__kvStore:lastGcMs:${dbName}:v${this.dbVersion}:${this.storeName}`;\n }\n\n private async getOrCreateDb() {\n if (!this.db) {\n this.db = await new Promise<IDBDatabase>((resolve, reject) => {\n const request = withOnError(\n globalThis.indexedDB.open(this.dbName, this.dbVersion),\n reject,\n );\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(this.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 this.db;\n }\n\n private async 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 this.getOrCreateDb();\n\n return await new Promise<T>((resolve, reject) => {\n const transaction = withOnError(\n db.transaction(this.storeName, mode),\n reject,\n );\n\n transaction.onabort = (event) => {\n reject(event);\n };\n\n const objectStore = transaction.objectStore(this.storeName);\n\n callback(objectStore, resolve, reject);\n });\n }\n\n /** Set a value in the store. */\n public async set<T>(\n key: string,\n value: T,\n expiryDeltaMs: number | Duration = this.defaultExpiryMs,\n ): Promise<T> {\n const nowMs = Date.now();\n const obj: KvStoredObject<T> = {\n key,\n value,\n storedMs: nowMs,\n expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs),\n };\n\n return await this.transact<T>(\n \"readwrite\",\n (objectStore, resolve, reject) => {\n const request = withOnError(objectStore.put(obj), reject);\n\n request.onsuccess = () => {\n resolve(value);\n\n this.gc(); // check GC on every write\n };\n },\n );\n }\n\n /** Delete one or multiple keys. */\n public async delete(key: string | string[]): Promise<void> {\n return await this.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 public async getStoredObject<T>(\n key: string,\n ): Promise<KvStoredObject<T> | undefined> {\n const stored = await this.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 obj = validateStoredObject(stored);\n if (!obj) {\n await this.delete(key);\n\n this.gc(); // check GC on every read of an expired key\n\n return undefined;\n }\n\n return obj;\n } catch (e) {\n console.error(`Invalid kv value: ${key}=${JSON.stringify(stored)}:`, e);\n await this.delete(key);\n\n this.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 public async get<T>(key: string): Promise<T | undefined> {\n const obj = await this.getStoredObject<T>(key);\n\n return obj?.value;\n }\n\n /** Generic way to iterate through all entries. */\n public 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 this.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 obj = validateStoredObject(cursor.value);\n if (obj !== undefined) {\n await callback(\n String(cursor.key),\n obj.value as T,\n obj.expiryMs,\n obj.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 public async size(): Promise<number> {\n let count = 0;\n await this.forEach(() => {\n count++;\n });\n return count;\n }\n\n /** Remove all items from the store. */\n public async clear(): Promise<void> {\n await this.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 public async asMap<T>(): Promise<Map<string, StoredObject<T>>> {\n const map = new Map<string, StoredObject<T>>();\n await this.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 public get lastGcMs(): number {\n const lastGcMsStr = globalThis.localStorage.getItem(this.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 public set lastGcMs(ms: number) {\n globalThis.localStorage.setItem(this.gcMsStorageKey, String(ms));\n }\n\n /** Perform garbage-collection if due, else do nothing. */\n public async gc(): Promise<void> {\n const lastGcMs = this.lastGcMs;\n\n // Set initial timestamp - no need GC now.\n if (!lastGcMs) {\n this.lastGcMs = Date.now();\n return;\n }\n\n if (Date.now() < lastGcMs + this.gcIntervalMs) {\n return; // not due for next GC yet\n }\n\n // GC is due now, so run it.\n await this.gcNow();\n }\n\n /**\n * Perform garbage collection immediately without checking whether we are\n * due for the next GC or not.\n */\n public async gcNow(): Promise<void> {\n console.log(`Starting kvStore GC on ${this.dbName} v${this.dbVersion}...`);\n\n // Prevent concurrent GC runs.\n this.lastGcMs = Date.now();\n\n const keysToDelete: string[] = [];\n await this.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 this.delete(keysToDelete);\n }\n\n console.log(\n `Finished kvStore GC on ${this.dbName} v${this.dbVersion} ` +\n `- deleted ${keysToDelete.length} keys`,\n );\n\n // Mark the end time as last GC time.\n this.lastGcMs = Date.now();\n }\n\n /** Returns `this` casted into a StorageAdapter<T>. */\n public asStorageAdapter<T>(): StorageAdapter<T> {\n return this as StorageAdapter<T>;\n }\n}\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 = new KvStore(KvStoreConfig.dbName);\n\n/**\n * Class to represent one key in the store with a default expiration.\n */\nexport class KvStoreItem<T> {\n public readonly defaultExpiryMs: number;\n\n public constructor(\n public readonly key: string,\n defaultExpiryMs: number | Duration = KvStoreConfig.expiryMs,\n public readonly store = kvStore,\n ) {\n this.defaultExpiryMs = defaultExpiryMs && durationOrMsToMs(defaultExpiryMs);\n }\n\n /** Set a value in the store. */\n public async set(\n value: T,\n expiryDeltaMs: number | undefined = this.defaultExpiryMs,\n ): Promise<void> {\n await this.store.set(this.key, value, expiryDeltaMs);\n }\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs, storedMs } =\n * await myKvItem.getStoredObject();\n */\n public async getStoredObject(): Promise<KvStoredObject<T> | undefined> {\n return await this.store.getStoredObject(this.key);\n }\n\n /** Get a value by key, or undefined if it does not exist. */\n public async get(): Promise<T | undefined> {\n return await this.store.get(this.key);\n }\n\n /** Delete this key from the store. */\n public async delete(): Promise<void> {\n await this.store.delete(this.key);\n }\n}\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,\n): KvStoreItem<T> {\n expiryMs = expiryMs && durationOrMsToMs(expiryMs);\n\n return new KvStoreItem<T>(key, expiryMs, store);\n}\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":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,kBAAAC,EAAA,gBAAAC,EAAA,qBAAAC,EAAA,YAAAC,EAAA,gBAAAC,IAAA,eAAAC,EAAAR,GC0LO,SAASS,EAAaC,EAA4B,CACvD,IAAMC,GAAUD,EAAS,MAAQ,GAAK,MAChCE,GAAWF,EAAS,OAAS,GAAK,KAClCG,GAAUH,EAAS,SAAW,GAAK,IACnCI,GAAUJ,EAAS,SAAW,GAAK,IACnCK,EAAOL,EAAS,cAAgB,EAEtC,OAAOC,EAASC,EAAUC,EAASC,EAASC,CAC9C,CAKO,SAASC,EAAiBN,EAAqC,CACpE,OAAO,OAAOA,GAAa,SAAWA,EAAWD,EAAaC,CAAQ,CACxE,CDhLO,IAAMO,EAAgB,CAK3B,OAAQ,UAMR,UAAW,EAMX,UAAW,UAGX,SAAU,MAAa,GAGvB,aAAc,KAChB,EAKO,SAASC,EAAiBC,EAAgC,CAC/D,OAAO,OAAOF,EAAeE,CAAM,CACrC,CAYA,SAASC,EACPC,EAC+B,CAC/B,GACE,GAACA,GACD,OAAOA,GAAQ,UACf,OAAOA,EAAI,KAAQ,UACnBA,EAAI,QAAU,QACd,OAAOA,EAAI,UAAa,UACxB,OAAOA,EAAI,UAAa,UACxB,KAAK,IAAI,GAAKA,EAAI,UAKpB,OAAOA,CACT,CAGA,SAASC,EACPC,EACAC,EACG,CACH,OAAAD,EAAQ,QAAWE,GAAU,CAC3BD,EAAOC,CAAK,CACd,EAEOF,CACT,CAUO,IAAMG,EAAN,KAAiD,CAY/C,YACWC,EAChBC,EAMA,CAPgB,YAAAD,EAQhB,KAAK,UAAYC,GAAS,WAAaX,EAAc,UACrD,KAAK,UAAYW,GAAS,WAAaX,EAAc,UAErD,KAAK,gBAAkBW,GAAS,gBAC5BC,EAAiBD,EAAQ,eAAe,EACxCX,EAAc,SAElB,KAAK,aAAeW,GAAS,aACzBC,EAAiBD,EAAQ,YAAY,EACrCX,EAAc,aAElB,KAAK,eAAiB,sBAAsBU,CAAM,KAAK,KAAK,SAAS,IAAI,KAAK,SAAS,EACzF,CA/BQ,GAGQ,eAEA,UACA,UACA,gBACA,aAyBhB,MAAc,eAAgB,CAC5B,OAAK,KAAK,KACR,KAAK,GAAK,MAAM,IAAI,QAAqB,CAACG,EAASN,IAAW,CAC5D,IAAMD,EAAUD,EACd,WAAW,UAAU,KAAK,KAAK,OAAQ,KAAK,SAAS,EACrDE,CACF,EAEAD,EAAQ,gBAAmBE,GAAU,CACvBA,EAAM,OACf,OAGoB,kBAAkB,KAAK,UAAW,CACvD,QAAS,KACX,CAAC,EAEW,YAAY,MAAO,MAAO,CACpC,OAAQ,EACV,CAAC,CACH,EAEAF,EAAQ,UAAaE,GAAU,CAC7B,IAAMM,EAAMN,EAAM,OACf,OACHK,EAAQC,CAAE,CACZ,CACF,CAAC,GAGI,KAAK,EACd,CAEA,MAAc,SACZC,EACAC,EAKY,CACZ,IAAMF,EAAK,MAAM,KAAK,cAAc,EAEpC,OAAO,MAAM,IAAI,QAAW,CAACD,EAASN,IAAW,CAC/C,IAAMU,EAAcZ,EAClBS,EAAG,YAAY,KAAK,UAAWC,CAAI,EACnCR,CACF,EAEAU,EAAY,QAAWT,GAAU,CAC/BD,EAAOC,CAAK,CACd,EAEA,IAAMU,EAAcD,EAAY,YAAY,KAAK,SAAS,EAE1DD,EAASE,EAAaL,EAASN,CAAM,CACvC,CAAC,CACH,CAGA,MAAa,IACXY,EACAC,EACAC,EAAmC,KAAK,gBAC5B,CACZ,IAAMC,EAAQ,KAAK,IAAI,EACjBlB,EAAyB,CAC7B,IAAAe,EACA,MAAAC,EACA,SAAUE,EACV,SAAUA,EAAQV,EAAiBS,CAAa,CAClD,EAEA,OAAO,MAAM,KAAK,SAChB,YACA,CAACH,EAAaL,EAASN,IAAW,CAChC,IAAMD,EAAUD,EAAYa,EAAY,IAAId,CAAG,EAAGG,CAAM,EAExDD,EAAQ,UAAY,IAAM,CACxBO,EAAQO,CAAK,EAEb,KAAK,GAAG,CACV,CACF,CACF,CACF,CAGA,MAAa,OAAOD,EAAuC,CACzD,OAAO,MAAM,KAAK,SAChB,YACA,CAACD,EAAaL,EAASN,IAAW,CAKhC,GAJAW,EAAY,YAAY,WAAa,IAAM,CACzCL,EAAQ,CACV,EAEI,OAAOM,GAAQ,SACjBd,EAAYa,EAAY,OAAOC,CAAG,EAAGZ,CAAM,MAE3C,SAAWgB,KAAKJ,EACdd,EAAYa,EAAY,OAAOK,CAAC,EAAGhB,CAAM,CAG/C,CACF,CACF,CAGA,MAAa,gBACXY,EACwC,CACxC,IAAMK,EAAS,MAAM,KAAK,SACxB,WACA,CAACN,EAAaL,EAASN,IAAW,CAChC,IAAMD,EAAUD,EAAYa,EAAY,IAAIC,CAAG,EAAGZ,CAAM,EAExDD,EAAQ,UAAY,IAAM,CACxBO,EAAQP,EAAQ,MAAM,CACxB,CACF,CACF,EAEA,GAAKkB,EAIL,GAAI,CACF,IAAMpB,EAAMD,EAAqBqB,CAAM,EACvC,GAAI,CAACpB,EAAK,CACR,MAAM,KAAK,OAAOe,CAAG,EAErB,KAAK,GAAG,EAER,MACF,CAEA,OAAOf,CACT,OAASqB,EAAG,CACV,QAAQ,MAAM,qBAAqBN,CAAG,IAAI,KAAK,UAAUK,CAAM,CAAC,IAAKC,CAAC,EACtE,MAAM,KAAK,OAAON,CAAG,EAErB,KAAK,GAAG,EAER,MACF,CACF,CAGA,MAAa,IAAOA,EAAqC,CAGvD,OAFY,MAAM,KAAK,gBAAmBA,CAAG,IAEjC,KACd,CAGA,MAAa,QACXH,EAMe,CACf,MAAM,KAAK,SAAe,WAAY,CAACE,EAAaL,EAASN,IAAW,CACtE,IAAMD,EAAUD,EAAYa,EAAY,WAAW,EAAGX,CAAM,EAE5DD,EAAQ,UAAY,MAAOE,GAAU,CACnC,IAAMkB,EACJlB,EAAM,OACN,OAEF,GAAIkB,EAAQ,CACV,GAAIA,EAAO,IAAK,CACd,IAAMtB,EAAMD,EAAqBuB,EAAO,KAAK,EACzCtB,IAAQ,QACV,MAAMY,EACJ,OAAOU,EAAO,GAAG,EACjBtB,EAAI,MACJA,EAAI,SACJA,EAAI,QACN,CAEJ,CACAsB,EAAO,SAAS,CAClB,MACEb,EAAQ,CAEZ,CACF,CAAC,CACH,CAOA,MAAa,MAAwB,CACnC,IAAIc,EAAQ,EACZ,aAAM,KAAK,QAAQ,IAAM,CACvBA,GACF,CAAC,EACMA,CACT,CAGA,MAAa,OAAuB,CAClC,MAAM,KAAK,SAAe,YAAa,CAACT,EAAaL,EAASN,IAAW,CACvE,IAAMD,EAAUD,EAAYa,EAAY,MAAM,EAAGX,CAAM,EAEvDD,EAAQ,UAAY,IAAM,CACxBO,EAAQ,CACV,CACF,CAAC,CACH,CAOA,MAAa,OAAkD,CAC7D,IAAMe,EAAM,IAAI,IAChB,aAAM,KAAK,QAAQ,CAACT,EAAKC,EAAOS,EAAUC,IAAa,CACrDF,EAAI,IAAIT,EAAK,CAAE,MAAOC,EAAY,SAAAS,EAAU,SAAAC,CAAS,CAAC,CACxD,CAAC,EACMF,CACT,CAGA,IAAW,UAAmB,CAC5B,IAAMG,EAAc,WAAW,aAAa,QAAQ,KAAK,cAAc,EACvE,GAAI,CAACA,EAAa,MAAO,GAEzB,IAAMC,EAAK,OAAOD,CAAW,EAC7B,OAAO,MAAMC,CAAE,EAAI,EAAIA,CACzB,CAGA,IAAW,SAASA,EAAY,CAC9B,WAAW,aAAa,QAAQ,KAAK,eAAgB,OAAOA,CAAE,CAAC,CACjE,CAGA,MAAa,IAAoB,CAC/B,IAAMC,EAAW,KAAK,SAGtB,GAAI,CAACA,EAAU,CACb,KAAK,SAAW,KAAK,IAAI,EACzB,MACF,CAEI,KAAK,IAAI,EAAIA,EAAW,KAAK,cAKjC,MAAM,KAAK,MAAM,CACnB,CAMA,MAAa,OAAuB,CAClC,QAAQ,IAAI,0BAA0B,KAAK,MAAM,KAAK,KAAK,SAAS,KAAK,EAGzE,KAAK,SAAW,KAAK,IAAI,EAEzB,IAAMC,EAAyB,CAAC,EAChC,MAAM,KAAK,QACT,MAAOf,EAAaC,EAAgBS,IAAqB,EACnDT,IAAU,QAAa,KAAK,IAAI,GAAKS,IACvCK,EAAa,KAAKf,CAAG,CAEzB,CACF,EAEIe,EAAa,QACf,MAAM,KAAK,OAAOA,CAAY,EAGhC,QAAQ,IACN,0BAA0B,KAAK,MAAM,KAAK,KAAK,SAAS,cACzCA,EAAa,MAAM,OACpC,EAGA,KAAK,SAAW,KAAK,IAAI,CAC3B,CAGO,kBAAyC,CAC9C,OAAO,IACT,CACF,EAMaC,EAAU,IAAI1B,EAAQT,EAAc,MAAM,EAK1CoC,EAAN,KAAqB,CAGnB,YACWjB,EAChBkB,EAAqCrC,EAAc,SACnCsC,EAAQH,EACxB,CAHgB,SAAAhB,EAEA,WAAAmB,EAEhB,KAAK,gBAAkBD,GAAmBzB,EAAiByB,CAAe,CAC5E,CARgB,gBAWhB,MAAa,IACXjB,EACAC,EAAoC,KAAK,gBAC1B,CACf,MAAM,KAAK,MAAM,IAAI,KAAK,IAAKD,EAAOC,CAAa,CACrD,CAQA,MAAa,iBAA0D,CACrE,OAAO,MAAM,KAAK,MAAM,gBAAgB,KAAK,GAAG,CAClD,CAGA,MAAa,KAA8B,CACzC,OAAO,MAAM,KAAK,MAAM,IAAI,KAAK,GAAG,CACtC,CAGA,MAAa,QAAwB,CACnC,MAAM,KAAK,MAAM,OAAO,KAAK,GAAG,CAClC,CACF,EAGO,SAASkB,EACdpB,EACAU,EACAS,EAAQH,EACQ,CAChB,OAAAN,EAAWA,GAAYjB,EAAiBiB,CAAQ,EAEzC,IAAIO,EAAejB,EAAKU,EAAUS,CAAK,CAChD","names":["kvStore_exports","__export","KvStore","KvStoreConfig","KvStoreItem","configureKvStore","kvStore","kvStoreItem","__toCommonJS","durationToMs","duration","daysMs","hoursMs","minsMs","secsMs","msMs","durationOrMsToMs","KvStoreConfig","configureKvStore","config","validateStoredObject","obj","withOnError","request","reject","event","KvStore","dbName","options","durationOrMsToMs","resolve","db","mode","callback","transaction","objectStore","key","value","expiryDeltaMs","nowMs","k","stored","e","cursor","count","map","expiryMs","storedMs","lastGcMsStr","ms","lastGcMs","keysToDelete","kvStore","KvStoreItem","defaultExpiryMs","store","kvStoreItem"]}
1
+ {"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"]}
package/kvStore.min.mjs CHANGED
@@ -1,2 +1,2 @@
1
- function D(r){let t=(r.days??0)*864e5,e=(r.hours??0)*36e5,s=(r.minutes??0)*6e4,n=(r.seconds??0)*1e3,o=r.milliseconds??0;return t+e+s+n+o}function d(r){return typeof r=="number"?r:D(r)}var l={dbName:"KVStore",dbVersion:1,storeName:"kvStore",expiryMs:864e5*30,gcIntervalMs:864e5};function R(r){Object.assign(l,r)}function f(r){if(!(!r||typeof r!="object"||typeof r.key!="string"||r.value===void 0||typeof r.storedMs!="number"||typeof r.expiryMs!="number"||Date.now()>=r.expiryMs))return r}function c(r,t){return r.onerror=e=>{t(e)},r}var b=class{constructor(t,e){this.dbName=t;this.dbVersion=e?.dbVersion??l.dbVersion,this.storeName=e?.storeName??l.storeName,this.defaultExpiryMs=e?.defaultExpiryMs?d(e.defaultExpiryMs):l.expiryMs,this.gcIntervalMs=e?.gcIntervalMs?d(e.gcIntervalMs):l.gcIntervalMs,this.gcMsStorageKey=`__kvStore:lastGcMs:${t}:v${this.dbVersion}:${this.storeName}`}db;gcMsStorageKey;dbVersion;storeName;defaultExpiryMs;gcIntervalMs;async getOrCreateDb(){return this.db||(this.db=await new Promise((t,e)=>{let s=c(globalThis.indexedDB.open(this.dbName,this.dbVersion),e);s.onupgradeneeded=n=>{n.target.result.createObjectStore(this.storeName,{keyPath:"key"}).createIndex("key","key",{unique:!0})},s.onsuccess=n=>{let o=n.target.result;t(o)}})),this.db}async transact(t,e){let s=await this.getOrCreateDb();return await new Promise((n,o)=>{let i=c(s.transaction(this.storeName,t),o);i.onabort=u=>{o(u)};let a=i.objectStore(this.storeName);e(a,n,o)})}async set(t,e,s=this.defaultExpiryMs){let n=Date.now(),o={key:t,value:e,storedMs:n,expiryMs:n+d(s)};return await this.transact("readwrite",(i,a,u)=>{let m=c(i.put(o),u);m.onsuccess=()=>{a(e),this.gc()}})}async delete(t){return await this.transact("readwrite",(e,s,n)=>{if(e.transaction.oncomplete=()=>{s()},typeof t=="string")c(e.delete(t),n);else for(let o of t)c(e.delete(o),n)})}async getStoredObject(t){let e=await this.transact("readonly",(s,n,o)=>{let i=c(s.get(t),o);i.onsuccess=()=>{n(i.result)}});if(e)try{let s=f(e);if(!s){await this.delete(t),this.gc();return}return s}catch(s){console.error(`Invalid kv value: ${t}=${JSON.stringify(e)}:`,s),await this.delete(t),this.gc();return}}async get(t){return(await this.getStoredObject(t))?.value}async forEach(t){await this.transact("readonly",(e,s,n)=>{let o=c(e.openCursor(),n);o.onsuccess=async i=>{let a=i.target.result;if(a){if(a.key){let u=f(a.value);u!==void 0&&await t(String(a.key),u.value,u.expiryMs,u.storedMs)}a.continue()}else s()}})}async size(){let t=0;return await this.forEach(()=>{t++}),t}async clear(){await this.transact("readwrite",(t,e,s)=>{let n=c(t.clear(),s);n.onsuccess=()=>{e()}})}async asMap(){let t=new Map;return await this.forEach((e,s,n,o)=>{t.set(e,{value:s,expiryMs:n,storedMs:o})}),t}get lastGcMs(){let t=globalThis.localStorage.getItem(this.gcMsStorageKey);if(!t)return 0;let e=Number(t);return isNaN(e)?0:e}set lastGcMs(t){globalThis.localStorage.setItem(this.gcMsStorageKey,String(t))}async gc(){let t=this.lastGcMs;if(!t){this.lastGcMs=Date.now();return}Date.now()<t+this.gcIntervalMs||await this.gcNow()}async gcNow(){console.log(`Starting kvStore GC on ${this.dbName} v${this.dbVersion}...`),this.lastGcMs=Date.now();let t=[];await this.forEach(async(e,s,n)=>{(s===void 0||Date.now()>=n)&&t.push(e)}),t.length&&await this.delete(t),console.log(`Finished kvStore GC on ${this.dbName} v${this.dbVersion} - deleted ${t.length} keys`),this.lastGcMs=Date.now()}asStorageAdapter(){return this}},h=new b(l.dbName),g=class{constructor(t,e=l.expiryMs,s=h){this.key=t;this.store=s;this.defaultExpiryMs=e&&d(e)}defaultExpiryMs;async set(t,e=this.defaultExpiryMs){await this.store.set(this.key,t,e)}async getStoredObject(){return await this.store.getStoredObject(this.key)}async get(){return await this.store.get(this.key)}async delete(){await this.store.delete(this.key)}};function I(r,t,e=h){return t=t&&d(t),new g(r,t,e)}export{b as KvStore,l as KvStoreConfig,g as KvStoreItem,R as configureKvStore,h as kvStore,I as kvStoreItem};
1
+ function x(t){let i=(t.days??0)*864e5,u=(t.hours??0)*36e5,c=(t.minutes??0)*6e4,f=(t.seconds??0)*1e3,p=t.milliseconds??0;return i+u+c+f+p}function b(t){return typeof t=="number"?t:x(t)}var y={dbName:"KVStore",dbVersion:1,storeName:"kvStore",expiryMs:864e5*30,gcIntervalMs:864e5};function N(t){Object.assign(y,t)}function v(t){if(!(!t||typeof t!="object"||typeof t.key!="string"||t.value===void 0||typeof t.storedMs!="number"||typeof t.expiryMs!="number"||Date.now()>=t.expiryMs))return t}function m(t,i){return t.onerror=u=>{i(u)},t}function P(t,i){let u,c=i?.dbVersion??y.dbVersion,f=i?.storeName??y.storeName,p=i?.defaultExpiryMs?b(i.defaultExpiryMs):y.expiryMs,D=i?.gcIntervalMs?b(i.gcIntervalMs):y.gcIntervalMs,M=`__kvStore:lastGcMs:${t}:v${c}:${f}`;async function w(){return u||(u=await new Promise((e,r)=>{let n=m(indexedDB.open(t,c),r);n.onupgradeneeded=o=>{o.target.result.createObjectStore(f,{keyPath:"key"}).createIndex("key","key",{unique:!0})},n.onsuccess=o=>{let a=o.target.result;e(a)}})),u}async function S(e,r){let n=await w();return await new Promise((o,a)=>{let d=m(n.transaction(f,e),a);d.onabort=g=>{a(g)};let l=d.objectStore(f);r(l,o,a)})}let s={dbName:t,dbVersion:c,storeName:f,defaultExpiryMs:p,gcIntervalMs:D,gcMsStorageKey:M,async set(e,r,n){let o=Date.now(),a={key:e,value:r,storedMs:o,expiryMs:o+b(n??p)};return await S("readwrite",(d,l,g)=>{let h=m(d.put(a),g);h.onsuccess=()=>{l(r),s.gc()}})},async delete(e){return await S("readwrite",(r,n,o)=>{if(r.transaction.oncomplete=()=>{n()},typeof e=="string")m(r.delete(e),o);else for(let a of e)m(r.delete(a),o)})},async getStoredObject(e){let r=await S("readonly",(n,o,a)=>{let d=m(n.get(e),a);d.onsuccess=()=>{o(d.result)}});if(r)try{let n=v(r);if(!n){await s.delete(e),s.gc();return}return n}catch(n){console.error(`Invalid kv value: ${e}=${JSON.stringify(r)}:`,n),await s.delete(e),s.gc();return}},async get(e){return(await s.getStoredObject(e))?.value},async forEach(e){await S("readonly",(r,n,o)=>{let a=m(r.openCursor(),o);a.onsuccess=async d=>{let l=d.target.result;if(l){if(l.key){let g=v(l.value);g!==void 0&&await e(String(l.key),g.value,g.expiryMs,g.storedMs)}l.continue()}else n()}})},async size(){let e=0;return await s.forEach(()=>{e++}),e},async clear(){await S("readwrite",(e,r,n)=>{let o=m(e.clear(),n);o.onsuccess=()=>{r()}})},async asMap(){let e=new Map;return await s.forEach((r,n,o,a)=>{e.set(r,{value:n,expiryMs:o,storedMs:a})}),e},getLastGcMs(){let e=localStorage.getItem(M);if(!e)return 0;let r=Number(e);return isNaN(r)?0:r},setLastGcMs(e){localStorage.setItem(M,String(e))},async gc(){let e=s.getLastGcMs();if(!e){s.setLastGcMs(Date.now());return}Date.now()<e+D||await s.gcNow()},async gcNow(){console.log(`Starting kvStore GC on ${t} v${c}...`),s.setLastGcMs(Date.now());let e=[];await s.forEach(async(r,n,o)=>{(n===void 0||Date.now()>=o)&&e.push(r)}),e.length&&await s.delete(e),console.log(`Finished kvStore GC on ${t} v${c} - deleted ${e.length} keys`),s.setLastGcMs(Date.now())},asStorageAdapter(){return s}};return s}var O=P(y.dbName);function R(t,i,u=O){let c=i&&b(i);return{key:t,defaultExpiryMs:c,store:u,async set(p,D){await u.set(t,p,D??c)},async getStoredObject(){return await u.getStoredObject(t)},async get(){return await u.get(t)},async delete(){await u.delete(t)}}}export{y as KvStoreConfig,N as configureKvStore,P as createKvStore,O as kvStore,R as kvStoreItem};
2
2
  //# sourceMappingURL=kvStore.min.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/duration.ts","../src/kvStore.ts"],"sourcesContent":["/**\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","/**\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 */\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\nexport class KvStore implements FullStorageAdapter<any> {\n /** We'll init the DB only on first use. */\n private db: IDBDatabase | undefined;\n\n /** Local storage key name for the last GC completed timestamp. */\n public readonly gcMsStorageKey: string;\n\n public readonly dbVersion: number;\n public readonly storeName: string;\n public readonly defaultExpiryMs: number;\n public readonly gcIntervalMs: number;\n\n public constructor(\n public readonly dbName: string,\n options?: {\n dbVersion?: number;\n storeName?: string;\n defaultExpiryMs?: number | Duration;\n gcIntervalMs?: number | Duration;\n },\n ) {\n this.dbVersion = options?.dbVersion ?? KvStoreConfig.dbVersion;\n this.storeName = options?.storeName ?? KvStoreConfig.storeName;\n\n this.defaultExpiryMs = options?.defaultExpiryMs\n ? durationOrMsToMs(options.defaultExpiryMs)\n : KvStoreConfig.expiryMs;\n\n this.gcIntervalMs = options?.gcIntervalMs\n ? durationOrMsToMs(options.gcIntervalMs)\n : KvStoreConfig.gcIntervalMs;\n\n this.gcMsStorageKey = `__kvStore:lastGcMs:${dbName}:v${this.dbVersion}:${this.storeName}`;\n }\n\n private async getOrCreateDb() {\n if (!this.db) {\n this.db = await new Promise<IDBDatabase>((resolve, reject) => {\n const request = withOnError(\n globalThis.indexedDB.open(this.dbName, this.dbVersion),\n reject,\n );\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(this.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 this.db;\n }\n\n private async 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 this.getOrCreateDb();\n\n return await new Promise<T>((resolve, reject) => {\n const transaction = withOnError(\n db.transaction(this.storeName, mode),\n reject,\n );\n\n transaction.onabort = (event) => {\n reject(event);\n };\n\n const objectStore = transaction.objectStore(this.storeName);\n\n callback(objectStore, resolve, reject);\n });\n }\n\n /** Set a value in the store. */\n public async set<T>(\n key: string,\n value: T,\n expiryDeltaMs: number | Duration = this.defaultExpiryMs,\n ): Promise<T> {\n const nowMs = Date.now();\n const obj: KvStoredObject<T> = {\n key,\n value,\n storedMs: nowMs,\n expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs),\n };\n\n return await this.transact<T>(\n \"readwrite\",\n (objectStore, resolve, reject) => {\n const request = withOnError(objectStore.put(obj), reject);\n\n request.onsuccess = () => {\n resolve(value);\n\n this.gc(); // check GC on every write\n };\n },\n );\n }\n\n /** Delete one or multiple keys. */\n public async delete(key: string | string[]): Promise<void> {\n return await this.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 public async getStoredObject<T>(\n key: string,\n ): Promise<KvStoredObject<T> | undefined> {\n const stored = await this.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 obj = validateStoredObject(stored);\n if (!obj) {\n await this.delete(key);\n\n this.gc(); // check GC on every read of an expired key\n\n return undefined;\n }\n\n return obj;\n } catch (e) {\n console.error(`Invalid kv value: ${key}=${JSON.stringify(stored)}:`, e);\n await this.delete(key);\n\n this.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 public async get<T>(key: string): Promise<T | undefined> {\n const obj = await this.getStoredObject<T>(key);\n\n return obj?.value;\n }\n\n /** Generic way to iterate through all entries. */\n public 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 this.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 obj = validateStoredObject(cursor.value);\n if (obj !== undefined) {\n await callback(\n String(cursor.key),\n obj.value as T,\n obj.expiryMs,\n obj.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 public async size(): Promise<number> {\n let count = 0;\n await this.forEach(() => {\n count++;\n });\n return count;\n }\n\n /** Remove all items from the store. */\n public async clear(): Promise<void> {\n await this.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 public async asMap<T>(): Promise<Map<string, StoredObject<T>>> {\n const map = new Map<string, StoredObject<T>>();\n await this.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 public get lastGcMs(): number {\n const lastGcMsStr = globalThis.localStorage.getItem(this.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 public set lastGcMs(ms: number) {\n globalThis.localStorage.setItem(this.gcMsStorageKey, String(ms));\n }\n\n /** Perform garbage-collection if due, else do nothing. */\n public async gc(): Promise<void> {\n const lastGcMs = this.lastGcMs;\n\n // Set initial timestamp - no need GC now.\n if (!lastGcMs) {\n this.lastGcMs = Date.now();\n return;\n }\n\n if (Date.now() < lastGcMs + this.gcIntervalMs) {\n return; // not due for next GC yet\n }\n\n // GC is due now, so run it.\n await this.gcNow();\n }\n\n /**\n * Perform garbage collection immediately without checking whether we are\n * due for the next GC or not.\n */\n public async gcNow(): Promise<void> {\n console.log(`Starting kvStore GC on ${this.dbName} v${this.dbVersion}...`);\n\n // Prevent concurrent GC runs.\n this.lastGcMs = Date.now();\n\n const keysToDelete: string[] = [];\n await this.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 this.delete(keysToDelete);\n }\n\n console.log(\n `Finished kvStore GC on ${this.dbName} v${this.dbVersion} ` +\n `- deleted ${keysToDelete.length} keys`,\n );\n\n // Mark the end time as last GC time.\n this.lastGcMs = Date.now();\n }\n\n /** Returns `this` casted into a StorageAdapter<T>. */\n public asStorageAdapter<T>(): StorageAdapter<T> {\n return this as StorageAdapter<T>;\n }\n}\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 = new KvStore(KvStoreConfig.dbName);\n\n/**\n * Class to represent one key in the store with a default expiration.\n */\nexport class KvStoreItem<T> {\n public readonly defaultExpiryMs: number;\n\n public constructor(\n public readonly key: string,\n defaultExpiryMs: number | Duration = KvStoreConfig.expiryMs,\n public readonly store = kvStore,\n ) {\n this.defaultExpiryMs = defaultExpiryMs && durationOrMsToMs(defaultExpiryMs);\n }\n\n /** Set a value in the store. */\n public async set(\n value: T,\n expiryDeltaMs: number | undefined = this.defaultExpiryMs,\n ): Promise<void> {\n await this.store.set(this.key, value, expiryDeltaMs);\n }\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs, storedMs } =\n * await myKvItem.getStoredObject();\n */\n public async getStoredObject(): Promise<KvStoredObject<T> | undefined> {\n return await this.store.getStoredObject(this.key);\n }\n\n /** Get a value by key, or undefined if it does not exist. */\n public async get(): Promise<T | undefined> {\n return await this.store.get(this.key);\n }\n\n /** Delete this key from the store. */\n public async delete(): Promise<void> {\n await this.store.delete(this.key);\n }\n}\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,\n): KvStoreItem<T> {\n expiryMs = expiryMs && durationOrMsToMs(expiryMs);\n\n return new KvStoreItem<T>(key, expiryMs, store);\n}\n"],"mappings":"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","names":["durationToMs","duration","daysMs","hoursMs","minsMs","secsMs","msMs","durationOrMsToMs","KvStoreConfig","configureKvStore","config","validateStoredObject","obj","withOnError","request","reject","event","KvStore","dbName","options","durationOrMsToMs","resolve","db","mode","callback","transaction","objectStore","key","value","expiryDeltaMs","nowMs","k","stored","e","cursor","count","map","expiryMs","storedMs","lastGcMsStr","ms","lastGcMs","keysToDelete","kvStore","KvStoreItem","defaultExpiryMs","store","kvStoreItem"]}
1
+ {"version":3,"sources":["../src/duration.ts","../src/kvStore.ts"],"sourcesContent":["/**\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","/**\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"],"mappings":"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","names":["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"]}