@choksheak/ts-utils 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +9 -3
  2. package/asNumber.cjs +3 -3
  3. package/asNumber.d.mts +1 -1
  4. package/asNumber.d.ts +1 -1
  5. package/asNumber.min.cjs +1 -1
  6. package/asNumber.min.cjs.map +1 -1
  7. package/asNumber.min.mjs +1 -1
  8. package/asNumber.min.mjs.map +1 -1
  9. package/asNumber.mjs +3 -3
  10. package/kvStore.cjs +230 -239
  11. package/kvStore.d.mts +142 -44
  12. package/kvStore.d.ts +142 -44
  13. package/kvStore.min.cjs +1 -1
  14. package/kvStore.min.cjs.map +1 -1
  15. package/kvStore.min.mjs +1 -1
  16. package/kvStore.min.mjs.map +1 -1
  17. package/kvStore.mjs +229 -237
  18. package/localStore.cjs +187 -186
  19. package/localStore.d.mts +135 -34
  20. package/localStore.d.ts +135 -34
  21. package/localStore.min.cjs +1 -1
  22. package/localStore.min.cjs.map +1 -1
  23. package/localStore.min.mjs +1 -1
  24. package/localStore.min.mjs.map +1 -1
  25. package/localStore.mjs +186 -184
  26. package/mean.cjs +3 -3
  27. package/mean.min.cjs +1 -1
  28. package/mean.min.cjs.map +1 -1
  29. package/mean.min.mjs +1 -1
  30. package/mean.min.mjs.map +1 -1
  31. package/mean.mjs +3 -3
  32. package/median.cjs +4 -4
  33. package/median.min.cjs +1 -1
  34. package/median.min.cjs.map +1 -1
  35. package/median.min.mjs +1 -1
  36. package/median.min.mjs.map +1 -1
  37. package/median.mjs +4 -4
  38. package/package.json +2 -34
  39. package/safeBtoa.d.mts +7 -0
  40. package/safeBtoa.d.ts +7 -0
  41. package/safeBtoa.min.cjs.map +1 -1
  42. package/safeBtoa.min.mjs.map +1 -1
  43. package/sum.cjs +3 -3
  44. package/sum.min.cjs +1 -1
  45. package/sum.min.cjs.map +1 -1
  46. package/sum.min.mjs +1 -1
  47. package/sum.min.mjs.map +1 -1
  48. package/sum.mjs +3 -3
  49. package/timer.cjs +19 -24
  50. package/timer.d.mts +9 -6
  51. package/timer.d.ts +9 -6
  52. package/timer.min.cjs +1 -1
  53. package/timer.min.cjs.map +1 -1
  54. package/timer.min.mjs +1 -1
  55. package/timer.min.mjs.map +1 -1
  56. package/timer.mjs +19 -23
  57. package/safeParseFloat.cjs +0 -33
  58. package/safeParseFloat.d.mts +0 -6
  59. package/safeParseFloat.d.ts +0 -6
  60. package/safeParseFloat.min.cjs +0 -2
  61. package/safeParseFloat.min.cjs.map +0 -1
  62. package/safeParseFloat.min.mjs +0 -2
  63. package/safeParseFloat.min.mjs.map +0 -1
  64. package/safeParseFloat.mjs +0 -8
  65. package/safeParseInt.cjs +0 -33
  66. package/safeParseInt.d.mts +0 -6
  67. package/safeParseInt.d.ts +0 -6
  68. package/safeParseInt.min.cjs +0 -2
  69. package/safeParseInt.min.cjs.map +0 -1
  70. package/safeParseInt.min.mjs +0 -2
  71. package/safeParseInt.min.mjs.map +0 -1
  72. package/safeParseInt.mjs +0 -8
package/localStore.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Duration } from './duration.js';
2
- import { FullStorageAdapter, StoredObject, StorageAdapter } from './storageAdapter.js';
2
+ import { StoredObject, StorageAdapter } from './storageAdapter.js';
3
3
 
4
4
  /**
5
5
  * Local storage key-value store with support for auto-expirations.
@@ -33,72 +33,174 @@ declare function configureLocalStore(config: Partial<LocalStoreConfig>): void;
33
33
  * You can create multiple LocalStores if you want, but most likely you will only
34
34
  * need to use the default `localStore` instance.
35
35
  */
36
- declare class LocalStore implements FullStorageAdapter<any> {
36
+ declare function createLocalStore(storeName: string, options?: {
37
+ defaultExpiryMs?: number | Duration;
38
+ gcIntervalMs?: number | Duration;
39
+ }): {
40
+ /** Input name for the store. */
37
41
  readonly storeName: string;
38
42
  /**
39
43
  * The prefix string for the local storage key which identifies items
40
44
  * belonging to this namespace.
41
45
  */
42
46
  readonly keyPrefix: string;
43
- /** Local storage key name for the last GC completed timestamp. */
44
- readonly gcMsStorageKey: string;
47
+ /** Default expiry to use if not specified in set(). */
45
48
  readonly defaultExpiryMs: number;
49
+ /** Time interval for when GC's occur. */
46
50
  readonly gcIntervalMs: number;
47
- constructor(storeName: string, options?: {
48
- defaultExpiryMs?: number | Duration;
49
- gcIntervalMs?: number | Duration;
50
- });
51
+ /** Local storage key name for the last GC completed timestamp. */
52
+ readonly gcMsStorageKey: string;
51
53
  /** Set a value in the store. */
52
- set<T>(key: string, value: T, expiryDeltaMs?: number | Duration): T;
54
+ readonly set: <T>(key: string, value: T, expiryDeltaMs?: number | Duration) => T;
53
55
  /** Delete one or multiple keys. */
54
- delete(key: string | string[]): void;
56
+ readonly delete: (key: string | string[]) => void;
55
57
  /** Mainly used to get the expiration timestamp of an object. */
56
- getStoredObject<T>(key: string): StoredObject<T> | undefined;
58
+ readonly getStoredObject: <T>(key: string) => StoredObject<T> | undefined;
57
59
  /** Get a value by key, or undefined if it does not exist. */
58
- get<T>(key: string): T | undefined;
60
+ readonly get: <T>(key: string) => T | undefined;
59
61
  /** Generic way to iterate through all entries. */
60
- forEach<T>(callback: (key: string, value: T, expiryMs: number, storedMs: number) => void): void;
62
+ readonly forEach: <T>(callback: (key: string, value: T, expiryMs: number, storedMs: number) => void) => void;
61
63
  /**
62
64
  * Returns the number of items in the store. Note that getting the size
63
65
  * requires iterating through the entire store because the items could expire
64
66
  * at any time, and hence the size is a dynamic number.
65
67
  */
66
- size(): number;
68
+ readonly size: () => number;
67
69
  /** Remove all items from the store. */
68
- clear(): void;
70
+ readonly clear: () => void;
69
71
  /**
70
72
  * Returns all items as map of key to value, mainly used for debugging dumps.
71
73
  * The type T is applied to all values, even though they might not be of type
72
74
  * T (in the case when you store different data types in the same store).
73
75
  */
74
- asMap<T>(): Map<string, StoredObject<T>>;
76
+ readonly asMap: <T>() => Map<string, StoredObject<T>>;
75
77
  /** Returns the ms timestamp for the last GC (garbage collection). */
76
- get lastGcMs(): number;
78
+ readonly getLastGcMs: () => number;
77
79
  /** Set the ms timestamp for the last GC (garbage collection). */
78
- set lastGcMs(ms: number);
80
+ readonly setLastGcMs: (ms: number) => void;
79
81
  /** Perform garbage-collection if due, else do nothing. */
80
- gc(): void;
82
+ readonly gc: () => void;
81
83
  /**
82
84
  * Perform garbage collection immediately without checking whether we are
83
85
  * due for the next GC or not.
84
86
  */
85
- gcNow(): void;
87
+ readonly gcNow: () => void;
86
88
  /** Returns `this` casted into a StorageAdapter<T>. */
87
- asStorageAdapter<T>(): StorageAdapter<T>;
88
- }
89
+ readonly asStorageAdapter: <T>() => StorageAdapter<T>;
90
+ };
91
+ type LocalStore = ReturnType<typeof createLocalStore>;
89
92
  /**
90
93
  * Default local store ready for immediate use. You can create new instances if
91
94
  * you want, but most likely you will only need one store instance.
92
95
  */
93
- declare const localStore: LocalStore;
94
- /**
95
- * Class to represent one key in the store with a default expiration.
96
- */
97
- declare class LocalStoreItem<T> {
98
- readonly key: string;
99
- readonly store: LocalStore;
96
+ declare const localStore: {
97
+ /** Input name for the store. */
98
+ readonly storeName: string;
99
+ /**
100
+ * The prefix string for the local storage key which identifies items
101
+ * belonging to this namespace.
102
+ */
103
+ readonly keyPrefix: string;
104
+ /** Default expiry to use if not specified in set(). */
100
105
  readonly defaultExpiryMs: number;
101
- constructor(key: string, defaultExpiryMs?: number | Duration, store?: LocalStore);
106
+ /** Time interval for when GC's occur. */
107
+ readonly gcIntervalMs: number;
108
+ /** Local storage key name for the last GC completed timestamp. */
109
+ readonly gcMsStorageKey: string;
110
+ /** Set a value in the store. */
111
+ readonly set: <T>(key: string, value: T, expiryDeltaMs?: number | Duration) => T;
112
+ /** Delete one or multiple keys. */
113
+ readonly delete: (key: string | string[]) => void;
114
+ /** Mainly used to get the expiration timestamp of an object. */
115
+ readonly getStoredObject: <T>(key: string) => StoredObject<T> | undefined;
116
+ /** Get a value by key, or undefined if it does not exist. */
117
+ readonly get: <T>(key: string) => T | undefined;
118
+ /** Generic way to iterate through all entries. */
119
+ readonly forEach: <T>(callback: (key: string, value: T, expiryMs: number, storedMs: number) => void) => void;
120
+ /**
121
+ * Returns the number of items in the store. Note that getting the size
122
+ * requires iterating through the entire store because the items could expire
123
+ * at any time, and hence the size is a dynamic number.
124
+ */
125
+ readonly size: () => number;
126
+ /** Remove all items from the store. */
127
+ readonly clear: () => void;
128
+ /**
129
+ * Returns all items as map of key to value, mainly used for debugging dumps.
130
+ * The type T is applied to all values, even though they might not be of type
131
+ * T (in the case when you store different data types in the same store).
132
+ */
133
+ readonly asMap: <T>() => Map<string, StoredObject<T>>;
134
+ /** Returns the ms timestamp for the last GC (garbage collection). */
135
+ readonly getLastGcMs: () => number;
136
+ /** Set the ms timestamp for the last GC (garbage collection). */
137
+ readonly setLastGcMs: (ms: number) => void;
138
+ /** Perform garbage-collection if due, else do nothing. */
139
+ readonly gc: () => void;
140
+ /**
141
+ * Perform garbage collection immediately without checking whether we are
142
+ * due for the next GC or not.
143
+ */
144
+ readonly gcNow: () => void;
145
+ /** Returns `this` casted into a StorageAdapter<T>. */
146
+ readonly asStorageAdapter: <T>() => StorageAdapter<T>;
147
+ };
148
+ /** Create a local store item with a key and a default expiration. */
149
+ declare function localStoreItem<T>(key: string, expiryMs?: number | Duration, store?: LocalStore): {
150
+ key: string;
151
+ defaultExpiryMs: number | undefined;
152
+ store: {
153
+ /** Input name for the store. */
154
+ readonly storeName: string;
155
+ /**
156
+ * The prefix string for the local storage key which identifies items
157
+ * belonging to this namespace.
158
+ */
159
+ readonly keyPrefix: string;
160
+ /** Default expiry to use if not specified in set(). */
161
+ readonly defaultExpiryMs: number;
162
+ /** Time interval for when GC's occur. */
163
+ readonly gcIntervalMs: number;
164
+ /** Local storage key name for the last GC completed timestamp. */
165
+ readonly gcMsStorageKey: string;
166
+ /** Set a value in the store. */
167
+ readonly set: <T_1>(key: string, value: T_1, expiryDeltaMs?: number | Duration) => T_1;
168
+ /** Delete one or multiple keys. */
169
+ readonly delete: (key: string | string[]) => void;
170
+ /** Mainly used to get the expiration timestamp of an object. */
171
+ readonly getStoredObject: <T_1>(key: string) => StoredObject<T_1> | undefined;
172
+ /** Get a value by key, or undefined if it does not exist. */
173
+ readonly get: <T_1>(key: string) => T_1 | undefined;
174
+ /** Generic way to iterate through all entries. */
175
+ readonly forEach: <T_1>(callback: (key: string, value: T_1, expiryMs: number, storedMs: number) => void) => void;
176
+ /**
177
+ * Returns the number of items in the store. Note that getting the size
178
+ * requires iterating through the entire store because the items could expire
179
+ * at any time, and hence the size is a dynamic number.
180
+ */
181
+ readonly size: () => number;
182
+ /** Remove all items from the store. */
183
+ readonly clear: () => void;
184
+ /**
185
+ * Returns all items as map of key to value, mainly used for debugging dumps.
186
+ * The type T is applied to all values, even though they might not be of type
187
+ * T (in the case when you store different data types in the same store).
188
+ */
189
+ readonly asMap: <T_1>() => Map<string, StoredObject<T_1>>;
190
+ /** Returns the ms timestamp for the last GC (garbage collection). */
191
+ readonly getLastGcMs: () => number;
192
+ /** Set the ms timestamp for the last GC (garbage collection). */
193
+ readonly setLastGcMs: (ms: number) => void;
194
+ /** Perform garbage-collection if due, else do nothing. */
195
+ readonly gc: () => void;
196
+ /**
197
+ * Perform garbage collection immediately without checking whether we are
198
+ * due for the next GC or not.
199
+ */
200
+ readonly gcNow: () => void;
201
+ /** Returns `this` casted into a StorageAdapter<T>. */
202
+ readonly asStorageAdapter: <T_1>() => StorageAdapter<T_1>;
203
+ };
102
204
  /** Set a value in the store. */
103
205
  set(value: T, expiryDeltaMs?: number | undefined): void;
104
206
  /**
@@ -112,8 +214,7 @@ declare class LocalStoreItem<T> {
112
214
  get(): T | undefined;
113
215
  /** Delete this key from the store. */
114
216
  delete(): void;
115
- }
116
- /** Create a local store item with a key and a default expiration. */
117
- declare function localStoreItem<T>(key: string, expiryMs?: number | Duration, store?: LocalStore): LocalStoreItem<T>;
217
+ };
218
+ type LocalStoreItem<T> = ReturnType<typeof localStoreItem<T>>;
118
219
 
119
- export { LocalStore, LocalStoreConfig, LocalStoreItem, configureLocalStore, localStore, localStoreItem };
220
+ export { type LocalStore, LocalStoreConfig, type LocalStoreItem, configureLocalStore, createLocalStore, localStore, localStoreItem };
@@ -1,2 +1,2 @@
1
- "use strict";var l=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var y=(r,t)=>{for(var e in t)l(r,e,{get:t[e],enumerable:!0})},b=(r,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of p(t))!h.call(r,o)&&o!==e&&l(r,o,{get:()=>t[o],enumerable:!(s=g(t,o))||s.enumerable});return r};var m=r=>b(l({},"__esModule",{value:!0}),r);var D={};y(D,{LocalStore:()=>u,LocalStoreConfig:()=>a,LocalStoreItem:()=>c,configureLocalStore:()=>M,localStore:()=>d,localStoreItem:()=>x});module.exports=m(D);function S(r){let t=(r.days??0)*864e5,e=(r.hours??0)*36e5,s=(r.minutes??0)*6e4,o=(r.seconds??0)*1e3,n=r.milliseconds??0;return t+e+s+o+n}function i(r){return typeof r=="number"?r:S(r)}var a={storeName:"ts-utils",expiryMs:864e5*30,gcIntervalMs:864e5};function M(r){Object.assign(a,r)}function T(r){if(!(!r||typeof r!="object"||r.value===void 0||typeof r.storedMs!="number"||typeof r.expiryMs!="number"||Date.now()>=r.expiryMs))return r}var u=class{constructor(t,e){this.storeName=t;this.keyPrefix=t+":",this.defaultExpiryMs=e?.defaultExpiryMs?i(e.defaultExpiryMs):a.expiryMs,this.gcIntervalMs=e?.gcIntervalMs?i(e.gcIntervalMs):a.gcIntervalMs,this.gcMsStorageKey=`__localStore:lastGcMs:${t}`}keyPrefix;gcMsStorageKey;defaultExpiryMs;gcIntervalMs;set(t,e,s=this.defaultExpiryMs){let o=Date.now(),n={value:e,storedMs:o,expiryMs:o+i(s)};return localStorage.setItem(this.keyPrefix+t,JSON.stringify(n)),this.gc(),e}delete(t){if(typeof t=="string")localStorage.removeItem(this.keyPrefix+t);else for(let e of t)localStorage.removeItem(this.keyPrefix+e)}getStoredObject(t){let e=this.keyPrefix+t,s=localStorage.getItem(e);if(s)try{let o=JSON.parse(s),n=T(o);if(!n){this.delete(e),this.gc();return}return n}catch(o){console.error(`Invalid local value: ${e}=${s}:`,o),this.delete(e),this.gc();return}}get(t){return this.getStoredObject(t)?.value}forEach(t){for(let e of Object.keys(localStorage)){if(!e.startsWith(this.keyPrefix))continue;let s=e.slice(this.keyPrefix.length),o=this.getStoredObject(s);o&&t(s,o.value,o.expiryMs,o.storedMs)}}size(){let t=0;return this.forEach(()=>{t++}),t}clear(){for(let t of Object.keys(localStorage))t.startsWith(this.keyPrefix)&&localStorage.removeItem(t)}asMap(){let t=new Map;return this.forEach((e,s,o,n)=>{t.set(e,{value:s,expiryMs:o,storedMs:n})}),t}get lastGcMs(){let t=globalThis.localStorage.getItem(this.gcMsStorageKey);if(!t)return 0;let e=Number(t);return isNaN(e)?0:e}set lastGcMs(t){globalThis.localStorage.setItem(this.gcMsStorageKey,String(t))}gc(){let t=this.lastGcMs;if(!t){this.lastGcMs=Date.now();return}Date.now()<t+this.gcIntervalMs||this.gcNow()}gcNow(){console.log(`Starting localStore GC on ${this.storeName}`),this.lastGcMs=Date.now();let t=0;this.forEach((e,s,o)=>{Date.now()>=o&&(this.delete(e),t++)}),console.log(`Finished localStore GC on ${this.storeName} - deleted ${t} keys`),this.lastGcMs=Date.now()}asStorageAdapter(){return this}},d=new u(a.storeName),c=class{constructor(t,e=a.expiryMs,s=d){this.key=t;this.store=s;this.defaultExpiryMs=e&&i(e)}defaultExpiryMs;set(t,e=this.defaultExpiryMs){this.store.set(this.key,t,e)}getStoredObject(){return this.store.getStoredObject(this.key)}get(){return this.store.get(this.key)}delete(){this.store.delete(this.key)}};function x(r,t,e=d){return t=t&&i(t),new c(r,t,e)}0&&(module.exports={LocalStore,LocalStoreConfig,LocalStoreItem,configureLocalStore,localStore,localStoreItem});
1
+ "use strict";var p=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var D=(t,s)=>{for(var r in s)p(t,r,{get:s[r],enumerable:!0})},h=(t,s,r,c)=>{if(s&&typeof s=="object"||typeof s=="function")for(let i of T(s))!b.call(t,i)&&i!==r&&p(t,i,{get:()=>s[i],enumerable:!(c=y(s,i))||c.enumerable});return t};var x=t=>h(p({},"__esModule",{value:!0}),t);var _={};D(_,{LocalStoreConfig:()=>g,configureLocalStore:()=>v,createLocalStore:()=>m,localStore:()=>M,localStoreItem:()=>I});module.exports=x(_);function O(t){let s=(t.days??0)*864e5,r=(t.hours??0)*36e5,c=(t.minutes??0)*6e4,i=(t.seconds??0)*1e3,l=t.milliseconds??0;return s+r+c+i+l}function f(t){return typeof t=="number"?t:O(t)}var g={storeName:"ts-utils",expiryMs:864e5*30,gcIntervalMs:864e5};function v(t){Object.assign(g,t)}function E(t){if(!(!t||typeof t!="object"||t.value===void 0||typeof t.storedMs!="number"||typeof t.expiryMs!="number"||Date.now()>=t.expiryMs))return t}function m(t,s){let r=t+":",c=s?.defaultExpiryMs?f(s.defaultExpiryMs):g.expiryMs,i=s?.gcIntervalMs?f(s.gcIntervalMs):g.gcIntervalMs,l=`__localStore:lastGcMs:${t}`,o={storeName:t,keyPrefix:r,defaultExpiryMs:c,gcIntervalMs:i,gcMsStorageKey:l,set(e,n,u){let a=Date.now(),d={value:n,storedMs:a,expiryMs:a+f(u??c)};return localStorage.setItem(r+e,JSON.stringify(d)),o.gc(),n},delete(e){if(typeof e=="string")localStorage.removeItem(r+e);else for(let n of e)localStorage.removeItem(r+n)},getStoredObject(e){let n=r+e,u=localStorage.getItem(n);if(u)try{let a=JSON.parse(u),d=E(a);if(!d){o.delete(n),o.gc();return}return d}catch(a){console.error(`Invalid local value: ${n}=${u}:`,a),o.delete(n),o.gc();return}},get(e){return o.getStoredObject(e)?.value},forEach(e){for(let n of Object.keys(localStorage)){if(!n.startsWith(r))continue;let u=n.slice(r.length),a=o.getStoredObject(u);a&&e(u,a.value,a.expiryMs,a.storedMs)}},size(){let e=0;return o.forEach(()=>{e++}),e},clear(){for(let e of Object.keys(localStorage))e.startsWith(r)&&localStorage.removeItem(e)},asMap(){let e=new Map;return o.forEach((n,u,a,d)=>{e.set(n,{value:u,expiryMs:a,storedMs:d})}),e},getLastGcMs(){let e=localStorage.getItem(l);if(!e)return 0;let n=Number(e);return isNaN(n)?0:n},setLastGcMs(e){localStorage.setItem(l,String(e))},gc(){let e=o.getLastGcMs();if(!e){o.setLastGcMs(Date.now());return}Date.now()<e+i||o.gcNow()},gcNow(){console.log(`Starting localStore GC on ${t}`),o.setLastGcMs(Date.now());let e=0;o.forEach((n,u,a)=>{Date.now()>=a&&(o.delete(n),e++)}),console.log(`Finished localStore GC on ${t} - deleted ${e} keys`),o.setLastGcMs(Date.now())},asStorageAdapter(){return o}};return o}var M=m(g.storeName);function I(t,s,r=M){let c=s&&f(s);return{key:t,defaultExpiryMs:c,store:r,set(l,o){r.set(t,l,o??c)},getStoredObject(){return r.getStoredObject(t)},get(){return r.get(t)},delete(){r.delete(t)}}}0&&(module.exports={LocalStoreConfig,configureLocalStore,createLocalStore,localStore,localStoreItem});
2
2
  //# sourceMappingURL=localStore.min.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/localStore.ts","../src/duration.ts"],"sourcesContent":["/**\n * Local storage key-value store with support for auto-expirations.\n *\n * Why use this?\n * 1. Extremely simple interface to use local storage.\n * 2. Auto-expirations with GC frees you from worrying about data clean-up.\n * 3. Any serializable data type can be stored (except undefined).\n *\n * How to use?\n * Just use the `localStore` global constant like the local storage.\n *\n * Why not use the localStorage directly?\n * localStorage does not provide auto-expirations with GC. If you don't need\n * this (items never expire), then just use localStorage directly.\n */\n\nimport { Duration, durationOrMsToMs } from \"./duration\";\nimport {\n FullStorageAdapter,\n StorageAdapter,\n StoredObject,\n} from \"./storageAdapter\";\nimport { MS_PER_DAY } from \"./timeConstants\";\n\n/** Global defaults can be updated directly. */\nexport const LocalStoreConfig = {\n /** All items with the same store name will share the same storage space. */\n storeName: \"ts-utils\",\n\n /** 30 days in ms. */\n expiryMs: MS_PER_DAY * 30,\n\n /** Do GC once per day. */\n gcIntervalMs: MS_PER_DAY,\n};\n\nexport type LocalStoreConfig = typeof LocalStoreConfig;\n\n/** Convenience function to update global defaults. */\nexport function configureLocalStore(config: Partial<LocalStoreConfig>) {\n Object.assign(LocalStoreConfig, config);\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 obj.value === undefined ||\n typeof obj.storedMs !== \"number\" ||\n typeof obj.expiryMs !== \"number\" ||\n Date.now() >= obj.expiryMs\n ) {\n return undefined;\n }\n\n return obj;\n}\n\n/**\n * You can create multiple LocalStores if you want, but most likely you will only\n * need to use the default `localStore` instance.\n */\n// Using `any` because the store could store any type of data for each key,\n// but the caller can specify a more specific type when calling each of the\n// methods.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class LocalStore implements FullStorageAdapter<any> {\n /**\n * The prefix string for the local storage key which identifies items\n * belonging to this namespace.\n */\n public readonly keyPrefix: string;\n\n /** Local storage key name for the last GC completed timestamp. */\n public readonly gcMsStorageKey: string;\n\n public readonly defaultExpiryMs: number;\n public readonly gcIntervalMs: number;\n\n public constructor(\n public readonly storeName: string,\n options?: {\n defaultExpiryMs?: number | Duration;\n gcIntervalMs?: number | Duration;\n },\n ) {\n this.keyPrefix = storeName + \":\";\n\n this.defaultExpiryMs = options?.defaultExpiryMs\n ? durationOrMsToMs(options.defaultExpiryMs)\n : LocalStoreConfig.expiryMs;\n\n this.gcIntervalMs = options?.gcIntervalMs\n ? durationOrMsToMs(options.gcIntervalMs)\n : LocalStoreConfig.gcIntervalMs;\n\n this.gcMsStorageKey = `__localStore:lastGcMs:${storeName}`;\n }\n\n /** Set a value in the store. */\n public set<T>(\n key: string,\n value: T,\n expiryDeltaMs: number | Duration = this.defaultExpiryMs,\n ): T {\n const nowMs = Date.now();\n const obj: StoredObject<T> = {\n value,\n storedMs: nowMs,\n expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs),\n };\n\n localStorage.setItem(this.keyPrefix + key, JSON.stringify(obj));\n\n this.gc(); // check GC on every write\n\n return value;\n }\n\n /** Delete one or multiple keys. */\n public delete(key: string | string[]): void {\n if (typeof key === \"string\") {\n localStorage.removeItem(this.keyPrefix + key);\n } else {\n for (const k of key) {\n localStorage.removeItem(this.keyPrefix + k);\n }\n }\n }\n\n /** Mainly used to get the expiration timestamp of an object. */\n public getStoredObject<T>(key: string): StoredObject<T> | undefined {\n const k = this.keyPrefix + key;\n const stored = localStorage.getItem(k);\n\n if (!stored) {\n return undefined;\n }\n\n try {\n const parsed = JSON.parse(stored);\n const obj = validateStoredObject(parsed);\n if (!obj) {\n this.delete(k);\n\n this.gc(); // check GC on every read of an expired key\n\n return undefined;\n }\n\n return obj as StoredObject<T>;\n } catch (e) {\n console.error(`Invalid local value: ${k}=${stored}:`, e);\n this.delete(k);\n\n this.gc(); // check GC on every read of an invalid key\n\n return undefined;\n }\n }\n\n /** Get a value by key, or undefined if it does not exist. */\n public get<T>(key: string): T | undefined {\n const obj = this.getStoredObject<T>(key);\n\n return obj?.value;\n }\n\n /** Generic way to iterate through all entries. */\n public forEach<T>(\n callback: (\n key: string,\n value: T,\n expiryMs: number,\n storedMs: number,\n ) => void,\n ): void {\n for (const k of Object.keys(localStorage)) {\n if (!k.startsWith(this.keyPrefix)) continue;\n\n const key = k.slice(this.keyPrefix.length);\n const obj = this.getStoredObject(key);\n\n if (!obj) continue;\n\n callback(key, obj.value as T, obj.expiryMs, obj.storedMs);\n }\n }\n\n /**\n * Returns the number of items in the store. Note that getting the size\n * requires iterating through the entire store because the items could expire\n * at any time, and hence the size is a dynamic number.\n */\n public size(): number {\n let count = 0;\n this.forEach(() => {\n count++;\n });\n return count;\n }\n\n /** Remove all items from the store. */\n public clear(): void {\n // Note that we don't need to use this.forEach() because we are just\n // going to delete all the items without checking for expiration.\n for (const key of Object.keys(localStorage)) {\n if (key.startsWith(this.keyPrefix)) {\n localStorage.removeItem(key);\n }\n }\n }\n\n /**\n * Returns all items as map of key to value, mainly used for debugging dumps.\n * The type T is applied to all values, even though they might not be of type\n * T (in the case when you store different data types in the same store).\n */\n public asMap<T>(): Map<string, StoredObject<T>> {\n const map = new Map<string, StoredObject<T>>();\n this.forEach((key, value, expiryMs, storedMs) => {\n map.set(key, { value: value as T, expiryMs, storedMs });\n });\n return map;\n }\n\n /** Returns the ms timestamp for the last GC (garbage collection). */\n public get lastGcMs(): number {\n const lastGcMsStr = globalThis.localStorage.getItem(this.gcMsStorageKey);\n if (!lastGcMsStr) return 0;\n\n const ms = Number(lastGcMsStr);\n return isNaN(ms) ? 0 : ms;\n }\n\n /** Set the ms timestamp for the last GC (garbage collection). */\n public set lastGcMs(ms: number) {\n globalThis.localStorage.setItem(this.gcMsStorageKey, String(ms));\n }\n\n /** Perform garbage-collection if due, else do nothing. */\n public gc(): void {\n const lastGcMs = this.lastGcMs;\n\n // Set initial timestamp - no need GC now.\n if (!lastGcMs) {\n this.lastGcMs = Date.now();\n return;\n }\n\n if (Date.now() < lastGcMs + this.gcIntervalMs) {\n return; // not due for next GC yet\n }\n\n // GC is due now, so run it.\n this.gcNow();\n }\n\n /**\n * Perform garbage collection immediately without checking whether we are\n * due for the next GC or not.\n */\n public gcNow(): void {\n console.log(`Starting localStore GC on ${this.storeName}`);\n\n // Prevent concurrent GC runs.\n this.lastGcMs = Date.now();\n let count = 0;\n\n this.forEach((key: string, value: unknown, expiryMs: number) => {\n if (Date.now() >= expiryMs) {\n this.delete(key);\n count++;\n }\n });\n\n console.log(\n `Finished localStore GC on ${this.storeName} - deleted ${count} keys`,\n );\n\n // Mark the end time as last GC time.\n this.lastGcMs = Date.now();\n }\n\n /** Returns `this` casted into a StorageAdapter<T>. */\n public asStorageAdapter<T>(): StorageAdapter<T> {\n return this as StorageAdapter<T>;\n }\n}\n\n/**\n * Default local 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 localStore = new LocalStore(LocalStoreConfig.storeName);\n\n/**\n * Class to represent one key in the store with a default expiration.\n */\nexport class LocalStoreItem<T> {\n public readonly defaultExpiryMs: number;\n\n public constructor(\n public readonly key: string,\n defaultExpiryMs: number | Duration = LocalStoreConfig.expiryMs,\n public readonly store = localStore,\n ) {\n this.defaultExpiryMs = defaultExpiryMs && durationOrMsToMs(defaultExpiryMs);\n }\n\n /** Set a value in the store. */\n public set(\n value: T,\n expiryDeltaMs: number | undefined = this.defaultExpiryMs,\n ): void {\n this.store.set(this.key, value, expiryDeltaMs);\n }\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs, storedMs } =\n * await myLocalItem.getStoredObject();\n */\n public getStoredObject(): StoredObject<T> | undefined {\n return this.store.getStoredObject(this.key);\n }\n\n /** Get a value by key, or undefined if it does not exist. */\n public get(): T | undefined {\n return this.store.get(this.key);\n }\n\n /** Delete this key from the store. */\n public delete(): void {\n this.store.delete(this.key);\n }\n}\n\n/** Create a local store item with a key and a default expiration. */\nexport function localStoreItem<T>(\n key: string,\n expiryMs?: number | Duration,\n store = localStore,\n): LocalStoreItem<T> {\n expiryMs = expiryMs && durationOrMsToMs(expiryMs);\n\n return new LocalStoreItem<T>(key, expiryMs, store);\n}\n","/**\n * Bunch of miscellaneous constants and utility functions related to handling\n * date and time durations.\n *\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file. Weeks have fixed durations, but are excluded because we\n * use days as the max duration supported.\n */\n\nimport {\n HOURS_PER_DAY,\n MINUTES_PER_HOUR,\n MS_PER_DAY,\n MS_PER_HOUR,\n MS_PER_MINUTE,\n MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n} from \"./timeConstants\";\n\nexport type Duration = {\n days?: number;\n hours?: number;\n minutes?: number;\n seconds?: number;\n milliseconds?: number;\n};\n\n/**\n * One of: days, hours, minutes, seconds, milliseconds\n */\nexport type DurationType = keyof Duration;\n\n/**\n * Order in which the duration type appears in the duration string.\n */\nexport const DURATION_TYPE_SEQUENCE: DurationType[] = [\n \"days\",\n \"hours\",\n \"minutes\",\n \"seconds\",\n \"milliseconds\",\n];\n\n/**\n * Follows the same format as Intl.DurationFormat.prototype.format().\n *\n * Short: 1 yr, 2 mths, 3 wks, 3 days, 4 hr, 5 min, 6 sec, 7 ms, 8 μs, 9 ns\n * Long: 1 year, 2 months, 3 weeks, 3 days, 4 hours, 5 minutes, 6 seconds,\n * 7 milliseconds, 8 microseconds, 9 nanoseconds\n * Narrow: 1y 2mo 3w 3d 4h 5m 6s 7ms 8μs 9ns\n */\nexport type DurationStyle = \"short\" | \"long\" | \"narrow\";\n\nexport type DurationSuffixMap = {\n short: string;\n shorts: string;\n long: string;\n longs: string;\n narrow: string;\n};\n\nexport type DurationSuffixType = keyof DurationSuffixMap;\n\nexport const DURATION_STYLE_SUFFIX_MAP: Record<\n DurationType,\n DurationSuffixMap\n> = {\n days: {\n short: \"day\",\n shorts: \"days\",\n long: \"day\",\n longs: \"days\",\n narrow: \"d\",\n },\n hours: {\n short: \"hr\",\n shorts: \"hrs\",\n long: \"hour\",\n longs: \"hours\",\n narrow: \"h\",\n },\n minutes: {\n short: \"min\",\n shorts: \"mins\",\n long: \"minute\",\n longs: \"minutes\",\n narrow: \"m\",\n },\n seconds: {\n short: \"sec\",\n shorts: \"secs\",\n long: \"second\",\n longs: \"seconds\",\n narrow: \"s\",\n },\n milliseconds: {\n short: \"ms\",\n shorts: \"ms\",\n long: \"millisecond\",\n longs: \"milliseconds\",\n narrow: \"ms\",\n },\n};\n\nfunction getDurationStyleForPlural(style: DurationStyle): DurationSuffixType {\n return style == \"short\" ? \"shorts\" : style === \"long\" ? \"longs\" : style;\n}\n\nfunction getValueAndUnitSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \"\" : \" \";\n}\n\nfunction getDurationTypeSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \" \" : \", \";\n}\n\n/**\n * Convert a milliseconds duration into a Duration object. If the given ms is\n * zero, then return an object with a single field of zero with duration type\n * of durationTypeForZero.\n *\n * @param durationTypeForZero Defaults to 'milliseconds'\n */\nexport function msToDuration(\n ms: number,\n durationTypeForZero?: DurationType,\n): Duration {\n if (ms === 0) {\n durationTypeForZero = durationTypeForZero ?? \"milliseconds\";\n return { [durationTypeForZero]: 0 };\n }\n\n const duration: Duration = {};\n\n for (let i = 0; i < 1; i++) {\n let seconds = Math.floor(ms / MS_PER_SECOND);\n const millis = ms - seconds * MS_PER_SECOND;\n\n if (millis > 0) {\n duration[\"milliseconds\"] = millis;\n }\n\n if (seconds === 0) {\n break;\n }\n\n let minutes = Math.floor(seconds / SECONDS_PER_MINUTE);\n seconds -= minutes * SECONDS_PER_MINUTE;\n\n if (seconds > 0) {\n duration[\"seconds\"] = seconds;\n }\n\n if (minutes === 0) {\n break;\n }\n\n let hours = Math.floor(minutes / MINUTES_PER_HOUR);\n minutes -= hours * MINUTES_PER_HOUR;\n\n if (minutes > 0) {\n duration[\"minutes\"] = minutes;\n }\n\n if (hours === 0) {\n break;\n }\n\n const days = Math.floor(hours / HOURS_PER_DAY);\n hours -= days * HOURS_PER_DAY;\n\n if (hours > 0) {\n duration[\"hours\"] = hours;\n }\n\n if (days > 0) {\n duration[\"days\"] = days;\n }\n }\n\n return duration;\n}\n\n/**\n * Returns the number of milliseconds for the given duration.\n */\nexport function durationToMs(duration: Duration): number {\n const daysMs = (duration.days ?? 0) * MS_PER_DAY;\n const hoursMs = (duration.hours ?? 0) * MS_PER_HOUR;\n const minsMs = (duration.minutes ?? 0) * MS_PER_MINUTE;\n const secsMs = (duration.seconds ?? 0) * MS_PER_SECOND;\n const msMs = duration.milliseconds ?? 0;\n\n return daysMs + hoursMs + minsMs + secsMs + msMs;\n}\n\n/**\n * Convenience function to return a duration given an ms or Duration.\n */\nexport function durationOrMsToMs(duration: number | Duration): number {\n return typeof duration === \"number\" ? duration : durationToMs(duration);\n}\n\n/**\n * Format a given Duration object into a string. If the object has no fields,\n * then returns an empty string.\n *\n * @param style Defaults to 'short'\n */\nexport function formatDuration(duration: Duration, style?: DurationStyle) {\n style = style ?? \"short\";\n const stylePlural = getDurationStyleForPlural(style);\n\n const space = getValueAndUnitSeparator(style);\n\n const a: string[] = [];\n\n for (const unit of DURATION_TYPE_SEQUENCE) {\n const value = duration[unit];\n if (value === undefined) continue;\n\n const suffixMap = DURATION_STYLE_SUFFIX_MAP[unit];\n const suffix = value === 1 ? suffixMap[style] : suffixMap[stylePlural];\n a.push(value + space + suffix);\n }\n\n const separator = getDurationTypeSeparator(style);\n return a.join(separator);\n}\n\n/**\n * Convert a millisecond duration into a human-readable duration string.\n *\n * @param options.durationTypeForZero - Defaults to 'milliseconds'\n * @param options.style - Defaults to 'short'\n */\nexport function readableDuration(\n ms: number,\n options?: { durationTypeForZero?: DurationType; style?: DurationStyle },\n): string {\n const duration = msToDuration(ms, options?.durationTypeForZero);\n\n return formatDuration(duration, options?.style);\n}\n\n/** A shortened duration string useful for logging timings. */\nexport function elapsed(ms: number): string {\n // Use long format for 1 minute or over.\n if (ms > MS_PER_MINUTE) {\n return readableDuration(ms);\n }\n\n // Use seconds format for over 100ms.\n if (ms > 100) {\n return `${(ms / 1000).toFixed(3)}s`;\n }\n\n // Use milliseconds format.\n return ms + \"ms\";\n}\n"],"mappings":"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","names":["localStore_exports","__export","LocalStore","LocalStoreConfig","LocalStoreItem","configureLocalStore","localStore","localStoreItem","__toCommonJS","durationToMs","duration","daysMs","hoursMs","minsMs","secsMs","msMs","durationOrMsToMs","LocalStoreConfig","configureLocalStore","config","validateStoredObject","obj","LocalStore","storeName","options","durationOrMsToMs","key","value","expiryDeltaMs","nowMs","k","stored","parsed","e","callback","count","map","expiryMs","storedMs","lastGcMsStr","ms","lastGcMs","localStore","LocalStoreItem","defaultExpiryMs","store","localStoreItem"]}
1
+ {"version":3,"sources":["../src/localStore.ts","../src/duration.ts"],"sourcesContent":["/**\n * Local storage key-value store with support for auto-expirations.\n *\n * Why use this?\n * 1. Extremely simple interface to use local storage.\n * 2. Auto-expirations with GC frees you from worrying about data clean-up.\n * 3. Any serializable data type can be stored (except undefined).\n *\n * How to use?\n * Just use the `localStore` global constant like the local storage.\n *\n * Why not use the localStorage directly?\n * localStorage does not provide auto-expirations with GC. If you don't need\n * this (items never expire), then just use localStorage directly.\n */\n\nimport { Duration, durationOrMsToMs } from \"./duration\";\nimport {\n FullStorageAdapter,\n StorageAdapter,\n StoredObject,\n} from \"./storageAdapter\";\nimport { MS_PER_DAY } from \"./timeConstants\";\n\n/** Global defaults can be updated directly. */\nexport const LocalStoreConfig = {\n /** All items with the same store name will share the same storage space. */\n storeName: \"ts-utils\",\n\n /** 30 days in ms. */\n expiryMs: MS_PER_DAY * 30,\n\n /** Do GC once per day. */\n gcIntervalMs: MS_PER_DAY,\n};\n\nexport type LocalStoreConfig = typeof LocalStoreConfig;\n\n/** Convenience function to update global defaults. */\nexport function configureLocalStore(config: Partial<LocalStoreConfig>) {\n Object.assign(LocalStoreConfig, config);\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 obj.value === undefined ||\n typeof obj.storedMs !== \"number\" ||\n typeof obj.expiryMs !== \"number\" ||\n Date.now() >= obj.expiryMs\n ) {\n return undefined;\n }\n\n return obj;\n}\n\n/**\n * You can create multiple LocalStores if you want, but most likely you will only\n * need to use the default `localStore` instance.\n */\n// Using `any` because the store could store any type of data for each key,\n// but the caller can specify a more specific type when calling each of the\n// methods.\n\nexport function createLocalStore(\n storeName: string,\n options?: {\n defaultExpiryMs?: number | Duration;\n gcIntervalMs?: number | Duration;\n },\n) {\n const keyPrefix = storeName + \":\";\n\n const defaultExpiryMs = options?.defaultExpiryMs\n ? durationOrMsToMs(options.defaultExpiryMs)\n : LocalStoreConfig.expiryMs;\n\n const gcIntervalMs = options?.gcIntervalMs\n ? durationOrMsToMs(options.gcIntervalMs)\n : LocalStoreConfig.gcIntervalMs;\n\n const gcMsStorageKey = `__localStore:lastGcMs:${storeName}`;\n\n const obj = {\n /** Input name for the store. */\n storeName,\n\n /**\n * The prefix string for the local storage key which identifies items\n * belonging to this namespace.\n */\n keyPrefix,\n\n /** Default expiry to use if not specified in set(). */\n defaultExpiryMs,\n\n /** Time interval for when GC's occur. */\n gcIntervalMs,\n\n /** Local storage key name for the last GC completed timestamp. */\n gcMsStorageKey,\n\n /** Set a value in the store. */\n set<T>(key: string, value: T, expiryDeltaMs?: number | Duration): T {\n const nowMs = Date.now();\n const stored: StoredObject<T> = {\n value,\n storedMs: nowMs,\n expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs ?? defaultExpiryMs),\n };\n\n localStorage.setItem(keyPrefix + key, JSON.stringify(stored));\n\n obj.gc(); // check GC on every write\n\n return value;\n },\n\n /** Delete one or multiple keys. */\n delete(key: string | string[]): void {\n if (typeof key === \"string\") {\n localStorage.removeItem(keyPrefix + key);\n } else {\n for (const k of key) {\n localStorage.removeItem(keyPrefix + k);\n }\n }\n },\n\n /** Mainly used to get the expiration timestamp of an object. */\n getStoredObject<T>(key: string): StoredObject<T> | undefined {\n const k = keyPrefix + key;\n const stored = localStorage.getItem(k);\n\n if (!stored) {\n return undefined;\n }\n\n try {\n const parsed = JSON.parse(stored);\n const valid = validateStoredObject(parsed);\n if (!valid) {\n obj.delete(k);\n\n obj.gc(); // check GC on every read of an expired key\n\n return undefined;\n }\n\n return valid as StoredObject<T>;\n } catch (e) {\n console.error(`Invalid local value: ${k}=${stored}:`, e);\n obj.delete(k);\n\n obj.gc(); // check GC on every read of an invalid key\n\n return undefined;\n }\n },\n\n /** Get a value by key, or undefined if it does not exist. */\n get<T>(key: string): T | undefined {\n const stored = obj.getStoredObject<T>(key);\n\n return stored?.value;\n },\n\n /** Generic way to iterate through all entries. */\n forEach<T>(\n callback: (\n key: string,\n value: T,\n expiryMs: number,\n storedMs: number,\n ) => void,\n ): void {\n for (const k of Object.keys(localStorage)) {\n if (!k.startsWith(keyPrefix)) continue;\n\n const key = k.slice(keyPrefix.length);\n const stored = obj.getStoredObject(key);\n\n if (!stored) continue;\n\n callback(key, stored.value as T, stored.expiryMs, stored.storedMs);\n }\n },\n\n /**\n * Returns the number of items in the store. Note that getting the size\n * requires iterating through the entire store because the items could expire\n * at any time, and hence the size is a dynamic number.\n */\n size(): number {\n let count = 0;\n obj.forEach(() => {\n count++;\n });\n return count;\n },\n\n /** Remove all items from the store. */\n clear(): void {\n // Note that we don't need to use obj.forEach() because we are just\n // going to delete all the items without checking for expiration.\n for (const key of Object.keys(localStorage)) {\n if (key.startsWith(keyPrefix)) {\n localStorage.removeItem(key);\n }\n }\n },\n\n /**\n * Returns all items as map of key to value, mainly used for debugging dumps.\n * The type T is applied to all values, even though they might not be of type\n * T (in the case when you store different data types in the same store).\n */\n asMap<T>(): Map<string, StoredObject<T>> {\n const map = new Map<string, StoredObject<T>>();\n obj.forEach(\n (key: string, value: T, expiryMs: number, storedMs: number) => {\n map.set(key, { value: value as T, expiryMs, storedMs });\n },\n );\n return map;\n },\n\n /** Returns the ms timestamp for the last GC (garbage collection). */\n getLastGcMs(): number {\n const lastGcMsStr = localStorage.getItem(gcMsStorageKey);\n if (!lastGcMsStr) return 0;\n\n const ms = Number(lastGcMsStr);\n return isNaN(ms) ? 0 : ms;\n },\n\n /** Set the ms timestamp for the last GC (garbage collection). */\n setLastGcMs(ms: number) {\n localStorage.setItem(gcMsStorageKey, String(ms));\n },\n\n /** Perform garbage-collection if due, else do nothing. */\n gc(): void {\n const lastGcMs = obj.getLastGcMs();\n\n // Set initial timestamp - no need GC now.\n if (!lastGcMs) {\n obj.setLastGcMs(Date.now());\n return;\n }\n\n if (Date.now() < lastGcMs + gcIntervalMs) {\n return; // not due for next GC yet\n }\n\n // GC is due now, so run it.\n obj.gcNow();\n },\n\n /**\n * Perform garbage collection immediately without checking whether we are\n * due for the next GC or not.\n */\n gcNow(): void {\n console.log(`Starting localStore GC on ${storeName}`);\n\n // Prevent concurrent GC runs.\n obj.setLastGcMs(Date.now());\n let count = 0;\n\n obj.forEach((key: string, value: unknown, expiryMs: number) => {\n if (Date.now() >= expiryMs) {\n obj.delete(key);\n count++;\n }\n });\n\n console.log(\n `Finished localStore GC on ${storeName} - deleted ${count} keys`,\n );\n\n // Mark the end time as last GC time.\n obj.setLastGcMs(Date.now());\n },\n\n /** Returns `this` casted into a StorageAdapter<T>. */\n asStorageAdapter<T>(): StorageAdapter<T> {\n return obj as StorageAdapter<T>;\n },\n } as const;\n\n // Using `any` because the store could store any type of data for each key,\n // but the caller can specify a more specific type when calling each of the\n // methods.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return obj satisfies FullStorageAdapter<any>;\n}\n\nexport type LocalStore = ReturnType<typeof createLocalStore>;\n\n/**\n * Default local 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 localStore = createLocalStore(LocalStoreConfig.storeName);\n\n/** Create a local store item with a key and a default expiration. */\nexport function localStoreItem<T>(\n key: string,\n expiryMs?: number | Duration,\n store: LocalStore = localStore,\n) {\n const defaultExpiryMs = expiryMs && durationOrMsToMs(expiryMs);\n\n const obj = {\n key,\n defaultExpiryMs,\n store,\n\n /** Set a value in the store. */\n set(value: T, expiryDeltaMs?: number | undefined): void {\n store.set(key, value, expiryDeltaMs ?? defaultExpiryMs);\n },\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs, storedMs } =\n * await myLocalItem.getStoredObject();\n */\n getStoredObject(): StoredObject<T> | undefined {\n return store.getStoredObject(key);\n },\n\n /** Get a value by key, or undefined if it does not exist. */\n get(): T | undefined {\n return store.get(key);\n },\n\n /** Delete this key from the store. */\n delete(): void {\n store.delete(key);\n },\n };\n\n return obj;\n}\n\nexport type LocalStoreItem<T> = ReturnType<typeof localStoreItem<T>>;\n","/**\n * Bunch of miscellaneous constants and utility functions related to handling\n * date and time durations.\n *\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file. Weeks have fixed durations, but are excluded because we\n * use days as the max duration supported.\n */\n\nimport {\n HOURS_PER_DAY,\n MINUTES_PER_HOUR,\n MS_PER_DAY,\n MS_PER_HOUR,\n MS_PER_MINUTE,\n MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n} from \"./timeConstants\";\n\nexport type Duration = {\n days?: number;\n hours?: number;\n minutes?: number;\n seconds?: number;\n milliseconds?: number;\n};\n\n/**\n * One of: days, hours, minutes, seconds, milliseconds\n */\nexport type DurationType = keyof Duration;\n\n/**\n * Order in which the duration type appears in the duration string.\n */\nexport const DURATION_TYPE_SEQUENCE: DurationType[] = [\n \"days\",\n \"hours\",\n \"minutes\",\n \"seconds\",\n \"milliseconds\",\n];\n\n/**\n * Follows the same format as Intl.DurationFormat.prototype.format().\n *\n * Short: 1 yr, 2 mths, 3 wks, 3 days, 4 hr, 5 min, 6 sec, 7 ms, 8 μs, 9 ns\n * Long: 1 year, 2 months, 3 weeks, 3 days, 4 hours, 5 minutes, 6 seconds,\n * 7 milliseconds, 8 microseconds, 9 nanoseconds\n * Narrow: 1y 2mo 3w 3d 4h 5m 6s 7ms 8μs 9ns\n */\nexport type DurationStyle = \"short\" | \"long\" | \"narrow\";\n\nexport type DurationSuffixMap = {\n short: string;\n shorts: string;\n long: string;\n longs: string;\n narrow: string;\n};\n\nexport type DurationSuffixType = keyof DurationSuffixMap;\n\nexport const DURATION_STYLE_SUFFIX_MAP: Record<\n DurationType,\n DurationSuffixMap\n> = {\n days: {\n short: \"day\",\n shorts: \"days\",\n long: \"day\",\n longs: \"days\",\n narrow: \"d\",\n },\n hours: {\n short: \"hr\",\n shorts: \"hrs\",\n long: \"hour\",\n longs: \"hours\",\n narrow: \"h\",\n },\n minutes: {\n short: \"min\",\n shorts: \"mins\",\n long: \"minute\",\n longs: \"minutes\",\n narrow: \"m\",\n },\n seconds: {\n short: \"sec\",\n shorts: \"secs\",\n long: \"second\",\n longs: \"seconds\",\n narrow: \"s\",\n },\n milliseconds: {\n short: \"ms\",\n shorts: \"ms\",\n long: \"millisecond\",\n longs: \"milliseconds\",\n narrow: \"ms\",\n },\n};\n\nfunction getDurationStyleForPlural(style: DurationStyle): DurationSuffixType {\n return style == \"short\" ? \"shorts\" : style === \"long\" ? \"longs\" : style;\n}\n\nfunction getValueAndUnitSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \"\" : \" \";\n}\n\nfunction getDurationTypeSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \" \" : \", \";\n}\n\n/**\n * Convert a milliseconds duration into a Duration object. If the given ms is\n * zero, then return an object with a single field of zero with duration type\n * of durationTypeForZero.\n *\n * @param durationTypeForZero Defaults to 'milliseconds'\n */\nexport function msToDuration(\n ms: number,\n durationTypeForZero?: DurationType,\n): Duration {\n if (ms === 0) {\n durationTypeForZero = durationTypeForZero ?? \"milliseconds\";\n return { [durationTypeForZero]: 0 };\n }\n\n const duration: Duration = {};\n\n for (let i = 0; i < 1; i++) {\n let seconds = Math.floor(ms / MS_PER_SECOND);\n const millis = ms - seconds * MS_PER_SECOND;\n\n if (millis > 0) {\n duration[\"milliseconds\"] = millis;\n }\n\n if (seconds === 0) {\n break;\n }\n\n let minutes = Math.floor(seconds / SECONDS_PER_MINUTE);\n seconds -= minutes * SECONDS_PER_MINUTE;\n\n if (seconds > 0) {\n duration[\"seconds\"] = seconds;\n }\n\n if (minutes === 0) {\n break;\n }\n\n let hours = Math.floor(minutes / MINUTES_PER_HOUR);\n minutes -= hours * MINUTES_PER_HOUR;\n\n if (minutes > 0) {\n duration[\"minutes\"] = minutes;\n }\n\n if (hours === 0) {\n break;\n }\n\n const days = Math.floor(hours / HOURS_PER_DAY);\n hours -= days * HOURS_PER_DAY;\n\n if (hours > 0) {\n duration[\"hours\"] = hours;\n }\n\n if (days > 0) {\n duration[\"days\"] = days;\n }\n }\n\n return duration;\n}\n\n/**\n * Returns the number of milliseconds for the given duration.\n */\nexport function durationToMs(duration: Duration): number {\n const daysMs = (duration.days ?? 0) * MS_PER_DAY;\n const hoursMs = (duration.hours ?? 0) * MS_PER_HOUR;\n const minsMs = (duration.minutes ?? 0) * MS_PER_MINUTE;\n const secsMs = (duration.seconds ?? 0) * MS_PER_SECOND;\n const msMs = duration.milliseconds ?? 0;\n\n return daysMs + hoursMs + minsMs + secsMs + msMs;\n}\n\n/**\n * Convenience function to return a duration given an ms or Duration.\n */\nexport function durationOrMsToMs(duration: number | Duration): number {\n return typeof duration === \"number\" ? duration : durationToMs(duration);\n}\n\n/**\n * Format a given Duration object into a string. If the object has no fields,\n * then returns an empty string.\n *\n * @param style Defaults to 'short'\n */\nexport function formatDuration(duration: Duration, style?: DurationStyle) {\n style = style ?? \"short\";\n const stylePlural = getDurationStyleForPlural(style);\n\n const space = getValueAndUnitSeparator(style);\n\n const a: string[] = [];\n\n for (const unit of DURATION_TYPE_SEQUENCE) {\n const value = duration[unit];\n if (value === undefined) continue;\n\n const suffixMap = DURATION_STYLE_SUFFIX_MAP[unit];\n const suffix = value === 1 ? suffixMap[style] : suffixMap[stylePlural];\n a.push(value + space + suffix);\n }\n\n const separator = getDurationTypeSeparator(style);\n return a.join(separator);\n}\n\n/**\n * Convert a millisecond duration into a human-readable duration string.\n *\n * @param options.durationTypeForZero - Defaults to 'milliseconds'\n * @param options.style - Defaults to 'short'\n */\nexport function readableDuration(\n ms: number,\n options?: { durationTypeForZero?: DurationType; style?: DurationStyle },\n): string {\n const duration = msToDuration(ms, options?.durationTypeForZero);\n\n return formatDuration(duration, options?.style);\n}\n\n/** A shortened duration string useful for logging timings. */\nexport function elapsed(ms: number): string {\n // Use long format for 1 minute or over.\n if (ms > MS_PER_MINUTE) {\n return readableDuration(ms);\n }\n\n // Use seconds format for over 100ms.\n if (ms > 100) {\n return `${(ms / 1000).toFixed(3)}s`;\n }\n\n // Use milliseconds format.\n return ms + \"ms\";\n}\n"],"mappings":"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","names":["localStore_exports","__export","LocalStoreConfig","configureLocalStore","createLocalStore","localStore","localStoreItem","__toCommonJS","durationToMs","duration","daysMs","hoursMs","minsMs","secsMs","msMs","durationOrMsToMs","LocalStoreConfig","configureLocalStore","config","validateStoredObject","obj","createLocalStore","storeName","options","keyPrefix","defaultExpiryMs","durationOrMsToMs","gcIntervalMs","gcMsStorageKey","key","value","expiryDeltaMs","nowMs","stored","k","parsed","valid","e","callback","count","map","expiryMs","storedMs","lastGcMsStr","ms","lastGcMs","localStore","localStoreItem","store"]}
@@ -1,2 +1,2 @@
1
- function f(r){let t=(r.days??0)*864e5,e=(r.hours??0)*36e5,s=(r.minutes??0)*6e4,o=(r.seconds??0)*1e3,n=r.milliseconds??0;return t+e+s+o+n}function i(r){return typeof r=="number"?r:f(r)}var a={storeName:"ts-utils",expiryMs:864e5*30,gcIntervalMs:864e5};function M(r){Object.assign(a,r)}function g(r){if(!(!r||typeof r!="object"||r.value===void 0||typeof r.storedMs!="number"||typeof r.expiryMs!="number"||Date.now()>=r.expiryMs))return r}var u=class{constructor(t,e){this.storeName=t;this.keyPrefix=t+":",this.defaultExpiryMs=e?.defaultExpiryMs?i(e.defaultExpiryMs):a.expiryMs,this.gcIntervalMs=e?.gcIntervalMs?i(e.gcIntervalMs):a.gcIntervalMs,this.gcMsStorageKey=`__localStore:lastGcMs:${t}`}keyPrefix;gcMsStorageKey;defaultExpiryMs;gcIntervalMs;set(t,e,s=this.defaultExpiryMs){let o=Date.now(),n={value:e,storedMs:o,expiryMs:o+i(s)};return localStorage.setItem(this.keyPrefix+t,JSON.stringify(n)),this.gc(),e}delete(t){if(typeof t=="string")localStorage.removeItem(this.keyPrefix+t);else for(let e of t)localStorage.removeItem(this.keyPrefix+e)}getStoredObject(t){let e=this.keyPrefix+t,s=localStorage.getItem(e);if(s)try{let o=JSON.parse(s),n=g(o);if(!n){this.delete(e),this.gc();return}return n}catch(o){console.error(`Invalid local value: ${e}=${s}:`,o),this.delete(e),this.gc();return}}get(t){return this.getStoredObject(t)?.value}forEach(t){for(let e of Object.keys(localStorage)){if(!e.startsWith(this.keyPrefix))continue;let s=e.slice(this.keyPrefix.length),o=this.getStoredObject(s);o&&t(s,o.value,o.expiryMs,o.storedMs)}}size(){let t=0;return this.forEach(()=>{t++}),t}clear(){for(let t of Object.keys(localStorage))t.startsWith(this.keyPrefix)&&localStorage.removeItem(t)}asMap(){let t=new Map;return this.forEach((e,s,o,n)=>{t.set(e,{value:s,expiryMs:o,storedMs:n})}),t}get lastGcMs(){let t=globalThis.localStorage.getItem(this.gcMsStorageKey);if(!t)return 0;let e=Number(t);return isNaN(e)?0:e}set lastGcMs(t){globalThis.localStorage.setItem(this.gcMsStorageKey,String(t))}gc(){let t=this.lastGcMs;if(!t){this.lastGcMs=Date.now();return}Date.now()<t+this.gcIntervalMs||this.gcNow()}gcNow(){console.log(`Starting localStore GC on ${this.storeName}`),this.lastGcMs=Date.now();let t=0;this.forEach((e,s,o)=>{Date.now()>=o&&(this.delete(e),t++)}),console.log(`Finished localStore GC on ${this.storeName} - deleted ${t} keys`),this.lastGcMs=Date.now()}asStorageAdapter(){return this}},d=new u(a.storeName),c=class{constructor(t,e=a.expiryMs,s=d){this.key=t;this.store=s;this.defaultExpiryMs=e&&i(e)}defaultExpiryMs;set(t,e=this.defaultExpiryMs){this.store.set(this.key,t,e)}getStoredObject(){return this.store.getStoredObject(this.key)}get(){return this.store.get(this.key)}delete(){this.store.delete(this.key)}};function T(r,t,e=d){return t=t&&i(t),new c(r,t,e)}export{u as LocalStore,a as LocalStoreConfig,c as LocalStoreItem,M as configureLocalStore,d as localStore,T as localStoreItem};
1
+ function S(t){let i=(t.days??0)*864e5,n=(t.hours??0)*36e5,u=(t.minutes??0)*6e4,d=(t.seconds??0)*1e3,c=t.milliseconds??0;return i+n+u+d+c}function f(t){return typeof t=="number"?t:S(t)}var g={storeName:"ts-utils",expiryMs:864e5*30,gcIntervalMs:864e5};function v(t){Object.assign(g,t)}function m(t){if(!(!t||typeof t!="object"||t.value===void 0||typeof t.storedMs!="number"||typeof t.expiryMs!="number"||Date.now()>=t.expiryMs))return t}function M(t,i){let n=t+":",u=i?.defaultExpiryMs?f(i.defaultExpiryMs):g.expiryMs,d=i?.gcIntervalMs?f(i.gcIntervalMs):g.gcIntervalMs,c=`__localStore:lastGcMs:${t}`,r={storeName:t,keyPrefix:n,defaultExpiryMs:u,gcIntervalMs:d,gcMsStorageKey:c,set(e,o,a){let s=Date.now(),l={value:o,storedMs:s,expiryMs:s+f(a??u)};return localStorage.setItem(n+e,JSON.stringify(l)),r.gc(),o},delete(e){if(typeof e=="string")localStorage.removeItem(n+e);else for(let o of e)localStorage.removeItem(n+o)},getStoredObject(e){let o=n+e,a=localStorage.getItem(o);if(a)try{let s=JSON.parse(a),l=m(s);if(!l){r.delete(o),r.gc();return}return l}catch(s){console.error(`Invalid local value: ${o}=${a}:`,s),r.delete(o),r.gc();return}},get(e){return r.getStoredObject(e)?.value},forEach(e){for(let o of Object.keys(localStorage)){if(!o.startsWith(n))continue;let a=o.slice(n.length),s=r.getStoredObject(a);s&&e(a,s.value,s.expiryMs,s.storedMs)}},size(){let e=0;return r.forEach(()=>{e++}),e},clear(){for(let e of Object.keys(localStorage))e.startsWith(n)&&localStorage.removeItem(e)},asMap(){let e=new Map;return r.forEach((o,a,s,l)=>{e.set(o,{value:a,expiryMs:s,storedMs:l})}),e},getLastGcMs(){let e=localStorage.getItem(c);if(!e)return 0;let o=Number(e);return isNaN(o)?0:o},setLastGcMs(e){localStorage.setItem(c,String(e))},gc(){let e=r.getLastGcMs();if(!e){r.setLastGcMs(Date.now());return}Date.now()<e+d||r.gcNow()},gcNow(){console.log(`Starting localStore GC on ${t}`),r.setLastGcMs(Date.now());let e=0;r.forEach((o,a,s)=>{Date.now()>=s&&(r.delete(o),e++)}),console.log(`Finished localStore GC on ${t} - deleted ${e} keys`),r.setLastGcMs(Date.now())},asStorageAdapter(){return r}};return r}var y=M(g.storeName);function E(t,i,n=y){let u=i&&f(i);return{key:t,defaultExpiryMs:u,store:n,set(c,r){n.set(t,c,r??u)},getStoredObject(){return n.getStoredObject(t)},get(){return n.get(t)},delete(){n.delete(t)}}}export{g as LocalStoreConfig,v as configureLocalStore,M as createLocalStore,y as localStore,E as localStoreItem};
2
2
  //# sourceMappingURL=localStore.min.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/duration.ts","../src/localStore.ts"],"sourcesContent":["/**\n * Bunch of miscellaneous constants and utility functions related to handling\n * date and time durations.\n *\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file. Weeks have fixed durations, but are excluded because we\n * use days as the max duration supported.\n */\n\nimport {\n HOURS_PER_DAY,\n MINUTES_PER_HOUR,\n MS_PER_DAY,\n MS_PER_HOUR,\n MS_PER_MINUTE,\n MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n} from \"./timeConstants\";\n\nexport type Duration = {\n days?: number;\n hours?: number;\n minutes?: number;\n seconds?: number;\n milliseconds?: number;\n};\n\n/**\n * One of: days, hours, minutes, seconds, milliseconds\n */\nexport type DurationType = keyof Duration;\n\n/**\n * Order in which the duration type appears in the duration string.\n */\nexport const DURATION_TYPE_SEQUENCE: DurationType[] = [\n \"days\",\n \"hours\",\n \"minutes\",\n \"seconds\",\n \"milliseconds\",\n];\n\n/**\n * Follows the same format as Intl.DurationFormat.prototype.format().\n *\n * Short: 1 yr, 2 mths, 3 wks, 3 days, 4 hr, 5 min, 6 sec, 7 ms, 8 μs, 9 ns\n * Long: 1 year, 2 months, 3 weeks, 3 days, 4 hours, 5 minutes, 6 seconds,\n * 7 milliseconds, 8 microseconds, 9 nanoseconds\n * Narrow: 1y 2mo 3w 3d 4h 5m 6s 7ms 8μs 9ns\n */\nexport type DurationStyle = \"short\" | \"long\" | \"narrow\";\n\nexport type DurationSuffixMap = {\n short: string;\n shorts: string;\n long: string;\n longs: string;\n narrow: string;\n};\n\nexport type DurationSuffixType = keyof DurationSuffixMap;\n\nexport const DURATION_STYLE_SUFFIX_MAP: Record<\n DurationType,\n DurationSuffixMap\n> = {\n days: {\n short: \"day\",\n shorts: \"days\",\n long: \"day\",\n longs: \"days\",\n narrow: \"d\",\n },\n hours: {\n short: \"hr\",\n shorts: \"hrs\",\n long: \"hour\",\n longs: \"hours\",\n narrow: \"h\",\n },\n minutes: {\n short: \"min\",\n shorts: \"mins\",\n long: \"minute\",\n longs: \"minutes\",\n narrow: \"m\",\n },\n seconds: {\n short: \"sec\",\n shorts: \"secs\",\n long: \"second\",\n longs: \"seconds\",\n narrow: \"s\",\n },\n milliseconds: {\n short: \"ms\",\n shorts: \"ms\",\n long: \"millisecond\",\n longs: \"milliseconds\",\n narrow: \"ms\",\n },\n};\n\nfunction getDurationStyleForPlural(style: DurationStyle): DurationSuffixType {\n return style == \"short\" ? \"shorts\" : style === \"long\" ? \"longs\" : style;\n}\n\nfunction getValueAndUnitSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \"\" : \" \";\n}\n\nfunction getDurationTypeSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \" \" : \", \";\n}\n\n/**\n * Convert a milliseconds duration into a Duration object. If the given ms is\n * zero, then return an object with a single field of zero with duration type\n * of durationTypeForZero.\n *\n * @param durationTypeForZero Defaults to 'milliseconds'\n */\nexport function msToDuration(\n ms: number,\n durationTypeForZero?: DurationType,\n): Duration {\n if (ms === 0) {\n durationTypeForZero = durationTypeForZero ?? \"milliseconds\";\n return { [durationTypeForZero]: 0 };\n }\n\n const duration: Duration = {};\n\n for (let i = 0; i < 1; i++) {\n let seconds = Math.floor(ms / MS_PER_SECOND);\n const millis = ms - seconds * MS_PER_SECOND;\n\n if (millis > 0) {\n duration[\"milliseconds\"] = millis;\n }\n\n if (seconds === 0) {\n break;\n }\n\n let minutes = Math.floor(seconds / SECONDS_PER_MINUTE);\n seconds -= minutes * SECONDS_PER_MINUTE;\n\n if (seconds > 0) {\n duration[\"seconds\"] = seconds;\n }\n\n if (minutes === 0) {\n break;\n }\n\n let hours = Math.floor(minutes / MINUTES_PER_HOUR);\n minutes -= hours * MINUTES_PER_HOUR;\n\n if (minutes > 0) {\n duration[\"minutes\"] = minutes;\n }\n\n if (hours === 0) {\n break;\n }\n\n const days = Math.floor(hours / HOURS_PER_DAY);\n hours -= days * HOURS_PER_DAY;\n\n if (hours > 0) {\n duration[\"hours\"] = hours;\n }\n\n if (days > 0) {\n duration[\"days\"] = days;\n }\n }\n\n return duration;\n}\n\n/**\n * Returns the number of milliseconds for the given duration.\n */\nexport function durationToMs(duration: Duration): number {\n const daysMs = (duration.days ?? 0) * MS_PER_DAY;\n const hoursMs = (duration.hours ?? 0) * MS_PER_HOUR;\n const minsMs = (duration.minutes ?? 0) * MS_PER_MINUTE;\n const secsMs = (duration.seconds ?? 0) * MS_PER_SECOND;\n const msMs = duration.milliseconds ?? 0;\n\n return daysMs + hoursMs + minsMs + secsMs + msMs;\n}\n\n/**\n * Convenience function to return a duration given an ms or Duration.\n */\nexport function durationOrMsToMs(duration: number | Duration): number {\n return typeof duration === \"number\" ? duration : durationToMs(duration);\n}\n\n/**\n * Format a given Duration object into a string. If the object has no fields,\n * then returns an empty string.\n *\n * @param style Defaults to 'short'\n */\nexport function formatDuration(duration: Duration, style?: DurationStyle) {\n style = style ?? \"short\";\n const stylePlural = getDurationStyleForPlural(style);\n\n const space = getValueAndUnitSeparator(style);\n\n const a: string[] = [];\n\n for (const unit of DURATION_TYPE_SEQUENCE) {\n const value = duration[unit];\n if (value === undefined) continue;\n\n const suffixMap = DURATION_STYLE_SUFFIX_MAP[unit];\n const suffix = value === 1 ? suffixMap[style] : suffixMap[stylePlural];\n a.push(value + space + suffix);\n }\n\n const separator = getDurationTypeSeparator(style);\n return a.join(separator);\n}\n\n/**\n * Convert a millisecond duration into a human-readable duration string.\n *\n * @param options.durationTypeForZero - Defaults to 'milliseconds'\n * @param options.style - Defaults to 'short'\n */\nexport function readableDuration(\n ms: number,\n options?: { durationTypeForZero?: DurationType; style?: DurationStyle },\n): string {\n const duration = msToDuration(ms, options?.durationTypeForZero);\n\n return formatDuration(duration, options?.style);\n}\n\n/** A shortened duration string useful for logging timings. */\nexport function elapsed(ms: number): string {\n // Use long format for 1 minute or over.\n if (ms > MS_PER_MINUTE) {\n return readableDuration(ms);\n }\n\n // Use seconds format for over 100ms.\n if (ms > 100) {\n return `${(ms / 1000).toFixed(3)}s`;\n }\n\n // Use milliseconds format.\n return ms + \"ms\";\n}\n","/**\n * Local storage key-value store with support for auto-expirations.\n *\n * Why use this?\n * 1. Extremely simple interface to use local storage.\n * 2. Auto-expirations with GC frees you from worrying about data clean-up.\n * 3. Any serializable data type can be stored (except undefined).\n *\n * How to use?\n * Just use the `localStore` global constant like the local storage.\n *\n * Why not use the localStorage directly?\n * localStorage does not provide auto-expirations with GC. If you don't need\n * this (items never expire), then just use localStorage directly.\n */\n\nimport { Duration, durationOrMsToMs } from \"./duration\";\nimport {\n FullStorageAdapter,\n StorageAdapter,\n StoredObject,\n} from \"./storageAdapter\";\nimport { MS_PER_DAY } from \"./timeConstants\";\n\n/** Global defaults can be updated directly. */\nexport const LocalStoreConfig = {\n /** All items with the same store name will share the same storage space. */\n storeName: \"ts-utils\",\n\n /** 30 days in ms. */\n expiryMs: MS_PER_DAY * 30,\n\n /** Do GC once per day. */\n gcIntervalMs: MS_PER_DAY,\n};\n\nexport type LocalStoreConfig = typeof LocalStoreConfig;\n\n/** Convenience function to update global defaults. */\nexport function configureLocalStore(config: Partial<LocalStoreConfig>) {\n Object.assign(LocalStoreConfig, config);\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 obj.value === undefined ||\n typeof obj.storedMs !== \"number\" ||\n typeof obj.expiryMs !== \"number\" ||\n Date.now() >= obj.expiryMs\n ) {\n return undefined;\n }\n\n return obj;\n}\n\n/**\n * You can create multiple LocalStores if you want, but most likely you will only\n * need to use the default `localStore` instance.\n */\n// Using `any` because the store could store any type of data for each key,\n// but the caller can specify a more specific type when calling each of the\n// methods.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class LocalStore implements FullStorageAdapter<any> {\n /**\n * The prefix string for the local storage key which identifies items\n * belonging to this namespace.\n */\n public readonly keyPrefix: string;\n\n /** Local storage key name for the last GC completed timestamp. */\n public readonly gcMsStorageKey: string;\n\n public readonly defaultExpiryMs: number;\n public readonly gcIntervalMs: number;\n\n public constructor(\n public readonly storeName: string,\n options?: {\n defaultExpiryMs?: number | Duration;\n gcIntervalMs?: number | Duration;\n },\n ) {\n this.keyPrefix = storeName + \":\";\n\n this.defaultExpiryMs = options?.defaultExpiryMs\n ? durationOrMsToMs(options.defaultExpiryMs)\n : LocalStoreConfig.expiryMs;\n\n this.gcIntervalMs = options?.gcIntervalMs\n ? durationOrMsToMs(options.gcIntervalMs)\n : LocalStoreConfig.gcIntervalMs;\n\n this.gcMsStorageKey = `__localStore:lastGcMs:${storeName}`;\n }\n\n /** Set a value in the store. */\n public set<T>(\n key: string,\n value: T,\n expiryDeltaMs: number | Duration = this.defaultExpiryMs,\n ): T {\n const nowMs = Date.now();\n const obj: StoredObject<T> = {\n value,\n storedMs: nowMs,\n expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs),\n };\n\n localStorage.setItem(this.keyPrefix + key, JSON.stringify(obj));\n\n this.gc(); // check GC on every write\n\n return value;\n }\n\n /** Delete one or multiple keys. */\n public delete(key: string | string[]): void {\n if (typeof key === \"string\") {\n localStorage.removeItem(this.keyPrefix + key);\n } else {\n for (const k of key) {\n localStorage.removeItem(this.keyPrefix + k);\n }\n }\n }\n\n /** Mainly used to get the expiration timestamp of an object. */\n public getStoredObject<T>(key: string): StoredObject<T> | undefined {\n const k = this.keyPrefix + key;\n const stored = localStorage.getItem(k);\n\n if (!stored) {\n return undefined;\n }\n\n try {\n const parsed = JSON.parse(stored);\n const obj = validateStoredObject(parsed);\n if (!obj) {\n this.delete(k);\n\n this.gc(); // check GC on every read of an expired key\n\n return undefined;\n }\n\n return obj as StoredObject<T>;\n } catch (e) {\n console.error(`Invalid local value: ${k}=${stored}:`, e);\n this.delete(k);\n\n this.gc(); // check GC on every read of an invalid key\n\n return undefined;\n }\n }\n\n /** Get a value by key, or undefined if it does not exist. */\n public get<T>(key: string): T | undefined {\n const obj = this.getStoredObject<T>(key);\n\n return obj?.value;\n }\n\n /** Generic way to iterate through all entries. */\n public forEach<T>(\n callback: (\n key: string,\n value: T,\n expiryMs: number,\n storedMs: number,\n ) => void,\n ): void {\n for (const k of Object.keys(localStorage)) {\n if (!k.startsWith(this.keyPrefix)) continue;\n\n const key = k.slice(this.keyPrefix.length);\n const obj = this.getStoredObject(key);\n\n if (!obj) continue;\n\n callback(key, obj.value as T, obj.expiryMs, obj.storedMs);\n }\n }\n\n /**\n * Returns the number of items in the store. Note that getting the size\n * requires iterating through the entire store because the items could expire\n * at any time, and hence the size is a dynamic number.\n */\n public size(): number {\n let count = 0;\n this.forEach(() => {\n count++;\n });\n return count;\n }\n\n /** Remove all items from the store. */\n public clear(): void {\n // Note that we don't need to use this.forEach() because we are just\n // going to delete all the items without checking for expiration.\n for (const key of Object.keys(localStorage)) {\n if (key.startsWith(this.keyPrefix)) {\n localStorage.removeItem(key);\n }\n }\n }\n\n /**\n * Returns all items as map of key to value, mainly used for debugging dumps.\n * The type T is applied to all values, even though they might not be of type\n * T (in the case when you store different data types in the same store).\n */\n public asMap<T>(): Map<string, StoredObject<T>> {\n const map = new Map<string, StoredObject<T>>();\n this.forEach((key, value, expiryMs, storedMs) => {\n map.set(key, { value: value as T, expiryMs, storedMs });\n });\n return map;\n }\n\n /** Returns the ms timestamp for the last GC (garbage collection). */\n public get lastGcMs(): number {\n const lastGcMsStr = globalThis.localStorage.getItem(this.gcMsStorageKey);\n if (!lastGcMsStr) return 0;\n\n const ms = Number(lastGcMsStr);\n return isNaN(ms) ? 0 : ms;\n }\n\n /** Set the ms timestamp for the last GC (garbage collection). */\n public set lastGcMs(ms: number) {\n globalThis.localStorage.setItem(this.gcMsStorageKey, String(ms));\n }\n\n /** Perform garbage-collection if due, else do nothing. */\n public gc(): void {\n const lastGcMs = this.lastGcMs;\n\n // Set initial timestamp - no need GC now.\n if (!lastGcMs) {\n this.lastGcMs = Date.now();\n return;\n }\n\n if (Date.now() < lastGcMs + this.gcIntervalMs) {\n return; // not due for next GC yet\n }\n\n // GC is due now, so run it.\n this.gcNow();\n }\n\n /**\n * Perform garbage collection immediately without checking whether we are\n * due for the next GC or not.\n */\n public gcNow(): void {\n console.log(`Starting localStore GC on ${this.storeName}`);\n\n // Prevent concurrent GC runs.\n this.lastGcMs = Date.now();\n let count = 0;\n\n this.forEach((key: string, value: unknown, expiryMs: number) => {\n if (Date.now() >= expiryMs) {\n this.delete(key);\n count++;\n }\n });\n\n console.log(\n `Finished localStore GC on ${this.storeName} - deleted ${count} keys`,\n );\n\n // Mark the end time as last GC time.\n this.lastGcMs = Date.now();\n }\n\n /** Returns `this` casted into a StorageAdapter<T>. */\n public asStorageAdapter<T>(): StorageAdapter<T> {\n return this as StorageAdapter<T>;\n }\n}\n\n/**\n * Default local 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 localStore = new LocalStore(LocalStoreConfig.storeName);\n\n/**\n * Class to represent one key in the store with a default expiration.\n */\nexport class LocalStoreItem<T> {\n public readonly defaultExpiryMs: number;\n\n public constructor(\n public readonly key: string,\n defaultExpiryMs: number | Duration = LocalStoreConfig.expiryMs,\n public readonly store = localStore,\n ) {\n this.defaultExpiryMs = defaultExpiryMs && durationOrMsToMs(defaultExpiryMs);\n }\n\n /** Set a value in the store. */\n public set(\n value: T,\n expiryDeltaMs: number | undefined = this.defaultExpiryMs,\n ): void {\n this.store.set(this.key, value, expiryDeltaMs);\n }\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs, storedMs } =\n * await myLocalItem.getStoredObject();\n */\n public getStoredObject(): StoredObject<T> | undefined {\n return this.store.getStoredObject(this.key);\n }\n\n /** Get a value by key, or undefined if it does not exist. */\n public get(): T | undefined {\n return this.store.get(this.key);\n }\n\n /** Delete this key from the store. */\n public delete(): void {\n this.store.delete(this.key);\n }\n}\n\n/** Create a local store item with a key and a default expiration. */\nexport function localStoreItem<T>(\n key: string,\n expiryMs?: number | Duration,\n store = localStore,\n): LocalStoreItem<T> {\n expiryMs = expiryMs && durationOrMsToMs(expiryMs);\n\n return new LocalStoreItem<T>(key, expiryMs, store);\n}\n"],"mappings":"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","names":["durationToMs","duration","daysMs","hoursMs","minsMs","secsMs","msMs","durationOrMsToMs","LocalStoreConfig","configureLocalStore","config","validateStoredObject","obj","LocalStore","storeName","options","durationOrMsToMs","key","value","expiryDeltaMs","nowMs","k","stored","parsed","e","callback","count","map","expiryMs","storedMs","lastGcMsStr","ms","lastGcMs","localStore","LocalStoreItem","defaultExpiryMs","store","localStoreItem"]}
1
+ {"version":3,"sources":["../src/duration.ts","../src/localStore.ts"],"sourcesContent":["/**\n * Bunch of miscellaneous constants and utility functions related to handling\n * date and time durations.\n *\n * Note that month and year do not have fixed durations, and hence are excluded\n * from this file. Weeks have fixed durations, but are excluded because we\n * use days as the max duration supported.\n */\n\nimport {\n HOURS_PER_DAY,\n MINUTES_PER_HOUR,\n MS_PER_DAY,\n MS_PER_HOUR,\n MS_PER_MINUTE,\n MS_PER_SECOND,\n SECONDS_PER_MINUTE,\n} from \"./timeConstants\";\n\nexport type Duration = {\n days?: number;\n hours?: number;\n minutes?: number;\n seconds?: number;\n milliseconds?: number;\n};\n\n/**\n * One of: days, hours, minutes, seconds, milliseconds\n */\nexport type DurationType = keyof Duration;\n\n/**\n * Order in which the duration type appears in the duration string.\n */\nexport const DURATION_TYPE_SEQUENCE: DurationType[] = [\n \"days\",\n \"hours\",\n \"minutes\",\n \"seconds\",\n \"milliseconds\",\n];\n\n/**\n * Follows the same format as Intl.DurationFormat.prototype.format().\n *\n * Short: 1 yr, 2 mths, 3 wks, 3 days, 4 hr, 5 min, 6 sec, 7 ms, 8 μs, 9 ns\n * Long: 1 year, 2 months, 3 weeks, 3 days, 4 hours, 5 minutes, 6 seconds,\n * 7 milliseconds, 8 microseconds, 9 nanoseconds\n * Narrow: 1y 2mo 3w 3d 4h 5m 6s 7ms 8μs 9ns\n */\nexport type DurationStyle = \"short\" | \"long\" | \"narrow\";\n\nexport type DurationSuffixMap = {\n short: string;\n shorts: string;\n long: string;\n longs: string;\n narrow: string;\n};\n\nexport type DurationSuffixType = keyof DurationSuffixMap;\n\nexport const DURATION_STYLE_SUFFIX_MAP: Record<\n DurationType,\n DurationSuffixMap\n> = {\n days: {\n short: \"day\",\n shorts: \"days\",\n long: \"day\",\n longs: \"days\",\n narrow: \"d\",\n },\n hours: {\n short: \"hr\",\n shorts: \"hrs\",\n long: \"hour\",\n longs: \"hours\",\n narrow: \"h\",\n },\n minutes: {\n short: \"min\",\n shorts: \"mins\",\n long: \"minute\",\n longs: \"minutes\",\n narrow: \"m\",\n },\n seconds: {\n short: \"sec\",\n shorts: \"secs\",\n long: \"second\",\n longs: \"seconds\",\n narrow: \"s\",\n },\n milliseconds: {\n short: \"ms\",\n shorts: \"ms\",\n long: \"millisecond\",\n longs: \"milliseconds\",\n narrow: \"ms\",\n },\n};\n\nfunction getDurationStyleForPlural(style: DurationStyle): DurationSuffixType {\n return style == \"short\" ? \"shorts\" : style === \"long\" ? \"longs\" : style;\n}\n\nfunction getValueAndUnitSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \"\" : \" \";\n}\n\nfunction getDurationTypeSeparator(style: DurationStyle): string {\n return style === \"narrow\" ? \" \" : \", \";\n}\n\n/**\n * Convert a milliseconds duration into a Duration object. If the given ms is\n * zero, then return an object with a single field of zero with duration type\n * of durationTypeForZero.\n *\n * @param durationTypeForZero Defaults to 'milliseconds'\n */\nexport function msToDuration(\n ms: number,\n durationTypeForZero?: DurationType,\n): Duration {\n if (ms === 0) {\n durationTypeForZero = durationTypeForZero ?? \"milliseconds\";\n return { [durationTypeForZero]: 0 };\n }\n\n const duration: Duration = {};\n\n for (let i = 0; i < 1; i++) {\n let seconds = Math.floor(ms / MS_PER_SECOND);\n const millis = ms - seconds * MS_PER_SECOND;\n\n if (millis > 0) {\n duration[\"milliseconds\"] = millis;\n }\n\n if (seconds === 0) {\n break;\n }\n\n let minutes = Math.floor(seconds / SECONDS_PER_MINUTE);\n seconds -= minutes * SECONDS_PER_MINUTE;\n\n if (seconds > 0) {\n duration[\"seconds\"] = seconds;\n }\n\n if (minutes === 0) {\n break;\n }\n\n let hours = Math.floor(minutes / MINUTES_PER_HOUR);\n minutes -= hours * MINUTES_PER_HOUR;\n\n if (minutes > 0) {\n duration[\"minutes\"] = minutes;\n }\n\n if (hours === 0) {\n break;\n }\n\n const days = Math.floor(hours / HOURS_PER_DAY);\n hours -= days * HOURS_PER_DAY;\n\n if (hours > 0) {\n duration[\"hours\"] = hours;\n }\n\n if (days > 0) {\n duration[\"days\"] = days;\n }\n }\n\n return duration;\n}\n\n/**\n * Returns the number of milliseconds for the given duration.\n */\nexport function durationToMs(duration: Duration): number {\n const daysMs = (duration.days ?? 0) * MS_PER_DAY;\n const hoursMs = (duration.hours ?? 0) * MS_PER_HOUR;\n const minsMs = (duration.minutes ?? 0) * MS_PER_MINUTE;\n const secsMs = (duration.seconds ?? 0) * MS_PER_SECOND;\n const msMs = duration.milliseconds ?? 0;\n\n return daysMs + hoursMs + minsMs + secsMs + msMs;\n}\n\n/**\n * Convenience function to return a duration given an ms or Duration.\n */\nexport function durationOrMsToMs(duration: number | Duration): number {\n return typeof duration === \"number\" ? duration : durationToMs(duration);\n}\n\n/**\n * Format a given Duration object into a string. If the object has no fields,\n * then returns an empty string.\n *\n * @param style Defaults to 'short'\n */\nexport function formatDuration(duration: Duration, style?: DurationStyle) {\n style = style ?? \"short\";\n const stylePlural = getDurationStyleForPlural(style);\n\n const space = getValueAndUnitSeparator(style);\n\n const a: string[] = [];\n\n for (const unit of DURATION_TYPE_SEQUENCE) {\n const value = duration[unit];\n if (value === undefined) continue;\n\n const suffixMap = DURATION_STYLE_SUFFIX_MAP[unit];\n const suffix = value === 1 ? suffixMap[style] : suffixMap[stylePlural];\n a.push(value + space + suffix);\n }\n\n const separator = getDurationTypeSeparator(style);\n return a.join(separator);\n}\n\n/**\n * Convert a millisecond duration into a human-readable duration string.\n *\n * @param options.durationTypeForZero - Defaults to 'milliseconds'\n * @param options.style - Defaults to 'short'\n */\nexport function readableDuration(\n ms: number,\n options?: { durationTypeForZero?: DurationType; style?: DurationStyle },\n): string {\n const duration = msToDuration(ms, options?.durationTypeForZero);\n\n return formatDuration(duration, options?.style);\n}\n\n/** A shortened duration string useful for logging timings. */\nexport function elapsed(ms: number): string {\n // Use long format for 1 minute or over.\n if (ms > MS_PER_MINUTE) {\n return readableDuration(ms);\n }\n\n // Use seconds format for over 100ms.\n if (ms > 100) {\n return `${(ms / 1000).toFixed(3)}s`;\n }\n\n // Use milliseconds format.\n return ms + \"ms\";\n}\n","/**\n * Local storage key-value store with support for auto-expirations.\n *\n * Why use this?\n * 1. Extremely simple interface to use local storage.\n * 2. Auto-expirations with GC frees you from worrying about data clean-up.\n * 3. Any serializable data type can be stored (except undefined).\n *\n * How to use?\n * Just use the `localStore` global constant like the local storage.\n *\n * Why not use the localStorage directly?\n * localStorage does not provide auto-expirations with GC. If you don't need\n * this (items never expire), then just use localStorage directly.\n */\n\nimport { Duration, durationOrMsToMs } from \"./duration\";\nimport {\n FullStorageAdapter,\n StorageAdapter,\n StoredObject,\n} from \"./storageAdapter\";\nimport { MS_PER_DAY } from \"./timeConstants\";\n\n/** Global defaults can be updated directly. */\nexport const LocalStoreConfig = {\n /** All items with the same store name will share the same storage space. */\n storeName: \"ts-utils\",\n\n /** 30 days in ms. */\n expiryMs: MS_PER_DAY * 30,\n\n /** Do GC once per day. */\n gcIntervalMs: MS_PER_DAY,\n};\n\nexport type LocalStoreConfig = typeof LocalStoreConfig;\n\n/** Convenience function to update global defaults. */\nexport function configureLocalStore(config: Partial<LocalStoreConfig>) {\n Object.assign(LocalStoreConfig, config);\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 obj.value === undefined ||\n typeof obj.storedMs !== \"number\" ||\n typeof obj.expiryMs !== \"number\" ||\n Date.now() >= obj.expiryMs\n ) {\n return undefined;\n }\n\n return obj;\n}\n\n/**\n * You can create multiple LocalStores if you want, but most likely you will only\n * need to use the default `localStore` instance.\n */\n// Using `any` because the store could store any type of data for each key,\n// but the caller can specify a more specific type when calling each of the\n// methods.\n\nexport function createLocalStore(\n storeName: string,\n options?: {\n defaultExpiryMs?: number | Duration;\n gcIntervalMs?: number | Duration;\n },\n) {\n const keyPrefix = storeName + \":\";\n\n const defaultExpiryMs = options?.defaultExpiryMs\n ? durationOrMsToMs(options.defaultExpiryMs)\n : LocalStoreConfig.expiryMs;\n\n const gcIntervalMs = options?.gcIntervalMs\n ? durationOrMsToMs(options.gcIntervalMs)\n : LocalStoreConfig.gcIntervalMs;\n\n const gcMsStorageKey = `__localStore:lastGcMs:${storeName}`;\n\n const obj = {\n /** Input name for the store. */\n storeName,\n\n /**\n * The prefix string for the local storage key which identifies items\n * belonging to this namespace.\n */\n keyPrefix,\n\n /** Default expiry to use if not specified in set(). */\n defaultExpiryMs,\n\n /** Time interval for when GC's occur. */\n gcIntervalMs,\n\n /** Local storage key name for the last GC completed timestamp. */\n gcMsStorageKey,\n\n /** Set a value in the store. */\n set<T>(key: string, value: T, expiryDeltaMs?: number | Duration): T {\n const nowMs = Date.now();\n const stored: StoredObject<T> = {\n value,\n storedMs: nowMs,\n expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs ?? defaultExpiryMs),\n };\n\n localStorage.setItem(keyPrefix + key, JSON.stringify(stored));\n\n obj.gc(); // check GC on every write\n\n return value;\n },\n\n /** Delete one or multiple keys. */\n delete(key: string | string[]): void {\n if (typeof key === \"string\") {\n localStorage.removeItem(keyPrefix + key);\n } else {\n for (const k of key) {\n localStorage.removeItem(keyPrefix + k);\n }\n }\n },\n\n /** Mainly used to get the expiration timestamp of an object. */\n getStoredObject<T>(key: string): StoredObject<T> | undefined {\n const k = keyPrefix + key;\n const stored = localStorage.getItem(k);\n\n if (!stored) {\n return undefined;\n }\n\n try {\n const parsed = JSON.parse(stored);\n const valid = validateStoredObject(parsed);\n if (!valid) {\n obj.delete(k);\n\n obj.gc(); // check GC on every read of an expired key\n\n return undefined;\n }\n\n return valid as StoredObject<T>;\n } catch (e) {\n console.error(`Invalid local value: ${k}=${stored}:`, e);\n obj.delete(k);\n\n obj.gc(); // check GC on every read of an invalid key\n\n return undefined;\n }\n },\n\n /** Get a value by key, or undefined if it does not exist. */\n get<T>(key: string): T | undefined {\n const stored = obj.getStoredObject<T>(key);\n\n return stored?.value;\n },\n\n /** Generic way to iterate through all entries. */\n forEach<T>(\n callback: (\n key: string,\n value: T,\n expiryMs: number,\n storedMs: number,\n ) => void,\n ): void {\n for (const k of Object.keys(localStorage)) {\n if (!k.startsWith(keyPrefix)) continue;\n\n const key = k.slice(keyPrefix.length);\n const stored = obj.getStoredObject(key);\n\n if (!stored) continue;\n\n callback(key, stored.value as T, stored.expiryMs, stored.storedMs);\n }\n },\n\n /**\n * Returns the number of items in the store. Note that getting the size\n * requires iterating through the entire store because the items could expire\n * at any time, and hence the size is a dynamic number.\n */\n size(): number {\n let count = 0;\n obj.forEach(() => {\n count++;\n });\n return count;\n },\n\n /** Remove all items from the store. */\n clear(): void {\n // Note that we don't need to use obj.forEach() because we are just\n // going to delete all the items without checking for expiration.\n for (const key of Object.keys(localStorage)) {\n if (key.startsWith(keyPrefix)) {\n localStorage.removeItem(key);\n }\n }\n },\n\n /**\n * Returns all items as map of key to value, mainly used for debugging dumps.\n * The type T is applied to all values, even though they might not be of type\n * T (in the case when you store different data types in the same store).\n */\n asMap<T>(): Map<string, StoredObject<T>> {\n const map = new Map<string, StoredObject<T>>();\n obj.forEach(\n (key: string, value: T, expiryMs: number, storedMs: number) => {\n map.set(key, { value: value as T, expiryMs, storedMs });\n },\n );\n return map;\n },\n\n /** Returns the ms timestamp for the last GC (garbage collection). */\n getLastGcMs(): number {\n const lastGcMsStr = localStorage.getItem(gcMsStorageKey);\n if (!lastGcMsStr) return 0;\n\n const ms = Number(lastGcMsStr);\n return isNaN(ms) ? 0 : ms;\n },\n\n /** Set the ms timestamp for the last GC (garbage collection). */\n setLastGcMs(ms: number) {\n localStorage.setItem(gcMsStorageKey, String(ms));\n },\n\n /** Perform garbage-collection if due, else do nothing. */\n gc(): void {\n const lastGcMs = obj.getLastGcMs();\n\n // Set initial timestamp - no need GC now.\n if (!lastGcMs) {\n obj.setLastGcMs(Date.now());\n return;\n }\n\n if (Date.now() < lastGcMs + gcIntervalMs) {\n return; // not due for next GC yet\n }\n\n // GC is due now, so run it.\n obj.gcNow();\n },\n\n /**\n * Perform garbage collection immediately without checking whether we are\n * due for the next GC or not.\n */\n gcNow(): void {\n console.log(`Starting localStore GC on ${storeName}`);\n\n // Prevent concurrent GC runs.\n obj.setLastGcMs(Date.now());\n let count = 0;\n\n obj.forEach((key: string, value: unknown, expiryMs: number) => {\n if (Date.now() >= expiryMs) {\n obj.delete(key);\n count++;\n }\n });\n\n console.log(\n `Finished localStore GC on ${storeName} - deleted ${count} keys`,\n );\n\n // Mark the end time as last GC time.\n obj.setLastGcMs(Date.now());\n },\n\n /** Returns `this` casted into a StorageAdapter<T>. */\n asStorageAdapter<T>(): StorageAdapter<T> {\n return obj as StorageAdapter<T>;\n },\n } as const;\n\n // Using `any` because the store could store any type of data for each key,\n // but the caller can specify a more specific type when calling each of the\n // methods.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return obj satisfies FullStorageAdapter<any>;\n}\n\nexport type LocalStore = ReturnType<typeof createLocalStore>;\n\n/**\n * Default local 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 localStore = createLocalStore(LocalStoreConfig.storeName);\n\n/** Create a local store item with a key and a default expiration. */\nexport function localStoreItem<T>(\n key: string,\n expiryMs?: number | Duration,\n store: LocalStore = localStore,\n) {\n const defaultExpiryMs = expiryMs && durationOrMsToMs(expiryMs);\n\n const obj = {\n key,\n defaultExpiryMs,\n store,\n\n /** Set a value in the store. */\n set(value: T, expiryDeltaMs?: number | undefined): void {\n store.set(key, value, expiryDeltaMs ?? defaultExpiryMs);\n },\n\n /**\n * Example usage:\n *\n * const { value, storedMs, expiryMs, storedMs } =\n * await myLocalItem.getStoredObject();\n */\n getStoredObject(): StoredObject<T> | undefined {\n return store.getStoredObject(key);\n },\n\n /** Get a value by key, or undefined if it does not exist. */\n get(): T | undefined {\n return store.get(key);\n },\n\n /** Delete this key from the store. */\n delete(): void {\n store.delete(key);\n },\n };\n\n return obj;\n}\n\nexport type LocalStoreItem<T> = ReturnType<typeof localStoreItem<T>>;\n"],"mappings":"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","names":["durationToMs","duration","daysMs","hoursMs","minsMs","secsMs","msMs","durationOrMsToMs","LocalStoreConfig","configureLocalStore","config","validateStoredObject","obj","createLocalStore","storeName","options","keyPrefix","defaultExpiryMs","durationOrMsToMs","gcIntervalMs","gcMsStorageKey","key","value","expiryDeltaMs","nowMs","stored","k","parsed","valid","e","callback","count","map","expiryMs","storedMs","lastGcMsStr","ms","lastGcMs","localStore","localStoreItem","store"]}