@choksheak/ts-utils 0.3.0 → 0.3.1

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 (60) hide show
  1. package/arrayBuffer.d.ts +4 -2
  2. package/assert.d.ts +3 -1
  3. package/average.d.ts +3 -1
  4. package/base64Url.d.ts +4 -2
  5. package/dateTimeStr.d.ts +15 -13
  6. package/duration.d.ts +15 -13
  7. package/isEmpty.d.ts +3 -1
  8. package/iterators.cjs +34 -0
  9. package/iterators.d.mts +4 -0
  10. package/iterators.d.ts +4 -0
  11. package/iterators.min.cjs +2 -0
  12. package/iterators.min.cjs.map +1 -0
  13. package/iterators.min.mjs +2 -0
  14. package/iterators.min.mjs.map +1 -0
  15. package/iterators.mjs +9 -0
  16. package/kvStore.cjs +103 -71
  17. package/kvStore.d.mts +91 -49
  18. package/kvStore.d.ts +95 -50
  19. package/kvStore.min.cjs +1 -1
  20. package/kvStore.min.cjs.map +1 -1
  21. package/kvStore.min.mjs +1 -1
  22. package/kvStore.min.mjs.map +1 -1
  23. package/kvStore.mjs +99 -66
  24. package/localStore.cjs +267 -0
  25. package/localStore.d.mts +119 -0
  26. package/localStore.d.ts +119 -0
  27. package/localStore.min.cjs +2 -0
  28. package/localStore.min.cjs.map +1 -0
  29. package/localStore.min.mjs +2 -0
  30. package/localStore.min.mjs.map +1 -0
  31. package/localStore.mjs +235 -0
  32. package/logging.d.ts +4 -2
  33. package/nonEmpty.d.ts +3 -1
  34. package/nonNil.d.ts +3 -1
  35. package/package.json +48 -15
  36. package/round.d.ts +4 -2
  37. package/safeBtoa.d.ts +3 -1
  38. package/safeParseFloat.d.ts +3 -1
  39. package/safeParseInt.d.ts +3 -1
  40. package/sha256.d.ts +3 -1
  41. package/sleep.d.ts +3 -1
  42. package/storageAdapter.cjs +18 -0
  43. package/storageAdapter.d.mts +33 -0
  44. package/storageAdapter.d.ts +33 -0
  45. package/storageAdapter.min.cjs +2 -0
  46. package/storageAdapter.min.cjs.map +1 -0
  47. package/storageAdapter.min.mjs +1 -0
  48. package/storageAdapter.min.mjs.map +1 -0
  49. package/storageAdapter.mjs +0 -0
  50. package/sum.d.ts +3 -1
  51. package/timeConstants.d.ts +16 -14
  52. package/timer.d.ts +4 -2
  53. package/localStorageCache.cjs +0 -119
  54. package/localStorageCache.d.mts +0 -57
  55. package/localStorageCache.d.ts +0 -55
  56. package/localStorageCache.min.cjs +0 -2
  57. package/localStorageCache.min.cjs.map +0 -1
  58. package/localStorageCache.min.mjs +0 -2
  59. package/localStorageCache.min.mjs.map +0 -1
  60. package/localStorageCache.mjs +0 -92
package/localStore.cjs ADDED
@@ -0,0 +1,267 @@
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/localStore.ts
21
+ var localStore_exports = {};
22
+ __export(localStore_exports, {
23
+ LocalStore: () => LocalStore,
24
+ LocalStoreConfig: () => LocalStoreConfig,
25
+ LocalStoreItem: () => LocalStoreItem,
26
+ configureLocalStore: () => configureLocalStore,
27
+ localStore: () => localStore,
28
+ localStoreItem: () => localStoreItem
29
+ });
30
+ module.exports = __toCommonJS(localStore_exports);
31
+
32
+ // src/timeConstants.ts
33
+ var MS_PER_SECOND = 1e3;
34
+ var MS_PER_MINUTE = 6e4;
35
+ var MS_PER_HOUR = 36e5;
36
+ var MS_PER_DAY = 864e5;
37
+
38
+ // src/duration.ts
39
+ function durationToMs(duration) {
40
+ const daysMs = (duration.days ?? 0) * MS_PER_DAY;
41
+ const hoursMs = (duration.hours ?? 0) * MS_PER_HOUR;
42
+ const minsMs = (duration.minutes ?? 0) * MS_PER_MINUTE;
43
+ const secsMs = (duration.seconds ?? 0) * MS_PER_SECOND;
44
+ const msMs = duration.milliseconds ?? 0;
45
+ return daysMs + hoursMs + minsMs + secsMs + msMs;
46
+ }
47
+ function durationOrMsToMs(duration) {
48
+ return typeof duration === "number" ? duration : durationToMs(duration);
49
+ }
50
+
51
+ // src/localStore.ts
52
+ var LocalStoreConfig = {
53
+ /** All items with the same store name will share the same storage space. */
54
+ storeName: "ts-utils",
55
+ /** 30 days in ms. */
56
+ expiryMs: MS_PER_DAY * 30,
57
+ /** Do GC once per day. */
58
+ gcIntervalMs: MS_PER_DAY
59
+ };
60
+ function configureLocalStore(config) {
61
+ Object.assign(LocalStoreConfig, config);
62
+ }
63
+ function validateStoredObject(obj) {
64
+ if (!obj || typeof obj !== "object" || obj.value === void 0 || typeof obj.storedMs !== "number" || typeof obj.expiryMs !== "number" || Date.now() >= obj.expiryMs) {
65
+ return void 0;
66
+ }
67
+ return obj;
68
+ }
69
+ var LocalStore = class {
70
+ constructor(storeName, options) {
71
+ this.storeName = storeName;
72
+ this.keyPrefix = storeName + ":";
73
+ this.defaultExpiryMs = options?.defaultExpiryMs ? durationOrMsToMs(options.defaultExpiryMs) : LocalStoreConfig.expiryMs;
74
+ this.gcIntervalMs = options?.gcIntervalMs ? durationOrMsToMs(options.gcIntervalMs) : LocalStoreConfig.gcIntervalMs;
75
+ this.gcMsStorageKey = `__localStore:lastGcMs:${storeName}`;
76
+ }
77
+ /**
78
+ * The prefix string for the local storage key which identifies items
79
+ * belonging to this namespace.
80
+ */
81
+ keyPrefix;
82
+ /** Local storage key name for the last GC completed timestamp. */
83
+ gcMsStorageKey;
84
+ defaultExpiryMs;
85
+ gcIntervalMs;
86
+ /** Set a value in the store. */
87
+ set(key, value, expiryDeltaMs = this.defaultExpiryMs) {
88
+ const nowMs = Date.now();
89
+ const obj = {
90
+ value,
91
+ storedMs: nowMs,
92
+ expiryMs: nowMs + durationOrMsToMs(expiryDeltaMs)
93
+ };
94
+ localStorage.setItem(this.keyPrefix + key, JSON.stringify(obj));
95
+ this.gc();
96
+ return value;
97
+ }
98
+ /** Delete one or multiple keys. */
99
+ delete(key) {
100
+ if (typeof key === "string") {
101
+ localStorage.removeItem(this.keyPrefix + key);
102
+ } else {
103
+ for (const k of key) {
104
+ localStorage.removeItem(this.keyPrefix + k);
105
+ }
106
+ }
107
+ }
108
+ /** Mainly used to get the expiration timestamp of an object. */
109
+ getStoredObject(key) {
110
+ const k = this.keyPrefix + key;
111
+ const stored = localStorage.getItem(k);
112
+ if (!stored) {
113
+ return void 0;
114
+ }
115
+ try {
116
+ const parsed = JSON.parse(stored);
117
+ const obj = validateStoredObject(parsed);
118
+ if (!obj) {
119
+ this.delete(k);
120
+ this.gc();
121
+ return void 0;
122
+ }
123
+ return obj;
124
+ } catch (e) {
125
+ console.error(`Invalid local value: ${k}=${stored}:`, e);
126
+ this.delete(k);
127
+ this.gc();
128
+ return void 0;
129
+ }
130
+ }
131
+ /** Get a value by key, or undefined if it does not exist. */
132
+ get(key) {
133
+ const obj = this.getStoredObject(key);
134
+ return obj?.value;
135
+ }
136
+ /** Generic way to iterate through all entries. */
137
+ forEach(callback) {
138
+ for (const k of Object.keys(localStorage)) {
139
+ if (!k.startsWith(this.keyPrefix)) continue;
140
+ const key = k.slice(this.keyPrefix.length);
141
+ const obj = this.getStoredObject(key);
142
+ if (!obj) continue;
143
+ callback(key, obj.value, obj.expiryMs, obj.storedMs);
144
+ }
145
+ }
146
+ /**
147
+ * Returns the number of items in the store. Note that getting the size
148
+ * requires iterating through the entire store because the items could expire
149
+ * at any time, and hence the size is a dynamic number.
150
+ */
151
+ size() {
152
+ let count = 0;
153
+ this.forEach(() => {
154
+ count++;
155
+ });
156
+ return count;
157
+ }
158
+ /** Remove all items from the store. */
159
+ clear() {
160
+ for (const key of Object.keys(localStorage)) {
161
+ if (key.startsWith(this.keyPrefix)) {
162
+ localStorage.removeItem(key);
163
+ }
164
+ }
165
+ }
166
+ /**
167
+ * Returns all items as map of key to value, mainly used for debugging dumps.
168
+ * The type T is applied to all values, even though they might not be of type
169
+ * T (in the case when you store different data types in the same store).
170
+ */
171
+ asMap() {
172
+ const map = /* @__PURE__ */ new Map();
173
+ this.forEach((key, value, expiryMs, storedMs) => {
174
+ map.set(key, { value, expiryMs, storedMs });
175
+ });
176
+ return map;
177
+ }
178
+ /** Returns the ms timestamp for the last GC (garbage collection). */
179
+ get lastGcMs() {
180
+ const lastGcMsStr = globalThis.localStorage.getItem(this.gcMsStorageKey);
181
+ if (!lastGcMsStr) return 0;
182
+ const ms = Number(lastGcMsStr);
183
+ return isNaN(ms) ? 0 : ms;
184
+ }
185
+ /** Set the ms timestamp for the last GC (garbage collection). */
186
+ set lastGcMs(ms) {
187
+ globalThis.localStorage.setItem(this.gcMsStorageKey, String(ms));
188
+ }
189
+ /** Perform garbage-collection if due, else do nothing. */
190
+ gc() {
191
+ const lastGcMs = this.lastGcMs;
192
+ if (!lastGcMs) {
193
+ this.lastGcMs = Date.now();
194
+ return;
195
+ }
196
+ if (Date.now() < lastGcMs + this.gcIntervalMs) {
197
+ return;
198
+ }
199
+ this.gcNow();
200
+ }
201
+ /**
202
+ * Perform garbage collection immediately without checking whether we are
203
+ * due for the next GC or not.
204
+ */
205
+ gcNow() {
206
+ console.log(`Starting localStore GC on ${this.storeName}`);
207
+ this.lastGcMs = Date.now();
208
+ let count = 0;
209
+ this.forEach((key, value, expiryMs) => {
210
+ if (Date.now() >= expiryMs) {
211
+ this.delete(key);
212
+ count++;
213
+ }
214
+ });
215
+ console.log(
216
+ `Finished localStore GC on ${this.storeName} - deleted ${count} keys`
217
+ );
218
+ this.lastGcMs = Date.now();
219
+ }
220
+ /** Returns `this` casted into a StorageAdapter<T>. */
221
+ asStorageAdapter() {
222
+ return this;
223
+ }
224
+ };
225
+ var localStore = new LocalStore(LocalStoreConfig.storeName);
226
+ var LocalStoreItem = class {
227
+ constructor(key, defaultExpiryMs = LocalStoreConfig.expiryMs, store = localStore) {
228
+ this.key = key;
229
+ this.store = store;
230
+ this.defaultExpiryMs = defaultExpiryMs && durationOrMsToMs(defaultExpiryMs);
231
+ }
232
+ defaultExpiryMs;
233
+ /** Set a value in the store. */
234
+ set(value, expiryDeltaMs = this.defaultExpiryMs) {
235
+ this.store.set(this.key, value, expiryDeltaMs);
236
+ }
237
+ /**
238
+ * Example usage:
239
+ *
240
+ * const { value, storedMs, expiryMs, storedMs } =
241
+ * await myLocalItem.getStoredObject();
242
+ */
243
+ getStoredObject() {
244
+ return this.store.getStoredObject(this.key);
245
+ }
246
+ /** Get a value by key, or undefined if it does not exist. */
247
+ get() {
248
+ return this.store.get(this.key);
249
+ }
250
+ /** Delete this key from the store. */
251
+ delete() {
252
+ this.store.delete(this.key);
253
+ }
254
+ };
255
+ function localStoreItem(key, expiryMs, store = localStore) {
256
+ expiryMs = expiryMs && durationOrMsToMs(expiryMs);
257
+ return new LocalStoreItem(key, expiryMs, store);
258
+ }
259
+ // Annotate the CommonJS export names for ESM import in node:
260
+ 0 && (module.exports = {
261
+ LocalStore,
262
+ LocalStoreConfig,
263
+ LocalStoreItem,
264
+ configureLocalStore,
265
+ localStore,
266
+ localStoreItem
267
+ });
@@ -0,0 +1,119 @@
1
+ import { Duration } from './duration.mjs';
2
+ import { FullStorageAdapter, StoredObject, StorageAdapter } from './storageAdapter.mjs';
3
+
4
+ /**
5
+ * Local storage key-value store with support for auto-expirations.
6
+ *
7
+ * Why use this?
8
+ * 1. Extremely simple interface to use local storage.
9
+ * 2. Auto-expirations with GC frees you from worrying about data clean-up.
10
+ * 3. Any serializable data type can be stored (except undefined).
11
+ *
12
+ * How to use?
13
+ * Just use the `localStore` global constant like the local storage.
14
+ *
15
+ * Why not use the localStorage directly?
16
+ * localStorage does not provide auto-expirations with GC. If you don't need
17
+ * this (items never expire), then just use localStorage directly.
18
+ */
19
+
20
+ /** Global defaults can be updated directly. */
21
+ declare const LocalStoreConfig: {
22
+ /** All items with the same store name will share the same storage space. */
23
+ storeName: string;
24
+ /** 30 days in ms. */
25
+ expiryMs: number;
26
+ /** Do GC once per day. */
27
+ gcIntervalMs: number;
28
+ };
29
+ type LocalStoreConfig = typeof LocalStoreConfig;
30
+ /** Convenience function to update global defaults. */
31
+ declare function configureLocalStore(config: Partial<LocalStoreConfig>): void;
32
+ /**
33
+ * You can create multiple LocalStores if you want, but most likely you will only
34
+ * need to use the default `localStore` instance.
35
+ */
36
+ declare class LocalStore implements FullStorageAdapter<any> {
37
+ readonly storeName: string;
38
+ /**
39
+ * The prefix string for the local storage key which identifies items
40
+ * belonging to this namespace.
41
+ */
42
+ readonly keyPrefix: string;
43
+ /** Local storage key name for the last GC completed timestamp. */
44
+ readonly gcMsStorageKey: string;
45
+ readonly defaultExpiryMs: number;
46
+ readonly gcIntervalMs: number;
47
+ constructor(storeName: string, options?: {
48
+ defaultExpiryMs?: number | Duration;
49
+ gcIntervalMs?: number | Duration;
50
+ });
51
+ /** Set a value in the store. */
52
+ set<T>(key: string, value: T, expiryDeltaMs?: number | Duration): T;
53
+ /** Delete one or multiple keys. */
54
+ delete(key: string | string[]): void;
55
+ /** Mainly used to get the expiration timestamp of an object. */
56
+ getStoredObject<T>(key: string): StoredObject<T> | undefined;
57
+ /** Get a value by key, or undefined if it does not exist. */
58
+ get<T>(key: string): T | undefined;
59
+ /** Generic way to iterate through all entries. */
60
+ forEach<T>(callback: (key: string, value: T, expiryMs: number, storedMs: number) => void): void;
61
+ /**
62
+ * Returns the number of items in the store. Note that getting the size
63
+ * requires iterating through the entire store because the items could expire
64
+ * at any time, and hence the size is a dynamic number.
65
+ */
66
+ size(): number;
67
+ /** Remove all items from the store. */
68
+ clear(): void;
69
+ /**
70
+ * Returns all items as map of key to value, mainly used for debugging dumps.
71
+ * The type T is applied to all values, even though they might not be of type
72
+ * T (in the case when you store different data types in the same store).
73
+ */
74
+ asMap<T>(): Map<string, StoredObject<T>>;
75
+ /** Returns the ms timestamp for the last GC (garbage collection). */
76
+ get lastGcMs(): number;
77
+ /** Set the ms timestamp for the last GC (garbage collection). */
78
+ set lastGcMs(ms: number);
79
+ /** Perform garbage-collection if due, else do nothing. */
80
+ gc(): void;
81
+ /**
82
+ * Perform garbage collection immediately without checking whether we are
83
+ * due for the next GC or not.
84
+ */
85
+ gcNow(): void;
86
+ /** Returns `this` casted into a StorageAdapter<T>. */
87
+ asStorageAdapter<T>(): StorageAdapter<T>;
88
+ }
89
+ /**
90
+ * Default local store ready for immediate use. You can create new instances if
91
+ * you want, but most likely you will only need one store instance.
92
+ */
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;
100
+ readonly defaultExpiryMs: number;
101
+ constructor(key: string, defaultExpiryMs?: number | Duration, store?: LocalStore);
102
+ /** Set a value in the store. */
103
+ set(value: T, expiryDeltaMs?: number | undefined): void;
104
+ /**
105
+ * Example usage:
106
+ *
107
+ * const { value, storedMs, expiryMs, storedMs } =
108
+ * await myLocalItem.getStoredObject();
109
+ */
110
+ getStoredObject(): StoredObject<T> | undefined;
111
+ /** Get a value by key, or undefined if it does not exist. */
112
+ get(): T | undefined;
113
+ /** Delete this key from the store. */
114
+ 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>;
118
+
119
+ export { LocalStore, LocalStoreConfig, LocalStoreItem, configureLocalStore, localStore, localStoreItem };
@@ -0,0 +1,119 @@
1
+ import { Duration } from './duration.js';
2
+ import { FullStorageAdapter, StoredObject, StorageAdapter } from './storageAdapter.js';
3
+
4
+ /**
5
+ * Local storage key-value store with support for auto-expirations.
6
+ *
7
+ * Why use this?
8
+ * 1. Extremely simple interface to use local storage.
9
+ * 2. Auto-expirations with GC frees you from worrying about data clean-up.
10
+ * 3. Any serializable data type can be stored (except undefined).
11
+ *
12
+ * How to use?
13
+ * Just use the `localStore` global constant like the local storage.
14
+ *
15
+ * Why not use the localStorage directly?
16
+ * localStorage does not provide auto-expirations with GC. If you don't need
17
+ * this (items never expire), then just use localStorage directly.
18
+ */
19
+
20
+ /** Global defaults can be updated directly. */
21
+ declare const LocalStoreConfig: {
22
+ /** All items with the same store name will share the same storage space. */
23
+ storeName: string;
24
+ /** 30 days in ms. */
25
+ expiryMs: number;
26
+ /** Do GC once per day. */
27
+ gcIntervalMs: number;
28
+ };
29
+ type LocalStoreConfig = typeof LocalStoreConfig;
30
+ /** Convenience function to update global defaults. */
31
+ declare function configureLocalStore(config: Partial<LocalStoreConfig>): void;
32
+ /**
33
+ * You can create multiple LocalStores if you want, but most likely you will only
34
+ * need to use the default `localStore` instance.
35
+ */
36
+ declare class LocalStore implements FullStorageAdapter<any> {
37
+ readonly storeName: string;
38
+ /**
39
+ * The prefix string for the local storage key which identifies items
40
+ * belonging to this namespace.
41
+ */
42
+ readonly keyPrefix: string;
43
+ /** Local storage key name for the last GC completed timestamp. */
44
+ readonly gcMsStorageKey: string;
45
+ readonly defaultExpiryMs: number;
46
+ readonly gcIntervalMs: number;
47
+ constructor(storeName: string, options?: {
48
+ defaultExpiryMs?: number | Duration;
49
+ gcIntervalMs?: number | Duration;
50
+ });
51
+ /** Set a value in the store. */
52
+ set<T>(key: string, value: T, expiryDeltaMs?: number | Duration): T;
53
+ /** Delete one or multiple keys. */
54
+ delete(key: string | string[]): void;
55
+ /** Mainly used to get the expiration timestamp of an object. */
56
+ getStoredObject<T>(key: string): StoredObject<T> | undefined;
57
+ /** Get a value by key, or undefined if it does not exist. */
58
+ get<T>(key: string): T | undefined;
59
+ /** Generic way to iterate through all entries. */
60
+ forEach<T>(callback: (key: string, value: T, expiryMs: number, storedMs: number) => void): void;
61
+ /**
62
+ * Returns the number of items in the store. Note that getting the size
63
+ * requires iterating through the entire store because the items could expire
64
+ * at any time, and hence the size is a dynamic number.
65
+ */
66
+ size(): number;
67
+ /** Remove all items from the store. */
68
+ clear(): void;
69
+ /**
70
+ * Returns all items as map of key to value, mainly used for debugging dumps.
71
+ * The type T is applied to all values, even though they might not be of type
72
+ * T (in the case when you store different data types in the same store).
73
+ */
74
+ asMap<T>(): Map<string, StoredObject<T>>;
75
+ /** Returns the ms timestamp for the last GC (garbage collection). */
76
+ get lastGcMs(): number;
77
+ /** Set the ms timestamp for the last GC (garbage collection). */
78
+ set lastGcMs(ms: number);
79
+ /** Perform garbage-collection if due, else do nothing. */
80
+ gc(): void;
81
+ /**
82
+ * Perform garbage collection immediately without checking whether we are
83
+ * due for the next GC or not.
84
+ */
85
+ gcNow(): void;
86
+ /** Returns `this` casted into a StorageAdapter<T>. */
87
+ asStorageAdapter<T>(): StorageAdapter<T>;
88
+ }
89
+ /**
90
+ * Default local store ready for immediate use. You can create new instances if
91
+ * you want, but most likely you will only need one store instance.
92
+ */
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;
100
+ readonly defaultExpiryMs: number;
101
+ constructor(key: string, defaultExpiryMs?: number | Duration, store?: LocalStore);
102
+ /** Set a value in the store. */
103
+ set(value: T, expiryDeltaMs?: number | undefined): void;
104
+ /**
105
+ * Example usage:
106
+ *
107
+ * const { value, storedMs, expiryMs, storedMs } =
108
+ * await myLocalItem.getStoredObject();
109
+ */
110
+ getStoredObject(): StoredObject<T> | undefined;
111
+ /** Get a value by key, or undefined if it does not exist. */
112
+ get(): T | undefined;
113
+ /** Delete this key from the store. */
114
+ 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>;
118
+
119
+ export { LocalStore, LocalStoreConfig, LocalStoreItem, configureLocalStore, localStore, localStoreItem };
@@ -0,0 +1,2 @@
1
+ "use strict";var d=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var S=Object.prototype.hasOwnProperty;var h=(r,t)=>{for(var e in t)d(r,e,{get:t[e],enumerable:!0})},y=(r,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of g(t))!S.call(r,o)&&o!==e&&d(r,o,{get:()=>t[o],enumerable:!(s=f(t,o))||s.enumerable});return r};var b=r=>y(d({},"__esModule",{value:!0}),r);var O={};h(O,{LocalStore:()=>c,LocalStoreConfig:()=>a,LocalStoreItem:()=>l,configureLocalStore:()=>_,localStore:()=>p,localStoreItem:()=>T});module.exports=b(O);function E(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:E(r)}var a={storeName:"ts-utils",expiryMs:864e5*30,gcIntervalMs:864e5};function _(r){Object.assign(a,r)}function D(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 c=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=D(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}},p=new c(a.storeName),l=class{constructor(t,e=a.expiryMs,s=p){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=p){return t=t&&i(t),new l(r,t,e)}0&&(module.exports={LocalStore,LocalStoreConfig,LocalStoreItem,configureLocalStore,localStore,localStoreItem});
2
+ //# sourceMappingURL=localStore.min.cjs.map
@@ -0,0 +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":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,gBAAAE,EAAA,qBAAAC,EAAA,mBAAAC,EAAA,wBAAAC,EAAA,eAAAC,EAAA,mBAAAC,IAAA,eAAAC,EAAAR,GC0LO,SAASS,EAAaC,EAA4B,CACvD,IAAMC,GAAUD,EAAS,MAAQ,GAAK,MAChCE,GAAWF,EAAS,OAAS,GAAK,KAClCG,GAAUH,EAAS,SAAW,GAAK,IACnCI,GAAUJ,EAAS,SAAW,GAAK,IACnCK,EAAOL,EAAS,cAAgB,EAEtC,OAAOC,EAASC,EAAUC,EAASC,EAASC,CAC9C,CAKO,SAASC,EAAiBN,EAAqC,CACpE,OAAO,OAAOA,GAAa,SAAWA,EAAWD,EAAaC,CAAQ,CACxE,CDhLO,IAAMO,EAAmB,CAE9B,UAAW,WAGX,SAAU,MAAa,GAGvB,aAAc,KAChB,EAKO,SAASC,EAAoBC,EAAmC,CACrE,OAAO,OAAOF,EAAkBE,CAAM,CACxC,CAMA,SAASC,EACPC,EAC6B,CAC7B,GACE,GAACA,GACD,OAAOA,GAAQ,UACfA,EAAI,QAAU,QACd,OAAOA,EAAI,UAAa,UACxB,OAAOA,EAAI,UAAa,UACxB,KAAK,IAAI,GAAKA,EAAI,UAKpB,OAAOA,CACT,CAUO,IAAMC,EAAN,KAAoD,CAalD,YACWC,EAChBC,EAIA,CALgB,eAAAD,EAMhB,KAAK,UAAYA,EAAY,IAE7B,KAAK,gBAAkBC,GAAS,gBAC5BC,EAAiBD,EAAQ,eAAe,EACxCP,EAAiB,SAErB,KAAK,aAAeO,GAAS,aACzBC,EAAiBD,EAAQ,YAAY,EACrCP,EAAiB,aAErB,KAAK,eAAiB,yBAAyBM,CAAS,EAC1D,CA1BgB,UAGA,eAEA,gBACA,aAuBT,IACLG,EACAC,EACAC,EAAmC,KAAK,gBACrC,CACH,IAAMC,EAAQ,KAAK,IAAI,EACjBR,EAAuB,CAC3B,MAAAM,EACA,SAAUE,EACV,SAAUA,EAAQJ,EAAiBG,CAAa,CAClD,EAEA,oBAAa,QAAQ,KAAK,UAAYF,EAAK,KAAK,UAAUL,CAAG,CAAC,EAE9D,KAAK,GAAG,EAEDM,CACT,CAGO,OAAOD,EAA8B,CAC1C,GAAI,OAAOA,GAAQ,SACjB,aAAa,WAAW,KAAK,UAAYA,CAAG,MAE5C,SAAWI,KAAKJ,EACd,aAAa,WAAW,KAAK,UAAYI,CAAC,CAGhD,CAGO,gBAAmBJ,EAA0C,CAClE,IAAMI,EAAI,KAAK,UAAYJ,EACrBK,EAAS,aAAa,QAAQD,CAAC,EAErC,GAAKC,EAIL,GAAI,CACF,IAAMC,EAAS,KAAK,MAAMD,CAAM,EAC1BV,EAAMD,EAAqBY,CAAM,EACvC,GAAI,CAACX,EAAK,CACR,KAAK,OAAOS,CAAC,EAEb,KAAK,GAAG,EAER,MACF,CAEA,OAAOT,CACT,OAASY,EAAG,CACV,QAAQ,MAAM,wBAAwBH,CAAC,IAAIC,CAAM,IAAKE,CAAC,EACvD,KAAK,OAAOH,CAAC,EAEb,KAAK,GAAG,EAER,MACF,CACF,CAGO,IAAOJ,EAA4B,CAGxC,OAFY,KAAK,gBAAmBA,CAAG,GAE3B,KACd,CAGO,QACLQ,EAMM,CACN,QAAWJ,KAAK,OAAO,KAAK,YAAY,EAAG,CACzC,GAAI,CAACA,EAAE,WAAW,KAAK,SAAS,EAAG,SAEnC,IAAMJ,EAAMI,EAAE,MAAM,KAAK,UAAU,MAAM,EACnCT,EAAM,KAAK,gBAAgBK,CAAG,EAE/BL,GAELa,EAASR,EAAKL,EAAI,MAAYA,EAAI,SAAUA,EAAI,QAAQ,CAC1D,CACF,CAOO,MAAe,CACpB,IAAIc,EAAQ,EACZ,YAAK,QAAQ,IAAM,CACjBA,GACF,CAAC,EACMA,CACT,CAGO,OAAc,CAGnB,QAAWT,KAAO,OAAO,KAAK,YAAY,EACpCA,EAAI,WAAW,KAAK,SAAS,GAC/B,aAAa,WAAWA,CAAG,CAGjC,CAOO,OAAyC,CAC9C,IAAMU,EAAM,IAAI,IAChB,YAAK,QAAQ,CAACV,EAAKC,EAAOU,EAAUC,IAAa,CAC/CF,EAAI,IAAIV,EAAK,CAAE,MAAOC,EAAY,SAAAU,EAAU,SAAAC,CAAS,CAAC,CACxD,CAAC,EACMF,CACT,CAGA,IAAW,UAAmB,CAC5B,IAAMG,EAAc,WAAW,aAAa,QAAQ,KAAK,cAAc,EACvE,GAAI,CAACA,EAAa,MAAO,GAEzB,IAAMC,EAAK,OAAOD,CAAW,EAC7B,OAAO,MAAMC,CAAE,EAAI,EAAIA,CACzB,CAGA,IAAW,SAASA,EAAY,CAC9B,WAAW,aAAa,QAAQ,KAAK,eAAgB,OAAOA,CAAE,CAAC,CACjE,CAGO,IAAW,CAChB,IAAMC,EAAW,KAAK,SAGtB,GAAI,CAACA,EAAU,CACb,KAAK,SAAW,KAAK,IAAI,EACzB,MACF,CAEI,KAAK,IAAI,EAAIA,EAAW,KAAK,cAKjC,KAAK,MAAM,CACb,CAMO,OAAc,CACnB,QAAQ,IAAI,6BAA6B,KAAK,SAAS,EAAE,EAGzD,KAAK,SAAW,KAAK,IAAI,EACzB,IAAIN,EAAQ,EAEZ,KAAK,QAAQ,CAACT,EAAaC,EAAgBU,IAAqB,CAC1D,KAAK,IAAI,GAAKA,IAChB,KAAK,OAAOX,CAAG,EACfS,IAEJ,CAAC,EAED,QAAQ,IACN,6BAA6B,KAAK,SAAS,cAAcA,CAAK,OAChE,EAGA,KAAK,SAAW,KAAK,IAAI,CAC3B,CAGO,kBAAyC,CAC9C,OAAO,IACT,CACF,EAMaO,EAAa,IAAIpB,EAAWL,EAAiB,SAAS,EAKtD0B,EAAN,KAAwB,CAGtB,YACWjB,EAChBkB,EAAqC3B,EAAiB,SACtC4B,EAAQH,EACxB,CAHgB,SAAAhB,EAEA,WAAAmB,EAEhB,KAAK,gBAAkBD,GAAmBnB,EAAiBmB,CAAe,CAC5E,CARgB,gBAWT,IACLjB,EACAC,EAAoC,KAAK,gBACnC,CACN,KAAK,MAAM,IAAI,KAAK,IAAKD,EAAOC,CAAa,CAC/C,CAQO,iBAA+C,CACpD,OAAO,KAAK,MAAM,gBAAgB,KAAK,GAAG,CAC5C,CAGO,KAAqB,CAC1B,OAAO,KAAK,MAAM,IAAI,KAAK,GAAG,CAChC,CAGO,QAAe,CACpB,KAAK,MAAM,OAAO,KAAK,GAAG,CAC5B,CACF,EAGO,SAASkB,EACdpB,EACAW,EACAQ,EAAQH,EACW,CACnB,OAAAL,EAAWA,GAAYZ,EAAiBY,CAAQ,EAEzC,IAAIM,EAAkBjB,EAAKW,EAAUQ,CAAK,CACnD","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"]}
@@ -0,0 +1,2 @@
1
+ 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 O(r){Object.assign(a,r)}function h(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 c=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=h(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 c(a.storeName),l=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 P(r,t,e=d){return t=t&&i(t),new l(r,t,e)}export{c as LocalStore,a as LocalStoreConfig,l as LocalStoreItem,O as configureLocalStore,d as localStore,P as localStoreItem};
2
+ //# sourceMappingURL=localStore.min.mjs.map
@@ -0,0 +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"]}