@rhombus-toolkit/collections 2.1.0 → 3.0.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 +52 -14
- package/dist/bundle/index.js +120 -41
- 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,58 @@ 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> {
|
|
51
61
|
#private;
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
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;
|
|
55
65
|
get(key: K): V | undefined;
|
|
56
66
|
has(key: K): boolean;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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;
|
|
66
104
|
}
|
|
67
105
|
|
|
68
|
-
export { AutoStack, ImmutableLinkedList, KindaWeakMap };
|
|
106
|
+
export { AutoStack, ImmutableLinkedList, KindaWeakMap, MultiKeyWeakMap };
|
package/dist/bundle/index.js
CHANGED
|
@@ -81,67 +81,146 @@ 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 {
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
99
|
+
#weak = new WeakMap;
|
|
100
|
+
#weakSize = 0;
|
|
101
|
+
#collected = new FinalizationRegistry(() => {
|
|
102
|
+
this.#weakSize--;
|
|
90
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
|
+
}
|
|
91
123
|
set(key, value) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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);
|
|
95
132
|
}
|
|
96
|
-
const ref = new WeakRef(value);
|
|
97
|
-
this.#registry.register(value, { key, ref }, ref);
|
|
98
|
-
this.#map.set(key, ref);
|
|
99
133
|
return this;
|
|
100
134
|
}
|
|
101
135
|
delete(key) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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;
|
|
105
143
|
}
|
|
106
|
-
this.#
|
|
107
|
-
this.#registry.unregister(held);
|
|
108
|
-
return held.deref() !== undefined;
|
|
144
|
+
return this.#strong?.delete(key) ?? false;
|
|
109
145
|
}
|
|
110
|
-
|
|
111
|
-
|
|
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;
|
|
112
153
|
}
|
|
113
|
-
|
|
114
|
-
|
|
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;
|
|
115
162
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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";
|
|
119
174
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
callbackfn.call(thisArg, value, key, this);
|
|
123
|
-
}
|
|
175
|
+
#findNodeAt(keys) {
|
|
176
|
+
return keys.reduce((node, key) => node?.next.get(key), this._root);
|
|
124
177
|
}
|
|
125
|
-
|
|
126
|
-
return
|
|
178
|
+
#ensureNodeAt(keys) {
|
|
179
|
+
return keys.reduce((node, key) => node.next.getOrInsertComputed(key, () => new Node), this._root);
|
|
127
180
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return held.filter((entry) => entry[1] !== undefined);
|
|
181
|
+
get(keys) {
|
|
182
|
+
return this.#findNodeAt(keys)?.entry?.value;
|
|
131
183
|
}
|
|
132
|
-
keys
|
|
133
|
-
return this
|
|
184
|
+
has(keys) {
|
|
185
|
+
return this.#findNodeAt(keys)?.entry !== undefined;
|
|
134
186
|
}
|
|
135
|
-
|
|
136
|
-
|
|
187
|
+
set(keys, value) {
|
|
188
|
+
this.#ensureNodeAt(keys).entry = { value };
|
|
189
|
+
return this;
|
|
137
190
|
}
|
|
138
|
-
|
|
139
|
-
|
|
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;
|
|
140
219
|
}
|
|
141
|
-
[Symbol.toStringTag] = "KindaWeakMap";
|
|
142
220
|
}
|
|
143
221
|
export {
|
|
144
222
|
AutoStack,
|
|
145
223
|
ImmutableLinkedList,
|
|
146
|
-
KindaWeakMap
|
|
224
|
+
KindaWeakMap,
|
|
225
|
+
MultiKeyWeakMap
|
|
147
226
|
};
|
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.0.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).",
|
|
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
|