aberdeen 1.0.5 → 1.0.6

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.
@@ -1,188 +1,197 @@
1
- type Item<T> = T & {[idx: symbol]: Item<T>}
1
+ type Item<T> = T & { [idx: symbol]: Item<T> };
2
2
 
3
3
  /**
4
4
  * A set-like collection of objects that can do iteration sorted by a specified index property.
5
5
  * It also allows retrieving an object by its index property, and quickly getting the object
6
6
  * that comes immediately after a given object.
7
- *
7
+ *
8
8
  * It's implemented as a skiplist, maintaining all meta-data as part of the objects that it
9
9
  * is tracking, for performance.
10
10
  */
11
11
  export class ReverseSortedSet<T extends object> {
12
- // A fake item, that is not actually T, but *does* contain symbols pointing at the first item for each level.
13
- private tail: Item<T>
14
- // As every SkipList instance has its own symbols, an object can be included in more than one SkipList.
15
- private symbols: symbol[]
16
-
17
- /**
18
- * Create an empty SortedSet.
19
- *
20
- * @param keyProp The name of the property that should be used as the index of this collection. Comparison
21
- * using `<` will be done on this property, so it should probably be a number or a string (or something that
22
- * has a useful toString-conversion).
23
- */
24
- constructor(private keyProp: keyof T) {
25
- this.tail = {} as Item<T>
26
- this.symbols = [Symbol(0)]
27
- }
28
-
29
- /**
30
- * Add an object to the `SortedSet`.
31
- *
32
- * @param item The object to be added to the set. One or more properties with
33
- * `Symbol` keys will be added to it, for `SortedSet` internals.
34
- * @returns `true` if the item was added, or `false` if it was *not* added
35
- * because the item already was part of this set.
36
- *
37
- * Note that though an item object may only be part of a particular `SortedSet`
38
- * once, index properties may be duplicate and an item object may be part of
39
- * more than one `SortedSet`.
40
- *
41
- * **IMPORTANT:** After adding an object, do not modify its index property,
42
- * as this will lead to undefined (broken) behavior on the entire set.
43
- *
44
- * Time complexity: O(log n)
45
- */
46
- add(item: T): boolean {
47
- if (this.symbols[0] in item) return false // Already included
48
-
49
- // Start at level 1. Keep upping the level by 1 with 1/8 chance.
50
- const level = 1 + (Math.clz32(Math.random() * 0xFFFFFFFF) >> 2)
51
- for(let l = this.symbols.length; l < level; l++) this.symbols.push(Symbol(l))
52
-
53
- const keyProp = this.keyProp
54
- const key = item[keyProp]
55
-
56
- let prev: Item<T> | undefined
57
- let current: Item<T> = this.tail;
58
- for (let l = this.symbols.length-1; l>=0; l--) {
59
- const symbol = this.symbols[l]
60
- while ((prev = current[symbol] as Item<T>) && prev[keyProp] > key) current = prev;
61
- if (l < level) {
62
- (item as any)[symbol] = current[symbol];
63
- (current as any)[symbol] = item;
64
- }
65
- }
66
-
67
- return true // Added
68
- }
69
-
70
- /**
71
- * @param item An object to test for inclusion in the set.
72
- * @returns true if this object item is already part of the set.
73
- */
74
- has(item: T): boolean {
75
- return this.symbols[0] in item;
76
- }
77
-
78
- /**
79
- * Remove and return the last item.
80
- * @returns what was previously the last item in the sorted set, or `undefined` if the set was empty.
81
- */
82
- fetchLast(): T | undefined {
83
- let item = this.tail[this.symbols[0]];
84
- if (item) {
85
- this.remove(item);
86
- return item;
87
- }
88
- }
89
-
90
- /**
91
- * @returns whether the set is empty (`true`) or has at least one item (`false`).
92
- */
93
- isEmpty(): boolean {
94
- return this.tail[this.symbols[0]] === undefined;
95
- }
96
-
97
- /**
98
- * Retrieve an item object based on its index property value.
99
- *
100
- * @param indexValue The index property value to search for.
101
- * @returns `undefined` if the index property value does not exist in the `SortedSet` or
102
- * otherwise the *first* item object that has this index value (meaning any further
103
- * instances can be iterated using `next()`).
104
- *
105
- * Time complexity: O(log n)
106
- */
107
- get(indexValue: string|number): T | undefined {
108
- const keyProp = this.keyProp
109
- let current = this.tail;
110
- let prev
111
- for (let l = this.symbols.length-1; l>=0; l--) {
112
- const symbol = this.symbols[l]
113
- while ((prev = current[symbol] as Item<T>) && prev[keyProp] > indexValue) current = prev;
114
- }
115
- return current[this.symbols[0]]?.[keyProp] === indexValue ? current[this.symbols[0]] : undefined;
116
- }
117
-
118
- /**
119
- * The iterator will go through the items in reverse index-order.
120
- */
121
- *[Symbol.iterator](): IterableIterator<T> {
122
- let symbol = this.symbols[0]
123
- let node: Item<T> | undefined = this.tail[symbol] as Item<T>;
124
- while (node) {
125
- yield node;
126
- node = node[symbol] as Item<T> | undefined;
127
- }
128
- }
129
-
130
- /**
131
- * Given an item object, returns the one that comes right before in the set.
132
- * @param item The object to start from.
133
- * @returns The next object, or `undefined` if there is none.
134
- *
135
- * Time complexity: O(1)
136
- */
137
- prev(item: T): T | undefined {
138
- return (item as Item<T>)[this.symbols[0]]
139
- }
140
-
141
- /**
142
- * Remove an item object from the set, deleting all meta-data keys that
143
- * were created on `add()`.
144
- * @param item The object to be removed.
145
- * @returns `true` on success or `false` when the item was not part of the set.
146
- *
147
- * Time complexity: O(log n)
148
- */
149
- remove(item: T): boolean {
150
- if (!(this.symbols[0] in item)) return false;
151
- const keyProp = this.keyProp
152
- const prop = item[keyProp];
153
-
154
- let prev: Item<T> | undefined
155
- let current: Item<T> = this.tail;
156
-
157
- for (let l = this.symbols.length - 1; l >= 0; l--) {
158
- const symbol = this.symbols[l];
159
- while ((prev = current[symbol] as Item<T>) && prev[keyProp] >= prop && prev !== item) current = prev
160
- if (prev === item) {
161
- (current as any)[symbol] = prev[symbol]
162
- delete prev[symbol]
163
- }
164
- }
165
-
166
- return prev === item
167
- }
168
-
169
- /**
170
- * Remove all items for the set.
171
- *
172
- * Time complexity: O(n)
173
- */
174
- clear(): void {
175
- const symbol = this.symbols[0];
176
- let current: Item<T> | undefined = this.tail;
177
- while (current) {
178
- const prev = current[symbol] as Item<T> | undefined
179
- for (const symbol of this.symbols) {
180
- if (!(symbol in current)) break
181
- delete current[symbol];
182
- }
183
- current = prev
184
- }
185
- this.tail = {} as Item<T>;
186
- }
12
+ // A fake item, that is not actually T, but *does* contain symbols pointing at the first item for each level.
13
+ private tail: Item<T>;
14
+ // As every SkipList instance has its own symbols, an object can be included in more than one SkipList.
15
+ private symbols: symbol[];
16
+
17
+ /**
18
+ * Create an empty SortedSet.
19
+ *
20
+ * @param keyProp The name of the property that should be used as the index of this collection. Comparison
21
+ * using `<` will be done on this property, so it should probably be a number or a string (or something that
22
+ * has a useful toString-conversion).
23
+ */
24
+ constructor(private keyProp: keyof T) {
25
+ this.tail = {} as Item<T>;
26
+ this.symbols = [Symbol(0)];
27
+ }
28
+
29
+ /**
30
+ * Add an object to the `SortedSet`.
31
+ *
32
+ * @param item The object to be added to the set. One or more properties with
33
+ * `Symbol` keys will be added to it, for `SortedSet` internals.
34
+ * @returns `true` if the item was added, or `false` if it was *not* added
35
+ * because the item already was part of this set.
36
+ *
37
+ * Note that though an item object may only be part of a particular `SortedSet`
38
+ * once, index properties may be duplicate and an item object may be part of
39
+ * more than one `SortedSet`.
40
+ *
41
+ * **IMPORTANT:** After adding an object, do not modify its index property,
42
+ * as this will lead to undefined (broken) behavior on the entire set.
43
+ *
44
+ * Time complexity: O(log n)
45
+ */
46
+ add(item: T): boolean {
47
+ if (this.symbols[0] in item) return false; // Already included
48
+
49
+ // Start at level 1. Keep upping the level by 1 with 1/8 chance.
50
+ const level = 1 + (Math.clz32(Math.random() * 0xffffffff) >> 2);
51
+ for (let l = this.symbols.length; l < level; l++)
52
+ this.symbols.push(Symbol(l));
53
+
54
+ const keyProp = this.keyProp;
55
+ const key = item[keyProp];
56
+
57
+ let prev: Item<T> | undefined;
58
+ let current: Item<T> = this.tail;
59
+ for (let l = this.symbols.length - 1; l >= 0; l--) {
60
+ const symbol = this.symbols[l];
61
+ while ((prev = current[symbol] as Item<T>) && prev[keyProp] > key)
62
+ current = prev;
63
+ if (l < level) {
64
+ (item as any)[symbol] = current[symbol];
65
+ (current as any)[symbol] = item;
66
+ }
67
+ }
68
+
69
+ return true; // Added
70
+ }
71
+
72
+ /**
73
+ * @param item An object to test for inclusion in the set.
74
+ * @returns true if this object item is already part of the set.
75
+ */
76
+ has(item: T): boolean {
77
+ return this.symbols[0] in item;
78
+ }
79
+
80
+ /**
81
+ * Remove and return the last item.
82
+ * @returns what was previously the last item in the sorted set, or `undefined` if the set was empty.
83
+ */
84
+ fetchLast(): T | undefined {
85
+ const item = this.tail[this.symbols[0]];
86
+ if (item) {
87
+ this.remove(item);
88
+ return item;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * @returns whether the set is empty (`true`) or has at least one item (`false`).
94
+ */
95
+ isEmpty(): boolean {
96
+ return this.tail[this.symbols[0]] === undefined;
97
+ }
98
+
99
+ /**
100
+ * Retrieve an item object based on its index property value.
101
+ *
102
+ * @param indexValue The index property value to search for.
103
+ * @returns `undefined` if the index property value does not exist in the `SortedSet` or
104
+ * otherwise the *first* item object that has this index value (meaning any further
105
+ * instances can be iterated using `next()`).
106
+ *
107
+ * Time complexity: O(log n)
108
+ */
109
+ get(indexValue: string | number): T | undefined {
110
+ const keyProp = this.keyProp;
111
+ let current = this.tail;
112
+ let prev: Item<T> | undefined;
113
+ for (let l = this.symbols.length - 1; l >= 0; l--) {
114
+ const symbol = this.symbols[l];
115
+ while ((prev = current[symbol] as Item<T>) && prev[keyProp] > indexValue)
116
+ current = prev;
117
+ }
118
+ return current[this.symbols[0]]?.[keyProp] === indexValue
119
+ ? current[this.symbols[0]]
120
+ : undefined;
121
+ }
122
+
123
+ /**
124
+ * The iterator will go through the items in reverse index-order.
125
+ */
126
+ *[Symbol.iterator](): IterableIterator<T> {
127
+ const symbol = this.symbols[0];
128
+ let node: Item<T> | undefined = this.tail[symbol] as Item<T>;
129
+ while (node) {
130
+ yield node;
131
+ node = node[symbol] as Item<T> | undefined;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Given an item object, returns the one that comes right before in the set.
137
+ * @param item The object to start from.
138
+ * @returns The next object, or `undefined` if there is none.
139
+ *
140
+ * Time complexity: O(1)
141
+ */
142
+ prev(item: T): T | undefined {
143
+ return (item as Item<T>)[this.symbols[0]];
144
+ }
145
+
146
+ /**
147
+ * Remove an item object from the set, deleting all meta-data keys that
148
+ * were created on `add()`.
149
+ * @param item The object to be removed.
150
+ * @returns `true` on success or `false` when the item was not part of the set.
151
+ *
152
+ * Time complexity: O(log n)
153
+ */
154
+ remove(item: T): boolean {
155
+ if (!(this.symbols[0] in item)) return false;
156
+ const keyProp = this.keyProp;
157
+ const prop = item[keyProp];
158
+
159
+ let prev: Item<T> | undefined;
160
+ let current: Item<T> = this.tail;
161
+
162
+ for (let l = this.symbols.length - 1; l >= 0; l--) {
163
+ const symbol = this.symbols[l];
164
+ while (
165
+ (prev = current[symbol] as Item<T>) &&
166
+ prev[keyProp] >= prop &&
167
+ prev !== item
168
+ )
169
+ current = prev;
170
+ if (prev === item) {
171
+ (current as any)[symbol] = prev[symbol];
172
+ delete prev[symbol];
173
+ }
174
+ }
175
+
176
+ return prev === item;
177
+ }
178
+
179
+ /**
180
+ * Remove all items for the set.
181
+ *
182
+ * Time complexity: O(n)
183
+ */
184
+ clear(): void {
185
+ const symbol = this.symbols[0];
186
+ let current: Item<T> | undefined = this.tail;
187
+ while (current) {
188
+ const prev = current[symbol] as Item<T> | undefined;
189
+ for (const symbol of this.symbols) {
190
+ if (!(symbol in current)) break;
191
+ delete current[symbol];
192
+ }
193
+ current = prev;
194
+ }
195
+ this.tail = {} as Item<T>;
196
+ }
187
197
  }
188
-
package/src/prediction.ts CHANGED
@@ -1,70 +1,85 @@
1
- import {withEmitHandler, defaultEmitHandler} from './aberdeen.js'
2
- import type { DatumType, TargetType } from './aberdeen.js';
1
+ import { defaultEmitHandler, withEmitHandler } from "./aberdeen.js";
2
+ import type { DatumType, TargetType } from "./aberdeen.js";
3
3
 
4
- /**
4
+ /**
5
5
  * Represents a set of changes that can be applied to proxied objects.
6
6
  * This is an opaque type - its internal structure is not part of the public API.
7
7
  * @private
8
8
  */
9
9
  export type Patch = Map<TargetType, Map<any, [DatumType, DatumType]>>;
10
10
 
11
-
12
11
  function recordPatch(func: () => void): Patch {
13
- const recordingPatch = new Map()
14
- withEmitHandler(function(target, index, newData, oldData) {
15
- addToPatch(recordingPatch, target, index, newData, oldData)
16
- }, func)
17
- return recordingPatch
12
+ const recordingPatch = new Map();
13
+ withEmitHandler((target, index, newData, oldData) => {
14
+ addToPatch(recordingPatch, target, index, newData, oldData);
15
+ }, func);
16
+ return recordingPatch;
18
17
  }
19
18
 
20
- function addToPatch(patch: Patch, collection: TargetType, index: any, newData: DatumType, oldData: DatumType) {
21
- let collectionMap = patch.get(collection)
19
+ function addToPatch(
20
+ patch: Patch,
21
+ collection: TargetType,
22
+ index: any,
23
+ newData: DatumType,
24
+ oldData: DatumType,
25
+ ) {
26
+ let collectionMap = patch.get(collection);
22
27
  if (!collectionMap) {
23
- collectionMap = new Map()
24
- patch.set(collection, collectionMap)
28
+ collectionMap = new Map();
29
+ patch.set(collection, collectionMap);
25
30
  }
26
- let prev = collectionMap.get(index)
27
- if (prev) oldData = prev[1]
28
- if (newData === oldData) collectionMap.delete(index)
29
- else collectionMap.set(index, [newData, oldData])
31
+ const prev = collectionMap.get(index);
32
+ const oldData0 = prev ? prev[1] : oldData;
33
+ if (newData === oldData0) collectionMap.delete(index);
34
+ else collectionMap.set(index, [newData, oldData0]);
30
35
  }
31
36
 
32
37
  function emitPatch(patch: Patch) {
33
- for(let [collection, collectionMap] of patch) {
34
- for(let [index, [newData, oldData]] of collectionMap) {
38
+ for (const [collection, collectionMap] of patch) {
39
+ for (const [index, [newData, oldData]] of collectionMap) {
35
40
  defaultEmitHandler(collection, index, newData, oldData);
36
41
  }
37
42
  }
38
43
  }
39
44
 
40
- function mergePatch(target: Patch, source: Patch, reverse: boolean = false) {
41
- for(let [collection, collectionMap] of source) {
42
- for(let [index, [newData, oldData]] of collectionMap) {
43
- addToPatch(target, collection, index, reverse ? oldData : newData, reverse ? newData : oldData)
45
+ function mergePatch(target: Patch, source: Patch, reverse = false) {
46
+ for (const [collection, collectionMap] of source) {
47
+ for (const [index, [newData, oldData]] of collectionMap) {
48
+ addToPatch(
49
+ target,
50
+ collection,
51
+ index,
52
+ reverse ? oldData : newData,
53
+ reverse ? newData : oldData,
54
+ );
44
55
  }
45
56
  }
46
57
  }
47
58
 
48
- function silentlyApplyPatch(patch: Patch, force: boolean = false): boolean {
49
- for(let [collection, collectionMap] of patch) {
50
- for(let [index, [newData, oldData]] of collectionMap) {
51
- let actualData = (collection as any)[index]
59
+ function silentlyApplyPatch(patch: Patch, force = false): boolean {
60
+ for (const [collection, collectionMap] of patch) {
61
+ for (const [index, [newData, oldData]] of collectionMap) {
62
+ const actualData = (collection as any)[index];
52
63
  if (actualData !== oldData) {
53
- if (force) setTimeout(() => { throw new Error(`Applying invalid patch: data ${actualData} is unequal to expected old data ${oldData} for index ${index}`)}, 0)
54
- else return false
64
+ if (force)
65
+ setTimeout(() => {
66
+ throw new Error(
67
+ `Applying invalid patch: data ${actualData} is unequal to expected old data ${oldData} for index ${index}`,
68
+ );
69
+ }, 0);
70
+ else return false;
55
71
  }
56
72
  }
57
73
  }
58
- for(let [collection, collectionMap] of patch) {
59
- for(let [index, [newData, oldData]] of collectionMap) {
60
- (collection as any)[index] = newData
74
+ for (const [collection, collectionMap] of patch) {
75
+ for (const [index, [newData, oldData]] of collectionMap) {
76
+ (collection as any)[index] = newData;
61
77
  }
62
78
  }
63
- return true
79
+ return true;
64
80
  }
65
81
 
66
-
67
- const appliedPredictions: Array<Patch> = []
82
+ const appliedPredictions: Array<Patch> = [];
68
83
 
69
84
  /**
70
85
  * Run the provided function, while treating all changes to Observables as predictions,
@@ -76,19 +91,19 @@ const appliedPredictions: Array<Patch> = []
76
91
  * @returns A `Patch` object. Don't modify it. This is only meant to be passed to `applyCanon`.
77
92
  */
78
93
  export function applyPrediction(predictFunc: () => void): Patch {
79
- let patch = recordPatch(predictFunc)
80
- appliedPredictions.push(patch)
81
- emitPatch(patch)
82
- return patch
94
+ const patch = recordPatch(predictFunc);
95
+ appliedPredictions.push(patch);
96
+ emitPatch(patch);
97
+ return patch;
83
98
  }
84
99
 
85
100
  /**
86
101
  * Temporarily revert all outstanding predictions, optionally run the provided function
87
102
  * (which will generally make authoritative changes to the data based on a server response),
88
- * and then attempt to reapply the predictions on top of the new canonical state, dropping
103
+ * and then attempt to reapply the predictions on top of the new canonical state, dropping
89
104
  * any predictions that can no longer be applied cleanly (the data has been modified) or
90
105
  * that were specified in `dropPredictions`.
91
- *
106
+ *
92
107
  * All of this is done such that redraws are only triggered if the overall effect is an
93
108
  * actual change to an `Observable`.
94
109
  * @param canonFunc The function to run without any predictions applied. This will typically
@@ -97,26 +112,29 @@ export function applyPrediction(predictFunc: () => void): Patch {
97
112
  * to undo. Typically, when a server response for a certain request is being handled,
98
113
  * you'd want to drop the prediction that was done for that request.
99
114
  */
100
- export function applyCanon(canonFunc?: (() => void), dropPredictions: Array<Patch> = []) {
101
-
102
- let resultPatch = new Map()
103
- for(let prediction of appliedPredictions) mergePatch(resultPatch, prediction, true)
104
- silentlyApplyPatch(resultPatch, true)
115
+ export function applyCanon(
116
+ canonFunc?: () => void,
117
+ dropPredictions: Array<Patch> = [],
118
+ ) {
119
+ const resultPatch = new Map();
120
+ for (const prediction of appliedPredictions)
121
+ mergePatch(resultPatch, prediction, true);
122
+ silentlyApplyPatch(resultPatch, true);
105
123
 
106
- for(let prediction of dropPredictions) {
107
- let pos = appliedPredictions.indexOf(prediction)
108
- if (pos >= 0) appliedPredictions.splice(pos, 1)
124
+ for (const prediction of dropPredictions) {
125
+ const pos = appliedPredictions.indexOf(prediction);
126
+ if (pos >= 0) appliedPredictions.splice(pos, 1);
109
127
  }
110
- if (canonFunc) mergePatch(resultPatch, recordPatch(canonFunc))
128
+ if (canonFunc) mergePatch(resultPatch, recordPatch(canonFunc));
111
129
 
112
- for(let idx=0; idx<appliedPredictions.length; idx++) {
130
+ for (let idx = 0; idx < appliedPredictions.length; idx++) {
113
131
  if (silentlyApplyPatch(appliedPredictions[idx])) {
114
- mergePatch(resultPatch, appliedPredictions[idx])
132
+ mergePatch(resultPatch, appliedPredictions[idx]);
115
133
  } else {
116
- appliedPredictions.splice(idx, 1)
117
- idx--
134
+ appliedPredictions.splice(idx, 1);
135
+ idx--;
118
136
  }
119
137
  }
120
138
 
121
- emitPatch(resultPatch)
139
+ emitPatch(resultPatch);
122
140
  }