dubc-ds-tmap 1.0.0 → 1.0.1
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 +259 -1
- package/dist/index.d.ts +9 -10
- package/dist/index.js +33 -23
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,3 +1,261 @@
|
|
|
1
1
|
# dubc-ds-tmap
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A map (dictionary) of key/value pairs that is sorted using a weighted
|
|
4
|
+
binary search tree.
|
|
5
|
+
|
|
6
|
+
Note that this data structure maps _arbitrary_ keys to _arbitrary_
|
|
7
|
+
values. When doing front-end development, that's usually not what
|
|
8
|
+
you actually need. Consider using `dubc-ds-tindex` instead, which
|
|
9
|
+
provides a dictionary where the keys are a function of the values.
|
|
10
|
+
|
|
11
|
+
## Constructor
|
|
12
|
+
|
|
13
|
+
```TypeScript
|
|
14
|
+
constructor(conf:Conf<K,V>, pairs:Pairs<K,V>)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
To construct a `TMap`, you need a configuration object that supplies
|
|
18
|
+
three things:
|
|
19
|
+
|
|
20
|
+
1. A `compare` function that determines how to sort the keys in the map.
|
|
21
|
+
2. A `unique` flag that determines whether the map can contain duplicate keys.
|
|
22
|
+
3. A `valueEq` function that determines how values should be compared.
|
|
23
|
+
|
|
24
|
+
You also can supply initial key/value pairs to populate the map. The
|
|
25
|
+
pairs can be specified in three ways:
|
|
26
|
+
|
|
27
|
+
1. You can specify an iterable of objects with `key` and `value` fields;
|
|
28
|
+
2. Or an iterable of key/value tuples, where the first part of the tuple
|
|
29
|
+
is the key and the second part of the tuplie is the value;
|
|
30
|
+
3. Or, if the keys are strings, you can use a plain JavaScript object.
|
|
31
|
+
|
|
32
|
+
If you want unique keys and can use `Object.is` for your `valueEq` function,
|
|
33
|
+
you can use the static `of` method to construct a `TMap` with variadic
|
|
34
|
+
key/value pairs.
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
|
|
38
|
+
```TypeScript
|
|
39
|
+
import { Conf, TMap } from "dubc-ds-tmap"
|
|
40
|
+
|
|
41
|
+
const compare = (a:string, b:string) => a.localeCompare(b)
|
|
42
|
+
|
|
43
|
+
const conf:Conf<string,number> = {
|
|
44
|
+
compare, unique:false, valueEq:Object.is,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// These five statements all do the same thing.
|
|
48
|
+
const map1 = new TMap(conf, [{key:"1", value:1}, {key:"2", value:2}])
|
|
49
|
+
const map2 = new TMap(conf, [["1", 1], ["2", 2]])
|
|
50
|
+
const map3 = new TMap(conf, {"1":1, "2":2})
|
|
51
|
+
const map4 = TMap.of(compare, {key:"1", value:1}, {key:"2", value:2})
|
|
52
|
+
const map5 = TMap.of(compare, ["1", 1], ["2", 2])
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Properties
|
|
56
|
+
|
|
57
|
+
### `.compare` O(1)
|
|
58
|
+
|
|
59
|
+
Returns the current function used to compare keys. This property is
|
|
60
|
+
mutable: If you change it, the map will re-sort (and fire a notification
|
|
61
|
+
to any listeners that the map has changed.)
|
|
62
|
+
|
|
63
|
+
### `.first` O(log n)
|
|
64
|
+
|
|
65
|
+
Returns the first key/value pair in the map, or `undefined` if the map
|
|
66
|
+
is empty.
|
|
67
|
+
|
|
68
|
+
### `.i` O(1)
|
|
69
|
+
|
|
70
|
+
Returns an iterator over the key/value pairs in the map. Each value
|
|
71
|
+
returned by the iterator will be an immutable object in the form `{key:k, value:v}`. (This property is inherited from `Base`.)
|
|
72
|
+
|
|
73
|
+
### `.last` O(log n)
|
|
74
|
+
|
|
75
|
+
Returns the last key/value pair in the map, or `undefined` if the map
|
|
76
|
+
is empty.
|
|
77
|
+
|
|
78
|
+
### `.only` O(1)
|
|
79
|
+
|
|
80
|
+
Returns the only key/value pair in the map. If the map doesn't have
|
|
81
|
+
exactly one element, throws an `Error`.
|
|
82
|
+
|
|
83
|
+
### `.size` O(1)
|
|
84
|
+
|
|
85
|
+
Returns the number of key/value pairs in the map.
|
|
86
|
+
|
|
87
|
+
## Methods
|
|
88
|
+
|
|
89
|
+
### `.after(k:K)` O(log n)
|
|
90
|
+
|
|
91
|
+
Returns the key/value pair whose key is strictly greater than the provided
|
|
92
|
+
key, or `undefined` if no such pair exists in the map.
|
|
93
|
+
|
|
94
|
+
### `.at(index:number)` O(log n)
|
|
95
|
+
|
|
96
|
+
Returns the key/value pair at the specified index in the sort order.
|
|
97
|
+
Throws a `TypeError` if the index is out of bounds.
|
|
98
|
+
|
|
99
|
+
### `.before(k:K)` O(log n)
|
|
100
|
+
|
|
101
|
+
Returns the key/value pair whose key is strictly less than the provided
|
|
102
|
+
key, or `undefined` if no such pair exists in the map.
|
|
103
|
+
|
|
104
|
+
### `.delete(k:K)` O(log n)
|
|
105
|
+
|
|
106
|
+
Removes the given key from the map. Returns the value that was mapped
|
|
107
|
+
to the key, or `undefined` if the key was not in the map.
|
|
108
|
+
|
|
109
|
+
### `.deleteAll(i:Iterable<K>)` O(k * log n)
|
|
110
|
+
|
|
111
|
+
Removes every key provided by the given iterable from the map.
|
|
112
|
+
Returns the number of pairs that were deleted.
|
|
113
|
+
|
|
114
|
+
### `.drop(f:(pair:Pair<K,V>)=>boolean)` O(n)
|
|
115
|
+
|
|
116
|
+
Safely removes all key/value pairs that match the provided predicate
|
|
117
|
+
function from the map. Use this instead of calling `delete` from within
|
|
118
|
+
an iteration. Returns the number of pairs that were dropped.
|
|
119
|
+
|
|
120
|
+
### `.from(k:K)` O(log n)
|
|
121
|
+
|
|
122
|
+
Returns the key/value pair whose key is greater than or equal to the provided
|
|
123
|
+
key, or `undefined` if no such pair exists in the map.
|
|
124
|
+
|
|
125
|
+
### `.get(k:K)` O(log n)
|
|
126
|
+
|
|
127
|
+
Returns the value for the given key, or `undefined` if no such key exists
|
|
128
|
+
in the map.
|
|
129
|
+
|
|
130
|
+
### `.has(k:K)` O(log n)
|
|
131
|
+
|
|
132
|
+
Returns true if the map contains the given key, or false if it doesn't.
|
|
133
|
+
|
|
134
|
+
### `.hasAll(i:Iterable<K>)` O(k * log n)
|
|
135
|
+
|
|
136
|
+
Returns true if the map contains every key produced by the given iterable,
|
|
137
|
+
or false if it doesn't.
|
|
138
|
+
|
|
139
|
+
### `.hear(f:(event:DSEvent<Pair<K,V>>))` O(1)
|
|
140
|
+
|
|
141
|
+
Adds a listener to the map. Any time the map changes (and _only_ when
|
|
142
|
+
the map actually changes), the listener function will be called.
|
|
143
|
+
|
|
144
|
+
Returns a number that can be sent to `unhear` to stop receiving events.
|
|
145
|
+
|
|
146
|
+
### `.keys()` O(1)
|
|
147
|
+
|
|
148
|
+
Returns an iterable iterator over the keys in the map.
|
|
149
|
+
|
|
150
|
+
### `.range(start:K, end:K, include?:Include)` O(1)
|
|
151
|
+
|
|
152
|
+
Returns an iterable iterator over a range of keys in the map. The
|
|
153
|
+
iterator will produce key/value pairs.
|
|
154
|
+
|
|
155
|
+
The optional third parameter specifies whether the range is inclusive
|
|
156
|
+
or exclusive, and must be one of the following four constants:
|
|
157
|
+
|
|
158
|
+
1. `IN_EX`: Start is inclusive, end is exclusive. This is the default.
|
|
159
|
+
2. `IN_IN`: Both start and end are inclusive.
|
|
160
|
+
3. `EX_IN`: Start is exclusive, end is inclusive.
|
|
161
|
+
4. `EX_EX`: Both start and end are exclusive.
|
|
162
|
+
|
|
163
|
+
### `.rank(k:K)` O(log n)
|
|
164
|
+
|
|
165
|
+
Returns the index of the given key in the map's sort order, or
|
|
166
|
+
`undefined` if the key is not in the map.
|
|
167
|
+
|
|
168
|
+
### `.replace(pairs:Pairs<K,V>)` O(k * log n)
|
|
169
|
+
|
|
170
|
+
Replaces the content of this map with new key/value pairs.
|
|
171
|
+
|
|
172
|
+
The provided pairs can be in one of the three forms accepted by
|
|
173
|
+
`TMap`'s constructor (objects with key and value fields, tuples of
|
|
174
|
+
keys and values, or, for string keys, a JavaScript object.)
|
|
175
|
+
|
|
176
|
+
### `.reversed()` O(1)
|
|
177
|
+
|
|
178
|
+
Returns an iterable iterator over the pairs in the map, in reverse
|
|
179
|
+
sort order.
|
|
180
|
+
|
|
181
|
+
### `.set(key:K, value:V)` O(log n)
|
|
182
|
+
|
|
183
|
+
Adds a pair to the map.
|
|
184
|
+
|
|
185
|
+
If the map uses unique keys and the key already exists in the map,
|
|
186
|
+
then `set` will replace the value for that key and return the old key.
|
|
187
|
+
|
|
188
|
+
Otherwise, `set` will add a new pair to the map and return `undefined`.
|
|
189
|
+
|
|
190
|
+
### `.setAll(pairs:Pairs<K,V>)` O(k * log n)
|
|
191
|
+
|
|
192
|
+
Adds many pairs to the map. Returns the number of pairs that were newly
|
|
193
|
+
added. (The number returned does not include any keys that were
|
|
194
|
+
already in a unique map.)
|
|
195
|
+
|
|
196
|
+
The provided pairs can be in one of the three forms accepted by
|
|
197
|
+
`TMap`'s constructor (objects with key and value fields, tuples of
|
|
198
|
+
keys and values, or, for string keys, a JavaScript object.)
|
|
199
|
+
|
|
200
|
+
### `.unhear(n:number)` O(1)
|
|
201
|
+
|
|
202
|
+
Removes the listener registered with the specified number.
|
|
203
|
+
|
|
204
|
+
### `.values()` O(1)
|
|
205
|
+
|
|
206
|
+
Returns an iterable iterator over the values in the map.
|
|
207
|
+
|
|
208
|
+
## Operations by Kind
|
|
209
|
+
|
|
210
|
+
### Listeners
|
|
211
|
+
|
|
212
|
+
* `hear(eventHandler)` to get events when the map changes
|
|
213
|
+
* `unhear(n)` to stop listening for changes
|
|
214
|
+
|
|
215
|
+
### Iterators
|
|
216
|
+
|
|
217
|
+
* `TMap` itself is an iterable, so you can use it directly in `for...of`
|
|
218
|
+
* `i` to get an iterator
|
|
219
|
+
* `keys()` if you just want the keys
|
|
220
|
+
* `range(start,end,include)` for partial iteration
|
|
221
|
+
* `reversed()` to iterate backwards
|
|
222
|
+
* `values()` if you just want the values
|
|
223
|
+
|
|
224
|
+
### Keys
|
|
225
|
+
|
|
226
|
+
* `get(key)` to get the value for a key
|
|
227
|
+
* `has(key)` to determine if the key exists
|
|
228
|
+
* `hasAll(iterable)` to determin if all the keys exist
|
|
229
|
+
* `only` to get the only pair
|
|
230
|
+
|
|
231
|
+
### Ordering
|
|
232
|
+
|
|
233
|
+
* `get compare` for the sort function
|
|
234
|
+
|
|
235
|
+
* `first` to get the first pair
|
|
236
|
+
* `last` to get the last pair
|
|
237
|
+
|
|
238
|
+
* `after(key)` to get the pair > key
|
|
239
|
+
* `before(key)` to get the pair < key
|
|
240
|
+
* `from(key)` to get the pair >= key
|
|
241
|
+
* `to(key)` to get the pair <= key
|
|
242
|
+
|
|
243
|
+
### Indices
|
|
244
|
+
|
|
245
|
+
* `at(index)` to get the key at an index
|
|
246
|
+
* `rank(key)` to get the index of a key
|
|
247
|
+
* `size` to get the number of pairs
|
|
248
|
+
|
|
249
|
+
### Adding/Changing
|
|
250
|
+
|
|
251
|
+
* `set compare` to re-sort the map
|
|
252
|
+
* `set(key,value)` add a pair to the map
|
|
253
|
+
* `setAll(pairs)` add many pairs to the map
|
|
254
|
+
* `replace(pairs)` clear the map then add many pairs
|
|
255
|
+
|
|
256
|
+
### Deleting
|
|
257
|
+
|
|
258
|
+
* `clear()` delete everything
|
|
259
|
+
* `delete(key)` delete a specific key
|
|
260
|
+
* `deleteAll(iterable)` delete many keys
|
|
261
|
+
* `drop(predicate)` delete pairs that match a predicate
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare const EX_EX: {
|
|
|
21
21
|
end: boolean;
|
|
22
22
|
};
|
|
23
23
|
type N<K extends {}, V> = Node<K, V> | undefined;
|
|
24
|
-
declare class Node<K extends {}, V> {
|
|
24
|
+
declare class Node<K extends {}, V> implements Pair<K, V> {
|
|
25
25
|
readonly key: K;
|
|
26
26
|
value: V;
|
|
27
27
|
weight: number;
|
|
@@ -51,20 +51,16 @@ export declare class TMap<K extends {}, V> extends Base<Pair<K, V>> {
|
|
|
51
51
|
get size(): number;
|
|
52
52
|
clear(): void;
|
|
53
53
|
replace(i: Pairs<K, V>): void;
|
|
54
|
-
drop(f: (pair: Pair<K, V>) => boolean):
|
|
54
|
+
drop(f: (pair: Pair<K, V>) => boolean): number;
|
|
55
55
|
get compare(): (a: K, b: K) => number;
|
|
56
56
|
set compare(cmp: (a: K, b: K) => number);
|
|
57
|
-
get keyEq(): (a: K, b: K) => boolean;
|
|
58
|
-
get unique(): boolean;
|
|
59
57
|
at(i: number): Pair<K, V>;
|
|
60
58
|
get only(): Pair<K, V>;
|
|
61
59
|
get first(): Pair<K, V> | undefined;
|
|
62
60
|
get last(): Pair<K, V> | undefined;
|
|
63
|
-
[Symbol.iterator](): Generator<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
private values2;
|
|
67
|
-
get values(): Iterable<V>;
|
|
61
|
+
[Symbol.iterator](): Generator<Pair<K, V>>;
|
|
62
|
+
keys(): Generator<K, void, unknown>;
|
|
63
|
+
values(): Generator<V, void, unknown>;
|
|
68
64
|
reversed(): Generator<Pair<K, V>, void, unknown>;
|
|
69
65
|
range(startKey: K, endKey: K, inc?: Include): Generator<Pair<K, V>, void, unknown>;
|
|
70
66
|
private find;
|
|
@@ -74,6 +70,7 @@ export declare class TMap<K extends {}, V> extends Base<Pair<K, V>> {
|
|
|
74
70
|
to(key: K): Pair<K, V> | undefined;
|
|
75
71
|
after(key: K): Pair<K, V> | undefined;
|
|
76
72
|
before(key: K): Pair<K, V> | undefined;
|
|
73
|
+
hasAll(keys: Iterable<K>): boolean;
|
|
77
74
|
rank(key: K): number | undefined;
|
|
78
75
|
protected rawPut(key: K, value: V): {
|
|
79
76
|
node: Node<K, V>;
|
|
@@ -82,7 +79,9 @@ export declare class TMap<K extends {}, V> extends Base<Pair<K, V>> {
|
|
|
82
79
|
set(key: K, value: V): V | undefined;
|
|
83
80
|
setAll(entries: Pairs<K, V>): number;
|
|
84
81
|
delete(key: K): V | undefined;
|
|
85
|
-
private
|
|
82
|
+
private delete2;
|
|
83
|
+
deleteAll(i: Iterable<K>): number;
|
|
84
|
+
private rawDelete;
|
|
86
85
|
private weigh;
|
|
87
86
|
}
|
|
88
87
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -104,13 +104,7 @@ export class TMap extends Base {
|
|
|
104
104
|
return new TMap(pairs, conf);
|
|
105
105
|
}
|
|
106
106
|
toEmpty() {
|
|
107
|
-
|
|
108
|
-
unique: this.conf.unique,
|
|
109
|
-
valueEq: this.conf.valueEq,
|
|
110
|
-
compare: this._compare,
|
|
111
|
-
};
|
|
112
|
-
const r = new TMap(this, conf2);
|
|
113
|
-
return r;
|
|
107
|
+
return new TMap([], this.conf);
|
|
114
108
|
}
|
|
115
109
|
get size() { var _a, _b; return (_b = (_a = this.root) === null || _a === void 0 ? void 0 : _a.weight) !== null && _b !== void 0 ? _b : 0; }
|
|
116
110
|
clear() {
|
|
@@ -135,16 +129,19 @@ export class TMap extends Base {
|
|
|
135
129
|
drop(f) {
|
|
136
130
|
let n = this.first;
|
|
137
131
|
let at = 0;
|
|
132
|
+
let c = 0;
|
|
138
133
|
while (n !== undefined) {
|
|
139
134
|
const x = n;
|
|
140
135
|
n = n.next();
|
|
141
136
|
if (f(x)) {
|
|
142
|
-
this.
|
|
137
|
+
this.rawDelete(x);
|
|
143
138
|
this.fire({ deleted: { items: [x], at: at } });
|
|
139
|
+
c++;
|
|
144
140
|
}
|
|
145
141
|
else
|
|
146
142
|
at++;
|
|
147
143
|
}
|
|
144
|
+
return c;
|
|
148
145
|
}
|
|
149
146
|
get compare() { return this._compare; }
|
|
150
147
|
set compare(cmp) {
|
|
@@ -159,13 +156,12 @@ export class TMap extends Base {
|
|
|
159
156
|
}
|
|
160
157
|
this.fire({ cleared: this.size, added: { items: this, at: 0 } });
|
|
161
158
|
}
|
|
162
|
-
get keyEq() { return (a, b) => this._compare(a, b) === 0; }
|
|
163
|
-
get unique() { return this.conf.unique; }
|
|
164
159
|
at(i) {
|
|
165
160
|
var _a;
|
|
166
161
|
let node = this.root;
|
|
167
162
|
let weight = this.size;
|
|
168
|
-
|
|
163
|
+
if (!Number.isSafeInteger(i))
|
|
164
|
+
throw new TypeError("index must be an integer");
|
|
169
165
|
if (i < 0 || i >= weight)
|
|
170
166
|
throw new TypeError("bounds");
|
|
171
167
|
while (true) {
|
|
@@ -213,20 +209,14 @@ export class TMap extends Base {
|
|
|
213
209
|
n = n.next();
|
|
214
210
|
}
|
|
215
211
|
}
|
|
216
|
-
*
|
|
212
|
+
*keys() {
|
|
217
213
|
for (const x of this)
|
|
218
214
|
yield x.key;
|
|
219
215
|
}
|
|
220
|
-
|
|
221
|
-
return this.keys2();
|
|
222
|
-
}
|
|
223
|
-
*values2() {
|
|
216
|
+
*values() {
|
|
224
217
|
for (const x of this)
|
|
225
218
|
yield x.value;
|
|
226
219
|
}
|
|
227
|
-
get values() {
|
|
228
|
-
return this.values2();
|
|
229
|
-
}
|
|
230
220
|
*reversed() {
|
|
231
221
|
let n = this.last;
|
|
232
222
|
while (n) {
|
|
@@ -249,7 +239,7 @@ export class TMap extends Base {
|
|
|
249
239
|
find(key, op = EQ) {
|
|
250
240
|
let node = this.root;
|
|
251
241
|
let ret;
|
|
252
|
-
const unique = this.unique;
|
|
242
|
+
const unique = this.conf.unique;
|
|
253
243
|
const cmp = this.compare;
|
|
254
244
|
while (node) {
|
|
255
245
|
const c = cmp(node.key, key);
|
|
@@ -281,13 +271,19 @@ export class TMap extends Base {
|
|
|
281
271
|
to(key) { return this.find(key, LE); }
|
|
282
272
|
after(key) { return this.find(key, GT); }
|
|
283
273
|
before(key) { return this.find(key, LT); }
|
|
274
|
+
hasAll(keys) {
|
|
275
|
+
for (const x of keys)
|
|
276
|
+
if (!this.has(x))
|
|
277
|
+
return false;
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
284
280
|
rank(key) {
|
|
285
281
|
var _a;
|
|
286
282
|
return (_a = this.find(key, EQ)) === null || _a === void 0 ? void 0 : _a.index();
|
|
287
283
|
}
|
|
288
284
|
rawPut(key, value) {
|
|
289
285
|
let previous = undefined, node = this.root;
|
|
290
|
-
let unique = this.unique ? 0 : undefined;
|
|
286
|
+
let unique = this.conf.unique ? 0 : undefined;
|
|
291
287
|
let c = 0, cmp = this.compare;
|
|
292
288
|
while (node) {
|
|
293
289
|
c = cmp(node.key, key);
|
|
@@ -347,12 +343,26 @@ export class TMap extends Base {
|
|
|
347
343
|
const node = this.find(key);
|
|
348
344
|
if (node === undefined)
|
|
349
345
|
return undefined;
|
|
346
|
+
return this.delete2(node);
|
|
347
|
+
}
|
|
348
|
+
delete2(node) {
|
|
350
349
|
const at = node.index();
|
|
351
|
-
this.
|
|
350
|
+
this.rawDelete(node);
|
|
352
351
|
this.fire({ deleted: { items: [node], at } });
|
|
353
352
|
return node.value;
|
|
354
353
|
}
|
|
355
|
-
|
|
354
|
+
deleteAll(i) {
|
|
355
|
+
let c = 0;
|
|
356
|
+
for (const x of i) {
|
|
357
|
+
const node = this.find(x);
|
|
358
|
+
if (node !== undefined) {
|
|
359
|
+
c++;
|
|
360
|
+
this.delete2(node);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return c;
|
|
364
|
+
}
|
|
365
|
+
rawDelete(node) {
|
|
356
366
|
const p = node.parent;
|
|
357
367
|
let left = node.left, right = node.right;
|
|
358
368
|
let bal;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dubc-ds-tmap",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.1",
|
|
5
5
|
"description": "Observable map based on a weighted binary search tree.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"homepage": "https://github.com/p-jack/dubc#readme",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"dubc-ds-base": "^1.0.2",
|
|
30
|
-
"dubc-ds-pairs": "^1.0.
|
|
30
|
+
"dubc-ds-pairs": "^1.0.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@vitest/coverage-v8": "^3.2.4",
|