@rhombus-toolkit/collections 2.0.2 → 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 +71 -14
- package/dist/bundle/index.js +144 -41
- package/package.json +5 -2
package/dist/bundle/index.d.ts
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
import { Func } from '@rhombus-toolkit/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A stack whose entries leave when their scope does.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Iterates outermost first.
|
|
8
|
+
*/
|
|
9
|
+
declare class AutoStack<T> implements Iterable<T> {
|
|
10
|
+
#private;
|
|
11
|
+
/** How many entries the stack holds. */
|
|
12
|
+
get length(): number;
|
|
13
|
+
/** Adds `item`; disposing the return truncates back to the pre-push depth, so an outer scope's dispose also drops what an inner one left. */
|
|
14
|
+
push(item: T): Disposable;
|
|
15
|
+
/** Whether `item` sits anywhere in the stack, not only at the top. */
|
|
16
|
+
has(item: T): boolean;
|
|
17
|
+
/** The innermost entry, absent where the stack holds nothing. */
|
|
18
|
+
peek(): T | undefined;
|
|
19
|
+
[Symbol.iterator](): ArrayIterator<T>;
|
|
20
|
+
}
|
|
21
|
+
|
|
1
22
|
/**
|
|
2
23
|
* A persistent singly-linked list — extending shares everything already there.
|
|
3
24
|
*
|
|
@@ -28,22 +49,58 @@ declare class ImmutableLinkedList<T> implements Iterable<T> {
|
|
|
28
49
|
tailToHead(): readonly T[];
|
|
29
50
|
}
|
|
30
51
|
|
|
31
|
-
|
|
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> {
|
|
32
61
|
#private;
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
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;
|
|
36
65
|
get(key: K): V | undefined;
|
|
37
66
|
has(key: K): boolean;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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;
|
|
47
104
|
}
|
|
48
105
|
|
|
49
|
-
export { ImmutableLinkedList, KindaWeakMap };
|
|
106
|
+
export { AutoStack, ImmutableLinkedList, KindaWeakMap, MultiKeyWeakMap };
|
package/dist/bundle/index.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
// src/AutoStack.ts
|
|
2
|
+
class AutoStack {
|
|
3
|
+
#items = [];
|
|
4
|
+
get length() {
|
|
5
|
+
return this.#items.length;
|
|
6
|
+
}
|
|
7
|
+
push(item) {
|
|
8
|
+
const pos = this.#items.length;
|
|
9
|
+
this.#items.push(item);
|
|
10
|
+
return { [Symbol.dispose]: () => {
|
|
11
|
+
this.#items.length = pos;
|
|
12
|
+
} };
|
|
13
|
+
}
|
|
14
|
+
has(item) {
|
|
15
|
+
return this.#items.includes(item);
|
|
16
|
+
}
|
|
17
|
+
peek() {
|
|
18
|
+
return this.#items.at(-1);
|
|
19
|
+
}
|
|
20
|
+
[Symbol.iterator]() {
|
|
21
|
+
return this.#items.values();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
1
24
|
// src/ImmutableLinkedList.ts
|
|
2
25
|
class ImmutableLinkedList {
|
|
3
26
|
static #nothing = new ImmutableLinkedList(undefined, undefined, 0);
|
|
@@ -58,66 +81,146 @@ function tailOf(link) {
|
|
|
58
81
|
return tail;
|
|
59
82
|
}
|
|
60
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
|
+
|
|
61
98
|
class KindaWeakMap {
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
99
|
+
#weak = new WeakMap;
|
|
100
|
+
#weakSize = 0;
|
|
101
|
+
#collected = new FinalizationRegistry(() => {
|
|
102
|
+
this.#weakSize--;
|
|
67
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
|
+
}
|
|
68
123
|
set(key, value) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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);
|
|
72
132
|
}
|
|
73
|
-
const ref = new WeakRef(value);
|
|
74
|
-
this.#registry.register(value, { key, ref }, ref);
|
|
75
|
-
this.#map.set(key, ref);
|
|
76
133
|
return this;
|
|
77
134
|
}
|
|
78
135
|
delete(key) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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;
|
|
82
143
|
}
|
|
83
|
-
this.#
|
|
84
|
-
this.#registry.unregister(held);
|
|
85
|
-
return held.deref() !== undefined;
|
|
144
|
+
return this.#strong?.delete(key) ?? false;
|
|
86
145
|
}
|
|
87
|
-
|
|
88
|
-
|
|
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;
|
|
89
153
|
}
|
|
90
|
-
|
|
91
|
-
|
|
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;
|
|
92
162
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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";
|
|
96
174
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
callbackfn.call(thisArg, value, key, this);
|
|
100
|
-
}
|
|
175
|
+
#findNodeAt(keys) {
|
|
176
|
+
return keys.reduce((node, key) => node?.next.get(key), this._root);
|
|
101
177
|
}
|
|
102
|
-
|
|
103
|
-
return
|
|
178
|
+
#ensureNodeAt(keys) {
|
|
179
|
+
return keys.reduce((node, key) => node.next.getOrInsertComputed(key, () => new Node), this._root);
|
|
104
180
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return held.filter((entry) => entry[1] !== undefined);
|
|
181
|
+
get(keys) {
|
|
182
|
+
return this.#findNodeAt(keys)?.entry?.value;
|
|
108
183
|
}
|
|
109
|
-
keys
|
|
110
|
-
return this
|
|
184
|
+
has(keys) {
|
|
185
|
+
return this.#findNodeAt(keys)?.entry !== undefined;
|
|
111
186
|
}
|
|
112
|
-
|
|
113
|
-
|
|
187
|
+
set(keys, value) {
|
|
188
|
+
this.#ensureNodeAt(keys).entry = { value };
|
|
189
|
+
return this;
|
|
114
190
|
}
|
|
115
|
-
|
|
116
|
-
|
|
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;
|
|
117
219
|
}
|
|
118
|
-
[Symbol.toStringTag] = "KindaWeakMap";
|
|
119
220
|
}
|
|
120
221
|
export {
|
|
222
|
+
AutoStack,
|
|
121
223
|
ImmutableLinkedList,
|
|
122
|
-
KindaWeakMap
|
|
224
|
+
KindaWeakMap,
|
|
225
|
+
MultiKeyWeakMap
|
|
123
226
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhombus-toolkit/collections",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Collection data structures: 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
|