@graffy/common 0.19.0 → 0.19.1-alpha.2
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/coding/alphabet.js +5 -0
- package/coding/args.d.ts +7 -0
- package/coding/args.js +92 -0
- package/coding/base64.d.ts +2 -0
- package/coding/base64.js +41 -0
- package/coding/decodeTree.d.ts +2 -0
- package/coding/decodeTree.js +198 -0
- package/coding/decorate.js +211 -0
- package/coding/encodeTree.d.ts +2 -0
- package/coding/encodeTree.js +251 -0
- package/coding/id.js +4 -0
- package/coding/index.d.ts +8 -0
- package/{types/coding/index.d.ts → coding/index.js} +4 -4
- package/coding/number.d.ts +2 -0
- package/coding/number.js +39 -0
- package/coding/pack.d.ts +2 -0
- package/coding/pack.js +71 -0
- package/coding/path.d.ts +3 -0
- package/coding/path.js +41 -0
- package/coding/string.d.ts +2 -0
- package/coding/string.js +8 -0
- package/coding/struct.d.ts +11 -0
- package/coding/struct.js +162 -0
- package/index.d.ts +6 -0
- package/node/find.d.ts +2 -0
- package/node/find.js +14 -0
- package/node/index.d.ts +2 -0
- package/{types/node/index.d.ts → node/index.js} +1 -0
- package/node/types.d.ts +6 -0
- package/node/types.js +18 -0
- package/object.d.ts +4 -0
- package/object.js +86 -0
- package/ops/add.js +64 -0
- package/ops/finalize.js +19 -0
- package/{types/ops → ops}/getKnown.d.ts +1 -4
- package/ops/getKnown.js +24 -0
- package/ops/index.d.ts +9 -0
- package/ops/merge.d.ts +3 -0
- package/ops/merge.js +112 -0
- package/ops/path.d.ts +6 -0
- package/ops/path.js +91 -0
- package/ops/setVersion.js +29 -0
- package/ops/sieve.d.ts +3 -0
- package/ops/sieve.js +193 -0
- package/ops/slice.d.ts +13 -0
- package/ops/slice.js +153 -0
- package/ops/step.d.ts +6 -0
- package/ops/step.js +61 -0
- package/package.json +14 -8
- package/stream/index.d.ts +2 -0
- package/stream/makeWatcher.d.ts +10 -0
- package/stream/makeWatcher.js +19 -0
- package/stream/mergeStreams.js +20 -0
- package/util.d.ts +15 -0
- package/util.js +106 -0
- package/index.cjs +0 -1673
- package/index.mjs +0 -1673
- package/types/coding/args.d.ts +0 -5
- package/types/coding/base64.d.ts +0 -2
- package/types/coding/decodeTree.d.ts +0 -2
- package/types/coding/encodeTree.d.ts +0 -2
- package/types/coding/number.d.ts +0 -2
- package/types/coding/pack.d.ts +0 -2
- package/types/coding/path.d.ts +0 -3
- package/types/coding/string.d.ts +0 -2
- package/types/coding/struct.d.ts +0 -11
- package/types/node/find.d.ts +0 -2
- package/types/node/types.d.ts +0 -6
- package/types/object.d.ts +0 -4
- package/types/ops/merge.d.ts +0 -3
- package/types/ops/path.d.ts +0 -6
- package/types/ops/sieve.d.ts +0 -3
- package/types/ops/slice.d.ts +0 -13
- package/types/ops/step.d.ts +0 -6
- package/types/stream/makeWatcher.d.ts +0 -16
- package/types/util.d.ts +0 -15
- package/{types/coding → coding}/alphabet.d.ts +0 -0
- package/{types/coding → coding}/decorate.d.ts +0 -0
- package/{types/coding → coding}/id.d.ts +0 -0
- package/{types/index.d.ts → index.js} +0 -0
- package/{types/ops → ops}/add.d.ts +0 -0
- package/{types/ops → ops}/finalize.d.ts +0 -0
- package/{types/ops/index.d.ts → ops/index.js} +1 -1
- /package/{types/ops → ops}/setVersion.d.ts +0 -0
- /package/{types/stream/index.d.ts → stream/index.js} +0 -0
- /package/{types/stream → stream}/mergeStreams.d.ts +0 -0
package/node/types.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function isRange(node) {
|
|
2
|
+
return node && typeof node.end !== 'undefined';
|
|
3
|
+
}
|
|
4
|
+
export function isBranch(node) {
|
|
5
|
+
return node && typeof node.children !== 'undefined';
|
|
6
|
+
}
|
|
7
|
+
export function isPrefix(node) {
|
|
8
|
+
return node?.prefix;
|
|
9
|
+
}
|
|
10
|
+
export function isLink(node) {
|
|
11
|
+
return node && typeof node.path !== 'undefined';
|
|
12
|
+
}
|
|
13
|
+
export function isOlder(node, version) {
|
|
14
|
+
return typeof node.version !== 'undefined' && node.version < version;
|
|
15
|
+
}
|
|
16
|
+
export function isNewer(node, version) {
|
|
17
|
+
return typeof node.version !== 'undefined' && node.version > version;
|
|
18
|
+
}
|
package/object.d.ts
ADDED
package/object.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import isEqual from 'lodash/isEqual.js';
|
|
2
|
+
import { splitArgs } from "./coding/index.js";
|
|
3
|
+
import { isEmpty } from "./util.js";
|
|
4
|
+
export function mergeObject(base, change) {
|
|
5
|
+
if (typeof change !== 'object' ||
|
|
6
|
+
typeof base !== 'object' ||
|
|
7
|
+
!base ||
|
|
8
|
+
!change) {
|
|
9
|
+
return change;
|
|
10
|
+
}
|
|
11
|
+
for (const prop in change) {
|
|
12
|
+
if (prop in base) {
|
|
13
|
+
const value = mergeObject(base[prop], change[prop]);
|
|
14
|
+
if (value === null) {
|
|
15
|
+
delete base[prop];
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
base[prop] = value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
base[prop] = change[prop];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return isEmpty(base) ? null : base;
|
|
26
|
+
}
|
|
27
|
+
export function cloneObject(object) {
|
|
28
|
+
if (typeof object !== 'object' || !object) {
|
|
29
|
+
return object;
|
|
30
|
+
}
|
|
31
|
+
const clone = {};
|
|
32
|
+
for (const prop in object) {
|
|
33
|
+
const value = cloneObject(object[prop]);
|
|
34
|
+
if (value === null)
|
|
35
|
+
continue;
|
|
36
|
+
clone[prop] = value;
|
|
37
|
+
}
|
|
38
|
+
return isEmpty(clone) ? null : clone;
|
|
39
|
+
}
|
|
40
|
+
export function wrapObject(object, path) {
|
|
41
|
+
if (!Array.isArray(path))
|
|
42
|
+
throw Error(`wrapObject.path_not_array ${path}`);
|
|
43
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
44
|
+
const $key = path[i];
|
|
45
|
+
if (typeof $key === 'string') {
|
|
46
|
+
object = { [$key]: object };
|
|
47
|
+
}
|
|
48
|
+
else if (Array.isArray(object)) {
|
|
49
|
+
object = [{ $key, $chi: object }];
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
object = [{ $key, ...object }];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return object;
|
|
56
|
+
}
|
|
57
|
+
export function unwrapObject(object, path) {
|
|
58
|
+
if (!Array.isArray(path))
|
|
59
|
+
throw Error(`unwrapObject.path_not_array ${path}`);
|
|
60
|
+
for (let i = 0; i < path.length; i++) {
|
|
61
|
+
if (!object || typeof object !== 'object')
|
|
62
|
+
return;
|
|
63
|
+
const $key = path[i];
|
|
64
|
+
if (typeof $key === 'string') {
|
|
65
|
+
if (Array.isArray(object)) {
|
|
66
|
+
throw Error(`unwrapObject.string_key_array:${$key}`);
|
|
67
|
+
}
|
|
68
|
+
object = object[$key];
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
if (!Array.isArray(object)) {
|
|
72
|
+
throw Error(`unwrapObject.arg_key_object:${JSON.stringify($key)}`);
|
|
73
|
+
}
|
|
74
|
+
const [page, filter] = splitArgs($key);
|
|
75
|
+
if (page && !page.$cursor) {
|
|
76
|
+
// TODO: Return a slice of this array
|
|
77
|
+
return object;
|
|
78
|
+
}
|
|
79
|
+
const target = page?.$cursor
|
|
80
|
+
? { ...filter, $cursor: page.$cursor }
|
|
81
|
+
: filter;
|
|
82
|
+
object = object.find(({ $key }) => isEqual($key, target));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return object;
|
|
86
|
+
}
|
package/ops/add.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { isBranch } from "../node/index.js";
|
|
2
|
+
import { cmp as compareKey, find } from "../util.js";
|
|
3
|
+
export default function add(base, diff) {
|
|
4
|
+
let changed = false;
|
|
5
|
+
// console.log('Add', diff);
|
|
6
|
+
// console.log('before add', base);
|
|
7
|
+
let index = 0;
|
|
8
|
+
for (const node of diff) {
|
|
9
|
+
const cmp = compare(node);
|
|
10
|
+
const nodeIsBranch = isBranch(node);
|
|
11
|
+
index = find(base, cmp, index);
|
|
12
|
+
const item = base[index];
|
|
13
|
+
const itemIsBranch = isBranch(item);
|
|
14
|
+
if (!item || cmp(item)) {
|
|
15
|
+
// This node doesn't exist in the base, insert it.
|
|
16
|
+
base.splice(index, 0, clone(node));
|
|
17
|
+
changed = true;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (nodeIsBranch && itemIsBranch) {
|
|
21
|
+
changed = add(item.children, node.children) || changed;
|
|
22
|
+
}
|
|
23
|
+
else if (nodeIsBranch) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
else if (itemIsBranch) {
|
|
27
|
+
item.value = node.value;
|
|
28
|
+
changed = true;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
item.value += node.value;
|
|
32
|
+
}
|
|
33
|
+
// If this item was decremented to zero, remove it.
|
|
34
|
+
const size = itemIsBranch ? item.children.length : item.value;
|
|
35
|
+
if (!size) {
|
|
36
|
+
base.splice(index, 1);
|
|
37
|
+
changed = true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return changed;
|
|
41
|
+
}
|
|
42
|
+
function compare(node) {
|
|
43
|
+
return (item) => {
|
|
44
|
+
const v = compareKey(item.key, node.key) ||
|
|
45
|
+
compareValue(!!item.end, !!node.end) ||
|
|
46
|
+
(item.end && compareKey(item.end, node.end)) ||
|
|
47
|
+
compareValue(item.limit, node.limit);
|
|
48
|
+
return v;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function compareValue(a, b) {
|
|
52
|
+
if (a === b)
|
|
53
|
+
return 0;
|
|
54
|
+
return a < b ? -1 : 1;
|
|
55
|
+
}
|
|
56
|
+
/*
|
|
57
|
+
We clone to ensure that the original query (passed as diff) never gets modified, even after successive adds.
|
|
58
|
+
*/
|
|
59
|
+
function clone(node) {
|
|
60
|
+
const copy = { ...node };
|
|
61
|
+
if (node.children)
|
|
62
|
+
copy.children = node.children.map((child) => clone(child));
|
|
63
|
+
return copy;
|
|
64
|
+
}
|
package/ops/finalize.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MAX_KEY, MIN_KEY } from "../util.js";
|
|
2
|
+
import { merge, setVersion, slice } from "./index.js";
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {any} graph
|
|
6
|
+
* @param {any} query
|
|
7
|
+
* @param {number | false} version
|
|
8
|
+
* @returns any
|
|
9
|
+
*/
|
|
10
|
+
export default function finalize(graph, query, version = Date.now()) {
|
|
11
|
+
let result = [{ key: MIN_KEY, end: MAX_KEY, version: 0 }];
|
|
12
|
+
merge(result, graph);
|
|
13
|
+
if (query)
|
|
14
|
+
result = slice(result, query).known || [];
|
|
15
|
+
if (version !== false)
|
|
16
|
+
result = setVersion(result, version, true);
|
|
17
|
+
// console.log('Finalizing', result, '\n\n', query, '\n\n', graph);
|
|
18
|
+
return result;
|
|
19
|
+
}
|
package/ops/getKnown.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
getKnown accepts a graph and returns a query for the knowledge
|
|
3
|
+
in that graph.
|
|
4
|
+
*/
|
|
5
|
+
import { cmp } from "../util.js";
|
|
6
|
+
export default function getKnown(graph, version = 0) {
|
|
7
|
+
const query = [];
|
|
8
|
+
for (const { key, end, children } of graph) {
|
|
9
|
+
const node = { key, version };
|
|
10
|
+
if (end) {
|
|
11
|
+
if (cmp(end, key) !== 0)
|
|
12
|
+
node.end = end;
|
|
13
|
+
node.value = 1;
|
|
14
|
+
}
|
|
15
|
+
if (children) {
|
|
16
|
+
node.children = getKnown(children);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
node.value = 1;
|
|
20
|
+
}
|
|
21
|
+
query.push(node);
|
|
22
|
+
}
|
|
23
|
+
return query;
|
|
24
|
+
}
|
package/ops/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as add } from './add.js';
|
|
2
|
+
export { default as finalize } from './finalize.js';
|
|
3
|
+
export { default as getKnown } from './getKnown.js';
|
|
4
|
+
export { default as merge } from './merge.js';
|
|
5
|
+
export * from './path.js';
|
|
6
|
+
export { default as setVersion } from './setVersion.js';
|
|
7
|
+
export { default as sieve } from './sieve.js';
|
|
8
|
+
export { default as slice } from './slice.js';
|
|
9
|
+
export * from './step.js';
|
package/ops/merge.d.ts
ADDED
package/ops/merge.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { findFirst, findLast, isBranch, isRange } from "../node/index.js";
|
|
2
|
+
import { cmp, MAX_KEY, MIN_KEY } from "../util.js";
|
|
3
|
+
import { keyAfter, keyBefore } from "./step.js";
|
|
4
|
+
export default function merge(current, changes) {
|
|
5
|
+
let index = 0;
|
|
6
|
+
if (typeof changes === 'undefined')
|
|
7
|
+
return current;
|
|
8
|
+
for (const change of changes) {
|
|
9
|
+
index = isRange(change)
|
|
10
|
+
? insertRange(current, change, index)
|
|
11
|
+
: insertNode(current, change, index);
|
|
12
|
+
}
|
|
13
|
+
return current;
|
|
14
|
+
}
|
|
15
|
+
export function insertRange(current, change, start = 0) {
|
|
16
|
+
const { key, end } = change;
|
|
17
|
+
const keyIx = findFirst(current, key, start);
|
|
18
|
+
const endIx = findLast(current, end, keyIx);
|
|
19
|
+
// If current contains nodes that are newer than this range, keep them.
|
|
20
|
+
// We do this by merging them back into insertions first.
|
|
21
|
+
const insertions = [change];
|
|
22
|
+
for (let i = keyIx; i < endIx; i++) {
|
|
23
|
+
const node = current[i];
|
|
24
|
+
if (isRange(node)) {
|
|
25
|
+
insertions.push(...mergeRanges(insertions.pop(), node));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
insertNode(insertions, node, insertions.length - 1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
current.splice(keyIx, endIx - keyIx, ...insertions);
|
|
32
|
+
return keyIx + insertions.length - 1;
|
|
33
|
+
}
|
|
34
|
+
function mergeRanges(base, node) {
|
|
35
|
+
// assertVersion(node, base.version);
|
|
36
|
+
if (node.version < base.version)
|
|
37
|
+
[node, base] = [base, node];
|
|
38
|
+
return [
|
|
39
|
+
cmp(base.key, node.key) < 0 && { ...base, end: keyBefore(node.key) },
|
|
40
|
+
node,
|
|
41
|
+
cmp(base.end, node.end) > 0 && { ...base, key: keyAfter(node.end) },
|
|
42
|
+
].filter(Boolean);
|
|
43
|
+
}
|
|
44
|
+
export function insertNode(current, change, start = 0) {
|
|
45
|
+
if (!current)
|
|
46
|
+
throw new Error(`merge.insertNode: ${current}`);
|
|
47
|
+
const key = change.key;
|
|
48
|
+
const index = findFirst(current, key, start);
|
|
49
|
+
const node = current[index];
|
|
50
|
+
if (node && cmp(node.key, key) <= 0) {
|
|
51
|
+
// This change overlaps with something that exists.
|
|
52
|
+
return isRange(node)
|
|
53
|
+
? insertNodeIntoRange(current, index, change)
|
|
54
|
+
: updateNode(current, index, change);
|
|
55
|
+
}
|
|
56
|
+
// This change does not overlap with any existing knowledge. Insert it
|
|
57
|
+
current.splice(index, 0, change);
|
|
58
|
+
return index + 1;
|
|
59
|
+
}
|
|
60
|
+
function insertNodeIntoRange(current, index, change) {
|
|
61
|
+
const key = change.key;
|
|
62
|
+
const range = current[index];
|
|
63
|
+
const newChange = getNewer(change, range);
|
|
64
|
+
if (!newChange)
|
|
65
|
+
return;
|
|
66
|
+
const insertions = [
|
|
67
|
+
cmp(range.key, key) < 0 && { ...range, end: keyBefore(key) },
|
|
68
|
+
newChange,
|
|
69
|
+
cmp(range.end, key) > 0 && { ...range, key: keyAfter(key) },
|
|
70
|
+
].filter(Boolean);
|
|
71
|
+
current.splice(index, 1, ...insertions);
|
|
72
|
+
// Subtract 1 to keep the final range in consideration for future insertions.
|
|
73
|
+
return index + insertions.length - 1;
|
|
74
|
+
}
|
|
75
|
+
function updateNode(current, index, change) {
|
|
76
|
+
const node = current[index];
|
|
77
|
+
if (isBranch(change) && isBranch(node)) {
|
|
78
|
+
// Both are branches: Recursively merge children.
|
|
79
|
+
merge(node.children, change.children);
|
|
80
|
+
}
|
|
81
|
+
else if (isBranch(node)) {
|
|
82
|
+
// Current node is a branch but the change is a leaf; if the branch
|
|
83
|
+
// has newer children, ignore the change and keep only those children;
|
|
84
|
+
// Otherwise, discard the branch and keep the change.
|
|
85
|
+
const newNode = getNewer(node, change);
|
|
86
|
+
current[index] = newNode || change;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Current node is a leaf. Replace with the change if it is newer.
|
|
90
|
+
const newChange = getNewer(change, node);
|
|
91
|
+
if (newChange)
|
|
92
|
+
current[index] = newChange;
|
|
93
|
+
}
|
|
94
|
+
if (change.prefix)
|
|
95
|
+
current[index].prefix = true;
|
|
96
|
+
return index + 1;
|
|
97
|
+
}
|
|
98
|
+
function getNewer(node, base) {
|
|
99
|
+
const { version } = base;
|
|
100
|
+
if (isBranch(node)) {
|
|
101
|
+
const children = [{ key: MIN_KEY, end: MAX_KEY, version }];
|
|
102
|
+
merge(children, node.children);
|
|
103
|
+
return children.length === 1 ? null : { ...node, children };
|
|
104
|
+
}
|
|
105
|
+
// assertVersion(node, version);
|
|
106
|
+
return node.version >= version ? node : null;
|
|
107
|
+
}
|
|
108
|
+
// function assertVersion(node, version) {
|
|
109
|
+
// // if (node.version === version) {
|
|
110
|
+
// // throw Error('merge.version_collision ' + [node.key, version].join(' '));
|
|
111
|
+
// // }
|
|
112
|
+
// }
|
package/ops/path.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const IS_VAL: unique symbol;
|
|
2
|
+
export declare function wrapValue(value: any, path: any, version?: number): any;
|
|
3
|
+
export declare function wrap(children: any, path: any, version?: number, prefix?: boolean): any;
|
|
4
|
+
export declare function unwrap(tree: any, path: any): any;
|
|
5
|
+
export declare function getNodeValue(node: any): any;
|
|
6
|
+
export declare function remove(children: any, path: any): any;
|
package/ops/path.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { findFirst, isBranch, isRange } from "../node/index.js";
|
|
2
|
+
import { cmp } from "../util.js";
|
|
3
|
+
export const IS_VAL = Symbol('IS_VAL');
|
|
4
|
+
function makeNode(seg, props) {
|
|
5
|
+
if (ArrayBuffer.isView(seg))
|
|
6
|
+
return { key: seg, ...props };
|
|
7
|
+
return { ...seg, ...props };
|
|
8
|
+
}
|
|
9
|
+
export function wrapValue(value, path, version = 0) {
|
|
10
|
+
const node = makeNode(path[path.length - 1], { value, version });
|
|
11
|
+
return wrap([node], path.slice(0, -1), version);
|
|
12
|
+
}
|
|
13
|
+
export function wrap(children, path, version = 0, prefix = false) {
|
|
14
|
+
if (!Array.isArray(path))
|
|
15
|
+
throw Error(`wrap.path_not_array ${path}`);
|
|
16
|
+
if (!path.length)
|
|
17
|
+
return children;
|
|
18
|
+
let i = path.length - 1;
|
|
19
|
+
// If it is a plain value, make it a value node
|
|
20
|
+
if (!Array.isArray(children)) {
|
|
21
|
+
children = [makeNode(path[i--], { value: children, version })];
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
if (!children.length)
|
|
25
|
+
return;
|
|
26
|
+
children = [makeNode(path[i--], { children, version })];
|
|
27
|
+
}
|
|
28
|
+
if (prefix)
|
|
29
|
+
children[0].prefix = true;
|
|
30
|
+
while (i >= 0)
|
|
31
|
+
children = [makeNode(path[i--], { children, version })];
|
|
32
|
+
return children;
|
|
33
|
+
}
|
|
34
|
+
export function unwrap(tree, path) {
|
|
35
|
+
if (!Array.isArray(path))
|
|
36
|
+
throw Error(`unwrap.path_not_array ${path}`);
|
|
37
|
+
let children = tree;
|
|
38
|
+
let node = { children };
|
|
39
|
+
for (let i = 0; i < path.length; i++) {
|
|
40
|
+
const key = path[i];
|
|
41
|
+
if (!ArrayBuffer.isView(key))
|
|
42
|
+
throw Error('unwrap.ranges_unsupported');
|
|
43
|
+
children = node.children;
|
|
44
|
+
if (!children)
|
|
45
|
+
return null; // This path does not exist.
|
|
46
|
+
node = children[findFirst(children, key)];
|
|
47
|
+
if (!node || cmp(node.key, key) > 0)
|
|
48
|
+
return undefined; // We lack knowledge.
|
|
49
|
+
if (isRange(node))
|
|
50
|
+
return null; // This is known to be null.
|
|
51
|
+
if (node.path)
|
|
52
|
+
return unwrap(tree, node.path.concat(path.slice(i + 1)));
|
|
53
|
+
}
|
|
54
|
+
return getNodeValue(node);
|
|
55
|
+
}
|
|
56
|
+
export function getNodeValue(node) {
|
|
57
|
+
if (node.children)
|
|
58
|
+
return node.children;
|
|
59
|
+
if (node.value && typeof node.value === 'object') {
|
|
60
|
+
node.value[IS_VAL] = true;
|
|
61
|
+
}
|
|
62
|
+
return node.value;
|
|
63
|
+
}
|
|
64
|
+
export function remove(children, path) {
|
|
65
|
+
if (!Array.isArray(path))
|
|
66
|
+
throw Error(`del.path_not_array ${path}`);
|
|
67
|
+
if (!children)
|
|
68
|
+
return null; // This path does not exist.
|
|
69
|
+
if (!path.length)
|
|
70
|
+
return []; // Remove everything.
|
|
71
|
+
const key = path[0];
|
|
72
|
+
const ix = findFirst(children, key);
|
|
73
|
+
const node = children[ix];
|
|
74
|
+
if (!node || cmp(node.key, key) > 0 || isRange(node))
|
|
75
|
+
return children;
|
|
76
|
+
// let filteredNode;
|
|
77
|
+
if (path.length === 1) {
|
|
78
|
+
// This is the final segment, delete the matching node from children.
|
|
79
|
+
return children.slice(0, ix).concat(children.slice(ix + 1));
|
|
80
|
+
}
|
|
81
|
+
if (!isBranch(node))
|
|
82
|
+
return children;
|
|
83
|
+
// Recurse into the next slice.
|
|
84
|
+
const filteredChildren = remove(node.children, path.slice(1));
|
|
85
|
+
if (filteredChildren === children)
|
|
86
|
+
return children;
|
|
87
|
+
const filteredNode = filteredChildren.length
|
|
88
|
+
? { ...node, children: filteredChildren }
|
|
89
|
+
: [];
|
|
90
|
+
return children.slice(0, ix).concat(filteredNode, children.slice(ix + 1));
|
|
91
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// import { isRange } from '../node/index.ts';
|
|
2
|
+
// import { keyAfter } from './step.ts';
|
|
3
|
+
export default function setVersion(graph, version, onlyIfZero = false) {
|
|
4
|
+
// mergeRanges(graph);
|
|
5
|
+
for (const node of graph) {
|
|
6
|
+
if (!(onlyIfZero && node.version))
|
|
7
|
+
node.version = version;
|
|
8
|
+
if (node.children)
|
|
9
|
+
setVersion(node.children, version, onlyIfZero);
|
|
10
|
+
}
|
|
11
|
+
return graph;
|
|
12
|
+
}
|
|
13
|
+
// function mergeRanges(graph) {
|
|
14
|
+
// for (let i = 0; i < graph.length; i++) {
|
|
15
|
+
// if (!isRange(graph[i])) continue;
|
|
16
|
+
// let j = i;
|
|
17
|
+
// do {
|
|
18
|
+
// j++;
|
|
19
|
+
// } while (isRange(graph[j]) && cmp(graph[j].key , keyAfter(graph[j - 1].end)) === 0);
|
|
20
|
+
// j = j - 1;
|
|
21
|
+
// if (j === i) continue;
|
|
22
|
+
// graph.splice(i, j - i + 1, {
|
|
23
|
+
// key: graph[i].key,
|
|
24
|
+
// end: graph[j].end,
|
|
25
|
+
// version: graph[i].version,
|
|
26
|
+
// });
|
|
27
|
+
// // i = j;
|
|
28
|
+
// }
|
|
29
|
+
// }
|
package/ops/sieve.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export default function sieve(current: any, changes: any, result?: any[]): any[];
|
|
2
|
+
export declare function insertRange(current: any, change: any, result: any, start?: number): number;
|
|
3
|
+
export declare function insertNode(current: any, change: any, result: any, start?: number): any;
|
package/ops/sieve.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { findFirst, findLast, isBranch, isRange } from "../node/index.js";
|
|
2
|
+
import { cmp, MAX_KEY, MIN_KEY } from "../util.js";
|
|
3
|
+
import { keyAfter, keyBefore } from "./step.js";
|
|
4
|
+
export default function sieve(current, changes, result = []) {
|
|
5
|
+
let index = 0;
|
|
6
|
+
for (const change of changes) {
|
|
7
|
+
index = isRange(change)
|
|
8
|
+
? insertRange(current, change, result, index)
|
|
9
|
+
: insertNode(current, change, result, index);
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
export function insertRange(current, change, result, start = 0) {
|
|
14
|
+
const { key, end } = change;
|
|
15
|
+
const keyIx = findFirst(current, key, start);
|
|
16
|
+
const endIx = findLast(current, end, keyIx);
|
|
17
|
+
if (keyIx === endIx &&
|
|
18
|
+
!(current[keyIx] &&
|
|
19
|
+
cmp(current[keyIx].key, end) <= 0 &&
|
|
20
|
+
cmp(current[keyIx].end || current[keyIx].key, key) >= 0)) {
|
|
21
|
+
// This range does not overlap with any existing data. Ignore it.
|
|
22
|
+
return keyIx;
|
|
23
|
+
}
|
|
24
|
+
const appliedChange = [];
|
|
25
|
+
let currentKey = change.key;
|
|
26
|
+
for (let i = keyIx; i < endIx; i++) {
|
|
27
|
+
const node = current[i];
|
|
28
|
+
// We treat a negative version as a non-existent node
|
|
29
|
+
// as this is a hack used by subscribe.
|
|
30
|
+
if (isRange(node) && node.version >= 0) {
|
|
31
|
+
if (cmp(node.key, currentKey) > 0) {
|
|
32
|
+
appliedChange.push({
|
|
33
|
+
key: currentKey,
|
|
34
|
+
end: keyBefore(node.key),
|
|
35
|
+
version: change.version,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
currentKey = keyAfter(node.end);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
if (getNewerChange(node, change)) {
|
|
42
|
+
appliedChange.push({
|
|
43
|
+
key: currentKey,
|
|
44
|
+
end: keyBefore(node.key),
|
|
45
|
+
version: change.version,
|
|
46
|
+
});
|
|
47
|
+
currentKey = keyAfter(node.key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (cmp(currentKey, change.end) >= 0) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (cmp(currentKey, change.end) <= 0) {
|
|
55
|
+
appliedChange.push({
|
|
56
|
+
key: currentKey,
|
|
57
|
+
end: change.end,
|
|
58
|
+
version: change.version,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (appliedChange.length)
|
|
62
|
+
result.push(...appliedChange);
|
|
63
|
+
// If current contains nodes that are newer than this range, keep them.
|
|
64
|
+
// We do this by merging them back into insertions first.
|
|
65
|
+
const insertions = [change];
|
|
66
|
+
for (let i = keyIx; i < endIx; i++) {
|
|
67
|
+
const node = current[i];
|
|
68
|
+
if (isRange(node)) {
|
|
69
|
+
// console.log('Sieve Range-Range', debug(change), debug(node));
|
|
70
|
+
insertions.push(...mergeRanges(insertions.pop(), node));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// console.log('Sieve Change-Node', debug(change), debug(node));
|
|
74
|
+
insertNode(insertions, node, [], insertions.length - 1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// console.log('Sieve:insertions', debug(insertions));
|
|
78
|
+
current.splice(keyIx, endIx - keyIx, ...insertions);
|
|
79
|
+
return keyIx + insertions.length - 1;
|
|
80
|
+
}
|
|
81
|
+
function mergeRanges(base, node) {
|
|
82
|
+
// assertVersion(node, base.version);
|
|
83
|
+
if (node.version < base.version)
|
|
84
|
+
[node, base] = [base, node];
|
|
85
|
+
// Ensure node is newer than base
|
|
86
|
+
return [
|
|
87
|
+
cmp(base.key, node.key) < 0 && { ...base, end: keyBefore(node.key) },
|
|
88
|
+
node,
|
|
89
|
+
cmp(base.end, node.end) > 0 && { ...base, key: keyAfter(node.end) },
|
|
90
|
+
].filter(Boolean);
|
|
91
|
+
}
|
|
92
|
+
export function insertNode(current, change, result, start = 0) {
|
|
93
|
+
const key = change.key;
|
|
94
|
+
const index = findFirst(current, key, start);
|
|
95
|
+
const node = current[index];
|
|
96
|
+
if (node && cmp(node.key, key) <= 0) {
|
|
97
|
+
// This change overlaps with something that exists.
|
|
98
|
+
return isRange(node)
|
|
99
|
+
? insertNodeIntoRange(current, index, change, result)
|
|
100
|
+
: updateNode(current, index, change, result);
|
|
101
|
+
}
|
|
102
|
+
// This change does not overlap with any existing knowledge. Skip it
|
|
103
|
+
// current.splice(index, 0, change);
|
|
104
|
+
return index;
|
|
105
|
+
}
|
|
106
|
+
function insertNodeIntoRange(current, index, change, result) {
|
|
107
|
+
const key = change.key;
|
|
108
|
+
const range = current[index];
|
|
109
|
+
const newChange = getNewerChange(change, range);
|
|
110
|
+
const newNode = getNewerNode(change, range);
|
|
111
|
+
if (!newChange)
|
|
112
|
+
return;
|
|
113
|
+
result.push(newChange);
|
|
114
|
+
const insertions = [
|
|
115
|
+
cmp(range.key, key) < 0 && { ...range, end: keyBefore(key) },
|
|
116
|
+
newNode,
|
|
117
|
+
cmp(range.end, key) > 0 && { ...range, key: keyAfter(key) },
|
|
118
|
+
].filter(Boolean);
|
|
119
|
+
current.splice(index, 1, ...insertions);
|
|
120
|
+
return index + insertions.length - 1;
|
|
121
|
+
}
|
|
122
|
+
function updateNode(current, index, change, result) {
|
|
123
|
+
const node = current[index];
|
|
124
|
+
if (isBranch(change) && isBranch(node)) {
|
|
125
|
+
// Both are branches: Recursively merge children.
|
|
126
|
+
const nextResult = [];
|
|
127
|
+
node.version = change.version;
|
|
128
|
+
sieve(node.children, change.children, nextResult);
|
|
129
|
+
if (nextResult.length)
|
|
130
|
+
result.push({ ...change, children: nextResult });
|
|
131
|
+
}
|
|
132
|
+
else if (isBranch(node)) {
|
|
133
|
+
// Current node is a branch but the change is a leaf; if the branch
|
|
134
|
+
// has newer children, ignore the change and keep only those children;
|
|
135
|
+
// Otherwise, discard the branch and keep the change.
|
|
136
|
+
const newNode = getNewerNode(node, change);
|
|
137
|
+
current[index] = newNode || change;
|
|
138
|
+
if (!newNode)
|
|
139
|
+
result.push(change);
|
|
140
|
+
// TODO: In the case of partial removal, what should result be?
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Current node is a leaf. Replace with the change if it is newer.
|
|
144
|
+
const newChange = getNewerChange(change, node);
|
|
145
|
+
const newNode = getNewerNode(change, node);
|
|
146
|
+
if (newNode)
|
|
147
|
+
current[index] = newNode;
|
|
148
|
+
// console.log(current);
|
|
149
|
+
if (newChange &&
|
|
150
|
+
(change.value !== node.value || !isPathEqual(change.path, node.path))) {
|
|
151
|
+
result.push(newChange);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return index + 1;
|
|
155
|
+
}
|
|
156
|
+
function isPathEqual(first, second) {
|
|
157
|
+
if (!(first || second))
|
|
158
|
+
return true;
|
|
159
|
+
if (!(first && second))
|
|
160
|
+
return false;
|
|
161
|
+
if (first.length !== second.length)
|
|
162
|
+
return false;
|
|
163
|
+
for (let i = 0; i < first.length; i++) {
|
|
164
|
+
if (first[i] !== second[i])
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
function getNewerNode(node, base) {
|
|
170
|
+
if (isBranch(node)) {
|
|
171
|
+
const emptyNode = { key: MIN_KEY, end: MAX_KEY, version: base.version };
|
|
172
|
+
const children = [emptyNode];
|
|
173
|
+
sieve(children, node.children);
|
|
174
|
+
return children.length === 1 && children[0] === emptyNode
|
|
175
|
+
? null
|
|
176
|
+
: { ...node, children };
|
|
177
|
+
}
|
|
178
|
+
// assertVersion(node, version);
|
|
179
|
+
return node.version >= base.version ? node : null;
|
|
180
|
+
}
|
|
181
|
+
function getNewerChange(node, base) {
|
|
182
|
+
if (isBranch(node)) {
|
|
183
|
+
const children = node.children.filter((child) => getNewerChange(child, base));
|
|
184
|
+
return children.length && { ...node, children };
|
|
185
|
+
}
|
|
186
|
+
// assertVersion(node, version);
|
|
187
|
+
return node.version >= base.version ? node : null;
|
|
188
|
+
}
|
|
189
|
+
// function assertVersion(node, version) {
|
|
190
|
+
// // if (node.version === version) {
|
|
191
|
+
// // throw Error('merge.version_collision ' + [node.key, version].join(' '));
|
|
192
|
+
// // }
|
|
193
|
+
// }
|