creo 0.0.2 → 0.0.4-dev
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/.env.development +1 -0
- package/.github/workflows/main.yml +24 -0
- package/README.md +1 -52
- package/TODOS.md +2 -0
- package/index.html +13 -0
- package/index.ts +1 -0
- package/package.json +12 -44
- package/src/DOM/Context.ts +36 -0
- package/src/DOM/DomEngine.ts +106 -0
- package/src/DOM/IRenderCycle.ts +9 -0
- package/src/DOM/Key.ts +1 -0
- package/src/DOM/Node.ts +472 -0
- package/src/DOM/Registry.ts +53 -0
- package/src/creo.ts +134 -0
- package/src/data-structures/assert/assert.ts +12 -0
- package/src/data-structures/indexed-map/IndexedMap.ts +281 -0
- package/src/data-structures/linked-map/LinkedMap.spec.ts +67 -0
- package/src/data-structures/linked-map/LinkedMap.ts +198 -0
- package/src/data-structures/list/List.spec.ts +181 -0
- package/src/data-structures/list/List.ts +195 -0
- package/src/data-structures/maybe/Maybe.ts +25 -0
- package/src/data-structures/null/null.ts +3 -0
- package/src/data-structures/record/IsRecordLike.spec.ts +29 -0
- package/src/data-structures/record/IsRecordLike.ts +3 -0
- package/src/data-structures/record/Record.spec.ts +240 -0
- package/src/data-structures/record/Record.ts +145 -0
- package/src/data-structures/shalllowEqual/shallowEqual.ts +26 -0
- package/src/data-structures/simpleKey/simpleKey.ts +8 -0
- package/src/data-structures/wildcard/wildcard.ts +1 -0
- package/src/examples/SimpleTodoList/SimpleTodoList.ts +53 -0
- package/src/examples/simple.ts +0 -0
- package/src/globals.d.ts +1 -0
- package/src/main.ts +24 -0
- package/src/style.css +41 -0
- package/src/ui/html/Block.ts +10 -0
- package/src/ui/html/Button.ts +12 -0
- package/src/ui/html/HStack.ts +10 -0
- package/src/ui/html/Inline.ts +12 -0
- package/src/ui/html/List.ts +10 -0
- package/src/ui/html/Text.ts +9 -0
- package/src/ui/html/VStack.ts +11 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +23 -0
- package/vite.config.js +10 -0
- package/LICENSE +0 -21
- package/dist/index.cjs +0 -54
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts +0 -19
- package/dist/index.js +0 -51
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -51
- package/dist/index.mjs.map +0 -1
- package/dist/index.umd.js +0 -61
- package/dist/index.umd.js.map +0 -1
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { Maybe } from "../maybe/Maybe";
|
|
2
|
+
|
|
3
|
+
type LinkNode<T, K> = {
|
|
4
|
+
value: T;
|
|
5
|
+
cachedIndexValues: Map<keyof T, T[keyof T]>;
|
|
6
|
+
prev: Maybe<LinkNode<T, K>>;
|
|
7
|
+
next: Maybe<LinkNode<T, K>>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Notifies when item gets added to a particular index
|
|
11
|
+
type IndexAddedSubscriber<T> = (val: T[keyof T]) => void;
|
|
12
|
+
|
|
13
|
+
export class IndexedMap<T extends object, K extends keyof T>
|
|
14
|
+
implements Iterable<T>
|
|
15
|
+
{
|
|
16
|
+
private head: Maybe<LinkNode<T, K>>;
|
|
17
|
+
private tail: Maybe<LinkNode<T, K>>;
|
|
18
|
+
private mapSize = 0;
|
|
19
|
+
private pk: K;
|
|
20
|
+
private map = new Map<T[K], LinkNode<T, K>>();
|
|
21
|
+
private indexes: Map<keyof T, Map<T[keyof T], Set<LinkNode<T, K>>>> =
|
|
22
|
+
new Map();
|
|
23
|
+
private indexAddedSubscribers: Map<keyof T, Set<IndexAddedSubscriber<T>>> =
|
|
24
|
+
new Map();
|
|
25
|
+
|
|
26
|
+
constructor(pk: K, indexFields: Array<keyof T> = []) {
|
|
27
|
+
this.pk = pk;
|
|
28
|
+
for (const field of indexFields) {
|
|
29
|
+
if (field === pk) continue;
|
|
30
|
+
this.indexes.set(field, new Map());
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Puts item to the end
|
|
35
|
+
// Removes previous item, if PK is matched
|
|
36
|
+
put(item: T): void {
|
|
37
|
+
const key = item[this.pk];
|
|
38
|
+
// Delete previous, if any
|
|
39
|
+
this.delete(key);
|
|
40
|
+
|
|
41
|
+
const node: LinkNode<T, K> = {
|
|
42
|
+
value: item,
|
|
43
|
+
cachedIndexValues: new Map(),
|
|
44
|
+
prev: this.tail,
|
|
45
|
+
next: null,
|
|
46
|
+
};
|
|
47
|
+
if (!this.head) {
|
|
48
|
+
this.head = node;
|
|
49
|
+
}
|
|
50
|
+
if (this.tail) {
|
|
51
|
+
this.tail.next = node;
|
|
52
|
+
}
|
|
53
|
+
this.tail = node;
|
|
54
|
+
this.map.set(key, node);
|
|
55
|
+
this.mapSize++;
|
|
56
|
+
|
|
57
|
+
// update secondary indexes
|
|
58
|
+
this.indexNewNode(node);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
putBefore(targetKey: T[K], item: T): void {
|
|
62
|
+
const target = this.map.get(targetKey);
|
|
63
|
+
if (target == null) {
|
|
64
|
+
throw new Error(`Key not found: ${String(targetKey)}`);
|
|
65
|
+
}
|
|
66
|
+
const key = item[this.pk];
|
|
67
|
+
if (targetKey === key) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.delete(key);
|
|
71
|
+
|
|
72
|
+
const node: LinkNode<T, K> = {
|
|
73
|
+
value: item,
|
|
74
|
+
cachedIndexValues: new Map(),
|
|
75
|
+
prev: target.prev,
|
|
76
|
+
next: target,
|
|
77
|
+
};
|
|
78
|
+
if (target.prev) {
|
|
79
|
+
target.prev.next = node;
|
|
80
|
+
} else {
|
|
81
|
+
this.head = node;
|
|
82
|
+
}
|
|
83
|
+
target.prev = node;
|
|
84
|
+
|
|
85
|
+
this.map.set(key, node);
|
|
86
|
+
this.mapSize++;
|
|
87
|
+
this.indexNewNode(node);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// TODO: need to make it in background, instead of making eng to call it manually
|
|
91
|
+
updateIndex(item: T) {
|
|
92
|
+
const node = this.map.get(item[this.pk]);
|
|
93
|
+
if (node == null) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
for (const [field, map] of this.indexes) {
|
|
97
|
+
const newVal = node.value[field];
|
|
98
|
+
const oldVal = node.cachedIndexValues.get(field);
|
|
99
|
+
if (oldVal !== newVal) {
|
|
100
|
+
const oldSet = this.indexes
|
|
101
|
+
.get(field)
|
|
102
|
+
// We know for sure, it's okay to index on that value
|
|
103
|
+
?.get(oldVal as T[keyof T]);
|
|
104
|
+
|
|
105
|
+
oldSet?.delete(node);
|
|
106
|
+
let set = this.indexes.get(field)?.get(newVal);
|
|
107
|
+
if (!set) {
|
|
108
|
+
set = new Set();
|
|
109
|
+
map.set(newVal, set);
|
|
110
|
+
}
|
|
111
|
+
set.add(node);
|
|
112
|
+
node.cachedIndexValues.set(field, newVal);
|
|
113
|
+
this.notifySubscribers(field, newVal);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private indexNewNode(node: LinkNode<T, K>) {
|
|
119
|
+
for (const [field, map] of this.indexes) {
|
|
120
|
+
const val = node.value[field];
|
|
121
|
+
node.cachedIndexValues.set(field, val);
|
|
122
|
+
let set = map.get(val);
|
|
123
|
+
if (!set) {
|
|
124
|
+
set = new Set();
|
|
125
|
+
map.set(val, set);
|
|
126
|
+
}
|
|
127
|
+
set.add(node);
|
|
128
|
+
this.notifySubscribers(field, val);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private notifySubscribers(field: keyof T, val: T[keyof T] | undefined) {
|
|
133
|
+
if (val === undefined) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const listeners = this.indexAddedSubscribers.get(field);
|
|
137
|
+
if (!listeners) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
for (const listener of listeners) {
|
|
141
|
+
listener(val);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
subscribeToIndexChange(
|
|
146
|
+
field: keyof T,
|
|
147
|
+
listener: IndexAddedSubscriber<T>,
|
|
148
|
+
): () => void {
|
|
149
|
+
if (!this.indexes.has(field)) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`Cannot subscribe to non-indexed field "${String(field)}"`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
let listeners = this.indexAddedSubscribers.get(field);
|
|
155
|
+
if (!listeners) {
|
|
156
|
+
listeners = new Set();
|
|
157
|
+
this.indexAddedSubscribers.set(field, listeners);
|
|
158
|
+
}
|
|
159
|
+
listeners.add(listener);
|
|
160
|
+
return () => {
|
|
161
|
+
listeners.delete(listener);
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
putAfter(targetKey: T[K], item: T): void {
|
|
166
|
+
const target = this.map.get(targetKey);
|
|
167
|
+
if (target == null) {
|
|
168
|
+
throw new Error(`Key not found: ${String(targetKey)}`);
|
|
169
|
+
}
|
|
170
|
+
const key = item[this.pk];
|
|
171
|
+
if (targetKey === key) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const node: LinkNode<T, K> = {
|
|
176
|
+
value: item,
|
|
177
|
+
cachedIndexValues: new Map(),
|
|
178
|
+
prev: target,
|
|
179
|
+
next: target.next,
|
|
180
|
+
};
|
|
181
|
+
if (target.next) {
|
|
182
|
+
target.next.prev = node;
|
|
183
|
+
} else {
|
|
184
|
+
this.tail = node;
|
|
185
|
+
}
|
|
186
|
+
target.next = node;
|
|
187
|
+
|
|
188
|
+
this.map.set(key, node);
|
|
189
|
+
this.mapSize++;
|
|
190
|
+
this.indexNewNode(node);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
delete(key: T[K]): boolean {
|
|
194
|
+
const node = this.map.get(key);
|
|
195
|
+
if (node == null) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// unlink from list
|
|
200
|
+
if (node.prev) {
|
|
201
|
+
node.prev.next = node.next;
|
|
202
|
+
} else {
|
|
203
|
+
this.head = node.next;
|
|
204
|
+
}
|
|
205
|
+
if (node.next) {
|
|
206
|
+
node.next.prev = node.prev;
|
|
207
|
+
} else {
|
|
208
|
+
this.tail = node.prev;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// remove from indexes
|
|
212
|
+
this.map.delete(key);
|
|
213
|
+
for (const [field, map] of this.indexes) {
|
|
214
|
+
const indexValue = node.value[field];
|
|
215
|
+
const set = map.get(indexValue);
|
|
216
|
+
if (set != null) {
|
|
217
|
+
set.delete(node);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
this.mapSize--;
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
at(n: number): Maybe<T> {
|
|
226
|
+
let current: Maybe<LinkNode<T, K>>;
|
|
227
|
+
if (n >= 0) {
|
|
228
|
+
current = this.head;
|
|
229
|
+
for (let i = 0; i < n && current != null; i++) {
|
|
230
|
+
current = current.next;
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
current = this.tail;
|
|
234
|
+
for (let i = n; i < -1 && current != null; i++) {
|
|
235
|
+
current = current.prev;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return current?.value;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
get(key: T[K]): Maybe<T> {
|
|
242
|
+
return this.map.get(key)?.value;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
getByIndex<K extends keyof T>(field: K, val: T[keyof T]): Array<T> {
|
|
246
|
+
const map = this.indexes.get(field);
|
|
247
|
+
if (!map) {
|
|
248
|
+
throw new Error(`No index defined for field "${String(field)}"`);
|
|
249
|
+
}
|
|
250
|
+
const set = map.get(val);
|
|
251
|
+
return set ? Array.from(set.values(), (node) => node.value) : [];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Get next item in insertion order
|
|
255
|
+
getNext(item: T): Maybe<T> {
|
|
256
|
+
return this.map.get(item[this.pk])?.next?.value;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Get previous item in insertion order
|
|
260
|
+
getPrev(item: T): Maybe<T> {
|
|
261
|
+
return this.map.get(item[this.pk])?.prev?.value;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
size(): number {
|
|
265
|
+
return this.mapSize;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
[Symbol.iterator](): Iterator<T> {
|
|
269
|
+
let curr = this.head;
|
|
270
|
+
return {
|
|
271
|
+
next(): IteratorResult<T> {
|
|
272
|
+
if (!curr) {
|
|
273
|
+
return { done: true, value: undefined! };
|
|
274
|
+
}
|
|
275
|
+
const value = curr.value;
|
|
276
|
+
curr = curr.next;
|
|
277
|
+
return { done: false, value };
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { LinkedMap } from "./LinkedMap";
|
|
3
|
+
|
|
4
|
+
type Entry = { key: string; value: number };
|
|
5
|
+
|
|
6
|
+
test("put adds items to the end", () => {
|
|
7
|
+
const map = new LinkedMap<Entry, "key">("key");
|
|
8
|
+
map.put({ key: "foo", value: 1 });
|
|
9
|
+
map.put({ key: "bar", value: 2 });
|
|
10
|
+
map.put({ key: "baz", value: 94 });
|
|
11
|
+
|
|
12
|
+
expect([...map].map((e) => [e.key, e.value])).toEqual([
|
|
13
|
+
["foo", 1],
|
|
14
|
+
["bar", 2],
|
|
15
|
+
["baz", 94],
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("putBefore and putAfter work correctly", () => {
|
|
20
|
+
const map = new LinkedMap<Entry, "key">("key");
|
|
21
|
+
map.put({ key: "foo", value: 1 });
|
|
22
|
+
map.put({ key: "bar", value: 2 });
|
|
23
|
+
map.put({ key: "baz", value: 94 });
|
|
24
|
+
|
|
25
|
+
map.putBefore("bar", { key: "hey", value: 13 }); // insert before "bar"
|
|
26
|
+
|
|
27
|
+
expect([...map].map((e) => [e.key, e.value])).toEqual([
|
|
28
|
+
["foo", 1],
|
|
29
|
+
["hey", 13],
|
|
30
|
+
["bar", 2],
|
|
31
|
+
["baz", 94],
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
map.putAfter("baz", { key: "zap", value: 42 }); // insert after "baz"
|
|
35
|
+
|
|
36
|
+
expect([...map].map((e) => [e.key, e.value])).toEqual([
|
|
37
|
+
["foo", 1],
|
|
38
|
+
["hey", 13],
|
|
39
|
+
["bar", 2],
|
|
40
|
+
["baz", 94],
|
|
41
|
+
["zap", 42],
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("map has access by key", () => {
|
|
46
|
+
const map = new LinkedMap<Entry, "key">("key");
|
|
47
|
+
map.put({ key: "foo", value: 1 });
|
|
48
|
+
map.put({ key: "bar", value: 2 });
|
|
49
|
+
map.put({ key: "hey", value: 13 });
|
|
50
|
+
map.put({ key: "baz", value: 94 });
|
|
51
|
+
|
|
52
|
+
expect(map.get("baz")?.value).toEqual(94);
|
|
53
|
+
expect(map.get("bar")?.value).toEqual(2);
|
|
54
|
+
expect(map.get("hey")?.value).toEqual(13);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("map has access by index", () => {
|
|
58
|
+
const map = new LinkedMap<Entry, "key">("key");
|
|
59
|
+
map.put({ key: "foo", value: 1 });
|
|
60
|
+
map.put({ key: "bar", value: 2 });
|
|
61
|
+
map.put({ key: "hey", value: 13 });
|
|
62
|
+
map.put({ key: "baz", value: 94 });
|
|
63
|
+
|
|
64
|
+
expect(map.at(-1)?.value).toEqual(94);
|
|
65
|
+
expect(map.at(2)?.value).toEqual(13);
|
|
66
|
+
expect(map.at(0)?.value).toEqual(1);
|
|
67
|
+
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Maybe } from "../maybe/Maybe";
|
|
2
|
+
|
|
3
|
+
type LinkNode<T, K> = {
|
|
4
|
+
value: T;
|
|
5
|
+
cachedIndexValues: Map<keyof T, T[keyof T]>;
|
|
6
|
+
prev: Maybe<LinkNode<T, K>>;
|
|
7
|
+
next: Maybe<LinkNode<T, K>>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export class LinkedMap<T extends object, K extends keyof T>
|
|
11
|
+
implements Iterable<T>
|
|
12
|
+
{
|
|
13
|
+
private head: Maybe<LinkNode<T, K>>;
|
|
14
|
+
private tail: Maybe<LinkNode<T, K>>;
|
|
15
|
+
private mapSize = 0;
|
|
16
|
+
private pk: K;
|
|
17
|
+
private map = new Map<T[K], LinkNode<T, K>>();
|
|
18
|
+
|
|
19
|
+
constructor(pk: K) {
|
|
20
|
+
this.pk = pk;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
putFirst(item: T): void {
|
|
24
|
+
const key = item[this.pk];
|
|
25
|
+
// Delete previous, if any
|
|
26
|
+
this.delete(key);
|
|
27
|
+
|
|
28
|
+
const node: LinkNode<T, K> = {
|
|
29
|
+
value: item,
|
|
30
|
+
cachedIndexValues: new Map(),
|
|
31
|
+
prev: this.tail,
|
|
32
|
+
next: null,
|
|
33
|
+
};
|
|
34
|
+
if (!this.tail) {
|
|
35
|
+
this.tail = node;
|
|
36
|
+
}
|
|
37
|
+
if (this.head) {
|
|
38
|
+
this.head.prev = node;
|
|
39
|
+
}
|
|
40
|
+
this.head = node;
|
|
41
|
+
this.map.set(key, node);
|
|
42
|
+
this.mapSize++;
|
|
43
|
+
}
|
|
44
|
+
// Puts item to the end
|
|
45
|
+
// Removes previous item, if PK is matched
|
|
46
|
+
put(item: T): void {
|
|
47
|
+
const key = item[this.pk];
|
|
48
|
+
// Delete previous, if any
|
|
49
|
+
this.delete(key);
|
|
50
|
+
|
|
51
|
+
const node: LinkNode<T, K> = {
|
|
52
|
+
value: item,
|
|
53
|
+
cachedIndexValues: new Map(),
|
|
54
|
+
prev: this.tail,
|
|
55
|
+
next: null,
|
|
56
|
+
};
|
|
57
|
+
if (!this.head) {
|
|
58
|
+
this.head = node;
|
|
59
|
+
}
|
|
60
|
+
if (this.tail) {
|
|
61
|
+
this.tail.next = node;
|
|
62
|
+
}
|
|
63
|
+
this.tail = node;
|
|
64
|
+
this.map.set(key, node);
|
|
65
|
+
this.mapSize++;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
putBefore(targetKey: T[K], item: T): void {
|
|
69
|
+
const target = this.map.get(targetKey);
|
|
70
|
+
if (target == null) {
|
|
71
|
+
throw new Error(`Key not found: ${String(targetKey)}`);
|
|
72
|
+
}
|
|
73
|
+
const key = item[this.pk];
|
|
74
|
+
if (targetKey === key) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.delete(key);
|
|
78
|
+
|
|
79
|
+
const node: LinkNode<T, K> = {
|
|
80
|
+
value: item,
|
|
81
|
+
cachedIndexValues: new Map(),
|
|
82
|
+
prev: target.prev,
|
|
83
|
+
next: target,
|
|
84
|
+
};
|
|
85
|
+
if (target.prev) {
|
|
86
|
+
target.prev.next = node;
|
|
87
|
+
} else {
|
|
88
|
+
this.head = node;
|
|
89
|
+
}
|
|
90
|
+
target.prev = node;
|
|
91
|
+
|
|
92
|
+
this.map.set(key, node);
|
|
93
|
+
this.mapSize++;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
putAfter(targetKey: T[K], item: T): void {
|
|
97
|
+
const target = this.map.get(targetKey);
|
|
98
|
+
if (target == null) {
|
|
99
|
+
throw new Error(`Key not found: ${String(targetKey)}`);
|
|
100
|
+
}
|
|
101
|
+
const key = item[this.pk];
|
|
102
|
+
if (targetKey === key) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const node: LinkNode<T, K> = {
|
|
107
|
+
value: item,
|
|
108
|
+
cachedIndexValues: new Map(),
|
|
109
|
+
prev: target,
|
|
110
|
+
next: target.next,
|
|
111
|
+
};
|
|
112
|
+
if (target.next) {
|
|
113
|
+
target.next.prev = node;
|
|
114
|
+
} else {
|
|
115
|
+
this.tail = node;
|
|
116
|
+
}
|
|
117
|
+
target.next = node;
|
|
118
|
+
|
|
119
|
+
this.map.set(key, node);
|
|
120
|
+
this.mapSize++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
delete(key: T[K]): boolean {
|
|
124
|
+
const node = this.map.get(key);
|
|
125
|
+
if (node == null) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// unlink from list
|
|
130
|
+
if (node.prev) {
|
|
131
|
+
node.prev.next = node.next;
|
|
132
|
+
} else {
|
|
133
|
+
this.head = node.next;
|
|
134
|
+
}
|
|
135
|
+
if (node.next) {
|
|
136
|
+
node.next.prev = node.prev;
|
|
137
|
+
} else {
|
|
138
|
+
this.tail = node.prev;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// remove from indexes
|
|
142
|
+
this.map.delete(key);
|
|
143
|
+
this.mapSize--;
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
at(n: number): Maybe<T> {
|
|
148
|
+
let current: Maybe<LinkNode<T, K>>;
|
|
149
|
+
if (n >= 0) {
|
|
150
|
+
current = this.head;
|
|
151
|
+
for (let i = 0; i < n && current != null; i++) {
|
|
152
|
+
current = current.next;
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
current = this.tail;
|
|
156
|
+
for (let i = n; i < -1 && current != null; i++) {
|
|
157
|
+
current = current.prev;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return current?.value;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
get(key: T[K]): Maybe<T> {
|
|
164
|
+
return this.map.get(key)?.value;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
has(item: T): boolean {
|
|
168
|
+
return this.map.get(item[this.pk])?.value === item;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Get next item in insertion order
|
|
172
|
+
getNext(item: T): Maybe<T> {
|
|
173
|
+
return this.map.get(item[this.pk])?.next?.value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Get previous item in insertion order
|
|
177
|
+
getPrev(item: T): Maybe<T> {
|
|
178
|
+
return this.map.get(item[this.pk])?.prev?.value;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
size(): number {
|
|
182
|
+
return this.mapSize;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
[Symbol.iterator](): Iterator<T> {
|
|
186
|
+
let curr = this.head;
|
|
187
|
+
return {
|
|
188
|
+
next(): IteratorResult<T> {
|
|
189
|
+
if (!curr) {
|
|
190
|
+
return { done: true, value: undefined! };
|
|
191
|
+
}
|
|
192
|
+
const value = curr.value;
|
|
193
|
+
curr = curr.next;
|
|
194
|
+
return { done: false, value };
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|