articulated 0.1.0 → 0.3.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/README.md +53 -26
- package/build/commonjs/id.d.ts +3 -6
- package/build/commonjs/id.js.map +1 -1
- package/build/commonjs/id_list.d.ts +152 -52
- package/build/commonjs/id_list.js +845 -186
- package/build/commonjs/id_list.js.map +1 -1
- package/build/commonjs/index.d.ts +1 -1
- package/build/commonjs/index.js +4 -1
- package/build/commonjs/index.js.map +1 -1
- package/build/commonjs/internal/leaf_map.d.ts +25 -0
- package/build/commonjs/internal/leaf_map.js +56 -0
- package/build/commonjs/internal/leaf_map.js.map +1 -0
- package/build/commonjs/internal/seq_map.d.ts +20 -0
- package/build/commonjs/internal/seq_map.js +42 -0
- package/build/commonjs/internal/seq_map.js.map +1 -0
- package/build/commonjs/saved_id_list.d.ts +5 -5
- package/build/esm/id.d.ts +3 -6
- package/build/esm/id.js.map +1 -1
- package/build/esm/id_list.d.ts +152 -52
- package/build/esm/id_list.js +842 -185
- package/build/esm/id_list.js.map +1 -1
- package/build/esm/index.d.ts +1 -1
- package/build/esm/index.js +1 -1
- package/build/esm/index.js.map +1 -1
- package/build/esm/internal/leaf_map.d.ts +25 -0
- package/build/esm/internal/leaf_map.js +49 -0
- package/build/esm/internal/leaf_map.js.map +1 -0
- package/build/esm/internal/seq_map.d.ts +20 -0
- package/build/esm/internal/seq_map.js +34 -0
- package/build/esm/internal/seq_map.js.map +1 -0
- package/build/esm/saved_id_list.d.ts +5 -5
- package/package.json +13 -2
- package/src/id.ts +3 -6
- package/src/id_list.ts +1066 -191
- package/src/index.ts +1 -1
- package/src/internal/leaf_map.ts +59 -0
- package/src/internal/seq_map.ts +50 -0
- package/src/saved_id_list.ts +5 -5
package/README.md
CHANGED
|
@@ -23,22 +23,23 @@ yarn add articulated
|
|
|
23
23
|
```typescript
|
|
24
24
|
import { IdList } from "articulated";
|
|
25
25
|
|
|
26
|
-
// Create an empty list
|
|
27
|
-
|
|
26
|
+
// Create an empty list.
|
|
27
|
+
let list = IdList.new();
|
|
28
28
|
|
|
29
|
-
// Insert a new
|
|
30
|
-
|
|
29
|
+
// Insert a new ElementId at the beginning.
|
|
30
|
+
// Note: Persistent (immutable) data structure! Mutators return a new IdList.
|
|
31
|
+
list = list.insertAfter(null, { bunchId: "user1", counter: 0 });
|
|
31
32
|
|
|
32
|
-
// Insert another
|
|
33
|
-
list.insertAfter(
|
|
33
|
+
// Insert another ElementId after the first.
|
|
34
|
+
list = list.insertAfter(
|
|
34
35
|
{ bunchId: "user1", counter: 0 },
|
|
35
36
|
{ bunchId: "user1", counter: 1 }
|
|
36
37
|
);
|
|
37
38
|
|
|
38
|
-
// Delete an
|
|
39
|
-
list.delete({ bunchId: "user1", counter: 0 });
|
|
39
|
+
// Delete an ElementId (marks as deleted but keeps as known).
|
|
40
|
+
list = list.delete({ bunchId: "user1", counter: 0 });
|
|
40
41
|
|
|
41
|
-
// Check if
|
|
42
|
+
// Check if ElementIds are present/known.
|
|
42
43
|
console.log(list.has({ bunchId: "user1", counter: 0 })); // false (deleted)
|
|
43
44
|
console.log(list.isKnown({ bunchId: "user1", counter: 0 })); // true (known but deleted)
|
|
44
45
|
```
|
|
@@ -50,9 +51,9 @@ console.log(list.isKnown({ bunchId: "user1", counter: 0 })); // true (known but
|
|
|
50
51
|
An `ElementId` is a globally unique identifier for a list element, composed of:
|
|
51
52
|
|
|
52
53
|
- `bunchId`: A string UUID or similar globally unique ID
|
|
53
|
-
- `counter`: A numeric value to distinguish
|
|
54
|
+
- `counter`: A numeric value to distinguish ElementIds in the same bunch
|
|
54
55
|
|
|
55
|
-
For optimal compression, when inserting multiple
|
|
56
|
+
For optimal compression, when inserting multiple ElementIds in a left-to-right sequence, use the same `bunchId` with sequential `counter` values.
|
|
56
57
|
|
|
57
58
|
```typescript
|
|
58
59
|
// Example of IDs that will compress well
|
|
@@ -63,39 +64,38 @@ const id3 = { bunchId: "abc123", counter: 2 };
|
|
|
63
64
|
|
|
64
65
|
### IdList Operations
|
|
65
66
|
|
|
67
|
+
To enable easy and efficient rollbacks, such as in a [server reconciliation](https://mattweidner.com/2024/06/04/server-architectures.html#1-server-reconciliation) architecture, IdList is a persistent (immutable) data structure. Mutating methods return a new IdList, sharing memory with the old IdList where possible.
|
|
68
|
+
|
|
66
69
|
#### Basic Operations
|
|
67
70
|
|
|
68
|
-
- `insertAfter(before, newId)`: Insert after a specific
|
|
69
|
-
- `insertBefore(after, newId)`: Insert before a specific
|
|
70
|
-
- `delete(id)`: Mark an
|
|
71
|
-
- `undelete(id)`: Restore a deleted
|
|
71
|
+
- `insertAfter(before, newId): IdList`: Insert after a specific ElementId
|
|
72
|
+
- `insertBefore(after, newId): IdList`: Insert before a specific ElementId
|
|
73
|
+
- `delete(id): IdList`: Mark an ElementId as deleted (remains known)
|
|
74
|
+
- `undelete(id): IdList`: Restore a deleted ElementId
|
|
72
75
|
|
|
73
|
-
####
|
|
76
|
+
#### Basic Accessors
|
|
74
77
|
|
|
75
|
-
- `
|
|
76
|
-
- `
|
|
77
|
-
- `indexOf(id, bias)`: Get the index of an element with optional bias for deleted elements
|
|
78
|
-
- `clone()`: Create a deep copy of the list
|
|
78
|
+
- `at(index)`: Get the ElementId at a specific index
|
|
79
|
+
- `indexOf(id, bias: "none" | "left" | "right" = "none")`: Get the index of an ElementId, with optional bias for deleted-but-known ElementIds
|
|
79
80
|
|
|
80
81
|
#### Bulk Operations
|
|
81
82
|
|
|
82
83
|
```typescript
|
|
83
84
|
// Insert multiple sequential ids at once
|
|
84
|
-
list.insertAfter(null, { bunchId: "user1", counter: 0 }, 5);
|
|
85
|
+
list = list.insertAfter(null, { bunchId: "user1", counter: 0 }, 5);
|
|
85
86
|
// Inserts 5 ids with bunchId="user1" and counters 0, 1, 2, 3, 4
|
|
86
87
|
```
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
#### Save and load
|
|
89
90
|
|
|
90
|
-
Save and
|
|
91
|
+
Save and load the list state in JSON form:
|
|
91
92
|
|
|
92
93
|
```typescript
|
|
93
94
|
// Save list state
|
|
94
95
|
const savedState = list.save();
|
|
95
96
|
|
|
96
|
-
// Later,
|
|
97
|
-
|
|
98
|
-
newList.load(savedState);
|
|
97
|
+
// Later, load from saved state
|
|
98
|
+
let newList = IdList.load(savedState);
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
## Use Cases
|
|
@@ -104,3 +104,30 @@ newList.load(savedState);
|
|
|
104
104
|
- Todo lists with collaborative editing
|
|
105
105
|
- Any list where elements' positions change but need stable identifiers
|
|
106
106
|
- Conflict-free replicated data type (CRDT) implementations
|
|
107
|
+
|
|
108
|
+
## Internals
|
|
109
|
+
|
|
110
|
+
IdList stores its state as a modified [B+Tree](https://en.wikipedia.org/wiki/B%2B_tree), described at the top of [its source code](./src/id_list.ts). Each leaf in the B+Tree represents multiple ElementIds (sharing a bunchId and sequential counters) in a compressed way; for normal collaborative text editing, expect 10-20 ElementIds per leaf.
|
|
111
|
+
|
|
112
|
+
To speed up searches, we also maintain a "bottom-up" tree that maps from each node to a sequence number identifying its parent. (Using sequence numbers instead of pointers is necessary for persistence.) The map is implemented using persistent balanced trees from [functional-red-black-tree](https://www.npmjs.com/package/functional-red-black-tree).
|
|
113
|
+
|
|
114
|
+
Asymptotic runtimes are given in terms of the number of leaves `L` and the maximum "fragmentation" of a leaf `F`, which is the number of times its ElementIds alternate between deleted vs present.
|
|
115
|
+
|
|
116
|
+
- insertAfter, insertBefore: `O(log^2(L) + F)`.
|
|
117
|
+
- The bottleneck is finding the B+Tree path of the before/after ElementId. This requires `O(log(L))` lookups in the bottom-up tree's map, each of which takes `O(log(L))` time. See the implementation of `IdList.locate`.
|
|
118
|
+
- delete, undelete: `O(log^2(L) + F)`.
|
|
119
|
+
- indexOf: `O(log^2(L) + F)`.
|
|
120
|
+
- Bottleneck is locating the id.
|
|
121
|
+
- at: `O(log(L) + F)`.
|
|
122
|
+
- Simple B+Tree search.
|
|
123
|
+
- has, isKnown: `O(log(L) + F)`
|
|
124
|
+
- Part of the bottom-up tree is a sorted map with leaf keys; due to the sort, we can also use that map to look up the leaf corresponding to an ElementId, in `O(log(L))` time.
|
|
125
|
+
- length: `O(1)`.
|
|
126
|
+
- Cached.
|
|
127
|
+
- save: `O(S + L)`, where `S <= L * F` is the saved state's length.
|
|
128
|
+
- load: `O(S * log(S))`
|
|
129
|
+
- The bottleneck is constructing the bottom-up tree: specifically, the map from each leaf to its parent's sequence number (`leafMap`). That map is itself a sorted tree, hence takes `O(L * log(L))` time to construct, and `L <= S`.
|
|
130
|
+
|
|
131
|
+
If you want to get a sense of what IdList is or how to implement your own version, consider reading the source code for [IdListSimple](./test/id_list_simple.ts), which behaves identically to IdList. It is short (<300 SLOC) and direct, using an array and `Array.splice`. The downside is that IdListSimple does not compress ElementIds, and all of its operations take `O(# ids)` time. We use it as a known-good implementation in our fuzz tests.
|
|
132
|
+
|
|
133
|
+
<!-- TODO: related work: CRDTs, ropes, list-positions, ?? -->
|
package/build/commonjs/id.d.ts
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ElementIds are conceptually the same as UUIDs (or nanoids, etc.).
|
|
5
5
|
* However, when a single thread generates a series of ElementIds, you are
|
|
6
|
-
* allowed to optimize by generating a single UUID/nanoid/etc. and using that as the
|
|
6
|
+
* allowed to optimize by generating a single UUID/nanoid/etc. and using that as the `bunchId`
|
|
7
7
|
* for a "bunch" of elements, with varying `counter`.
|
|
8
8
|
* The resulting ElementIds compress better than a set of UUIDs, but they are
|
|
9
|
-
* still globally unique, even if another thread/user
|
|
9
|
+
* still globally unique, even if another thread/device/user generates ElementIds concurrently.
|
|
10
10
|
*
|
|
11
|
-
* For example, if a user types a sentence from left to right, you
|
|
11
|
+
* For example, if a user types a sentence from left to right, you can generate a
|
|
12
12
|
* single `bunchId` and assign their characters the sequential ElementIds
|
|
13
13
|
* `{ bunchId, counter: 0 }, { bunchId, counter: 1 }, { bunchId, counter: 2 }, ...`.
|
|
14
14
|
* An IdList will store all of these as a single object instead of
|
|
@@ -31,9 +31,6 @@ export interface ElementId {
|
|
|
31
31
|
* IdList is optimized for this case, but it is not mandatory.
|
|
32
32
|
* In particular, it is okay if future edits cause the sequential ids to be
|
|
33
33
|
* separated, partially deleted, or even reordered.
|
|
34
|
-
*
|
|
35
|
-
* Negative integers are supported by IdList (e.g., for optimized right-to-left insertions),
|
|
36
|
-
* though you may choose to avoid these in your application, to make serialization easier.
|
|
37
34
|
*/
|
|
38
35
|
readonly counter: number;
|
|
39
36
|
}
|
package/build/commonjs/id.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/id.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/id.ts"],"names":[],"mappings":";;;AAqCA;;GAEG;AACH,SAAgB,QAAQ,CAAC,CAAY,EAAE,CAAY;IACjD,OAAO,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC;AAC5D,CAAC;AAFD,4BAEC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAgB,SAAS,CAAC,OAAkB,EAAE,KAAa;IACzD,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAVD,8BAUC"}
|
|
@@ -1,11 +1,79 @@
|
|
|
1
|
+
import { SparseIndices } from "sparse-array-rled";
|
|
1
2
|
import { ElementId } from "./id";
|
|
3
|
+
import { MutableLeafMap } from "./internal/leaf_map";
|
|
4
|
+
import { MutableSeqMap } from "./internal/seq_map";
|
|
2
5
|
import { SavedIdList } from "./saved_id_list";
|
|
3
|
-
interface
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
export interface LeafNode {
|
|
7
|
+
readonly bunchId: string;
|
|
8
|
+
readonly startCounter: number;
|
|
9
|
+
readonly count: number;
|
|
10
|
+
/**
|
|
11
|
+
* The present counter values in this leaf node.
|
|
12
|
+
*
|
|
13
|
+
* Note that it is indexed by counter, not by (counter - this.startCounter).
|
|
14
|
+
*/
|
|
15
|
+
readonly present: SparseIndices;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* An inner node with inner-node children.
|
|
19
|
+
*/
|
|
20
|
+
export declare class InnerNodeInner {
|
|
21
|
+
/**
|
|
22
|
+
* A unique identifer for this node within its IdTree.
|
|
23
|
+
*/
|
|
24
|
+
readonly seq: number;
|
|
25
|
+
readonly children: readonly InnerNode[];
|
|
26
|
+
readonly size: number;
|
|
27
|
+
readonly knownSize: number;
|
|
28
|
+
constructor(
|
|
29
|
+
/**
|
|
30
|
+
* A unique identifer for this node within its IdTree.
|
|
31
|
+
*/
|
|
32
|
+
seq: number, children: readonly InnerNode[],
|
|
33
|
+
/**
|
|
34
|
+
* We add entries for the children to this map, overwriting any existing parentSeqs.
|
|
35
|
+
*
|
|
36
|
+
* Pass null to skip when you are doing it yourself. Regardless, you need to
|
|
37
|
+
* delete any outdated entries yourself.
|
|
38
|
+
*/
|
|
39
|
+
parentSeqsMut: MutableSeqMap | null);
|
|
6
40
|
}
|
|
7
41
|
/**
|
|
8
|
-
*
|
|
42
|
+
* An inner node with leaf children.
|
|
43
|
+
*/
|
|
44
|
+
export declare class InnerNodeLeaf {
|
|
45
|
+
/**
|
|
46
|
+
* A unique identifer for this node within its IdTree.
|
|
47
|
+
*/
|
|
48
|
+
readonly seq: number;
|
|
49
|
+
readonly children: readonly LeafNode[];
|
|
50
|
+
readonly size: number;
|
|
51
|
+
readonly knownSize: number;
|
|
52
|
+
constructor(
|
|
53
|
+
/**
|
|
54
|
+
* A unique identifer for this node within its IdTree.
|
|
55
|
+
*/
|
|
56
|
+
seq: number, children: readonly LeafNode[],
|
|
57
|
+
/**
|
|
58
|
+
* We add entries for the children to this map, overwriting any existing parentSeqs.
|
|
59
|
+
*
|
|
60
|
+
* Pass null to skip when you are doing it yourself.
|
|
61
|
+
*/
|
|
62
|
+
leafMapMut: MutableLeafMap | null);
|
|
63
|
+
}
|
|
64
|
+
export type InnerNode = InnerNodeInner | InnerNodeLeaf;
|
|
65
|
+
/**
|
|
66
|
+
* The B+Tree's branching factor, i.e., the max number of children of a node.
|
|
67
|
+
*
|
|
68
|
+
* Note that our B+Tree has no keys - in particular, no keys in internal nodes.
|
|
69
|
+
*
|
|
70
|
+
* Wiki B+Tree: "B+ trees can also be used for data stored in RAM.
|
|
71
|
+
* In this case a reasonable choice for block size would be the size of [the] processor's cache line."
|
|
72
|
+
* (64 byte cache line) / (8 byte pointer) = 8.
|
|
73
|
+
*/
|
|
74
|
+
export declare const M = 8;
|
|
75
|
+
/**
|
|
76
|
+
* A list of ElementIds, as a persistent (immutable) data structure.
|
|
9
77
|
*
|
|
10
78
|
* An IdList helps you assign a unique immutable id to each element of a list, such
|
|
11
79
|
* as a todo-list or a text document (= list of characters). That way, you can keep track
|
|
@@ -14,11 +82,14 @@ interface ListElement {
|
|
|
14
82
|
*
|
|
15
83
|
* Any id that has been inserted into an IdList remains **known** to that list indefinitely,
|
|
16
84
|
* allowing you to reference it in insertAfter/insertBefore operations. Calling {@link delete}
|
|
17
|
-
* merely marks an id as deleted (not present); it
|
|
85
|
+
* merely marks an id as deleted (= not present); a deleted id does not count towards the length of the list or index-based accessors, but it does remain in memory as a "tombstone".
|
|
18
86
|
* This is useful in collaborative settings, since another user might instruct you to
|
|
19
87
|
* call `insertAfter(before, newId)` when you have already deleted `before` locally.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
88
|
+
*
|
|
89
|
+
* To enable easy and efficient rollbacks, such as in a
|
|
90
|
+
* [server reconciliation](https://mattweidner.com/2024/06/04/server-architectures.html#1-server-reconciliation)
|
|
91
|
+
* architecture, IdList is a persistent (immutable) data structure. Mutating methods
|
|
92
|
+
* return a new IdList, sharing memory with the old IdList where possible.
|
|
22
93
|
*
|
|
23
94
|
* See {@link ElementId} for advice on generating ElementIds. IdList is optimized for
|
|
24
95
|
* the case where sequential ElementIds often have the same bunchId and sequential counters.
|
|
@@ -26,18 +97,34 @@ interface ListElement {
|
|
|
26
97
|
* cause such ids to be separated, partially deleted, or even reordered.
|
|
27
98
|
*/
|
|
28
99
|
export declare class IdList {
|
|
29
|
-
private readonly
|
|
30
|
-
|
|
100
|
+
private readonly root;
|
|
101
|
+
/**
|
|
102
|
+
* A persistent sorted map from each leaf to its parent node's seq.
|
|
103
|
+
*
|
|
104
|
+
* Besides parentSeqs, we also use this to lookup leaves by ElementId.
|
|
105
|
+
*/
|
|
106
|
+
private readonly leafMap;
|
|
107
|
+
/**
|
|
108
|
+
* A persistent map from each InnerNode's seq to its parent node's seq.
|
|
109
|
+
*
|
|
110
|
+
* We map the root's seq to 0 (in our constructor).
|
|
111
|
+
*/
|
|
112
|
+
private readonly parentSeqs;
|
|
113
|
+
/**
|
|
114
|
+
* Internal - construct an IdList using a static method (e.g. `IdList.new`).
|
|
115
|
+
*/
|
|
116
|
+
private constructor();
|
|
31
117
|
/**
|
|
32
118
|
* Constructs an empty list.
|
|
33
119
|
*
|
|
34
|
-
* To begin with a non-empty list, use {@link IdList.from}
|
|
120
|
+
* To begin with a non-empty list, use {@link IdList.from}, {@link IdList.fromIds},
|
|
121
|
+
* or {@link IdList.load}.
|
|
35
122
|
*/
|
|
36
|
-
|
|
123
|
+
static new(): IdList;
|
|
37
124
|
/**
|
|
38
125
|
* Constructs a list with the given known ids and their isDeleted status, in list order.
|
|
39
126
|
*/
|
|
40
|
-
static from(
|
|
127
|
+
static from(knownIds: Iterable<{
|
|
41
128
|
id: ElementId;
|
|
42
129
|
isDeleted: boolean;
|
|
43
130
|
}>): IdList;
|
|
@@ -51,6 +138,8 @@ export declare class IdList {
|
|
|
51
138
|
static fromIds(ids: Iterable<ElementId>): IdList;
|
|
52
139
|
/**
|
|
53
140
|
* Inserts `newId` immediately after the given id (`before`), which may be deleted.
|
|
141
|
+
* A new IdList is returned and the current list remains unchanged.
|
|
142
|
+
*
|
|
54
143
|
* All ids to the right of `before` are shifted one index to the right, in the manner
|
|
55
144
|
* of [Array.splice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice).
|
|
56
145
|
*
|
|
@@ -60,11 +149,13 @@ export declare class IdList {
|
|
|
60
149
|
* @param count Provide this to bulk-insert `count` ids from left-to-right,
|
|
61
150
|
* starting with newId and proceeding with the same bunchId and sequential counters.
|
|
62
151
|
* @throws If `before` is not known.
|
|
63
|
-
* @throws If
|
|
152
|
+
* @throws If any inserted id is already known.
|
|
64
153
|
*/
|
|
65
|
-
insertAfter(before: ElementId | null, newId: ElementId, count?: number):
|
|
154
|
+
insertAfter(before: ElementId | null, newId: ElementId, count?: number): IdList;
|
|
66
155
|
/**
|
|
67
156
|
* Inserts `newId` immediately before the given id (`after`), which may be deleted.
|
|
157
|
+
* A new IdList is returned and the current list remains unchanged.
|
|
158
|
+
*
|
|
68
159
|
* All ids to the right of `after`, plus `after` itself, are shifted one index to the right, in the manner
|
|
69
160
|
* of [Array.splice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice).
|
|
70
161
|
*
|
|
@@ -77,39 +168,45 @@ export declare class IdList {
|
|
|
77
168
|
* @throws If `after` is not known.
|
|
78
169
|
* @throws If `newId` is already known.
|
|
79
170
|
*/
|
|
80
|
-
insertBefore(after: ElementId | null, newId: ElementId, count?: number):
|
|
171
|
+
insertBefore(after: ElementId | null, newId: ElementId, count?: number): IdList;
|
|
81
172
|
/**
|
|
82
|
-
*
|
|
173
|
+
* Marks `id` as deleted from this list.
|
|
174
|
+
* A new IdList is returned and the current list remains unchanged.
|
|
83
175
|
*
|
|
84
|
-
*
|
|
85
|
-
* it remains known
|
|
176
|
+
* Once deleted, `id` does not count towards the length of the list or index-based accessors.
|
|
177
|
+
* However, it remains known (a "tombstone").
|
|
178
|
+
* Because `id` is still known, you can reference it in future insertAfter/insertBefore
|
|
86
179
|
* operations, including ones sent concurrently by other devices.
|
|
180
|
+
* This does have a memory cost, but it is compressed in common cases.
|
|
87
181
|
*
|
|
88
|
-
* If `id` is already not known, this method does nothing.
|
|
182
|
+
* If `id` is already deleted or is not known, this method does nothing.
|
|
89
183
|
*/
|
|
90
|
-
|
|
184
|
+
delete(id: ElementId): IdList;
|
|
91
185
|
/**
|
|
92
|
-
*
|
|
186
|
+
* Un-marks `id` as deleted from this list, making it present again.
|
|
187
|
+
* A new IdList is returned and the current list remains unchanged.
|
|
93
188
|
*
|
|
94
|
-
*
|
|
95
|
-
* operations, including ones sent concurrently by other devices.
|
|
96
|
-
* However, it does occupy space in memory (compressed in common cases).
|
|
189
|
+
* This method is an exact inverse to {@link delete}.
|
|
97
190
|
*
|
|
98
|
-
*
|
|
99
|
-
* that makes `id` no longer known, see {@link uninsert}.
|
|
191
|
+
* If `id` is already present, this method does nothing.
|
|
100
192
|
*
|
|
101
|
-
* If `id` is
|
|
193
|
+
* @throws If `id` is not known.
|
|
102
194
|
*/
|
|
103
|
-
|
|
195
|
+
undelete(id: ElementId): IdList;
|
|
104
196
|
/**
|
|
105
|
-
*
|
|
106
|
-
* This is an exact inverse to {@link delete}.
|
|
197
|
+
* Returns the path from id's leaf node to the root, or null if id is not found.
|
|
107
198
|
*
|
|
108
|
-
*
|
|
199
|
+
* The path contains each node and its index in its parent's node, starting with id's
|
|
200
|
+
* LeafNode and ending at a child of the root.
|
|
201
|
+
*/
|
|
202
|
+
private locate;
|
|
203
|
+
/**
|
|
204
|
+
* Replaces the leaf at the given path with newLeaves.
|
|
205
|
+
* Returns a proper (sufficiently balanced) B+Tree with updated sizes.
|
|
109
206
|
*
|
|
110
|
-
*
|
|
207
|
+
* newLeaves.length must be in [1, M].
|
|
111
208
|
*/
|
|
112
|
-
|
|
209
|
+
private replaceLeaf;
|
|
113
210
|
/**
|
|
114
211
|
* Returns whether `id` is present in the list, i.e., it is known and not deleted.
|
|
115
212
|
*
|
|
@@ -124,6 +221,16 @@ export declare class IdList {
|
|
|
124
221
|
* Compare to {@link has}.
|
|
125
222
|
*/
|
|
126
223
|
isKnown(id: ElementId): boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Returns true if any of the given bulk ids are known.
|
|
226
|
+
*/
|
|
227
|
+
private isAnyKnown;
|
|
228
|
+
/**
|
|
229
|
+
* The length of the list, counting only present ids.
|
|
230
|
+
*
|
|
231
|
+
* To include known but deleted ids, use `this.knownIds.length`.
|
|
232
|
+
*/
|
|
233
|
+
get length(): number;
|
|
127
234
|
/**
|
|
128
235
|
* Returns the id at the given index in the list.
|
|
129
236
|
*
|
|
@@ -142,10 +249,6 @@ export declare class IdList {
|
|
|
142
249
|
* @throws If `id` is not known.
|
|
143
250
|
*/
|
|
144
251
|
indexOf(id: ElementId, bias?: "none" | "left" | "right"): number;
|
|
145
|
-
/**
|
|
146
|
-
* The length of the list.
|
|
147
|
-
*/
|
|
148
|
-
get length(): number;
|
|
149
252
|
/**
|
|
150
253
|
* Iterates over all present ids in the list.
|
|
151
254
|
*/
|
|
@@ -157,18 +260,14 @@ export declare class IdList {
|
|
|
157
260
|
/**
|
|
158
261
|
* Iterates over all __known__ ids in the list, indicating which are deleted.
|
|
159
262
|
*/
|
|
160
|
-
|
|
263
|
+
valuesWithIsDeleted(): IterableIterator<{
|
|
161
264
|
id: ElementId;
|
|
162
265
|
isDeleted: boolean;
|
|
163
266
|
}>;
|
|
164
|
-
/**
|
|
165
|
-
* Returns an independent copy of this list, including known but deleted ids.
|
|
166
|
-
*/
|
|
167
|
-
clone(): IdList;
|
|
168
267
|
private _knownIds?;
|
|
169
268
|
/**
|
|
170
|
-
* A
|
|
171
|
-
* That is, it ignores
|
|
269
|
+
* A view of this list that treats all known ids as present.
|
|
270
|
+
* That is, it ignores is-deleted status when computing list indices or iterating.
|
|
172
271
|
*/
|
|
173
272
|
get knownIds(): KnownIdView;
|
|
174
273
|
/**
|
|
@@ -179,23 +278,25 @@ export declare class IdList {
|
|
|
179
278
|
*/
|
|
180
279
|
save(): SavedIdList;
|
|
181
280
|
/**
|
|
182
|
-
* Loads a saved state returned by {@link save}
|
|
281
|
+
* Loads a saved state returned by {@link save}.
|
|
183
282
|
*/
|
|
184
|
-
load(savedState: SavedIdList):
|
|
283
|
+
static load(savedState: SavedIdList): IdList;
|
|
185
284
|
}
|
|
186
285
|
/**
|
|
187
|
-
* A
|
|
188
|
-
* That is, this class ignores the underlying list's
|
|
286
|
+
* A view of an IdList that treats all known ids as present.
|
|
287
|
+
* That is, this class ignores the underlying list's is-deleted status when computing list indices.
|
|
288
|
+
* Access using {@link IdList.knownIds}.
|
|
189
289
|
*
|
|
190
|
-
* To mutate,
|
|
290
|
+
* Like IdList, KnownIdView is immutable. To mutate, use a mutating method on the original IdList
|
|
291
|
+
* and access the returned list's `knownIds`.
|
|
191
292
|
*/
|
|
192
293
|
export declare class KnownIdView {
|
|
193
294
|
readonly list: IdList;
|
|
194
|
-
private readonly
|
|
295
|
+
private readonly root;
|
|
195
296
|
/**
|
|
196
297
|
* Internal use only. Use {@link IdList.knownIds} instead.
|
|
197
298
|
*/
|
|
198
|
-
constructor(list: IdList,
|
|
299
|
+
constructor(list: IdList, root: InnerNode);
|
|
199
300
|
/**
|
|
200
301
|
* Returns the id at the given index in this view.
|
|
201
302
|
*
|
|
@@ -223,4 +324,3 @@ export declare class KnownIdView {
|
|
|
223
324
|
*/
|
|
224
325
|
values(): IterableIterator<ElementId>;
|
|
225
326
|
}
|
|
226
|
-
export {};
|