/
abstract_map.ts
130 lines (121 loc) · 4.08 KB
/
abstract_map.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { Collab } from "../core";
import { IMap, MapEventsRecord } from "./i_map";
export interface MakeAbstractMap_Methods<
K,
V,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
SetArgs extends unknown[] = [V]
> {
/**
* Calls delete on every value in the map.
*
* Override this method if you want to optimize this
* behavior.
*/
clear(): void;
forEach(
callbackfn: (value: V, key: K, map: this) => void,
thisArg?: any // eslint-disable-line @typescript-eslint/no-explicit-any
): void;
[Symbol.iterator](): IterableIterator<[K, V]>;
keys(): IterableIterator<K>;
values(): IterableIterator<V>;
/**
* @return [...this].toString()
*/
toString(): string;
/**
* Searches linearly through the map using the default
* iterator,
* comparing values to searchElement using ===.
*
* Override this method if you want to optimize the
* search.
*/
keyOf(searchElement: V): K | undefined;
}
/**
* Mixin that adds default implementations of [[IMap]]
* methods to a base class `Base`. `Base` is assumed to extend [[Collab]]
* and implement the remaining IMap methods (or leave them abstract).
*
* The implemented methods are those in [[MakeAbstractMap_Methods]].
* You may override their implementations in subclasses.
*
* Typically, you do not need to use this mixin directly. Instead,
* use our predefined instances for the most common `Base` classes:
* [[AbstractMap_Collab]], [[AbstractMap_CObject]], [[AbstractMap_CPrimitive]],
* [[AbstractMap_PrimitiveCRDT]].
*
* If you do need to apply this mixin to a different `Base`, beware that
* it tricky to use in TypeScript. Specifically, the mixin requires generic type
* parameters, but you cannot pass a class's generic type parameters to
* a mixin that it extends. To work around this, we recommend:
* 1. Declare the mixin usage and its type separately, in `.js`
* and `.d.ts` files. See the source of [[AbstractMap_Collab]]
* for an example.
* 2. In `tsconfig.json`, set `"allowJs": true`.
* 3. In your build script, after running `tsc`, copy the `.d.ts` file to the
* output folder. Otherwise, by default TypeScript auto-generates its own
* `.d.ts` file from the `.js` file
* (see [https://github.com/microsoft/TypeScript/issues/39231](https://github.com/microsoft/TypeScript/issues/39231)).
*/
export function MakeAbstractMap<
K,
V,
SetArgs extends unknown[],
Events extends MapEventsRecord<K, V>,
TBase extends abstract new (...args: any[]) => {
set(key: K, ...args: SetArgs): V | undefined;
delete(key: K): void;
get(key: K): V | undefined;
has(key: K): boolean;
entries(): IterableIterator<[K, V]>;
readonly size: number;
} & Collab<Events>
>(
Base: TBase
): TBase &
(abstract new (...args: any[]) => MakeAbstractMap_Methods<K, V, SetArgs>) {
abstract class Mixin extends Base implements IMap<K, V, SetArgs, Events> {
constructor(...args: any[]) {
super(...args);
}
clear(): void {
for (const key of this.keys()) this.delete(key);
}
forEach(
callbackfn: (value: V, key: K, map: this) => void,
thisArg?: any // eslint-disable-line @typescript-eslint/no-explicit-any
): void {
// Not sure if this gives the exact same semantics
// as Map if callbackfn modifies this during the
// loop. (Given that Array.forEach has a rather
// funky polyfill on MDN, I expect Map.forEach is
// similarly funky.) Although users probably shouldn't
// be doing that anyway.
for (const [key, value] of this) {
callbackfn.call(thisArg, value, key, this);
}
}
[Symbol.iterator](): IterableIterator<[K, V]> {
return this.entries();
}
*keys(): IterableIterator<K> {
for (const [key] of this) yield key;
}
*values(): IterableIterator<V> {
for (const [, value] of this) yield value;
}
toString(): string {
return [...this].toString();
}
keyOf(searchElement: V): K | undefined {
for (const [key, value] of this) {
if (value === searchElement) return key;
}
return undefined;
}
}
return Mixin;
}