@choksheak/ts-utils 0.2.0 → 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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/localStorageCache.ts","../src/timeConstants.ts","../src/duration.ts"],"sourcesContent":["import { Duration, durationToMs } from \"./duration\";\n\nexport type StoredItem<T> = { value: T; expireMs: 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 =\n typeof expires === \"number\" ? expires : durationToMs(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(value: T): void {\n const expireMs = Date.now() + this.expireDeltaMs;\n const toStore: StoredItem<T> = { value, expireMs };\n const valueStr = JSON.stringify(toStore);\n\n globalThis.localStorage.setItem(this.key, valueStr);\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 jsonStr = globalThis.localStorage.getItem(this.key);\n\n if (!jsonStr) {\n return this.defaultValue;\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 !(\"expireMs\" in obj) ||\n typeof obj.expireMs !== \"number\" ||\n Date.now() >= obj.expireMs\n ) {\n this.remove();\n return this.defaultValue;\n }\n\n return obj.value;\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 this.defaultValue;\n }\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 * 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;;;AF3KO,SAAS,UACd,KACA,SACA,WAAW,MACX,cACA;AACA,QAAM,gBACJ,OAAO,YAAY,WAAW,UAAU,aAAa,OAAO;AAE9D,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,IAAI,OAAgB;AACzB,UAAM,WAAW,KAAK,IAAI,IAAI,KAAK;AACnC,UAAM,UAAyB,EAAE,OAAO,SAAS;AACjD,UAAM,WAAW,KAAK,UAAU,OAAO;AAEvC,eAAW,aAAa,QAAQ,KAAK,KAAK,QAAQ;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKO,MAAqB;AAC1B,UAAM,UAAU,WAAW,aAAa,QAAQ,KAAK,GAAG;AAExD,QAAI,CAAC,SAAS;AACZ,aAAO,KAAK;AAAA,IACd;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,KAAK,IAAI,KAAK,IAAI,UAClB;AACA,aAAK,OAAO;AACZ,eAAO,KAAK;AAAA,MACd;AAEA,aAAO,IAAI;AAAA,IACb,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,KAAK;AAAA,IACd;AAAA,EACF;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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@choksheak/ts-utils",
3
3
  "license": "The Unlicense",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "Random Typescript utilities with support for full tree-shaking",
6
6
  "private": false,
7
7
  "scripts": {
@@ -13,43 +13,53 @@ export function toDate(ts: AnyDateTime): Date {
13
13
  }
14
14
 
15
15
  /**
16
- * Returns a date in yyyy-MM-dd format. E.g. '2000-01-02'.
16
+ * Returns a date in yyyy-MM format. E.g. '2000-01'.
17
17
  *
18
18
  * @param dt Specify a date object or default to the current date.
19
19
  * @param separator Defaults to '-'.
20
20
  */
21
- export function yyyyMmDd(dt = new Date(), separator = "-"): string {
21
+ export function yyyyMm(dt = new Date(), separator = "-"): string {
22
22
  const yr = dt.getFullYear();
23
23
  const mth = dt.getMonth() + 1;
24
+
25
+ return yr + separator + (mth < 10 ? "0" + mth : mth);
26
+ }
27
+
28
+ /**
29
+ * Returns a date in yyyy-MM-dd format. E.g. '2000-01-02'.
30
+ *
31
+ * @param dt Specify a date object or default to the current date.
32
+ * @param separator Defaults to '-'.
33
+ */
34
+ export function yyyyMmDd(dt = new Date(), separator = "-"): string {
24
35
  const day = dt.getDate();
25
36
 
26
- return (
27
- yr +
28
- separator +
29
- (mth < 10 ? "0" + mth : mth) +
30
- separator +
31
- (day < 10 ? "0" + day : day)
32
- );
37
+ return yyyyMm(dt, separator) + separator + (day < 10 ? "0" + day : day);
33
38
  }
34
39
 
35
40
  /**
36
- * Returns a date in hh:mm:ss format. E.g. '01:02:03'.
41
+ * Returns a date in hh:mm format. E.g. '01:02'.
37
42
  *
38
43
  * @param dt Specify a date object or default to the current date/time.
39
44
  * @param separator Defaults to ':'.
40
45
  */
41
- export function hhMmSs(dt = new Date(), separator = ":"): string {
46
+ export function hhMm(dt = new Date(), separator = ":"): string {
42
47
  const hr = dt.getHours();
43
48
  const min = dt.getMinutes();
49
+
50
+ return (hr < 10 ? "0" + hr : hr) + separator + (min < 10 ? "0" + min : min);
51
+ }
52
+
53
+ /**
54
+ * Returns a date in hh:mm:ss format. E.g. '01:02:03'.
55
+ *
56
+ * @param dt Specify a date object or default to the current date/time.
57
+ * @param separator Defaults to ':'.
58
+ */
59
+ export function hhMmSs(dt = new Date(), separator = ":"): string {
44
60
  const sec = dt.getSeconds();
45
61
 
46
- return (
47
- (hr < 10 ? "0" + hr : hr) +
48
- separator +
49
- (min < 10 ? "0" + min : min) +
50
- separator +
51
- (sec < 10 ? "0" + sec : sec)
52
- );
62
+ return hhMm(dt, separator) + separator + (sec < 10 ? "0" + sec : sec);
53
63
  }
54
64
 
55
65
  /**
package/src/duration.ts CHANGED
@@ -194,6 +194,13 @@ export function durationToMs(duration: Duration): number {
194
194
  return daysMs + hoursMs + minsMs + secsMs + msMs;
195
195
  }
196
196
 
197
+ /**
198
+ * Convenience function to return a duration given an ms or Duration.
199
+ */
200
+ export function durationOrMsToMs(duration: number | Duration): number {
201
+ return typeof duration === "number" ? duration : durationToMs(duration);
202
+ }
203
+
197
204
  /**
198
205
  * Format a given Duration object into a string. If the object has no fields,
199
206
  * then returns an empty string.
@@ -235,3 +242,19 @@ export function readableDuration(
235
242
 
236
243
  return formatDuration(duration, options?.style);
237
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/kvStore.ts CHANGED
@@ -11,6 +11,8 @@
11
11
  * Just use the `kvStore` global constant like the local storage.
12
12
  */
13
13
 
14
+ import { Duration, durationOrMsToMs } from "./duration";
15
+
14
16
  // Updating the DB name will cause all old entries to be gone.
15
17
  const DEFAULT_DB_NAME = "KVStore";
16
18
 
@@ -32,7 +34,8 @@ export const GC_INTERVAL_MS = MILLIS_PER_DAY;
32
34
  type StoredObject<T> = {
33
35
  key: string;
34
36
  value: T;
35
- expireMs: number;
37
+ storedMs: number;
38
+ expiryMs: number;
36
39
  };
37
40
 
38
41
  /**
@@ -48,10 +51,12 @@ function validateStoredObject<T>(
48
51
  !("key" in obj) ||
49
52
  typeof obj.key !== "string" ||
50
53
  !("value" in obj) ||
54
+ !("storedMs" in obj) ||
55
+ typeof obj.storedMs !== "number" ||
51
56
  obj.value === undefined ||
52
- !("expireMs" in obj) ||
53
- typeof obj.expireMs !== "number" ||
54
- Date.now() >= obj.expireMs
57
+ !("expiryMs" in obj) ||
58
+ typeof obj.expiryMs !== "number" ||
59
+ Date.now() >= obj.expiryMs
55
60
  ) {
56
61
  return undefined;
57
62
  }
@@ -168,12 +173,14 @@ export class KVStore {
168
173
  public async set<T>(
169
174
  key: string,
170
175
  value: T,
171
- expiryDeltaMs: number = this.defaultExpiryDeltaMs,
176
+ expiryDeltaMs: number | Duration = this.defaultExpiryDeltaMs,
172
177
  ): Promise<T> {
173
- const obj = {
178
+ const nowMs = Date.now();
179
+ const obj: StoredObject<T> = {
174
180
  key,
175
181
  value,
176
- expireMs: Date.now() + expiryDeltaMs,
182
+ storedMs: nowMs,
183
+ expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs),
177
184
  };
178
185
 
179
186
  return await this.transact<T>(
@@ -210,7 +217,9 @@ export class KVStore {
210
217
  );
211
218
  }
212
219
 
213
- public async get<T>(key: string): Promise<T | undefined> {
220
+ public async getStoredObject<T>(
221
+ key: string,
222
+ ): Promise<StoredObject<T> | undefined> {
214
223
  const stored = await this.transact<StoredObject<T> | undefined>(
215
224
  "readonly",
216
225
  (objectStore, resolve, reject) => {
@@ -236,7 +245,7 @@ export class KVStore {
236
245
  return undefined;
237
246
  }
238
247
 
239
- return obj.value;
248
+ return obj;
240
249
  } catch (e) {
241
250
  console.error(`Invalid kv value: ${key}=${JSON.stringify(stored)}:`, e);
242
251
  await this.delete(key);
@@ -247,11 +256,17 @@ export class KVStore {
247
256
  }
248
257
  }
249
258
 
259
+ public async get<T>(key: string): Promise<T | undefined> {
260
+ const obj = await this.getStoredObject<T>(key);
261
+
262
+ return obj?.value;
263
+ }
264
+
250
265
  public async forEach(
251
266
  callback: (
252
267
  key: string,
253
268
  value: unknown,
254
- expireMs: number,
269
+ expiryMs: number,
255
270
  ) => void | Promise<void>,
256
271
  ): Promise<void> {
257
272
  await this.transact<void>("readonly", (objectStore, resolve, reject) => {
@@ -266,7 +281,7 @@ export class KVStore {
266
281
  if (cursor.key) {
267
282
  const obj = validateStoredObject(cursor.value);
268
283
  if (obj) {
269
- await callback(String(cursor.key), obj.value, obj.expireMs);
284
+ await callback(String(cursor.key), obj.value, obj.expiryMs);
270
285
  } else {
271
286
  await callback(String(cursor.key), undefined, 0);
272
287
  }
@@ -300,8 +315,8 @@ export class KVStore {
300
315
  /** Mainly for debugging dumps. */
301
316
  public async asMap(): Promise<Map<string, unknown>> {
302
317
  const map = new Map<string, unknown>();
303
- await this.forEach((key, value, expireMs) => {
304
- map.set(key, { value, expireMs });
318
+ await this.forEach((key, value, expiryMs) => {
319
+ map.set(key, { value, expiryMs });
305
320
  });
306
321
  return map;
307
322
  }
@@ -345,8 +360,8 @@ export class KVStore {
345
360
 
346
361
  const keysToDelete: string[] = [];
347
362
  await this.forEach(
348
- async (key: string, value: unknown, expireMs: number) => {
349
- if (value === undefined || Date.now() >= expireMs) {
363
+ async (key: string, value: unknown, expiryMs: number) => {
364
+ if (value === undefined || Date.now() >= expiryMs) {
350
365
  keysToDelete.push(key);
351
366
  }
352
367
  },
@@ -391,7 +406,16 @@ class KvStoreItem<T> {
391
406
  public readonly store = kvStore,
392
407
  ) {}
393
408
 
394
- public async get(): Promise<Awaited<T> | undefined> {
409
+ /**
410
+ * Example usage:
411
+ *
412
+ * const { value, storedMs, expiryMs } = await myKvItem.getStoredObject();
413
+ */
414
+ public async getStoredObject(): Promise<StoredObject<T> | undefined> {
415
+ return await this.store.getStoredObject(this.key);
416
+ }
417
+
418
+ public async get(): Promise<T | undefined> {
395
419
  return await this.store.get(this.key);
396
420
  }
397
421
 
@@ -412,7 +436,9 @@ class KvStoreItem<T> {
412
436
  */
413
437
  export function kvStoreItem<T>(
414
438
  key: string,
415
- defaultExpiryDeltaMs: number,
439
+ defaultExpiration: number | Duration,
416
440
  ): KvStoreItem<T> {
441
+ const defaultExpiryDeltaMs = durationOrMsToMs(defaultExpiration);
442
+
417
443
  return new KvStoreItem<T>(key, defaultExpiryDeltaMs);
418
444
  }
@@ -1,6 +1,6 @@
1
- import { Duration, durationToMs } from "./duration";
1
+ import { Duration, durationOrMsToMs } from "./duration";
2
2
 
3
- export type StoredItem<T> = { value: T; expireMs: number };
3
+ export type StoredItem<T> = { value: T; storedMs: number; expiryMs: number };
4
4
 
5
5
  /**
6
6
  * Simple local storage cache with support for auto-expiration.
@@ -27,8 +27,7 @@ export function storeItem<T>(
27
27
  logError = true,
28
28
  defaultValue?: T,
29
29
  ) {
30
- const expireDeltaMs =
31
- typeof expires === "number" ? expires : durationToMs(expires);
30
+ const expireDeltaMs = durationOrMsToMs(expires);
32
31
 
33
32
  return new CacheItem<T>(key, expireDeltaMs, logError, defaultValue);
34
33
  }
@@ -47,22 +46,31 @@ class CacheItem<T> {
47
46
  /**
48
47
  * Set the value of this item with auto-expiration.
49
48
  */
50
- public set(value: T): void {
51
- const expireMs = Date.now() + this.expireDeltaMs;
52
- const toStore: StoredItem<T> = { value, expireMs };
49
+ public set(
50
+ value: T,
51
+ expiryDelta: number | Duration = this.expireDeltaMs,
52
+ ): void {
53
+ const nowMs = Date.now();
54
+ const toStore: StoredItem<T> = {
55
+ value,
56
+ storedMs: nowMs,
57
+ expiryMs: nowMs + durationOrMsToMs(expiryDelta),
58
+ };
53
59
  const valueStr = JSON.stringify(toStore);
54
60
 
55
61
  globalThis.localStorage.setItem(this.key, valueStr);
56
62
  }
57
63
 
58
64
  /**
59
- * Get the value of this item, or undefined if value is not set or expired.
65
+ * Example usage:
66
+ *
67
+ * const { value, storedMs, expiryMs } = await myItem.getStoredItem();
60
68
  */
61
- public get(): T | undefined {
69
+ public getStoredItem(): StoredItem<T> | undefined {
62
70
  const jsonStr = globalThis.localStorage.getItem(this.key);
63
71
 
64
72
  if (!jsonStr) {
65
- return this.defaultValue;
73
+ return undefined;
66
74
  }
67
75
 
68
76
  try {
@@ -72,15 +80,17 @@ class CacheItem<T> {
72
80
  !obj ||
73
81
  typeof obj !== "object" ||
74
82
  !("value" in obj) ||
75
- !("expireMs" in obj) ||
76
- typeof obj.expireMs !== "number" ||
77
- Date.now() >= obj.expireMs
83
+ !("storedMs" in obj) ||
84
+ typeof obj.storedMs !== "number" ||
85
+ !("expiryMs" in obj) ||
86
+ typeof obj.expiryMs !== "number" ||
87
+ Date.now() >= obj.expiryMs
78
88
  ) {
79
89
  this.remove();
80
- return this.defaultValue;
90
+ return undefined;
81
91
  }
82
92
 
83
- return obj.value;
93
+ return obj;
84
94
  } catch (e) {
85
95
  if (this.logError) {
86
96
  console.error(
@@ -89,10 +99,19 @@ class CacheItem<T> {
89
99
  );
90
100
  }
91
101
  this.remove();
92
- return this.defaultValue;
102
+ return undefined;
93
103
  }
94
104
  }
95
105
 
106
+ /**
107
+ * Get the value of this item, or undefined if value is not set or expired.
108
+ */
109
+ public get(): T | undefined {
110
+ const stored = this.getStoredItem();
111
+
112
+ return stored !== undefined ? stored.value : this.defaultValue;
113
+ }
114
+
96
115
  /**
97
116
  * Remove the value of this item.
98
117
  */
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