@rhombus-toolkit/collections 2.1.0 → 3.1.0
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/dist/bundle/index.d.ts +69 -5
- package/dist/bundle/index.js +147 -2
- package/package.json +5 -2
package/dist/bundle/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Func } from '@rhombus-toolkit/types';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* A stack whose entries leave when their scope does.
|
|
3
5
|
*
|
|
@@ -47,22 +49,84 @@ declare class ImmutableLinkedList<T> implements Iterable<T> {
|
|
|
47
49
|
tailToHead(): readonly T[];
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
/**
|
|
53
|
+
* A map that holds each key weakly when it can: objects, functions, and unregistered symbols go
|
|
54
|
+
* in a `WeakMap`; everything else goes in a `Map`.
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* An entry under a weakly held key goes when the key is collected; one under any other key lives
|
|
58
|
+
* as long as the map.
|
|
59
|
+
*/
|
|
60
|
+
declare class KindaWeakMap<in out K = unknown, in out V = unknown> {
|
|
61
|
+
#private;
|
|
62
|
+
/** Entries held right now; a collected key still counts until its cleanup has run, so this can read high, never low. */
|
|
63
|
+
get size(): number;
|
|
64
|
+
get [Symbol.toStringTag](): string;
|
|
65
|
+
get(key: K): V | undefined;
|
|
66
|
+
has(key: K): boolean;
|
|
67
|
+
set(key: K, value: V): this;
|
|
68
|
+
delete(key: K): boolean;
|
|
69
|
+
/** The entry under `key`, storing `value` there first when there is none. */
|
|
70
|
+
getOrInsert(key: K, value: V): V;
|
|
71
|
+
/** The entry under `key`, storing what `compute` answers there first when there is none. A throw stores nothing. */
|
|
72
|
+
getOrInsertComputed(key: K, compute: Func<[K], V>): V;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Where a tuple ends: the entry stored there, and the node each further key leads to. */
|
|
76
|
+
declare class Node<Value> {
|
|
77
|
+
readonly next: KindaWeakMap<unknown, Node<Value>>;
|
|
78
|
+
/** Boxed, so a stored `undefined` is an entry rather than an absence. */
|
|
79
|
+
entry: {
|
|
80
|
+
value: Value;
|
|
81
|
+
} | undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A `KindaWeakMap` keyed by a tuple, matched key by key rather than by the tuple's identity.
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* Tuples of every length share one map, the empty tuple included. An entry goes when any weakly
|
|
88
|
+
* held key in its tuple is collected; a tuple of primitives lives as long as the map.
|
|
89
|
+
*/
|
|
90
|
+
declare class MultiKeyWeakMap<in out Keys extends readonly unknown[] = unknown[], in out Value = unknown> implements WeakMap<Keys, Value> {
|
|
91
|
+
#private;
|
|
92
|
+
/** @internal The spec reads it to see pruning. */
|
|
93
|
+
readonly _root: Node<Value>;
|
|
94
|
+
get [Symbol.toStringTag](): string;
|
|
95
|
+
get(keys: Keys): Value | undefined;
|
|
96
|
+
has(keys: Keys): boolean;
|
|
97
|
+
set(keys: Keys, value: Value): this;
|
|
98
|
+
/** Longer tuples through `keys` keep their entries; a prefix left holding nothing is released. */
|
|
99
|
+
delete(keys: Keys): boolean;
|
|
100
|
+
/** The entry at `keys`, storing `value` there first when there is none. */
|
|
101
|
+
getOrInsert(keys: Keys, value: Value): Value;
|
|
102
|
+
/** The entry at `keys`, storing what `compute` answers there first when there is none. A throw stores nothing. */
|
|
103
|
+
getOrInsertComputed(keys: Keys, compute: Func<[Keys], Value>): Value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A `Map` whose values are held weakly, so an entry is removed once nothing else holds its value.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* Not a `Map` by `instanceof`.
|
|
111
|
+
*/
|
|
112
|
+
declare class WeakValuedMap<K, V extends WeakKey> implements Map<K, V> {
|
|
51
113
|
#private;
|
|
52
|
-
|
|
114
|
+
constructor();
|
|
115
|
+
constructor(entries: Iterable<readonly [K, V]> | null);
|
|
116
|
+
/** Re-setting an existing key leaves its place in iteration order unchanged, as with `Map`. */
|
|
53
117
|
set(key: K, value: V): this;
|
|
54
118
|
delete(key: K): boolean;
|
|
55
119
|
get(key: K): V | undefined;
|
|
56
120
|
has(key: K): boolean;
|
|
57
121
|
clear(): void;
|
|
58
122
|
forEach(callbackfn: (value: V, key: K, map: this) => void, thisArg?: any): void;
|
|
59
|
-
/**
|
|
123
|
+
/** Computed by walking every entry and counting those whose value is still alive. */
|
|
60
124
|
get size(): number;
|
|
61
125
|
entries(): MapIterator<[K, V]>;
|
|
62
126
|
keys(): MapIterator<K>;
|
|
63
127
|
values(): MapIterator<V>;
|
|
64
128
|
[Symbol.iterator](): MapIterator<[K, V]>;
|
|
65
|
-
readonly [Symbol.toStringTag]: "
|
|
129
|
+
readonly [Symbol.toStringTag]: "WeakValuedMap";
|
|
66
130
|
}
|
|
67
131
|
|
|
68
|
-
export { AutoStack, ImmutableLinkedList, KindaWeakMap };
|
|
132
|
+
export { AutoStack, ImmutableLinkedList, KindaWeakMap, MultiKeyWeakMap, WeakValuedMap };
|
package/dist/bundle/index.js
CHANGED
|
@@ -81,13 +81,156 @@ function tailOf(link) {
|
|
|
81
81
|
return tail;
|
|
82
82
|
}
|
|
83
83
|
// src/KindaWeakMap.ts
|
|
84
|
+
function isWeaklyHoldable(key) {
|
|
85
|
+
const type = typeof key;
|
|
86
|
+
if (type === "object") {
|
|
87
|
+
return key !== null;
|
|
88
|
+
}
|
|
89
|
+
if (type === "function") {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (type === "symbol") {
|
|
93
|
+
return Symbol.keyFor(key) === undefined;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
class KindaWeakMap {
|
|
99
|
+
#weak = new WeakMap;
|
|
100
|
+
#weakSize = 0;
|
|
101
|
+
#collected = new FinalizationRegistry(() => {
|
|
102
|
+
this.#weakSize--;
|
|
103
|
+
});
|
|
104
|
+
#strong;
|
|
105
|
+
get size() {
|
|
106
|
+
return (this.#strong?.size ?? 0) + this.#weakSize;
|
|
107
|
+
}
|
|
108
|
+
get [Symbol.toStringTag]() {
|
|
109
|
+
return "KindaWeakMap";
|
|
110
|
+
}
|
|
111
|
+
get(key) {
|
|
112
|
+
if (isWeaklyHoldable(key)) {
|
|
113
|
+
return this.#weak.get(key);
|
|
114
|
+
}
|
|
115
|
+
return this.#strong?.get(key);
|
|
116
|
+
}
|
|
117
|
+
has(key) {
|
|
118
|
+
if (isWeaklyHoldable(key)) {
|
|
119
|
+
return this.#weak.has(key);
|
|
120
|
+
}
|
|
121
|
+
return this.#strong?.has(key) ?? false;
|
|
122
|
+
}
|
|
123
|
+
set(key, value) {
|
|
124
|
+
if (isWeaklyHoldable(key)) {
|
|
125
|
+
if (!this.#weak.has(key)) {
|
|
126
|
+
this.#weakSize++;
|
|
127
|
+
this.#collected.register(key, undefined, key);
|
|
128
|
+
}
|
|
129
|
+
this.#weak.set(key, value);
|
|
130
|
+
} else {
|
|
131
|
+
(this.#strong ??= new Map).set(key, value);
|
|
132
|
+
}
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
delete(key) {
|
|
136
|
+
if (isWeaklyHoldable(key)) {
|
|
137
|
+
if (!this.#weak.delete(key)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
this.#weakSize--;
|
|
141
|
+
this.#collected.unregister(key);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return this.#strong?.delete(key) ?? false;
|
|
145
|
+
}
|
|
146
|
+
getOrInsert(key, value) {
|
|
147
|
+
const existing = this.get(key);
|
|
148
|
+
if (existing !== undefined || this.has(key)) {
|
|
149
|
+
return existing;
|
|
150
|
+
}
|
|
151
|
+
this.set(key, value);
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
getOrInsertComputed(key, compute) {
|
|
155
|
+
const existing = this.get(key);
|
|
156
|
+
if (existing !== undefined || this.has(key)) {
|
|
157
|
+
return existing;
|
|
158
|
+
}
|
|
159
|
+
const value = compute(key);
|
|
160
|
+
this.set(key, value);
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// src/MultiKeyWeakMap.ts
|
|
165
|
+
class Node {
|
|
166
|
+
next = new KindaWeakMap;
|
|
167
|
+
entry;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
class MultiKeyWeakMap {
|
|
171
|
+
_root = new Node;
|
|
172
|
+
get [Symbol.toStringTag]() {
|
|
173
|
+
return "MultiKeyWeakMap";
|
|
174
|
+
}
|
|
175
|
+
#findNodeAt(keys) {
|
|
176
|
+
return keys.reduce((node, key) => node?.next.get(key), this._root);
|
|
177
|
+
}
|
|
178
|
+
#ensureNodeAt(keys) {
|
|
179
|
+
return keys.reduce((node, key) => node.next.getOrInsertComputed(key, () => new Node), this._root);
|
|
180
|
+
}
|
|
181
|
+
get(keys) {
|
|
182
|
+
return this.#findNodeAt(keys)?.entry?.value;
|
|
183
|
+
}
|
|
184
|
+
has(keys) {
|
|
185
|
+
return this.#findNodeAt(keys)?.entry !== undefined;
|
|
186
|
+
}
|
|
187
|
+
set(keys, value) {
|
|
188
|
+
this.#ensureNodeAt(keys).entry = { value };
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
delete(keys) {
|
|
192
|
+
const path = [this._root];
|
|
193
|
+
for (const key of keys) {
|
|
194
|
+
const next = path[path.length - 1].next.get(key);
|
|
195
|
+
if (!next) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
path.push(next);
|
|
199
|
+
}
|
|
200
|
+
const leaf = path[path.length - 1];
|
|
201
|
+
if (leaf.entry === undefined) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
leaf.entry = undefined;
|
|
205
|
+
for (let depth = keys.length;depth > 0; depth--) {
|
|
206
|
+
const node = path[depth];
|
|
207
|
+
if (node.entry !== undefined || node.next.size) {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
path[depth - 1].next.delete(keys[depth - 1]);
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
getOrInsert(keys, value) {
|
|
215
|
+
return (this.#ensureNodeAt(keys).entry ??= { value }).value;
|
|
216
|
+
}
|
|
217
|
+
getOrInsertComputed(keys, compute) {
|
|
218
|
+
return (this.#ensureNodeAt(keys).entry ??= { value: compute(keys) }).value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// src/WeakValuedMap.ts
|
|
222
|
+
class WeakValuedMap {
|
|
85
223
|
#map = new Map;
|
|
86
224
|
#registry = new FinalizationRegistry(({ key, ref }) => {
|
|
87
225
|
if (this.#map.get(key) === ref) {
|
|
88
226
|
this.#map.delete(key);
|
|
89
227
|
}
|
|
90
228
|
});
|
|
229
|
+
constructor(entries) {
|
|
230
|
+
for (const [key, value] of entries ?? []) {
|
|
231
|
+
this.set(key, value);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
91
234
|
set(key, value) {
|
|
92
235
|
const held = this.#map.get(key);
|
|
93
236
|
if (held) {
|
|
@@ -138,10 +281,12 @@ class KindaWeakMap {
|
|
|
138
281
|
[Symbol.iterator]() {
|
|
139
282
|
return this.entries();
|
|
140
283
|
}
|
|
141
|
-
[Symbol.toStringTag] = "
|
|
284
|
+
[Symbol.toStringTag] = "WeakValuedMap";
|
|
142
285
|
}
|
|
143
286
|
export {
|
|
144
287
|
AutoStack,
|
|
145
288
|
ImmutableLinkedList,
|
|
146
|
-
KindaWeakMap
|
|
289
|
+
KindaWeakMap,
|
|
290
|
+
MultiKeyWeakMap,
|
|
291
|
+
WeakValuedMap
|
|
147
292
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhombus-toolkit/collections",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Collection data structures: AutoStack (entries leave with their scope), ImmutableLinkedList (persistent, both ends known), KindaWeakMap (strong
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"description": "Collection data structures: AutoStack (entries leave with their scope), ImmutableLinkedList (persistent, both ends known), KindaWeakMap (weak where it can, strong otherwise), MultiKeyWeakMap (a KindaWeakMap keyed by a tuple), WeakValuedMap (values held weakly).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/rhombus-toolkit/ts.git",
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
],
|
|
24
24
|
"keywords": [],
|
|
25
25
|
"author": "Thomas Butler",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@rhombus-toolkit/types": "4.0.1"
|
|
28
|
+
},
|
|
26
29
|
"publishConfig": {
|
|
27
30
|
"access": "public",
|
|
28
31
|
"provenance": true
|