@webreflection/utils 0.2.19 → 0.2.21

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.21",
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,101 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @template K,V
5
+ * @extends Map<K,V>
6
+ */
7
+ export default class Cache extends Map {
8
+ /**
9
+ * @template K,V
10
+ * @type {(self: Cache<K,V>) => void}
11
+ */
12
+ #cleanup = () => {
13
+ this.#run = true;
14
+ this.#queue.splice(0).forEach(this.delete, this);
15
+ };
16
+
17
+ /** @type {number} */
18
+ #delay;
19
+
20
+ /** @type {K[]} */
21
+ #queue = [];
22
+
23
+ /** @type {boolean} */
24
+ #run = true;
25
+
26
+ /**
27
+ * @param {K} key
28
+ */
29
+ #push(key) {
30
+ this.#queue.push(key);
31
+ if (this.#run) {
32
+ this.#run = false;
33
+ if (this.#delay < 1) {
34
+ // @ts-ignore
35
+ queueMicrotask(this.#cleanup);
36
+ }
37
+ else {
38
+ // @ts-ignore
39
+ setTimeout(this.#cleanup, this.#delay);
40
+ }
41
+ }
42
+ }
43
+
44
+ clear() {
45
+ this.#queue.splice(0);
46
+ super.clear();
47
+ }
48
+
49
+ /**
50
+ * @param {number} [delay]
51
+ */
52
+ constructor(delay = 0) {
53
+ super();
54
+ this.#delay = delay;
55
+ }
56
+
57
+ /**
58
+ * @param {K} key
59
+ * @param {V} value
60
+ * @returns
61
+ */
62
+ getOrInsert(key, value) {
63
+ const known = this.get(key);
64
+ return known === void 0 && !this.has(key) ? this.put(key, value) : known;
65
+ }
66
+
67
+ /**
68
+ * @param {K} key
69
+ * @param {(key: K) => V} callback
70
+ * @returns
71
+ */
72
+ getOrInsertComputed(key, callback) {
73
+ const known = this.get(key);
74
+ return known === void 0 && !this.has(key) ? this.put(key, callback(key)) : known;
75
+ }
76
+
77
+ /**
78
+ * This method is slightly faster than `getOrInsert` or `getOrInsertComputed`
79
+ * but it does not check if the key already exists, possibly pushing it twice.
80
+ * However, doing `ref.get(key) ?? ref.put(key, value)` provides same functionality as
81
+ * `ref.getOrInsert(key, value)` or `ref.getOrInsertComputed(key, callback)`.
82
+ * @param {K} key
83
+ * @param {V} value
84
+ * @returns
85
+ */
86
+ put(key, value) {
87
+ this.#push(key);
88
+ super.set(key, value);
89
+ return value;
90
+ }
91
+
92
+ /**
93
+ * @param {K} key
94
+ * @param {V} value
95
+ * @returns
96
+ */
97
+ set(key, value) {
98
+ if (!this.has(key)) this.#push(key);
99
+ return super.set(key, value);
100
+ }
101
+ }
package/src/index.md ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: Utilities
3
+ permalink: /src/
4
+ ---
5
+
6
+ {% include_relative README.md %}
package/src/registry.js CHANGED
@@ -68,6 +68,11 @@ export default class Registry extends Map {
68
68
  for (const [key, value] of iterable ?? []) this.set(key, value);
69
69
  }
70
70
 
71
+ clear() {
72
+ if (this.#unique) fail('Unable to clear registry with unique keys');
73
+ super.clear();
74
+ }
75
+
71
76
  /**
72
77
  * Remove a key when unique-key protection is disabled.
73
78
  *
@@ -88,8 +93,8 @@ export default class Registry extends Map {
88
93
  */
89
94
  set(key, value) {
90
95
  if (!this.#key(key)) fail('Invalid key:', key);
91
- if (this.#unique && super.has(key)) fail('Duplicate key:', key);
92
96
  if (!this.#value(value)) fail('Invalid value:', value);
97
+ if (this.#unique && super.has(key)) fail('Duplicate key:', key);
93
98
  return super.set(key, value);
94
99
  }
95
100
  }
@@ -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
+ }