@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 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.19",
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.com/WebReflection/utils#readme",
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/{{UTILITY}}` or `https://cdn.jsdelivr.net/npm/@webreflection/utils/src/{{UTILITY}}.js`.
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 **sticky** to ensure that weakly referenced targets always produce the same bound method within the same realm.
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
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: Utilities
3
+ permalink: /src/
4
+ ---
5
+
6
+ {% include_relative README.md %}
@@ -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
+ }