@choksheak/ts-utils 0.2.1 → 0.2.2
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.
- package/duration.d.ts +2 -0
- package/duration.js +11 -0
- package/duration.js.map +1 -1
- package/kvStore.js.map +1 -1
- package/localStorageCache.js.map +1 -1
- package/package.json +1 -1
- package/src/duration.ts +16 -0
- package/src/timer.ts +33 -0
- package/timer.d.ts +11 -0
- package/timer.js +189 -0
- package/timer.js.map +1 -0
package/duration.d.ts
CHANGED
package/duration.js
CHANGED
|
@@ -24,6 +24,7 @@ __export(duration_exports, {
|
|
|
24
24
|
DURATION_TYPE_SEQUENCE: () => DURATION_TYPE_SEQUENCE,
|
|
25
25
|
durationOrMsToMs: () => durationOrMsToMs,
|
|
26
26
|
durationToMs: () => durationToMs,
|
|
27
|
+
elapsed: () => elapsed,
|
|
27
28
|
formatDuration: () => formatDuration,
|
|
28
29
|
msToDuration: () => msToDuration,
|
|
29
30
|
readableDuration: () => readableDuration
|
|
@@ -166,12 +167,22 @@ function readableDuration(ms, options) {
|
|
|
166
167
|
const duration = msToDuration(ms, options == null ? void 0 : options.durationTypeForZero);
|
|
167
168
|
return formatDuration(duration, options == null ? void 0 : options.style);
|
|
168
169
|
}
|
|
170
|
+
function elapsed(ms) {
|
|
171
|
+
if (ms > MS_PER_MINUTE) {
|
|
172
|
+
return readableDuration(ms);
|
|
173
|
+
}
|
|
174
|
+
if (ms > 100) {
|
|
175
|
+
return `${(ms / 1e3).toFixed(3)}s`;
|
|
176
|
+
}
|
|
177
|
+
return ms + "ms";
|
|
178
|
+
}
|
|
169
179
|
// Annotate the CommonJS export names for ESM import in node:
|
|
170
180
|
0 && (module.exports = {
|
|
171
181
|
DURATION_STYLE_SUFFIX_MAP,
|
|
172
182
|
DURATION_TYPE_SEQUENCE,
|
|
173
183
|
durationOrMsToMs,
|
|
174
184
|
durationToMs,
|
|
185
|
+
elapsed,
|
|
175
186
|
formatDuration,
|
|
176
187
|
msToDuration,
|
|
177
188
|
readableDuration
|
package/duration.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/duration.ts","../src/timeConstants.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 MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n MINUTES_PER_HOUR,\n HOURS_PER_DAY,\n MS_PER_DAY,\n MS_PER_MINUTE,\n MS_PER_HOUR,\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 * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nexport const MS_PER_SECOND = 1000;\nexport const MS_PER_MINUTE = 60_000;\nexport const MS_PER_HOUR = 3_600_000;\nexport const MS_PER_DAY = 86_400_000;\nexport const MS_PER_WEEK = 604_800_000;\n\nexport const SECONDS_PER_MINUTE = 60;\nexport const SECONDS_PER_HOUR = 3_600;\nexport const SECONDS_PER_DAY = 86_400;\nexport const SECONDS_PER_WEEK = 604_800;\n\nexport const MINUTES_PER_HOUR = 60;\nexport const MINUTES_PER_DAY = 1440;\nexport const MINUTES_PER_WEEK = 10_080;\n\nexport const HOURS_PER_DAY = 24;\nexport const HOURS_PER_WEEK = 168;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/duration.ts","../src/timeConstants.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 MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n MINUTES_PER_HOUR,\n HOURS_PER_DAY,\n MS_PER_DAY,\n MS_PER_MINUTE,\n MS_PER_HOUR,\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 * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nexport const MS_PER_SECOND = 1000;\nexport const MS_PER_MINUTE = 60_000;\nexport const MS_PER_HOUR = 3_600_000;\nexport const MS_PER_DAY = 86_400_000;\nexport const MS_PER_WEEK = 604_800_000;\n\nexport const SECONDS_PER_MINUTE = 60;\nexport const SECONDS_PER_HOUR = 3_600;\nexport const SECONDS_PER_DAY = 86_400;\nexport const SECONDS_PER_WEEK = 604_800;\n\nexport const MINUTES_PER_HOUR = 60;\nexport const MINUTES_PER_DAY = 1440;\nexport const MINUTES_PER_WEEK = 10_080;\n\nexport const HOURS_PER_DAY = 24;\nexport const HOURS_PER_WEEK = 168;\n"],"mappings":"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","names":[]}
|
package/kvStore.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/kvStore.ts","../src/timeConstants.ts","../src/duration.ts"],"sourcesContent":["/**\n * Indexed DB key-value store with support for auto-expirations.\n *\n * Why use this?\n * 1. No need to worry about running out of storage.\n * 2. Extremely simple interface to use indexed DBs.\n * 3. Auto-expirations frees you from worrying about data clean-up.\n * 4. 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.\n */\n\nimport { Duration, durationOrMsToMs } from \"./duration\";\n\n// Updating the DB name will cause all old entries to be gone.\nconst DEFAULT_DB_NAME = \"KVStore\";\n\n// Updating the version will cause all old entries to be gone.\nconst DEFAULT_DB_VERSION = 1;\n\n// Use a constant store name to keep things simple.\nconst STORE_NAME = \"kvStore\";\n\n/** One day in milliseconds. */\nexport const MILLIS_PER_DAY = 86_400_000;\n\n/** 30 days in ms. */\nexport const DEFAULT_EXPIRY_DELTA_MS = MILLIS_PER_DAY * 30;\n\n/** Do GC once per day. */\nexport const GC_INTERVAL_MS = MILLIS_PER_DAY;\n\ntype StoredObject<T> = {\n key: string;\n value: T;\n storedMs: number;\n expiryMs: number;\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: StoredObject<T>,\n): StoredObject<T> | undefined {\n if (\n !obj ||\n typeof obj !== \"object\" ||\n !(\"key\" in obj) ||\n typeof obj.key !== \"string\" ||\n !(\"value\" in obj) ||\n !(\"storedMs\" in obj) ||\n typeof obj.storedMs !== \"number\" ||\n obj.value === undefined ||\n !(\"expiryMs\" in obj) ||\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\nexport class KVStoreField<T> {\n public constructor(\n public readonly store: KVStore,\n public readonly key: string,\n ) {}\n\n public get(): Promise<T | undefined> {\n return this.store.get(this.key);\n }\n\n public set(t: T): Promise<T> {\n return this.store.set(this.key, t);\n }\n\n public delete(): Promise<void> {\n return this.store.delete(this.key);\n }\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 class KVStore {\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 constructor(\n public readonly dbName: string,\n public readonly dbVersion: number,\n public readonly defaultExpiryDeltaMs: number,\n ) {\n this.gcMsStorageKey = `__kvStore:lastGcMs:${dbName}:v${dbVersion}:${STORE_NAME}`;\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(STORE_NAME, {\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(db.transaction(STORE_NAME, mode), reject);\n\n transaction.onabort = (event) => {\n reject(event);\n };\n\n const objectStore = transaction.objectStore(STORE_NAME);\n\n callback(objectStore, resolve, reject);\n });\n }\n\n public async set<T>(\n key: string,\n value: T,\n expiryDeltaMs: number | Duration = this.defaultExpiryDeltaMs,\n ): Promise<T> {\n const nowMs = Date.now();\n const obj: StoredObject<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 public async getStoredObject<T>(\n key: string,\n ): Promise<StoredObject<T> | undefined> {\n const stored = await this.transact<StoredObject<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 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 public async forEach(\n callback: (\n key: string,\n value: unknown,\n expiryMs: 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) {\n await callback(String(cursor.key), obj.value, obj.expiryMs);\n } else {\n await callback(String(cursor.key), undefined, 0);\n }\n }\n cursor.continue();\n } else {\n resolve();\n }\n };\n });\n }\n\n /** Cannot be a getter because this needs to be async. */\n public async size() {\n let count = 0;\n await this.forEach(() => {\n count++;\n });\n return count;\n }\n\n public async clear() {\n const keys: string[] = [];\n await this.forEach((key) => {\n keys.push(key);\n });\n\n await this.delete(keys);\n }\n\n /** Mainly for debugging dumps. */\n public async asMap(): Promise<Map<string, unknown>> {\n const map = new Map<string, unknown>();\n await this.forEach((key, value, expiryMs) => {\n map.set(key, { value, expiryMs });\n });\n return map;\n }\n\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 public set lastGcMs(ms: number) {\n globalThis.localStorage.setItem(this.gcMsStorageKey, String(ms));\n }\n\n /** Perform garbage-collection if due. */\n public async gc() {\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 + GC_INTERVAL_MS) {\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 /** Perform garbage-collection immediately without checking. */\n public async gcNow() {\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 /** Get an independent store item with a locked key and value type. */\n public field<T>(key: string) {\n return new KVStoreField<T>(this, key);\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(\n DEFAULT_DB_NAME,\n DEFAULT_DB_VERSION,\n DEFAULT_EXPIRY_DELTA_MS,\n);\n\n/**\n * Class to represent one key in the store with a default expiration.\n */\nclass KvStoreItem<T> {\n public constructor(\n public readonly key: string,\n public readonly defaultExpiryDeltaMs: number,\n public readonly store = kvStore,\n ) {}\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs } = await myKvItem.getStoredObject();\n */\n public async getStoredObject(): Promise<StoredObject<T> | undefined> {\n return await this.store.getStoredObject(this.key);\n }\n\n public async get(): Promise<T | undefined> {\n return await this.store.get(this.key);\n }\n\n public async set(\n value: T,\n expiryDeltaMs: number = this.defaultExpiryDeltaMs,\n ): Promise<void> {\n await this.store.set(this.key, value, expiryDeltaMs);\n }\n\n public async delete(): Promise<void> {\n await this.store.delete(this.key);\n }\n}\n\n/**\n * Create a KV store item with a key and a default expiration.\n */\nexport function kvStoreItem<T>(\n key: string,\n defaultExpiration: number | Duration,\n): KvStoreItem<T> {\n const defaultExpiryDeltaMs = durationOrMsToMs(defaultExpiration);\n\n return new KvStoreItem<T>(key, defaultExpiryDeltaMs);\n}\n","/**\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nexport const MS_PER_SECOND = 1000;\nexport const MS_PER_MINUTE = 60_000;\nexport const MS_PER_HOUR = 3_600_000;\nexport const MS_PER_DAY = 86_400_000;\nexport const MS_PER_WEEK = 604_800_000;\n\nexport const SECONDS_PER_MINUTE = 60;\nexport const SECONDS_PER_HOUR = 3_600;\nexport const SECONDS_PER_DAY = 86_400;\nexport const SECONDS_PER_WEEK = 604_800;\n\nexport const MINUTES_PER_HOUR = 60;\nexport const MINUTES_PER_DAY = 1440;\nexport const MINUTES_PER_WEEK = 10_080;\n\nexport const HOURS_PER_DAY = 24;\nexport const HOURS_PER_WEEK = 168;\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 MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n MINUTES_PER_HOUR,\n HOURS_PER_DAY,\n MS_PER_DAY,\n MS_PER_MINUTE,\n MS_PER_HOUR,\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKO,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AACtB,IAAM,cAAc;AACpB,IAAM,aAAa;;;ACkLnB,SAAS,aAAa,UAA4B;AA1LzD;AA2LE,QAAM,WAAU,cAAS,SAAT,YAAiB,KAAK;AACtC,QAAM,YAAW,cAAS,UAAT,YAAkB,KAAK;AACxC,QAAM,WAAU,cAAS,YAAT,YAAoB,KAAK;AACzC,QAAM,WAAU,cAAS,YAAT,YAAoB,KAAK;AACzC,QAAM,QAAO,cAAS,iBAAT,YAAyB;AAEtC,SAAO,SAAS,UAAU,SAAS,SAAS;AAC9C;AAKO,SAAS,iBAAiB,UAAqC;AACpE,SAAO,OAAO,aAAa,WAAW,WAAW,aAAa,QAAQ;AACxE;;;AFzLA,IAAM,kBAAkB;AAGxB,IAAM,qBAAqB;AAG3B,IAAM,aAAa;AAGZ,IAAM,iBAAiB;AAGvB,IAAM,0BAA0B,iBAAiB;AAGjD,IAAM,iBAAiB;AAa9B,SAAS,qBACP,KAC6B;AAC7B,MACE,CAAC,OACD,OAAO,QAAQ,YACf,EAAE,SAAS,QACX,OAAO,IAAI,QAAQ,YACnB,EAAE,WAAW,QACb,EAAE,cAAc,QAChB,OAAO,IAAI,aAAa,YACxB,IAAI,UAAU,UACd,EAAE,cAAc,QAChB,OAAO,IAAI,aAAa,YACxB,KAAK,IAAI,KAAK,IAAI,UAClB;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAGA,SAAS,YACP,SACA,QACG;AACH,UAAQ,UAAU,CAAC,UAAU;AAC3B,WAAO,KAAK;AAAA,EACd;AAEA,SAAO;AACT;AAEO,IAAM,eAAN,MAAsB;AAAA,EACpB,YACW,OACA,KAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAEI,MAA8B;AACnC,WAAO,KAAK,MAAM,IAAI,KAAK,GAAG;AAAA,EAChC;AAAA,EAEO,IAAI,GAAkB;AAC3B,WAAO,KAAK,MAAM,IAAI,KAAK,KAAK,CAAC;AAAA,EACnC;AAAA,EAEO,SAAwB;AAC7B,WAAO,KAAK,MAAM,OAAO,KAAK,GAAG;AAAA,EACnC;AACF;AAMO,IAAM,UAAN,MAAc;AAAA,EAOZ,YACW,QACA,WACA,sBAChB;AAHgB;AACA;AACA;AAEhB,SAAK,iBAAiB,sBAAsB,MAAM,KAAK,SAAS,IAAI,UAAU;AAAA,EAChF;AAAA,EAEc,gBAAgB;AAAA;AAC5B,UAAI,CAAC,KAAK,IAAI;AACZ,aAAK,KAAK,MAAM,IAAI,QAAqB,CAAC,SAAS,WAAW;AAC5D,gBAAM,UAAU;AAAA,YACd,WAAW,UAAU,KAAK,KAAK,QAAQ,KAAK,SAAS;AAAA,YACrD;AAAA,UACF;AAEA,kBAAQ,kBAAkB,CAAC,UAAU;AACnC,kBAAM,KAAM,MAAM,OACf;AAGH,kBAAM,cAAc,GAAG,kBAAkB,YAAY;AAAA,cACnD,SAAS;AAAA,YACX,CAAC;AAED,wBAAY,YAAY,OAAO,OAAO;AAAA,cACpC,QAAQ;AAAA,YACV,CAAC;AAAA,UACH;AAEA,kBAAQ,YAAY,CAAC,UAAU;AAC7B,kBAAM,KAAM,MAAM,OACf;AACH,oBAAQ,EAAE;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH;AAEA,aAAO,KAAK;AAAA,IACd;AAAA;AAAA,EAEc,SACZ,MACA,UAKY;AAAA;AACZ,YAAM,KAAK,MAAM,KAAK,cAAc;AAEpC,aAAO,MAAM,IAAI,QAAW,CAAC,SAAS,WAAW;AAC/C,cAAM,cAAc,YAAY,GAAG,YAAY,YAAY,IAAI,GAAG,MAAM;AAExE,oBAAY,UAAU,CAAC,UAAU;AAC/B,iBAAO,KAAK;AAAA,QACd;AAEA,cAAM,cAAc,YAAY,YAAY,UAAU;AAEtD,iBAAS,aAAa,SAAS,MAAM;AAAA,MACvC,CAAC;AAAA,IACH;AAAA;AAAA,EAEa,IACX,IACA,IAEY;AAAA,+CAHZ,KACA,OACA,gBAAmC,KAAK,sBAC5B;AACZ,YAAM,QAAQ,KAAK,IAAI;AACvB,YAAM,MAAuB;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV,UAAU,QAAQ,iBAAiB,aAAa;AAAA,MAClD;AAEA,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA,CAAC,aAAa,SAAS,WAAW;AAChC,gBAAM,UAAU,YAAY,YAAY,IAAI,GAAG,GAAG,MAAM;AAExD,kBAAQ,YAAY,MAAM;AACxB,oBAAQ,KAAK;AAEb,iBAAK,GAAG;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;AAAA,EAGa,OAAO,KAAuC;AAAA;AACzD,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA,CAAC,aAAa,SAAS,WAAW;AAChC,sBAAY,YAAY,aAAa,MAAM;AACzC,oBAAQ;AAAA,UACV;AAEA,cAAI,OAAO,QAAQ,UAAU;AAC3B,wBAAY,YAAY,OAAO,GAAG,GAAG,MAAM;AAAA,UAC7C,OAAO;AACL,uBAAW,KAAK,KAAK;AACnB,0BAAY,YAAY,OAAO,CAAC,GAAG,MAAM;AAAA,YAC3C;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA,EAEa,gBACX,KACsC;AAAA;AACtC,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB;AAAA,QACA,CAAC,aAAa,SAAS,WAAW;AAChC,gBAAM,UAAU,YAAY,YAAY,IAAI,GAAG,GAAG,MAAM;AAExD,kBAAQ,YAAY,MAAM;AACxB,oBAAQ,QAAQ,MAAM;AAAA,UACxB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,MACT;AAEA,UAAI;AACF,cAAM,MAAM,qBAAqB,MAAM;AACvC,YAAI,CAAC,KAAK;AACR,gBAAM,KAAK,OAAO,GAAG;AAErB,eAAK,GAAG;AAER,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,MACT,SAAS,GAAG;AACV,gBAAQ,MAAM,qBAAqB,GAAG,IAAI,KAAK,UAAU,MAAM,CAAC,KAAK,CAAC;AACtE,cAAM,KAAK,OAAO,GAAG;AAErB,aAAK,GAAG;AAER,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA,EAEa,IAAO,KAAqC;AAAA;AACvD,YAAM,MAAM,MAAM,KAAK,gBAAmB,GAAG;AAE7C,aAAO,2BAAK;AAAA,IACd;AAAA;AAAA,EAEa,QACX,UAKe;AAAA;AACf,YAAM,KAAK,SAAe,YAAY,CAAC,aAAa,SAAS,WAAW;AACtE,cAAM,UAAU,YAAY,YAAY,WAAW,GAAG,MAAM;AAE5D,gBAAQ,YAAY,CAAO,UAAU;AACnC,gBAAM,SACJ,MAAM,OACN;AAEF,cAAI,QAAQ;AACV,gBAAI,OAAO,KAAK;AACd,oBAAM,MAAM,qBAAqB,OAAO,KAAK;AAC7C,kBAAI,KAAK;AACP,sBAAM,SAAS,OAAO,OAAO,GAAG,GAAG,IAAI,OAAO,IAAI,QAAQ;AAAA,cAC5D,OAAO;AACL,sBAAM,SAAS,OAAO,OAAO,GAAG,GAAG,QAAW,CAAC;AAAA,cACjD;AAAA,YACF;AACA,mBAAO,SAAS;AAAA,UAClB,OAAO;AACL,oBAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA;AAAA;AAAA,EAGa,OAAO;AAAA;AAClB,UAAI,QAAQ;AACZ,YAAM,KAAK,QAAQ,MAAM;AACvB;AAAA,MACF,CAAC;AACD,aAAO;AAAA,IACT;AAAA;AAAA,EAEa,QAAQ;AAAA;AACnB,YAAM,OAAiB,CAAC;AACxB,YAAM,KAAK,QAAQ,CAAC,QAAQ;AAC1B,aAAK,KAAK,GAAG;AAAA,MACf,CAAC;AAED,YAAM,KAAK,OAAO,IAAI;AAAA,IACxB;AAAA;AAAA;AAAA,EAGa,QAAuC;AAAA;AAClD,YAAM,MAAM,oBAAI,IAAqB;AACrC,YAAM,KAAK,QAAQ,CAAC,KAAK,OAAO,aAAa;AAC3C,YAAI,IAAI,KAAK,EAAE,OAAO,SAAS,CAAC;AAAA,MAClC,CAAC;AACD,aAAO;AAAA,IACT;AAAA;AAAA,EAEA,IAAW,WAAmB;AAC5B,UAAM,cAAc,WAAW,aAAa,QAAQ,KAAK,cAAc;AACvE,QAAI,CAAC,YAAa,QAAO;AAEzB,UAAM,KAAK,OAAO,WAAW;AAC7B,WAAO,MAAM,EAAE,IAAI,IAAI;AAAA,EACzB;AAAA,EAEA,IAAW,SAAS,IAAY;AAC9B,eAAW,aAAa,QAAQ,KAAK,gBAAgB,OAAO,EAAE,CAAC;AAAA,EACjE;AAAA;AAAA,EAGa,KAAK;AAAA;AAChB,YAAM,WAAW,KAAK;AAGtB,UAAI,CAAC,UAAU;AACb,aAAK,WAAW,KAAK,IAAI;AACzB;AAAA,MACF;AAEA,UAAI,KAAK,IAAI,IAAI,WAAW,gBAAgB;AAC1C;AAAA,MACF;AAGA,YAAM,KAAK,MAAM;AAAA,IACnB;AAAA;AAAA;AAAA,EAGa,QAAQ;AAAA;AACnB,cAAQ,IAAI,0BAA0B,KAAK,MAAM,KAAK,KAAK,SAAS,KAAK;AAGzE,WAAK,WAAW,KAAK,IAAI;AAEzB,YAAM,eAAyB,CAAC;AAChC,YAAM,KAAK;AAAA,QACT,CAAO,KAAa,OAAgB,aAAqB;AACvD,cAAI,UAAU,UAAa,KAAK,IAAI,KAAK,UAAU;AACjD,yBAAa,KAAK,GAAG;AAAA,UACvB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,aAAa,QAAQ;AACvB,cAAM,KAAK,OAAO,YAAY;AAAA,MAChC;AAEA,cAAQ;AAAA,QACN,0BAA0B,KAAK,MAAM,KAAK,KAAK,SAAS,cACzC,aAAa,MAAM;AAAA,MACpC;AAGA,WAAK,WAAW,KAAK,IAAI;AAAA,IAC3B;AAAA;AAAA;AAAA,EAGO,MAAS,KAAa;AAC3B,WAAO,IAAI,aAAgB,MAAM,GAAG;AAAA,EACtC;AACF;AAMO,IAAM,UAAU,IAAI;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF;AAKA,IAAM,cAAN,MAAqB;AAAA,EACZ,YACW,KACA,sBACA,QAAQ,SACxB;AAHgB;AACA;AACA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,kBAAwD;AAAA;AACnE,aAAO,MAAM,KAAK,MAAM,gBAAgB,KAAK,GAAG;AAAA,IAClD;AAAA;AAAA,EAEa,MAA8B;AAAA;AACzC,aAAO,MAAM,KAAK,MAAM,IAAI,KAAK,GAAG;AAAA,IACtC;AAAA;AAAA,EAEa,IACX,IAEe;AAAA,+CAFf,OACA,gBAAwB,KAAK,sBACd;AACf,YAAM,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO,aAAa;AAAA,IACrD;AAAA;AAAA,EAEa,SAAwB;AAAA;AACnC,YAAM,KAAK,MAAM,OAAO,KAAK,GAAG;AAAA,IAClC;AAAA;AACF;AAKO,SAAS,YACd,KACA,mBACgB;AAChB,QAAM,uBAAuB,iBAAiB,iBAAiB;AAE/D,SAAO,IAAI,YAAe,KAAK,oBAAoB;AACrD;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/kvStore.ts","../src/timeConstants.ts","../src/duration.ts"],"sourcesContent":["/**\n * Indexed DB key-value store with support for auto-expirations.\n *\n * Why use this?\n * 1. No need to worry about running out of storage.\n * 2. Extremely simple interface to use indexed DBs.\n * 3. Auto-expirations frees you from worrying about data clean-up.\n * 4. 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.\n */\n\nimport { Duration, durationOrMsToMs } from \"./duration\";\n\n// Updating the DB name will cause all old entries to be gone.\nconst DEFAULT_DB_NAME = \"KVStore\";\n\n// Updating the version will cause all old entries to be gone.\nconst DEFAULT_DB_VERSION = 1;\n\n// Use a constant store name to keep things simple.\nconst STORE_NAME = \"kvStore\";\n\n/** One day in milliseconds. */\nexport const MILLIS_PER_DAY = 86_400_000;\n\n/** 30 days in ms. */\nexport const DEFAULT_EXPIRY_DELTA_MS = MILLIS_PER_DAY * 30;\n\n/** Do GC once per day. */\nexport const GC_INTERVAL_MS = MILLIS_PER_DAY;\n\ntype StoredObject<T> = {\n key: string;\n value: T;\n storedMs: number;\n expiryMs: number;\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: StoredObject<T>,\n): StoredObject<T> | undefined {\n if (\n !obj ||\n typeof obj !== \"object\" ||\n !(\"key\" in obj) ||\n typeof obj.key !== \"string\" ||\n !(\"value\" in obj) ||\n !(\"storedMs\" in obj) ||\n typeof obj.storedMs !== \"number\" ||\n obj.value === undefined ||\n !(\"expiryMs\" in obj) ||\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\nexport class KVStoreField<T> {\n public constructor(\n public readonly store: KVStore,\n public readonly key: string,\n ) {}\n\n public get(): Promise<T | undefined> {\n return this.store.get(this.key);\n }\n\n public set(t: T): Promise<T> {\n return this.store.set(this.key, t);\n }\n\n public delete(): Promise<void> {\n return this.store.delete(this.key);\n }\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 class KVStore {\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 constructor(\n public readonly dbName: string,\n public readonly dbVersion: number,\n public readonly defaultExpiryDeltaMs: number,\n ) {\n this.gcMsStorageKey = `__kvStore:lastGcMs:${dbName}:v${dbVersion}:${STORE_NAME}`;\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(STORE_NAME, {\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(db.transaction(STORE_NAME, mode), reject);\n\n transaction.onabort = (event) => {\n reject(event);\n };\n\n const objectStore = transaction.objectStore(STORE_NAME);\n\n callback(objectStore, resolve, reject);\n });\n }\n\n public async set<T>(\n key: string,\n value: T,\n expiryDeltaMs: number | Duration = this.defaultExpiryDeltaMs,\n ): Promise<T> {\n const nowMs = Date.now();\n const obj: StoredObject<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 public async getStoredObject<T>(\n key: string,\n ): Promise<StoredObject<T> | undefined> {\n const stored = await this.transact<StoredObject<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 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 public async forEach(\n callback: (\n key: string,\n value: unknown,\n expiryMs: 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) {\n await callback(String(cursor.key), obj.value, obj.expiryMs);\n } else {\n await callback(String(cursor.key), undefined, 0);\n }\n }\n cursor.continue();\n } else {\n resolve();\n }\n };\n });\n }\n\n /** Cannot be a getter because this needs to be async. */\n public async size() {\n let count = 0;\n await this.forEach(() => {\n count++;\n });\n return count;\n }\n\n public async clear() {\n const keys: string[] = [];\n await this.forEach((key) => {\n keys.push(key);\n });\n\n await this.delete(keys);\n }\n\n /** Mainly for debugging dumps. */\n public async asMap(): Promise<Map<string, unknown>> {\n const map = new Map<string, unknown>();\n await this.forEach((key, value, expiryMs) => {\n map.set(key, { value, expiryMs });\n });\n return map;\n }\n\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 public set lastGcMs(ms: number) {\n globalThis.localStorage.setItem(this.gcMsStorageKey, String(ms));\n }\n\n /** Perform garbage-collection if due. */\n public async gc() {\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 + GC_INTERVAL_MS) {\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 /** Perform garbage-collection immediately without checking. */\n public async gcNow() {\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 /** Get an independent store item with a locked key and value type. */\n public field<T>(key: string) {\n return new KVStoreField<T>(this, key);\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(\n DEFAULT_DB_NAME,\n DEFAULT_DB_VERSION,\n DEFAULT_EXPIRY_DELTA_MS,\n);\n\n/**\n * Class to represent one key in the store with a default expiration.\n */\nclass KvStoreItem<T> {\n public constructor(\n public readonly key: string,\n public readonly defaultExpiryDeltaMs: number,\n public readonly store = kvStore,\n ) {}\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs } = await myKvItem.getStoredObject();\n */\n public async getStoredObject(): Promise<StoredObject<T> | undefined> {\n return await this.store.getStoredObject(this.key);\n }\n\n public async get(): Promise<T | undefined> {\n return await this.store.get(this.key);\n }\n\n public async set(\n value: T,\n expiryDeltaMs: number = this.defaultExpiryDeltaMs,\n ): Promise<void> {\n await this.store.set(this.key, value, expiryDeltaMs);\n }\n\n public async delete(): Promise<void> {\n await this.store.delete(this.key);\n }\n}\n\n/**\n * Create a KV store item with a key and a default expiration.\n */\nexport function kvStoreItem<T>(\n key: string,\n defaultExpiration: number | Duration,\n): KvStoreItem<T> {\n const defaultExpiryDeltaMs = durationOrMsToMs(defaultExpiration);\n\n return new KvStoreItem<T>(key, defaultExpiryDeltaMs);\n}\n","/**\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nexport const MS_PER_SECOND = 1000;\nexport const MS_PER_MINUTE = 60_000;\nexport const MS_PER_HOUR = 3_600_000;\nexport const MS_PER_DAY = 86_400_000;\nexport const MS_PER_WEEK = 604_800_000;\n\nexport const SECONDS_PER_MINUTE = 60;\nexport const SECONDS_PER_HOUR = 3_600;\nexport const SECONDS_PER_DAY = 86_400;\nexport const SECONDS_PER_WEEK = 604_800;\n\nexport const MINUTES_PER_HOUR = 60;\nexport const MINUTES_PER_DAY = 1440;\nexport const MINUTES_PER_WEEK = 10_080;\n\nexport const HOURS_PER_DAY = 24;\nexport const HOURS_PER_WEEK = 168;\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 MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n MINUTES_PER_HOUR,\n HOURS_PER_DAY,\n MS_PER_DAY,\n MS_PER_MINUTE,\n MS_PER_HOUR,\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":[]}
|
package/localStorageCache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/localStorageCache.ts","../src/timeConstants.ts","../src/duration.ts"],"sourcesContent":["import { Duration, durationOrMsToMs } from \"./duration\";\n\nexport type StoredItem<T> = { value: T; storedMs: number; expiryMs: number };\n\n/**\n * Simple local storage cache with support for auto-expiration.\n * Note that this works in the browser context only because nodejs does not\n * have local storage.\n *\n * Create a cache item accessor object with auto-expiration. The value will\n * always be stored as a string by applying JSON.stringify(), and will be\n * returned in the same object type by applying JSON.parse().\n *\n * In order to provide proper type-checking, please always specify the T\n * type parameter. E.g. const item = storeItem<string>(\"name\", 10_000);\n *\n * @param key The store key in local storage.\n * @param expires Either a number in milliseconds, or a Duration object\n * @param logError Log an error if we found an invalid object in the store.\n * The invalid object is usually a string that cannot be parsed as JSON.\n * @param defaultValue Specify a default value to use for the object. Defaults\n * to undefined.\n */\nexport function storeItem<T>(\n key: string,\n expires: number | Duration,\n logError = true,\n defaultValue?: T,\n) {\n const expireDeltaMs = durationOrMsToMs(expires);\n\n return new CacheItem<T>(key, expireDeltaMs, logError, defaultValue);\n}\n\nclass CacheItem<T> {\n /**\n * Create a cache item accessor object with auto-expiration.\n */\n public constructor(\n public readonly key: string,\n public readonly expireDeltaMs: number,\n public readonly logError: boolean,\n public readonly defaultValue: T | undefined,\n ) {}\n\n /**\n * Set the value of this item with auto-expiration.\n */\n public set(\n value: T,\n expiryDelta: number | Duration = this.expireDeltaMs,\n ): void {\n const nowMs = Date.now();\n const toStore: StoredItem<T> = {\n value,\n storedMs: nowMs,\n expiryMs: nowMs + durationOrMsToMs(expiryDelta),\n };\n const valueStr = JSON.stringify(toStore);\n\n globalThis.localStorage.setItem(this.key, valueStr);\n }\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs } = await myItem.getStoredItem();\n */\n public getStoredItem(): StoredItem<T> | undefined {\n const jsonStr = globalThis.localStorage.getItem(this.key);\n\n if (!jsonStr) {\n return undefined;\n }\n\n try {\n const obj: StoredItem<T> | undefined = JSON.parse(jsonStr);\n\n if (\n !obj ||\n typeof obj !== \"object\" ||\n !(\"value\" in obj) ||\n !(\"storedMs\" in obj) ||\n typeof obj.storedMs !== \"number\" ||\n !(\"expiryMs\" in obj) ||\n typeof obj.expiryMs !== \"number\" ||\n Date.now() >= obj.expiryMs\n ) {\n this.remove();\n return undefined;\n }\n\n return obj;\n } catch (e) {\n if (this.logError) {\n console.error(\n `Found invalid storage value: ${this.key}=${jsonStr}:`,\n e,\n );\n }\n this.remove();\n return undefined;\n }\n }\n\n /**\n * Get the value of this item, or undefined if value is not set or expired.\n */\n public get(): T | undefined {\n const stored = this.getStoredItem();\n\n return stored !== undefined ? stored.value : this.defaultValue;\n }\n\n /**\n * Remove the value of this item.\n */\n public remove(): void {\n globalThis.localStorage.removeItem(this.key);\n }\n}\n","/**\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nexport const MS_PER_SECOND = 1000;\nexport const MS_PER_MINUTE = 60_000;\nexport const MS_PER_HOUR = 3_600_000;\nexport const MS_PER_DAY = 86_400_000;\nexport const MS_PER_WEEK = 604_800_000;\n\nexport const SECONDS_PER_MINUTE = 60;\nexport const SECONDS_PER_HOUR = 3_600;\nexport const SECONDS_PER_DAY = 86_400;\nexport const SECONDS_PER_WEEK = 604_800;\n\nexport const MINUTES_PER_HOUR = 60;\nexport const MINUTES_PER_DAY = 1440;\nexport const MINUTES_PER_WEEK = 10_080;\n\nexport const HOURS_PER_DAY = 24;\nexport const HOURS_PER_WEEK = 168;\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 MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n MINUTES_PER_HOUR,\n HOURS_PER_DAY,\n MS_PER_DAY,\n MS_PER_MINUTE,\n MS_PER_HOUR,\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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKO,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AACtB,IAAM,cAAc;AACpB,IAAM,aAAa;;;ACkLnB,SAAS,aAAa,UAA4B;AA1LzD;AA2LE,QAAM,WAAU,cAAS,SAAT,YAAiB,KAAK;AACtC,QAAM,YAAW,cAAS,UAAT,YAAkB,KAAK;AACxC,QAAM,WAAU,cAAS,YAAT,YAAoB,KAAK;AACzC,QAAM,WAAU,cAAS,YAAT,YAAoB,KAAK;AACzC,QAAM,QAAO,cAAS,iBAAT,YAAyB;AAEtC,SAAO,SAAS,UAAU,SAAS,SAAS;AAC9C;AAKO,SAAS,iBAAiB,UAAqC;AACpE,SAAO,OAAO,aAAa,WAAW,WAAW,aAAa,QAAQ;AACxE;;;AFlLO,SAAS,UACd,KACA,SACA,WAAW,MACX,cACA;AACA,QAAM,gBAAgB,iBAAiB,OAAO;AAE9C,SAAO,IAAI,UAAa,KAAK,eAAe,UAAU,YAAY;AACpE;AAEA,IAAM,YAAN,MAAmB;AAAA;AAAA;AAAA;AAAA,EAIV,YACW,KACA,eACA,UACA,cAChB;AAJgB;AACA;AACA;AACA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKI,IACL,OACA,cAAiC,KAAK,eAChC;AACN,UAAM,QAAQ,KAAK,IAAI;AACvB,UAAM,UAAyB;AAAA,MAC7B;AAAA,MACA,UAAU;AAAA,MACV,UAAU,QAAQ,iBAAiB,WAAW;AAAA,IAChD;AACA,UAAM,WAAW,KAAK,UAAU,OAAO;AAEvC,eAAW,aAAa,QAAQ,KAAK,KAAK,QAAQ;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,gBAA2C;AAChD,UAAM,UAAU,WAAW,aAAa,QAAQ,KAAK,GAAG;AAExD,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,MAAiC,KAAK,MAAM,OAAO;AAEzD,UACE,CAAC,OACD,OAAO,QAAQ,YACf,EAAE,WAAW,QACb,EAAE,cAAc,QAChB,OAAO,IAAI,aAAa,YACxB,EAAE,cAAc,QAChB,OAAO,IAAI,aAAa,YACxB,KAAK,IAAI,KAAK,IAAI,UAClB;AACA,aAAK,OAAO;AACZ,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT,SAAS,GAAG;AACV,UAAI,KAAK,UAAU;AACjB,gBAAQ;AAAA,UACN,gCAAgC,KAAK,GAAG,IAAI,OAAO;AAAA,UACnD;AAAA,QACF;AAAA,MACF;AACA,WAAK,OAAO;AACZ,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,MAAqB;AAC1B,UAAM,SAAS,KAAK,cAAc;AAElC,WAAO,WAAW,SAAY,OAAO,QAAQ,KAAK;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKO,SAAe;AACpB,eAAW,aAAa,WAAW,KAAK,GAAG;AAAA,EAC7C;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/localStorageCache.ts","../src/timeConstants.ts","../src/duration.ts"],"sourcesContent":["import { Duration, durationOrMsToMs } from \"./duration\";\n\nexport type StoredItem<T> = { value: T; storedMs: number; expiryMs: number };\n\n/**\n * Simple local storage cache with support for auto-expiration.\n * Note that this works in the browser context only because nodejs does not\n * have local storage.\n *\n * Create a cache item accessor object with auto-expiration. The value will\n * always be stored as a string by applying JSON.stringify(), and will be\n * returned in the same object type by applying JSON.parse().\n *\n * In order to provide proper type-checking, please always specify the T\n * type parameter. E.g. const item = storeItem<string>(\"name\", 10_000);\n *\n * @param key The store key in local storage.\n * @param expires Either a number in milliseconds, or a Duration object\n * @param logError Log an error if we found an invalid object in the store.\n * The invalid object is usually a string that cannot be parsed as JSON.\n * @param defaultValue Specify a default value to use for the object. Defaults\n * to undefined.\n */\nexport function storeItem<T>(\n key: string,\n expires: number | Duration,\n logError = true,\n defaultValue?: T,\n) {\n const expireDeltaMs = durationOrMsToMs(expires);\n\n return new CacheItem<T>(key, expireDeltaMs, logError, defaultValue);\n}\n\nclass CacheItem<T> {\n /**\n * Create a cache item accessor object with auto-expiration.\n */\n public constructor(\n public readonly key: string,\n public readonly expireDeltaMs: number,\n public readonly logError: boolean,\n public readonly defaultValue: T | undefined,\n ) {}\n\n /**\n * Set the value of this item with auto-expiration.\n */\n public set(\n value: T,\n expiryDelta: number | Duration = this.expireDeltaMs,\n ): void {\n const nowMs = Date.now();\n const toStore: StoredItem<T> = {\n value,\n storedMs: nowMs,\n expiryMs: nowMs + durationOrMsToMs(expiryDelta),\n };\n const valueStr = JSON.stringify(toStore);\n\n globalThis.localStorage.setItem(this.key, valueStr);\n }\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs } = await myItem.getStoredItem();\n */\n public getStoredItem(): StoredItem<T> | undefined {\n const jsonStr = globalThis.localStorage.getItem(this.key);\n\n if (!jsonStr) {\n return undefined;\n }\n\n try {\n const obj: StoredItem<T> | undefined = JSON.parse(jsonStr);\n\n if (\n !obj ||\n typeof obj !== \"object\" ||\n !(\"value\" in obj) ||\n !(\"storedMs\" in obj) ||\n typeof obj.storedMs !== \"number\" ||\n !(\"expiryMs\" in obj) ||\n typeof obj.expiryMs !== \"number\" ||\n Date.now() >= obj.expiryMs\n ) {\n this.remove();\n return undefined;\n }\n\n return obj;\n } catch (e) {\n if (this.logError) {\n console.error(\n `Found invalid storage value: ${this.key}=${jsonStr}:`,\n e,\n );\n }\n this.remove();\n return undefined;\n }\n }\n\n /**\n * Get the value of this item, or undefined if value is not set or expired.\n */\n public get(): T | undefined {\n const stored = this.getStoredItem();\n\n return stored !== undefined ? stored.value : this.defaultValue;\n }\n\n /**\n * Remove the value of this item.\n */\n public remove(): void {\n globalThis.localStorage.removeItem(this.key);\n }\n}\n","/**\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nexport const MS_PER_SECOND = 1000;\nexport const MS_PER_MINUTE = 60_000;\nexport const MS_PER_HOUR = 3_600_000;\nexport const MS_PER_DAY = 86_400_000;\nexport const MS_PER_WEEK = 604_800_000;\n\nexport const SECONDS_PER_MINUTE = 60;\nexport const SECONDS_PER_HOUR = 3_600;\nexport const SECONDS_PER_DAY = 86_400;\nexport const SECONDS_PER_WEEK = 604_800;\n\nexport const MINUTES_PER_HOUR = 60;\nexport const MINUTES_PER_DAY = 1440;\nexport const MINUTES_PER_WEEK = 10_080;\n\nexport const HOURS_PER_DAY = 24;\nexport const HOURS_PER_WEEK = 168;\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 MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n MINUTES_PER_HOUR,\n HOURS_PER_DAY,\n MS_PER_DAY,\n MS_PER_MINUTE,\n MS_PER_HOUR,\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKO,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AACtB,IAAM,cAAc;AACpB,IAAM,aAAa;;;ACkLnB,SAAS,aAAa,UAA4B;AA1LzD;AA2LE,QAAM,WAAU,cAAS,SAAT,YAAiB,KAAK;AACtC,QAAM,YAAW,cAAS,UAAT,YAAkB,KAAK;AACxC,QAAM,WAAU,cAAS,YAAT,YAAoB,KAAK;AACzC,QAAM,WAAU,cAAS,YAAT,YAAoB,KAAK;AACzC,QAAM,QAAO,cAAS,iBAAT,YAAyB;AAEtC,SAAO,SAAS,UAAU,SAAS,SAAS;AAC9C;AAKO,SAAS,iBAAiB,UAAqC;AACpE,SAAO,OAAO,aAAa,WAAW,WAAW,aAAa,QAAQ;AACxE;;;AFlLO,SAAS,UACd,KACA,SACA,WAAW,MACX,cACA;AACA,QAAM,gBAAgB,iBAAiB,OAAO;AAE9C,SAAO,IAAI,UAAa,KAAK,eAAe,UAAU,YAAY;AACpE;AAEA,IAAM,YAAN,MAAmB;AAAA;AAAA;AAAA;AAAA,EAIV,YACW,KACA,eACA,UACA,cAChB;AAJgB;AACA;AACA;AACA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKI,IACL,OACA,cAAiC,KAAK,eAChC;AACN,UAAM,QAAQ,KAAK,IAAI;AACvB,UAAM,UAAyB;AAAA,MAC7B;AAAA,MACA,UAAU;AAAA,MACV,UAAU,QAAQ,iBAAiB,WAAW;AAAA,IAChD;AACA,UAAM,WAAW,KAAK,UAAU,OAAO;AAEvC,eAAW,aAAa,QAAQ,KAAK,KAAK,QAAQ;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,gBAA2C;AAChD,UAAM,UAAU,WAAW,aAAa,QAAQ,KAAK,GAAG;AAExD,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,MAAiC,KAAK,MAAM,OAAO;AAEzD,UACE,CAAC,OACD,OAAO,QAAQ,YACf,EAAE,WAAW,QACb,EAAE,cAAc,QAChB,OAAO,IAAI,aAAa,YACxB,EAAE,cAAc,QAChB,OAAO,IAAI,aAAa,YACxB,KAAK,IAAI,KAAK,IAAI,UAClB;AACA,aAAK,OAAO;AACZ,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT,SAAS,GAAG;AACV,UAAI,KAAK,UAAU;AACjB,gBAAQ;AAAA,UACN,gCAAgC,KAAK,GAAG,IAAI,OAAO;AAAA,UACnD;AAAA,QACF;AAAA,MACF;AACA,WAAK,OAAO;AACZ,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,MAAqB;AAC1B,UAAM,SAAS,KAAK,cAAc;AAElC,WAAO,WAAW,SAAY,OAAO,QAAQ,KAAK;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKO,SAAe;AACpB,eAAW,aAAa,WAAW,KAAK,GAAG;AAAA,EAC7C;AACF;","names":[]}
|
package/package.json
CHANGED
package/src/duration.ts
CHANGED
|
@@ -242,3 +242,19 @@ export function readableDuration(
|
|
|
242
242
|
|
|
243
243
|
return formatDuration(duration, options?.style);
|
|
244
244
|
}
|
|
245
|
+
|
|
246
|
+
/** A shortened duration string useful for logging timings. */
|
|
247
|
+
export function elapsed(ms: number): string {
|
|
248
|
+
// Use long format for 1 minute or over.
|
|
249
|
+
if (ms > MS_PER_MINUTE) {
|
|
250
|
+
return readableDuration(ms);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Use seconds format for over 100ms.
|
|
254
|
+
if (ms > 100) {
|
|
255
|
+
return `${(ms / 1000).toFixed(3)}s`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Use milliseconds format.
|
|
259
|
+
return ms + "ms";
|
|
260
|
+
}
|
package/src/timer.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { elapsed } from "./duration";
|
|
2
|
+
|
|
3
|
+
export class Timer {
|
|
4
|
+
public startMs: number;
|
|
5
|
+
public endMs = 0;
|
|
6
|
+
|
|
7
|
+
public constructor() {
|
|
8
|
+
this.startMs = Date.now();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public stop(): void {
|
|
12
|
+
this.endMs = Date.now();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public restart(): void {
|
|
16
|
+
this.endMs = 0;
|
|
17
|
+
this.startMs = Date.now();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public elapsedMs(): number {
|
|
21
|
+
const stopMs = this.endMs || Date.now();
|
|
22
|
+
return stopMs - this.startMs;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public toString(): string {
|
|
26
|
+
return elapsed(this.elapsedMs());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Shorthand for `new Timer()` to make this easier to use. */
|
|
31
|
+
export function timer(): Timer {
|
|
32
|
+
return new Timer();
|
|
33
|
+
}
|
package/timer.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class Timer {
|
|
2
|
+
startMs: number;
|
|
3
|
+
endMs: number;
|
|
4
|
+
constructor();
|
|
5
|
+
stop(): void;
|
|
6
|
+
restart(): void;
|
|
7
|
+
elapsedMs(): number;
|
|
8
|
+
toString(): string;
|
|
9
|
+
}
|
|
10
|
+
/** Shorthand for `new Timer()` to make this easier to use. */
|
|
11
|
+
export declare function timer(): Timer;
|
package/timer.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/timer.ts
|
|
21
|
+
var timer_exports = {};
|
|
22
|
+
__export(timer_exports, {
|
|
23
|
+
Timer: () => Timer,
|
|
24
|
+
timer: () => timer
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(timer_exports);
|
|
27
|
+
|
|
28
|
+
// src/timeConstants.ts
|
|
29
|
+
var MS_PER_SECOND = 1e3;
|
|
30
|
+
var MS_PER_MINUTE = 6e4;
|
|
31
|
+
var SECONDS_PER_MINUTE = 60;
|
|
32
|
+
var MINUTES_PER_HOUR = 60;
|
|
33
|
+
var HOURS_PER_DAY = 24;
|
|
34
|
+
|
|
35
|
+
// src/duration.ts
|
|
36
|
+
var DURATION_TYPE_SEQUENCE = [
|
|
37
|
+
"days",
|
|
38
|
+
"hours",
|
|
39
|
+
"minutes",
|
|
40
|
+
"seconds",
|
|
41
|
+
"milliseconds"
|
|
42
|
+
];
|
|
43
|
+
var DURATION_STYLE_SUFFIX_MAP = {
|
|
44
|
+
days: {
|
|
45
|
+
short: "day",
|
|
46
|
+
shorts: "days",
|
|
47
|
+
long: "day",
|
|
48
|
+
longs: "days",
|
|
49
|
+
narrow: "d"
|
|
50
|
+
},
|
|
51
|
+
hours: {
|
|
52
|
+
short: "hr",
|
|
53
|
+
shorts: "hrs",
|
|
54
|
+
long: "hour",
|
|
55
|
+
longs: "hours",
|
|
56
|
+
narrow: "h"
|
|
57
|
+
},
|
|
58
|
+
minutes: {
|
|
59
|
+
short: "min",
|
|
60
|
+
shorts: "mins",
|
|
61
|
+
long: "minute",
|
|
62
|
+
longs: "minutes",
|
|
63
|
+
narrow: "m"
|
|
64
|
+
},
|
|
65
|
+
seconds: {
|
|
66
|
+
short: "sec",
|
|
67
|
+
shorts: "secs",
|
|
68
|
+
long: "second",
|
|
69
|
+
longs: "seconds",
|
|
70
|
+
narrow: "s"
|
|
71
|
+
},
|
|
72
|
+
milliseconds: {
|
|
73
|
+
short: "ms",
|
|
74
|
+
shorts: "ms",
|
|
75
|
+
long: "millisecond",
|
|
76
|
+
longs: "milliseconds",
|
|
77
|
+
narrow: "ms"
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
function getDurationStyleForPlural(style) {
|
|
81
|
+
return style == "short" ? "shorts" : style === "long" ? "longs" : style;
|
|
82
|
+
}
|
|
83
|
+
function getValueAndUnitSeparator(style) {
|
|
84
|
+
return style === "narrow" ? "" : " ";
|
|
85
|
+
}
|
|
86
|
+
function getDurationTypeSeparator(style) {
|
|
87
|
+
return style === "narrow" ? " " : ", ";
|
|
88
|
+
}
|
|
89
|
+
function msToDuration(ms, durationTypeForZero) {
|
|
90
|
+
if (ms === 0) {
|
|
91
|
+
durationTypeForZero = durationTypeForZero != null ? durationTypeForZero : "milliseconds";
|
|
92
|
+
return { [durationTypeForZero]: 0 };
|
|
93
|
+
}
|
|
94
|
+
const duration = {};
|
|
95
|
+
for (let i = 0; i < 1; i++) {
|
|
96
|
+
let seconds = Math.floor(ms / MS_PER_SECOND);
|
|
97
|
+
const millis = ms - seconds * MS_PER_SECOND;
|
|
98
|
+
if (millis > 0) {
|
|
99
|
+
duration["milliseconds"] = millis;
|
|
100
|
+
}
|
|
101
|
+
if (seconds === 0) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
let minutes = Math.floor(seconds / SECONDS_PER_MINUTE);
|
|
105
|
+
seconds -= minutes * SECONDS_PER_MINUTE;
|
|
106
|
+
if (seconds > 0) {
|
|
107
|
+
duration["seconds"] = seconds;
|
|
108
|
+
}
|
|
109
|
+
if (minutes === 0) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
let hours = Math.floor(minutes / MINUTES_PER_HOUR);
|
|
113
|
+
minutes -= hours * MINUTES_PER_HOUR;
|
|
114
|
+
if (minutes > 0) {
|
|
115
|
+
duration["minutes"] = minutes;
|
|
116
|
+
}
|
|
117
|
+
if (hours === 0) {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
const days = Math.floor(hours / HOURS_PER_DAY);
|
|
121
|
+
hours -= days * HOURS_PER_DAY;
|
|
122
|
+
if (hours > 0) {
|
|
123
|
+
duration["hours"] = hours;
|
|
124
|
+
}
|
|
125
|
+
if (days > 0) {
|
|
126
|
+
duration["days"] = days;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return duration;
|
|
130
|
+
}
|
|
131
|
+
function formatDuration(duration, style) {
|
|
132
|
+
style = style != null ? style : "short";
|
|
133
|
+
const stylePlural = getDurationStyleForPlural(style);
|
|
134
|
+
const space = getValueAndUnitSeparator(style);
|
|
135
|
+
const a = [];
|
|
136
|
+
for (const unit of DURATION_TYPE_SEQUENCE) {
|
|
137
|
+
const value = duration[unit];
|
|
138
|
+
if (value === void 0) continue;
|
|
139
|
+
const suffixMap = DURATION_STYLE_SUFFIX_MAP[unit];
|
|
140
|
+
const suffix = value === 1 ? suffixMap[style] : suffixMap[stylePlural];
|
|
141
|
+
a.push(value + space + suffix);
|
|
142
|
+
}
|
|
143
|
+
const separator = getDurationTypeSeparator(style);
|
|
144
|
+
return a.join(separator);
|
|
145
|
+
}
|
|
146
|
+
function readableDuration(ms, options) {
|
|
147
|
+
const duration = msToDuration(ms, options == null ? void 0 : options.durationTypeForZero);
|
|
148
|
+
return formatDuration(duration, options == null ? void 0 : options.style);
|
|
149
|
+
}
|
|
150
|
+
function elapsed(ms) {
|
|
151
|
+
if (ms > MS_PER_MINUTE) {
|
|
152
|
+
return readableDuration(ms);
|
|
153
|
+
}
|
|
154
|
+
if (ms > 100) {
|
|
155
|
+
return `${(ms / 1e3).toFixed(3)}s`;
|
|
156
|
+
}
|
|
157
|
+
return ms + "ms";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/timer.ts
|
|
161
|
+
var Timer = class {
|
|
162
|
+
constructor() {
|
|
163
|
+
this.endMs = 0;
|
|
164
|
+
this.startMs = Date.now();
|
|
165
|
+
}
|
|
166
|
+
stop() {
|
|
167
|
+
this.endMs = Date.now();
|
|
168
|
+
}
|
|
169
|
+
restart() {
|
|
170
|
+
this.endMs = 0;
|
|
171
|
+
this.startMs = Date.now();
|
|
172
|
+
}
|
|
173
|
+
elapsedMs() {
|
|
174
|
+
const stopMs = this.endMs || Date.now();
|
|
175
|
+
return stopMs - this.startMs;
|
|
176
|
+
}
|
|
177
|
+
toString() {
|
|
178
|
+
return elapsed(this.elapsedMs());
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
function timer() {
|
|
182
|
+
return new Timer();
|
|
183
|
+
}
|
|
184
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
185
|
+
0 && (module.exports = {
|
|
186
|
+
Timer,
|
|
187
|
+
timer
|
|
188
|
+
});
|
|
189
|
+
//# sourceMappingURL=timer.js.map
|
package/timer.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/timer.ts","../src/timeConstants.ts","../src/duration.ts"],"sourcesContent":["import { elapsed } from \"./duration\";\n\nexport class Timer {\n public startMs: number;\n public endMs = 0;\n\n public constructor() {\n this.startMs = Date.now();\n }\n\n public stop(): void {\n this.endMs = Date.now();\n }\n\n public restart(): void {\n this.endMs = 0;\n this.startMs = Date.now();\n }\n\n public elapsedMs(): number {\n const stopMs = this.endMs || Date.now();\n return stopMs - this.startMs;\n }\n\n public toString(): string {\n return elapsed(this.elapsedMs());\n }\n}\n\n/** Shorthand for `new Timer()` to make this easier to use. */\nexport function timer(): Timer {\n return new Timer();\n}\n","/**\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file.\n */\n\nexport const MS_PER_SECOND = 1000;\nexport const MS_PER_MINUTE = 60_000;\nexport const MS_PER_HOUR = 3_600_000;\nexport const MS_PER_DAY = 86_400_000;\nexport const MS_PER_WEEK = 604_800_000;\n\nexport const SECONDS_PER_MINUTE = 60;\nexport const SECONDS_PER_HOUR = 3_600;\nexport const SECONDS_PER_DAY = 86_400;\nexport const SECONDS_PER_WEEK = 604_800;\n\nexport const MINUTES_PER_HOUR = 60;\nexport const MINUTES_PER_DAY = 1440;\nexport const MINUTES_PER_WEEK = 10_080;\n\nexport const HOURS_PER_DAY = 24;\nexport const HOURS_PER_WEEK = 168;\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 MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n MINUTES_PER_HOUR,\n HOURS_PER_DAY,\n MS_PER_DAY,\n MS_PER_MINUTE,\n MS_PER_HOUR,\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":[]}
|