@webreflection/utils 0.2.19 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +8 -2
- package/src/README.md +31 -2
- package/src/cache.js +103 -0
- package/src/index.md +6 -0
- package/types/cache.d.ts +39 -0
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ A curated, *TypeScript*-friendly [collection](./src/) of utilities:
|
|
|
11
11
|
* **[ascii](./src#ascii)** - basic string-to-buffer conversion without validation
|
|
12
12
|
* **[bound-once](./src#bound-once)** - retrieve unique bound methods per realm
|
|
13
13
|
* **[bound](./src#bound)** - retrieve one-off bound methods
|
|
14
|
+
* **[cache](./src#cache)** - use a temporal `Map` to reuse repeated key lookups within the same tick, or for a short timed window
|
|
14
15
|
* **[iterable](./src#iterable)** - make plain objects iterable as `Object.entries(object)` pairs, without touching objects that are already iterable
|
|
15
16
|
* **[json-storage](./src#json-storage)** - use `localStorage` or `sessionStorage` through a JSON-aware, iterable, *Map*-friendly API
|
|
16
17
|
* **[registry](./src#registry)** - use a `Map`-like API with key/value validation and duplicate-key protection by default
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webreflection/utils",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": {
|
|
6
6
|
"./all": "./types/all.d.ts",
|
|
7
7
|
"./ascii": "./types/ascii.d.ts",
|
|
8
8
|
"./bound-once": "./types/bound-once.d.ts",
|
|
9
9
|
"./bound": "./types/bound.d.ts",
|
|
10
|
+
"./cache": "./types/cache.d.ts",
|
|
10
11
|
"./iterable": "./types/iterable.d.ts",
|
|
11
12
|
"./json-storage": "./types/json-storage.d.ts",
|
|
12
13
|
"./registry": "./types/registry.d.ts",
|
|
@@ -31,6 +32,10 @@
|
|
|
31
32
|
"types": "./types/bound.d.ts",
|
|
32
33
|
"import": "./src/bound.js"
|
|
33
34
|
},
|
|
35
|
+
"./cache": {
|
|
36
|
+
"types": "./types/cache.d.ts",
|
|
37
|
+
"import": "./src/cache.js"
|
|
38
|
+
},
|
|
34
39
|
"./iterable": {
|
|
35
40
|
"types": "./types/iterable.d.ts",
|
|
36
41
|
"import": "./src/iterable.js"
|
|
@@ -62,6 +67,7 @@
|
|
|
62
67
|
"ascii",
|
|
63
68
|
"bound-once",
|
|
64
69
|
"bound",
|
|
70
|
+
"cache",
|
|
65
71
|
"iterable",
|
|
66
72
|
"json-storage",
|
|
67
73
|
"registry",
|
|
@@ -96,7 +102,7 @@
|
|
|
96
102
|
"bugs": {
|
|
97
103
|
"url": "https://github.com/WebReflection/utils/issues"
|
|
98
104
|
},
|
|
99
|
-
"homepage": "https://github.
|
|
105
|
+
"homepage": "https://webreflection.github.io/utils/",
|
|
100
106
|
"overrides": {
|
|
101
107
|
"c8": {
|
|
102
108
|
"yargs": "^18.0.0"
|
package/src/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @webreflection/utils
|
|
2
2
|
|
|
3
|
-
Each utility can be loaded from a *CDN* via either `https://esm.run/@webreflection/utils/
|
|
3
|
+
Each utility can be loaded from a *CDN* via either `https://esm.run/@webreflection/utils/UTILITY` or `https://cdn.jsdelivr.net/npm/@webreflection/utils/src/UTILITY.js`.
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
This document describes each utility separately.
|
|
@@ -53,7 +53,7 @@ whatever argument limit your runtime has for `String.fromCharCode`.
|
|
|
53
53
|
|
|
54
54
|
This is equivalent to **bound**, except each bound method is created only once. It is useful when bound method identity must be preserved across multiple calls.
|
|
55
55
|
|
|
56
|
-
This variant uses
|
|
56
|
+
This variant uses [sticky](#sticky) to ensure that weakly referenced targets always produce the same bound method within the same realm.
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
## bound
|
|
@@ -71,6 +71,35 @@ resolve(4);
|
|
|
71
71
|
The **bound-once** variant ensures that repeated accesses, such as `boundOnce(Promise).all`, always return the same bound method.
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
## cache
|
|
75
|
+
|
|
76
|
+
A temporal `Map` subclass for short-lived memoization. It keeps newly added
|
|
77
|
+
entries only until its scheduled cleanup runs, making it useful to reuse
|
|
78
|
+
expensive work for repeated access to the same key without keeping the value
|
|
79
|
+
around as a long-term cache.
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
import Cache from '@webreflection/utils/cache';
|
|
83
|
+
|
|
84
|
+
const users = new Cache;
|
|
85
|
+
|
|
86
|
+
const loadUser = id => users.getOrInsertComputed(
|
|
87
|
+
id,
|
|
88
|
+
id => fetch(`/users/${id}`).then(response => response.json())
|
|
89
|
+
);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
When the constructor `delay` is omitted, `0`, or less than `0`, cleanup is
|
|
93
|
+
queued as a microtask, so same-tick lookups can share the stored value and the
|
|
94
|
+
map clears itself before the next task. Pass a positive delay, such as
|
|
95
|
+
`new Cache(100)`, to keep entries until a timer removes them instead.
|
|
96
|
+
|
|
97
|
+
Use `getOrInsert(key, value)` or `getOrInsertComputed(key, callback)` when the
|
|
98
|
+
value should only be stored if missing. Use `put(key, value)` for the faster
|
|
99
|
+
`cache.get(key) ?? cache.put(key, value)` pattern when duplicate queue entries
|
|
100
|
+
are acceptable.
|
|
101
|
+
|
|
102
|
+
|
|
74
103
|
## iterable
|
|
75
104
|
|
|
76
105
|
Ensures an object can be consumed by `for...of`, spread, `Array.from`, and
|
package/src/cache.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @template K,V
|
|
5
|
+
* @type {(self: Cache<K,V>) => void}
|
|
6
|
+
*/
|
|
7
|
+
let cleanup;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @template K,V
|
|
11
|
+
* @extends Map<K,V>
|
|
12
|
+
*/
|
|
13
|
+
export default class Cache extends Map {
|
|
14
|
+
static {
|
|
15
|
+
cleanup = self => {
|
|
16
|
+
self.#run = true;
|
|
17
|
+
self.#queue.splice(0).forEach(self.delete, self);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** @type {number} */
|
|
22
|
+
#delay;
|
|
23
|
+
|
|
24
|
+
/** @type {K[]} */
|
|
25
|
+
#queue = [];
|
|
26
|
+
|
|
27
|
+
/** @type {boolean} */
|
|
28
|
+
#run = true;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {K} key
|
|
32
|
+
*/
|
|
33
|
+
#push(key) {
|
|
34
|
+
this.#queue.push(key);
|
|
35
|
+
if (this.#run) {
|
|
36
|
+
this.#run = false;
|
|
37
|
+
if (this.#delay < 1) {
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
queueMicrotask(() => cleanup(this));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
setTimeout(cleanup, this.#delay, this);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
clear() {
|
|
49
|
+
this.#queue.splice(0);
|
|
50
|
+
super.clear();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {number} [delay]
|
|
55
|
+
*/
|
|
56
|
+
constructor(delay = 0) {
|
|
57
|
+
super();
|
|
58
|
+
this.#delay = delay;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {K} key
|
|
63
|
+
* @param {V} value
|
|
64
|
+
* @returns
|
|
65
|
+
*/
|
|
66
|
+
getOrInsert(key, value) {
|
|
67
|
+
return this.has(key) ? this.get(key) : this.put(key, value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {K} key
|
|
72
|
+
* @param {(key: K) => V} callback
|
|
73
|
+
* @returns
|
|
74
|
+
*/
|
|
75
|
+
getOrInsertComputed(key, callback) {
|
|
76
|
+
return this.has(key) ? this.get(key) : this.put(key, callback(key));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* This method is slightly faster than `getOrInsert` or `getOrInsertComputed`
|
|
81
|
+
* but it does not check if the key already exists, possibly pushing it twice.
|
|
82
|
+
* However, doing `ref.get(key) ?? ref.put(key, value)` provides same functionality as
|
|
83
|
+
* `ref.getOrInsert(key, value)` or `ref.getOrInsertComputed(key, callback)`.
|
|
84
|
+
* @param {K} key
|
|
85
|
+
* @param {V} value
|
|
86
|
+
* @returns
|
|
87
|
+
*/
|
|
88
|
+
put(key, value) {
|
|
89
|
+
this.#push(key);
|
|
90
|
+
super.set(key, value);
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {K} key
|
|
96
|
+
* @param {V} value
|
|
97
|
+
* @returns
|
|
98
|
+
*/
|
|
99
|
+
set(key, value) {
|
|
100
|
+
if (!this.has(key)) this.#push(key);
|
|
101
|
+
return super.set(key, value);
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/index.md
ADDED
package/types/cache.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template K,V
|
|
3
|
+
* @extends Map<K,V>
|
|
4
|
+
*/
|
|
5
|
+
export default class Cache<K, V> extends Map<K, V> {
|
|
6
|
+
/**
|
|
7
|
+
* @param {number} [delay]
|
|
8
|
+
*/
|
|
9
|
+
constructor(delay?: number);
|
|
10
|
+
/**
|
|
11
|
+
* @param {K} key
|
|
12
|
+
* @param {V} value
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
getOrInsert(key: K, value: V): V | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* @param {K} key
|
|
18
|
+
* @param {(key: K) => V} callback
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
21
|
+
getOrInsertComputed(key: K, callback: (key: K) => V): V | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* This method is slightly faster than `getOrInsert` or `getOrInsertComputed`
|
|
24
|
+
* but it does not check if the key already exists, possibly pushing it twice.
|
|
25
|
+
* However, doing `ref.get(key) ?? ref.put(key, value)` provides same functionality as
|
|
26
|
+
* `ref.getOrInsert(key, value)` or `ref.getOrInsertComputed(key, callback)`.
|
|
27
|
+
* @param {K} key
|
|
28
|
+
* @param {V} value
|
|
29
|
+
* @returns
|
|
30
|
+
*/
|
|
31
|
+
put(key: K, value: V): V;
|
|
32
|
+
/**
|
|
33
|
+
* @param {K} key
|
|
34
|
+
* @param {V} value
|
|
35
|
+
* @returns
|
|
36
|
+
*/
|
|
37
|
+
set(key: K, value: V): this;
|
|
38
|
+
#private;
|
|
39
|
+
}
|