@graffy/common 0.18.1-alpha.2 → 0.19.1-alpha.1
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 +5 -0
- package/coding/args.js +93 -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 +197 -0
- package/coding/decorate.js +211 -0
- package/coding/encodeTree.d.ts +2 -0
- package/coding/encodeTree.js +248 -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 +163 -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/{types/ops → ops}/finalize.d.ts +1 -1
- 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/{types/ops → ops}/slice.d.ts +2 -6
- 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/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/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/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
|
+
// }
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
export default function slice(graph: any, query: any, root: any): Result;
|
|
2
|
-
export function sliceRange(graph: any, query: any, result: any): void;
|
|
3
1
|
declare class Result {
|
|
4
2
|
constructor(root: any);
|
|
5
|
-
root: any;
|
|
6
3
|
addKnown(node: any): void;
|
|
7
|
-
known: any;
|
|
8
4
|
addUnknown(node: any): void;
|
|
9
|
-
unknown: any;
|
|
10
5
|
addLinked(children: any): any;
|
|
11
|
-
linked: any;
|
|
12
6
|
}
|
|
7
|
+
export default function slice(graph: any, query: any, root: any): Result;
|
|
8
|
+
export declare function sliceRange(graph: any, query: any, result: any): void;
|
|
13
9
|
export {};
|
package/ops/slice.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { findFirst, findLast, isBranch, isLink, isOlder, isRange, } from "../node/index.js";
|
|
2
|
+
import { cmp, isMinKey, MAX_KEY, MIN_KEY } from "../util.js";
|
|
3
|
+
import add from "./add.js";
|
|
4
|
+
import merge from "./merge.js";
|
|
5
|
+
import { wrap } from "./path.js";
|
|
6
|
+
import { keyAfter, keyBefore } from "./step.js";
|
|
7
|
+
class Result {
|
|
8
|
+
constructor(root) {
|
|
9
|
+
// When linked queries are added, they are forwarded to the root.
|
|
10
|
+
this.root = root || this;
|
|
11
|
+
}
|
|
12
|
+
addKnown(node) {
|
|
13
|
+
this.known = this.known || [];
|
|
14
|
+
merge(this.known, [node]);
|
|
15
|
+
}
|
|
16
|
+
addUnknown(node) {
|
|
17
|
+
this.unknown = this.unknown || [];
|
|
18
|
+
this.unknown.push(node);
|
|
19
|
+
}
|
|
20
|
+
addLinked(children) {
|
|
21
|
+
if (this.root !== this)
|
|
22
|
+
return this.root.addLinked(children);
|
|
23
|
+
this.linked = this.linked || [];
|
|
24
|
+
add(this.linked, children);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export default function slice(graph, query, root) {
|
|
28
|
+
const result = new Result(root);
|
|
29
|
+
let currentQuery = query;
|
|
30
|
+
while (currentQuery) {
|
|
31
|
+
let index = 0;
|
|
32
|
+
for (const queryNode of currentQuery) {
|
|
33
|
+
if (isRange(queryNode)) {
|
|
34
|
+
sliceRange(graph, queryNode, result);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const key = queryNode.key;
|
|
38
|
+
index = findFirst(graph, key);
|
|
39
|
+
sliceNode(graph[index], queryNode, result);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
currentQuery = root ? undefined : result.linked;
|
|
43
|
+
delete result.linked;
|
|
44
|
+
}
|
|
45
|
+
delete result.root;
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
function sliceNode(graph, query, result) {
|
|
49
|
+
const { key, version } = query;
|
|
50
|
+
const { root } = result;
|
|
51
|
+
// console.log('Slicing', graph, query);
|
|
52
|
+
if (!graph || cmp(graph.key, key) > 0 || isOlder(graph, version)) {
|
|
53
|
+
// The node found doesn't match the query or it's too old.
|
|
54
|
+
result.addUnknown(query);
|
|
55
|
+
}
|
|
56
|
+
else if (isRange(graph)) {
|
|
57
|
+
// The graph is indicating that this value was deleted.
|
|
58
|
+
if (isBranch(query)) {
|
|
59
|
+
const { known } = slice([{ key: MIN_KEY, end: MAX_KEY, version: graph.version }], query.children);
|
|
60
|
+
result.addKnown({ key, version: graph.version, children: known });
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
result.addKnown({ key, end: key, version: graph.version });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (isBranch(graph) && isBranch(query)) {
|
|
67
|
+
// Both sides are branches; recurse into them.
|
|
68
|
+
const { known, unknown } = slice(graph.children, query.children, root);
|
|
69
|
+
if (known)
|
|
70
|
+
result.addKnown({ ...graph, children: known });
|
|
71
|
+
if (unknown)
|
|
72
|
+
result.addUnknown({ ...query, children: unknown });
|
|
73
|
+
}
|
|
74
|
+
else if (isLink(graph)) {
|
|
75
|
+
result.addKnown(graph);
|
|
76
|
+
if (graph.prefix && isRange(query)) {
|
|
77
|
+
result.addLinked(wrap([query], graph.path, version, true));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
result.addLinked(wrap(query.children || query.value, graph.path, version, graph.prefix || query.prefix));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (isBranch(graph)) {
|
|
84
|
+
// The graph is a branch and query is requesting all children here.
|
|
85
|
+
result.addKnown(graph);
|
|
86
|
+
}
|
|
87
|
+
else if (isBranch(query)) {
|
|
88
|
+
// One graph is a leaf while the query is a leaf; return null.
|
|
89
|
+
const { known } = slice([{ key: MIN_KEY, end: MAX_KEY, version: graph.version }], query.children);
|
|
90
|
+
result.addKnown({ key, version: graph.version, children: known });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
result.addKnown(graph);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function sliceRange(graph, query, result) {
|
|
97
|
+
let { key, end, limit = Number.POSITIVE_INFINITY, version } = query;
|
|
98
|
+
const step = cmp(key, end) < 0 ? 1 : -1;
|
|
99
|
+
// Prefixes are used to combine filtering and pagination. In schemas where
|
|
100
|
+
// prefixes are expected but a particular graph does not have a filter, it
|
|
101
|
+
// will have a prefix node with an empty string as key.
|
|
102
|
+
if (isMinKey(graph[0].key) && graph[0].prefix && graph[0].children) {
|
|
103
|
+
const { known, unknown } = slice(graph[0].children, [query], result.root);
|
|
104
|
+
if (known)
|
|
105
|
+
result.addKnown({ ...graph[0], children: known });
|
|
106
|
+
if (unknown)
|
|
107
|
+
result.addUnknown({ ...query[0], children: unknown });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (cmp(key, end) < 0) {
|
|
111
|
+
for (let i = findFirst(graph, key); cmp(key, end) <= 0 && limit > 0; i++) {
|
|
112
|
+
const node = graph[i];
|
|
113
|
+
if (!node || cmp(key, node.key) < 0 || isOlder(node, version))
|
|
114
|
+
break;
|
|
115
|
+
if (isRange(node)) {
|
|
116
|
+
result.addKnown(getOverlap(node, key, end));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
sliceNode(node, { ...query, key }, result);
|
|
120
|
+
limit--;
|
|
121
|
+
}
|
|
122
|
+
key = keyAfter(node.end || node.key);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
for (let i = findLast(graph, key) - 1; cmp(key, end) >= 0 && limit > 0; i--) {
|
|
127
|
+
const node = graph[i];
|
|
128
|
+
if (!node || cmp(key, node.end || node.key) > 0 || isOlder(node, version))
|
|
129
|
+
break;
|
|
130
|
+
if (isRange(node)) {
|
|
131
|
+
result.addKnown(getOverlap(node, end, key));
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
sliceNode(node, { ...query, key }, result);
|
|
135
|
+
limit--;
|
|
136
|
+
}
|
|
137
|
+
key = keyBefore(node.key);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (limit && (step < 0 ? cmp(key, end) > 0 : cmp(key, end) < 0)) {
|
|
141
|
+
const unknown = { ...query, key, end, limit };
|
|
142
|
+
result.addUnknown(unknown);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function getOverlap(node, key, end) {
|
|
146
|
+
if (cmp(node.key, key) >= 0 && cmp(node.end, end) <= 0)
|
|
147
|
+
return node;
|
|
148
|
+
return {
|
|
149
|
+
...node,
|
|
150
|
+
key: cmp(node.key, key) > 0 ? node.key : key,
|
|
151
|
+
end: cmp(node.end, end) < 0 ? node.end : end,
|
|
152
|
+
};
|
|
153
|
+
}
|
package/ops/step.d.ts
ADDED
package/ops/step.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { addStringify, isMaxKey, isMinKey } from "../util.js";
|
|
2
|
+
export function keyStep(key) {
|
|
3
|
+
if (isMinKey(key))
|
|
4
|
+
return { key, step: 1 };
|
|
5
|
+
if (isMaxKey(key))
|
|
6
|
+
return { key, step: -1 };
|
|
7
|
+
const l = key.length - 1;
|
|
8
|
+
let newKey;
|
|
9
|
+
let step;
|
|
10
|
+
switch (key[l]) {
|
|
11
|
+
case 0:
|
|
12
|
+
newKey = key.slice(0, l);
|
|
13
|
+
addStringify(newKey);
|
|
14
|
+
step = 1;
|
|
15
|
+
break;
|
|
16
|
+
case 0xff:
|
|
17
|
+
newKey = key.slice(0, l);
|
|
18
|
+
addStringify(newKey);
|
|
19
|
+
newKey[l - 1]++;
|
|
20
|
+
step = -1;
|
|
21
|
+
break;
|
|
22
|
+
default:
|
|
23
|
+
newKey = key;
|
|
24
|
+
step = 0;
|
|
25
|
+
}
|
|
26
|
+
return { key: newKey, step };
|
|
27
|
+
}
|
|
28
|
+
export function keyBefore(key) {
|
|
29
|
+
if (isMinKey(key) || isMaxKey(key))
|
|
30
|
+
return key;
|
|
31
|
+
const l = key.length - 1;
|
|
32
|
+
let newKey;
|
|
33
|
+
if (key[l] === 0) {
|
|
34
|
+
newKey = key.slice(0, l);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
newKey = new Uint8Array(l + 2);
|
|
38
|
+
newKey.set(key, 0);
|
|
39
|
+
newKey[l]--;
|
|
40
|
+
newKey[l + 1] = 0xff;
|
|
41
|
+
}
|
|
42
|
+
addStringify(newKey);
|
|
43
|
+
return newKey;
|
|
44
|
+
}
|
|
45
|
+
export function keyAfter(key) {
|
|
46
|
+
if (isMaxKey(key))
|
|
47
|
+
return key;
|
|
48
|
+
const l = key.length - 1;
|
|
49
|
+
let newKey;
|
|
50
|
+
if (key[l] === 0xff) {
|
|
51
|
+
newKey = key.slice(0, l);
|
|
52
|
+
newKey[l - 1]++;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
newKey = new Uint8Array(l + 2);
|
|
56
|
+
newKey.set(key, 0);
|
|
57
|
+
newKey[l + 1] = 0;
|
|
58
|
+
}
|
|
59
|
+
addStringify(newKey);
|
|
60
|
+
return newKey;
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -2,23 +2,29 @@
|
|
|
2
2
|
"name": "@graffy/common",
|
|
3
3
|
"description": "Common libraries that used by various Graffy modules.",
|
|
4
4
|
"author": "aravind (https://github.com/aravindet)",
|
|
5
|
-
"version": "0.
|
|
6
|
-
"main": "./index.
|
|
5
|
+
"version": "0.19.1-alpha.1",
|
|
6
|
+
"main": "./cjs/index.js",
|
|
7
7
|
"exports": {
|
|
8
|
-
"
|
|
9
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./index.js",
|
|
10
|
+
"types": "./index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"./*": {
|
|
13
|
+
"import": "./*.js",
|
|
14
|
+
"types": "./*.d.ts"
|
|
15
|
+
}
|
|
10
16
|
},
|
|
11
|
-
"
|
|
12
|
-
"types": "./types/index.d.ts",
|
|
17
|
+
"types": "./index.d.ts",
|
|
13
18
|
"repository": {
|
|
14
19
|
"type": "git",
|
|
15
20
|
"url": "git+https://github.com/usegraffy/graffy.git"
|
|
16
21
|
},
|
|
17
22
|
"license": "Apache-2.0",
|
|
18
23
|
"dependencies": {
|
|
19
|
-
"lodash": "^4.17.23",
|
|
20
24
|
"debug": "^4.4.3",
|
|
21
|
-
"
|
|
25
|
+
"lodash": "^4.17.23",
|
|
26
|
+
"nanoid": "^5.1.6",
|
|
27
|
+
"@graffy/stream": "0.19.1-alpha.1",
|
|
22
28
|
"merge-async-iterators": "^0.2.1"
|
|
23
29
|
}
|
|
24
30
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { makeStream } from '@graffy/stream';
|
|
2
|
+
export default function makeWatcher() {
|
|
3
|
+
const listeners = new Set();
|
|
4
|
+
function write(change) {
|
|
5
|
+
for (const push of listeners)
|
|
6
|
+
push(change);
|
|
7
|
+
}
|
|
8
|
+
function watch(...args) {
|
|
9
|
+
return makeStream((push, _end) => {
|
|
10
|
+
listeners.add(push);
|
|
11
|
+
// We do this instead of binding the first value to a variable
|
|
12
|
+
// to decide between pushing undefined and not making a push.
|
|
13
|
+
if (args.length)
|
|
14
|
+
Promise.resolve(args[0]).then(push);
|
|
15
|
+
return () => listeners.delete(push);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return { write, watch };
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import mergeIterators from 'merge-async-iterators';
|
|
2
|
+
import { merge } from "../ops/index.js";
|
|
3
|
+
export default async function* mergeStreams(...streams) {
|
|
4
|
+
const firstValues = (await Promise.all(streams.map((stream) => stream.next()))).map((iter) => iter.value);
|
|
5
|
+
// If even one is a change-only stream, the result is a change-only stream
|
|
6
|
+
if (firstValues.some((value) => typeof value === 'undefined')) {
|
|
7
|
+
yield undefined;
|
|
8
|
+
for (const value of firstValues) {
|
|
9
|
+
if (typeof value !== 'undefined')
|
|
10
|
+
yield value;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
const merged = [];
|
|
15
|
+
for (const value of firstValues)
|
|
16
|
+
merge(merged, value);
|
|
17
|
+
yield merged;
|
|
18
|
+
}
|
|
19
|
+
yield* mergeIterators(streams);
|
|
20
|
+
}
|
package/util.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const MIN_KEY: Uint8Array<ArrayBuffer>;
|
|
2
|
+
export declare const MAX_KEY: Uint8Array<ArrayBuffer>;
|
|
3
|
+
export declare function isMinKey(key: any): boolean;
|
|
4
|
+
export declare function isMaxKey(key: any): boolean;
|
|
5
|
+
export declare function err(message: any, { cause, ...args }?: {
|
|
6
|
+
cause?: any;
|
|
7
|
+
}): void;
|
|
8
|
+
export declare function errIf(message: any, condition: any, args: any): void;
|
|
9
|
+
export declare function isEmpty(object: any): boolean;
|
|
10
|
+
export declare function isDef(value: any): boolean;
|
|
11
|
+
export declare function isPlainObject(arg: any): boolean;
|
|
12
|
+
export declare function clone(obj: any): any;
|
|
13
|
+
export declare function cmp(a: any, b: any): 0 | 1 | -1;
|
|
14
|
+
export declare function find(items: any, compare: any, first?: number, last?: any): number;
|
|
15
|
+
export declare function addStringify(buffer: any): any;
|
package/util.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
const isDebugMode = debug('graffy*').enabled;
|
|
3
|
+
export const MIN_KEY = new Uint8Array();
|
|
4
|
+
export const MAX_KEY = new Uint8Array([0xff]);
|
|
5
|
+
export function isMinKey(key) {
|
|
6
|
+
return key.length === 0;
|
|
7
|
+
}
|
|
8
|
+
export function isMaxKey(key) {
|
|
9
|
+
return key.length === 1 && key[0] === 0xff;
|
|
10
|
+
}
|
|
11
|
+
export function err(message, { cause = null, ...args } = {}) {
|
|
12
|
+
const e = new Error(message + (args ? ` ${JSON.stringify(args)}` : ''));
|
|
13
|
+
e.cause = cause;
|
|
14
|
+
throw e;
|
|
15
|
+
}
|
|
16
|
+
export function errIf(message, condition, args) {
|
|
17
|
+
if (condition)
|
|
18
|
+
err(message, args);
|
|
19
|
+
}
|
|
20
|
+
export function isEmpty(object) {
|
|
21
|
+
for (const _ in object)
|
|
22
|
+
return false;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
export function isDef(value) {
|
|
26
|
+
return typeof value !== 'undefined';
|
|
27
|
+
}
|
|
28
|
+
export function isPlainObject(arg) {
|
|
29
|
+
return (typeof arg === 'object' &&
|
|
30
|
+
arg &&
|
|
31
|
+
!Array.isArray(arg) &&
|
|
32
|
+
!ArrayBuffer.isView(arg));
|
|
33
|
+
}
|
|
34
|
+
export function clone(obj) {
|
|
35
|
+
if (Array.isArray(obj)) {
|
|
36
|
+
return obj.slice(0);
|
|
37
|
+
}
|
|
38
|
+
return { ...obj };
|
|
39
|
+
}
|
|
40
|
+
export function cmp(a, b) {
|
|
41
|
+
const l = a.length < b.length ? a.length : b.length;
|
|
42
|
+
for (let i = 0; i < l; i++) {
|
|
43
|
+
if (a[i] < b[i])
|
|
44
|
+
return -1;
|
|
45
|
+
if (a[i] > b[i])
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
if (a.length < b.length)
|
|
49
|
+
return -1;
|
|
50
|
+
if (a.length > b.length)
|
|
51
|
+
return 1;
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
export function find(items, compare, first = 0, last = items.length) {
|
|
55
|
+
let currentFirst = first;
|
|
56
|
+
let currentLast = last;
|
|
57
|
+
while (currentFirst < currentLast) {
|
|
58
|
+
// console.log(currentFirst, currentLast);
|
|
59
|
+
const ix = ((currentFirst + currentLast) / 2) | 0;
|
|
60
|
+
const d = compare(items[ix]);
|
|
61
|
+
// console.log(ix, items[ix], d);
|
|
62
|
+
if (d < 0) {
|
|
63
|
+
currentFirst = ix + 1;
|
|
64
|
+
}
|
|
65
|
+
else if (d > 0) {
|
|
66
|
+
currentLast = ix;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
return ix;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return currentFirst;
|
|
73
|
+
}
|
|
74
|
+
function stringify() {
|
|
75
|
+
if (this?.length === 0)
|
|
76
|
+
return '\u00b7';
|
|
77
|
+
let str = '';
|
|
78
|
+
let bull = false;
|
|
79
|
+
this?.forEach?.((value, i) => {
|
|
80
|
+
if (value >= 32 && value <= 126) {
|
|
81
|
+
str += String.fromCharCode(value);
|
|
82
|
+
bull = true;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
str +=
|
|
86
|
+
(bull ? '\u00b7' : '') +
|
|
87
|
+
`0${value.toString(16)}`.slice(-2) +
|
|
88
|
+
(i < this.length - 1 ? '\u00b7' : '');
|
|
89
|
+
bull = false;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return str;
|
|
93
|
+
}
|
|
94
|
+
const inspectSymbol = Symbol.for('nodejs.util.inspect.custom');
|
|
95
|
+
export function addStringify(buffer) {
|
|
96
|
+
if (!isDebugMode)
|
|
97
|
+
return buffer;
|
|
98
|
+
if ('toJSON' in buffer || inspectSymbol in buffer)
|
|
99
|
+
return buffer;
|
|
100
|
+
buffer.toJSON = stringify;
|
|
101
|
+
buffer.toString = stringify;
|
|
102
|
+
buffer[inspectSymbol] = stringify;
|
|
103
|
+
return buffer;
|
|
104
|
+
}
|
|
105
|
+
addStringify(MIN_KEY);
|
|
106
|
+
addStringify(MAX_KEY);
|