@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
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import isEqual from 'lodash/isEqual.js';
|
|
2
|
+
import { add, merge, setVersion, wrap } from "../ops/index.js";
|
|
3
|
+
import { clone, cmp, isDef, isEmpty, isPlainObject, MAX_KEY, MIN_KEY, } from "../util.js";
|
|
4
|
+
import { encode as encodeArgs, splitArgs } from "./args.js";
|
|
5
|
+
import { encode as encodePath, splitRef } from "./path.js";
|
|
6
|
+
const ROOT_KEY = Symbol();
|
|
7
|
+
/**
|
|
8
|
+
@param {any} value
|
|
9
|
+
@param {{version?: number, isGraph?: boolean}} options
|
|
10
|
+
*/
|
|
11
|
+
function encode(value, { version, isGraph } = {}) {
|
|
12
|
+
const links = [];
|
|
13
|
+
function pushLink($ref, $ver, props, $val, $chi) {
|
|
14
|
+
const [range, _] = splitRef($ref);
|
|
15
|
+
const node = !isEmpty(props)
|
|
16
|
+
? makeNode(range ? [{ $key: range, ...props }] : props, undefined, $ver)
|
|
17
|
+
: isDef($chi)
|
|
18
|
+
? makeNode(range ? [{ $key: range, $chi }] : $chi, undefined, $ver)
|
|
19
|
+
: null;
|
|
20
|
+
// biome-ignore format: ternary chain
|
|
21
|
+
const children = node ? node.children :
|
|
22
|
+
isDef($val) ? $val :
|
|
23
|
+
isGraph ? undefined : 1;
|
|
24
|
+
if (children) {
|
|
25
|
+
links.push(wrap(children, encodePath($ref), $ver, !!range)[0]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const combine = isGraph ? merge : add;
|
|
29
|
+
function makeNode(object, key, ver, parentPuts = []) {
|
|
30
|
+
if (!isDef(object))
|
|
31
|
+
return;
|
|
32
|
+
const { $key, $ver, $ref, $val, $chi, $put, ...props } = object || {};
|
|
33
|
+
// Turn any non-enumerable properties of object into enumerable,
|
|
34
|
+
// so they're included in ...object below.
|
|
35
|
+
if (typeof object === 'object' && object && !Array.isArray(object)) {
|
|
36
|
+
object = {
|
|
37
|
+
...Object.fromEntries(Object.entries({
|
|
38
|
+
$key,
|
|
39
|
+
$ver,
|
|
40
|
+
$ref,
|
|
41
|
+
$val,
|
|
42
|
+
$chi,
|
|
43
|
+
$put,
|
|
44
|
+
}).filter(([_, val]) => isDef(val))),
|
|
45
|
+
...props,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// An empty object is considered equivalent to undefined.
|
|
49
|
+
if (typeof object === 'object' && object && isEmpty(object))
|
|
50
|
+
return;
|
|
51
|
+
if (isDef($ver))
|
|
52
|
+
ver = $ver;
|
|
53
|
+
if (isPlainObject($key)) {
|
|
54
|
+
const [page, filter] = splitArgs($key);
|
|
55
|
+
if (page) {
|
|
56
|
+
/*
|
|
57
|
+
CONSTRUCT PREFIX NODES
|
|
58
|
+
When we encounter a range key in a graph, it means one of these:
|
|
59
|
+
1. an empty range, e.g. { $key: { $after: 'foo' } }
|
|
60
|
+
2. a range reference, e.g.
|
|
61
|
+
{ $key: { x: 1, $all: true }, $ref: ['foo', { $all: true }] }
|
|
62
|
+
3. as a shortcut to avoid repetition in query results e.g.
|
|
63
|
+
{ $key: { x: 1, $all: true }, $chi: [
|
|
64
|
+
{ $key: 'a', $val: 'A' },
|
|
65
|
+
{ $key: 'b', $val: 'B' } ] }
|
|
66
|
+
is equivalent to:
|
|
67
|
+
[ { $key: { x: 1, $before: 'a' } },
|
|
68
|
+
{ $key: { x: 1, $cursor: 'a' }, $val: 'A' },
|
|
69
|
+
{ $key: { x: 1, $after: 'a', $before: 'b' } },
|
|
70
|
+
{ $key: { x: 1, $cursor: 'b' }, $val: 'B' } ]
|
|
71
|
+
|
|
72
|
+
Cases 2. and 3. are handled below: Basically we strip out the "page"
|
|
73
|
+
part from the key (leaving only the filter), construct a node with that,
|
|
74
|
+
then add the "prefix" flag to the node.
|
|
75
|
+
|
|
76
|
+
The page part is passed as $put for constructing children (when it's a
|
|
77
|
+
graph with children)
|
|
78
|
+
*/
|
|
79
|
+
const foundPuts = parentPuts
|
|
80
|
+
.filter(([_, putFilter]) => isEqual(filter, putFilter))
|
|
81
|
+
.map(([range]) => range);
|
|
82
|
+
if (isGraph &&
|
|
83
|
+
!isDef(page.$cursor) &&
|
|
84
|
+
($ref || $val || $chi || $put || !isEmpty(props))) {
|
|
85
|
+
object.$key = filter || {};
|
|
86
|
+
object.$put = foundPuts;
|
|
87
|
+
const node = makeNode(object, key, ver);
|
|
88
|
+
if (!filter)
|
|
89
|
+
node.key = MIN_KEY;
|
|
90
|
+
node.prefix = true;
|
|
91
|
+
return node;
|
|
92
|
+
}
|
|
93
|
+
if ((!isDef(key) || Number.isInteger(key)) &&
|
|
94
|
+
(filter || isDef(page.$cursor))) {
|
|
95
|
+
object.$key = isDef(page.$cursor) ? page.$cursor : page;
|
|
96
|
+
const wrapper = {
|
|
97
|
+
$key: filter || {},
|
|
98
|
+
$chi: [object],
|
|
99
|
+
};
|
|
100
|
+
if (isGraph)
|
|
101
|
+
wrapper.$put = foundPuts;
|
|
102
|
+
const node = makeNode(wrapper, key, ver);
|
|
103
|
+
if (!filter)
|
|
104
|
+
node.key = MIN_KEY;
|
|
105
|
+
node.prefix = true;
|
|
106
|
+
return node;
|
|
107
|
+
}
|
|
108
|
+
// console.log('No prefix made', { key, $key, object });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
let putRange = [];
|
|
112
|
+
const prefixPuts = [];
|
|
113
|
+
// If this is a plain array (without keyed objects), we should "put" the
|
|
114
|
+
// entire positive integer range to give it atomic write behavior.
|
|
115
|
+
if (Array.isArray(object) &&
|
|
116
|
+
!isDef($put) &&
|
|
117
|
+
!isDef($val) &&
|
|
118
|
+
!object.some((it) => isDef(it?.$key))) {
|
|
119
|
+
putRange = [encodeArgs({ $since: 0, $until: Number.POSITIVE_INFINITY })];
|
|
120
|
+
}
|
|
121
|
+
function classifyPut(put) {
|
|
122
|
+
const [range, filter] = splitArgs(put);
|
|
123
|
+
if (filter) {
|
|
124
|
+
prefixPuts.push([range, filter]);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
putRange.push(encodeArgs(put));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if ($put === true) {
|
|
131
|
+
putRange = [{ key: MIN_KEY, end: MAX_KEY }];
|
|
132
|
+
}
|
|
133
|
+
else if (Array.isArray($put)) {
|
|
134
|
+
$put.forEach(classifyPut);
|
|
135
|
+
}
|
|
136
|
+
else if (isDef($put)) {
|
|
137
|
+
classifyPut($put);
|
|
138
|
+
}
|
|
139
|
+
if (isDef($key) && (Number.isInteger(key) || !isDef(key)))
|
|
140
|
+
key = $key;
|
|
141
|
+
const node = key === ROOT_KEY || !isDef(key) ? {} : encodeArgs(key);
|
|
142
|
+
// console.log('Set version', node.key, ver);
|
|
143
|
+
node.version = ver;
|
|
144
|
+
// console.log('Constructed', { node, $key, key });
|
|
145
|
+
if (object === null) {
|
|
146
|
+
node.end = node.key;
|
|
147
|
+
}
|
|
148
|
+
else if (isDef($key) && isDef(key) && key !== $key) {
|
|
149
|
+
// An array has been omitted because there is only one child.
|
|
150
|
+
node.children = [makeNode(object, undefined, ver, prefixPuts)].filter(Boolean);
|
|
151
|
+
// We don't want to add a $put at this level.
|
|
152
|
+
return node;
|
|
153
|
+
}
|
|
154
|
+
else if ($ref) {
|
|
155
|
+
pushLink($ref, node.version, props, $val, $chi);
|
|
156
|
+
if (!isGraph)
|
|
157
|
+
return; // Drop query aliases from encoded format
|
|
158
|
+
node.path = encodePath($ref);
|
|
159
|
+
}
|
|
160
|
+
else if ($val === true) {
|
|
161
|
+
node.value = Array.isArray(object) ? clone(object) : props;
|
|
162
|
+
}
|
|
163
|
+
else if (isDef($val)) {
|
|
164
|
+
node.value = $val;
|
|
165
|
+
}
|
|
166
|
+
else if (typeof object !== 'object') {
|
|
167
|
+
node.value = isGraph || typeof object === 'number' ? object : 1;
|
|
168
|
+
}
|
|
169
|
+
else if (isDef($chi)) {
|
|
170
|
+
const children = $chi
|
|
171
|
+
.map((obj) => makeNode(obj, undefined, ver, prefixPuts))
|
|
172
|
+
.filter(Boolean)
|
|
173
|
+
.sort((a, b) => cmp(a.key, b.key));
|
|
174
|
+
if (children.length) {
|
|
175
|
+
node.children = children;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else if (Array.isArray(object)) {
|
|
179
|
+
const children = object
|
|
180
|
+
.map((obj, i) => makeNode(obj, i, ver, prefixPuts))
|
|
181
|
+
.filter(Boolean)
|
|
182
|
+
.reduce((acc, it) => {
|
|
183
|
+
combine(acc, [it]);
|
|
184
|
+
return acc;
|
|
185
|
+
}, []);
|
|
186
|
+
if (children.length) {
|
|
187
|
+
node.children = children;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const children = Object.keys(props)
|
|
192
|
+
.sort()
|
|
193
|
+
.map((key) => makeNode(object[key], key, ver))
|
|
194
|
+
.filter(Boolean);
|
|
195
|
+
if (children.length) {
|
|
196
|
+
node.children = children;
|
|
197
|
+
}
|
|
198
|
+
else if (putRange.length) {
|
|
199
|
+
// Do nothing; this is to avoid falling into the later branches
|
|
200
|
+
}
|
|
201
|
+
else if (isGraph) {
|
|
202
|
+
// Some inconsistency here.
|
|
203
|
+
// { foo: {} } === undefined (we know nothing)
|
|
204
|
+
// but { $key: 'foo' }] === { foo: null } (we know foo doesn't exist)
|
|
205
|
+
// This is because when using the $key notation, we can't use null.
|
|
206
|
+
if (!(isDef($key) || isDef($put)))
|
|
207
|
+
return;
|
|
208
|
+
if (node.key && !node.end)
|
|
209
|
+
node.end = node.key;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
if (!isDef($key))
|
|
213
|
+
return;
|
|
214
|
+
node.value = 1;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (isGraph && putRange.length && !isDef(node.path) && !isDef(node.value)) {
|
|
218
|
+
const putRangeClone = putRange.map(({ key, end }) => ({
|
|
219
|
+
key,
|
|
220
|
+
end,
|
|
221
|
+
version: 0,
|
|
222
|
+
}));
|
|
223
|
+
node.children = merge(putRangeClone, node.children || []);
|
|
224
|
+
}
|
|
225
|
+
if (
|
|
226
|
+
// (key === ROOT_KEY || isDef(node.key)) &&
|
|
227
|
+
node.children?.length ||
|
|
228
|
+
isDef(node.end) ||
|
|
229
|
+
isDef(node.value) ||
|
|
230
|
+
isDef(node.path)) {
|
|
231
|
+
return node;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (value?.$key)
|
|
235
|
+
value = [value];
|
|
236
|
+
const result = makeNode(value, ROOT_KEY, version)?.children || [];
|
|
237
|
+
while (links.length) {
|
|
238
|
+
combine(result, [links.pop()]);
|
|
239
|
+
}
|
|
240
|
+
// console.log('Encoded', isGraph, value);
|
|
241
|
+
// console.log('Result', result);
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
export function encodeGraph(obj, version = Date.now()) {
|
|
245
|
+
const encoded = encode(obj, { version, isGraph: true });
|
|
246
|
+
const versioned = setVersion(encoded, version, true);
|
|
247
|
+
return versioned;
|
|
248
|
+
}
|
|
249
|
+
export function encodeQuery(obj, version = 0) {
|
|
250
|
+
return encode(obj, { version, isGraph: false });
|
|
251
|
+
}
|
package/coding/id.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { decode as decodeArgs, encode as encodeArgs, splitArgs, } from './args.js';
|
|
2
|
+
export * from './decodeTree.js';
|
|
3
|
+
export { default as decorate } from './decorate.js';
|
|
4
|
+
export * from './encodeTree.js';
|
|
5
|
+
export { default as makeId } from './id.js';
|
|
6
|
+
export * from './pack.js';
|
|
7
|
+
export { decode as decodePath, encode as encodePath, splitRef, } from './path.js';
|
|
8
|
+
export { decode as decodeValue, encode as encodeValue } from './struct.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
export { decode as decodeArgs, encode as encodeArgs, splitArgs, } from "./args.js";
|
|
1
2
|
export * from "./decodeTree.js";
|
|
2
|
-
export * from "./encodeTree.js";
|
|
3
|
-
export * from "./pack.js";
|
|
4
3
|
export { default as decorate } from "./decorate.js";
|
|
4
|
+
export * from "./encodeTree.js";
|
|
5
5
|
export { default as makeId } from "./id.js";
|
|
6
|
-
export
|
|
7
|
-
export { decode as decodePath, encode as encodePath, splitRef } from "./path.js";
|
|
6
|
+
export * from "./pack.js";
|
|
7
|
+
export { decode as decodePath, encode as encodePath, splitRef, } from "./path.js";
|
|
8
8
|
export { decode as decodeValue, encode as encodeValue } from "./struct.js";
|
package/coding/number.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Order-preserving encoding of 64-bit floating point numbers.
|
|
3
|
+
*/
|
|
4
|
+
function TwosComplement(view) {
|
|
5
|
+
const lo = -view.getUint32(4) >>> 0;
|
|
6
|
+
const carry = lo ? 0 : -1;
|
|
7
|
+
const hi = (~view.getUint32(0) + carry) >>> 0;
|
|
8
|
+
view.setUint32(0, hi);
|
|
9
|
+
view.setUint32(4, lo);
|
|
10
|
+
}
|
|
11
|
+
export function encode(number) {
|
|
12
|
+
const buffer = new ArrayBuffer(8);
|
|
13
|
+
const view = new DataView(buffer);
|
|
14
|
+
view.setFloat64(0, number);
|
|
15
|
+
/* if first bit is set */
|
|
16
|
+
if (number < 0) {
|
|
17
|
+
TwosComplement(view);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
/* non-negative number, just set the leading bit. */
|
|
21
|
+
view.setUint8(0, view.getUint8(0) | 0x80);
|
|
22
|
+
}
|
|
23
|
+
return new Uint8Array(buffer);
|
|
24
|
+
}
|
|
25
|
+
export function decode(u8Arr) {
|
|
26
|
+
const copy = new Uint8Array(8);
|
|
27
|
+
copy.set(u8Arr, 0);
|
|
28
|
+
const { buffer, byteOffset, byteLength } = copy;
|
|
29
|
+
const view = new DataView(buffer, byteOffset, byteLength);
|
|
30
|
+
const high = view.getUint8(0);
|
|
31
|
+
if (high & 0x80) {
|
|
32
|
+
// originally a non-negative number. Just set the sign bit back to 0.
|
|
33
|
+
view.setUint8(0, high & 0x7f);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
TwosComplement(view);
|
|
37
|
+
}
|
|
38
|
+
return view.getFloat64(0);
|
|
39
|
+
}
|
package/coding/pack.d.ts
ADDED
package/coding/pack.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { decode, encode } from "./base64.js";
|
|
2
|
+
import { decodeValue, encodeValue } from "./index.js";
|
|
3
|
+
import { STR } from "./struct.js";
|
|
4
|
+
const props = [
|
|
5
|
+
'end',
|
|
6
|
+
'version',
|
|
7
|
+
'limit',
|
|
8
|
+
'value',
|
|
9
|
+
'path',
|
|
10
|
+
'prefix',
|
|
11
|
+
'children',
|
|
12
|
+
];
|
|
13
|
+
function serializeKey(key) {
|
|
14
|
+
if (key[0] === STR) {
|
|
15
|
+
const last = key[key.length - 1];
|
|
16
|
+
if (last !== 0 && last !== 0xff) {
|
|
17
|
+
return decodeValue(key);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return `\0${encode(key)}`;
|
|
21
|
+
}
|
|
22
|
+
function deserializeKey(key) {
|
|
23
|
+
if (key[0] === '\0')
|
|
24
|
+
return decode(key.slice(1));
|
|
25
|
+
return encodeValue(key);
|
|
26
|
+
}
|
|
27
|
+
export function pack(children, parentVersion) {
|
|
28
|
+
if (!Array.isArray(children))
|
|
29
|
+
return children;
|
|
30
|
+
const array = children.map((node) => props.reduce((array, prop, i) => {
|
|
31
|
+
if (!(prop in node))
|
|
32
|
+
return array;
|
|
33
|
+
let value = node[prop];
|
|
34
|
+
if (prop === 'version' && value === parentVersion)
|
|
35
|
+
return array;
|
|
36
|
+
if (prop === 'children')
|
|
37
|
+
value = pack(value, node.version);
|
|
38
|
+
if (prop === 'end')
|
|
39
|
+
value = serializeKey(value);
|
|
40
|
+
if (prop === 'path')
|
|
41
|
+
value = value.map(serializeKey);
|
|
42
|
+
array[1] |= 1 << i;
|
|
43
|
+
array.push(value);
|
|
44
|
+
return array;
|
|
45
|
+
}, [serializeKey(node.key), 0]));
|
|
46
|
+
return array;
|
|
47
|
+
}
|
|
48
|
+
export function unpack(children, parentVersion) {
|
|
49
|
+
if (!Array.isArray(children))
|
|
50
|
+
return children;
|
|
51
|
+
const node = children.map(([key, type, ...values]) => props.reduce((node, prop, i) => {
|
|
52
|
+
if (!(type & (1 << i)))
|
|
53
|
+
return node;
|
|
54
|
+
let value = values.shift();
|
|
55
|
+
if (prop === 'children')
|
|
56
|
+
value = unpack(value, node.version);
|
|
57
|
+
if (prop === 'end')
|
|
58
|
+
value = deserializeKey(value);
|
|
59
|
+
if (prop === 'path')
|
|
60
|
+
value = value.map(deserializeKey);
|
|
61
|
+
node[prop] = value;
|
|
62
|
+
return node;
|
|
63
|
+
}, { key: deserializeKey(key), version: parentVersion }));
|
|
64
|
+
return node;
|
|
65
|
+
}
|
|
66
|
+
// export function serialize(payload) {
|
|
67
|
+
// return JSON.stringify(pack(payload));
|
|
68
|
+
// }
|
|
69
|
+
// export function deserialize(str) {
|
|
70
|
+
// return unpack(JSON.parse(str));
|
|
71
|
+
// }
|
package/coding/path.d.ts
ADDED
package/coding/path.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { isPlainObject, MIN_KEY } from "../util.js";
|
|
2
|
+
import { decode as decodeArgs, encode as encodeArgs, splitArgs, } from "./args.js";
|
|
3
|
+
const PATH_SEPARATOR = '.';
|
|
4
|
+
export function encode(path) {
|
|
5
|
+
if (typeof path === 'string') {
|
|
6
|
+
if (!path.length || path === PATH_SEPARATOR)
|
|
7
|
+
return [];
|
|
8
|
+
path = path.split(PATH_SEPARATOR);
|
|
9
|
+
}
|
|
10
|
+
if (!Array.isArray(path)) {
|
|
11
|
+
throw Error(`encodePath.invalid:${JSON.stringify(path)}`);
|
|
12
|
+
}
|
|
13
|
+
function encodeSegment(seg) {
|
|
14
|
+
if (ArrayBuffer.isView(seg))
|
|
15
|
+
return seg;
|
|
16
|
+
const node = encodeArgs(seg);
|
|
17
|
+
if (node.end)
|
|
18
|
+
return node;
|
|
19
|
+
return node.key;
|
|
20
|
+
}
|
|
21
|
+
if (isPlainObject(path[path.length - 1])) {
|
|
22
|
+
const [page, filter] = splitArgs(path[path.length - 1]);
|
|
23
|
+
if (page)
|
|
24
|
+
path = path.slice(0, -1).concat([filter || MIN_KEY]);
|
|
25
|
+
}
|
|
26
|
+
return path.map(encodeSegment);
|
|
27
|
+
}
|
|
28
|
+
export function decode(path) {
|
|
29
|
+
if (!Array.isArray(path)) {
|
|
30
|
+
throw Error(`decodePath.invalid:${JSON.stringify(path)}`);
|
|
31
|
+
}
|
|
32
|
+
return path.map((key) => decodeArgs({ key }));
|
|
33
|
+
}
|
|
34
|
+
export function splitRef($ref) {
|
|
35
|
+
if (!Array.isArray($ref))
|
|
36
|
+
return [];
|
|
37
|
+
const tail = $ref[$ref.length - 1];
|
|
38
|
+
if (!isPlainObject(tail))
|
|
39
|
+
return [];
|
|
40
|
+
return splitArgs(tail);
|
|
41
|
+
}
|
package/coding/string.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const END = 0;
|
|
2
|
+
export declare const NULL = 1;
|
|
3
|
+
export declare const FALSE = 2;
|
|
4
|
+
export declare const TRUE = 3;
|
|
5
|
+
export declare const NUM = 4;
|
|
6
|
+
export declare const STR = 5;
|
|
7
|
+
export declare const ARR = 6;
|
|
8
|
+
export declare const OBJ = 7;
|
|
9
|
+
export declare const EOK = 127;
|
|
10
|
+
export declare function encode(value: any): Uint8Array<any>;
|
|
11
|
+
export declare function decode(buffer: any): any;
|
package/coding/struct.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { addStringify } from "../util.js";
|
|
2
|
+
import { decode as decodeNumber, encode as encodeNumber } from "./number.js";
|
|
3
|
+
import { decode as decodeString, encode as encodeString } from "./string.js";
|
|
4
|
+
/*
|
|
5
|
+
Sortable encoding of JSON objects for Graffy keys.
|
|
6
|
+
|
|
7
|
+
The constraints are:
|
|
8
|
+
- Sorting a byte stream should
|
|
9
|
+
*/
|
|
10
|
+
export const END = 0;
|
|
11
|
+
export const NULL = 1;
|
|
12
|
+
export const FALSE = 2;
|
|
13
|
+
export const TRUE = 3;
|
|
14
|
+
export const NUM = 4;
|
|
15
|
+
export const STR = 5;
|
|
16
|
+
export const ARR = 6;
|
|
17
|
+
export const OBJ = 7;
|
|
18
|
+
export const EOK = 127; // end-of-key
|
|
19
|
+
function encodeArray(array) {
|
|
20
|
+
return [ARR, ...array.flatMap((value) => encodeParts(value)), END];
|
|
21
|
+
}
|
|
22
|
+
function encodeObject(object) {
|
|
23
|
+
const keys = Object.keys(object).sort();
|
|
24
|
+
return [
|
|
25
|
+
OBJ,
|
|
26
|
+
...keys.flatMap((key) => [
|
|
27
|
+
STR,
|
|
28
|
+
encodeString(key),
|
|
29
|
+
END,
|
|
30
|
+
...encodeParts(object[key]),
|
|
31
|
+
]),
|
|
32
|
+
END,
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
function encodeParts(value) {
|
|
36
|
+
if (value === null)
|
|
37
|
+
return [NULL];
|
|
38
|
+
if (value === false)
|
|
39
|
+
return [FALSE];
|
|
40
|
+
if (value === true)
|
|
41
|
+
return [TRUE];
|
|
42
|
+
if (typeof value === 'number')
|
|
43
|
+
return [NUM, encodeNumber(value)];
|
|
44
|
+
if (typeof value === 'string')
|
|
45
|
+
return [STR, encodeString(value), END];
|
|
46
|
+
if (Array.isArray(value))
|
|
47
|
+
return encodeArray(value);
|
|
48
|
+
if (typeof value === 'object')
|
|
49
|
+
return encodeObject(value);
|
|
50
|
+
return [NULL];
|
|
51
|
+
}
|
|
52
|
+
export function encode(value) {
|
|
53
|
+
const parts = encodeParts(value);
|
|
54
|
+
// Ensure that there are no trailing zeros, so keyBefore() can work.
|
|
55
|
+
// decode() handles this by assuming as many trailing
|
|
56
|
+
// zeros as necessary.
|
|
57
|
+
while (parts[parts.length - 1] === END)
|
|
58
|
+
parts.pop();
|
|
59
|
+
// The last byte may occasionally a 0 or 255, which conflicts with keyBefore
|
|
60
|
+
// and keyAfter. This usually happens when the final part encodes a positive
|
|
61
|
+
// or negative integer. In such cases, we try to remove trailing zeros, and
|
|
62
|
+
// if that doesn't do it (we have a trailing 255) we instead append a dummy
|
|
63
|
+
// suffix that will be ignored by decode.
|
|
64
|
+
const lastPart = parts[parts.length - 1];
|
|
65
|
+
if (typeof lastPart !== 'number') {
|
|
66
|
+
let end = lastPart.length - 1;
|
|
67
|
+
while (end >= 0 && !lastPart[end])
|
|
68
|
+
end--;
|
|
69
|
+
if (lastPart[end] !== 0xff) {
|
|
70
|
+
parts[parts.length - 1] = lastPart.slice(0, end + 1);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
parts.push(EOK);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const length = parts.reduce((sum, part) => sum + (typeof part === 'number' ? 1 : part.length), 0);
|
|
77
|
+
const buffer = new Uint8Array(length);
|
|
78
|
+
let i = 0;
|
|
79
|
+
for (const part of parts) {
|
|
80
|
+
if (typeof part === 'number') {
|
|
81
|
+
buffer[i] = part;
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
buffer.set(part, i);
|
|
86
|
+
i += part.length;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
addStringify(buffer);
|
|
90
|
+
return buffer;
|
|
91
|
+
}
|
|
92
|
+
const nextKey = new WeakMap();
|
|
93
|
+
export function decode(buffer) {
|
|
94
|
+
let i = 0;
|
|
95
|
+
const stack = [[]];
|
|
96
|
+
function readString() {
|
|
97
|
+
const start = i;
|
|
98
|
+
while (i < buffer.length && buffer[i] !== END)
|
|
99
|
+
i++;
|
|
100
|
+
const str = decodeString(buffer.subarray(start, i));
|
|
101
|
+
i++;
|
|
102
|
+
return str;
|
|
103
|
+
}
|
|
104
|
+
function pushToken(type, value) {
|
|
105
|
+
const current = stack[stack.length - 1];
|
|
106
|
+
if (type === ARR || type === OBJ)
|
|
107
|
+
stack.push(value);
|
|
108
|
+
if (!current)
|
|
109
|
+
return;
|
|
110
|
+
if (Array.isArray(current)) {
|
|
111
|
+
current.push(value);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
if (nextKey.has(current)) {
|
|
115
|
+
current[nextKey.get(current)] = value;
|
|
116
|
+
nextKey.delete(current);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
nextKey.set(current, value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function popToken() {
|
|
124
|
+
stack.pop();
|
|
125
|
+
}
|
|
126
|
+
while (i < buffer.length) {
|
|
127
|
+
const type = buffer[i];
|
|
128
|
+
const start = ++i;
|
|
129
|
+
switch (type) {
|
|
130
|
+
case EOK:
|
|
131
|
+
return stack[0][0];
|
|
132
|
+
case END:
|
|
133
|
+
popToken();
|
|
134
|
+
break;
|
|
135
|
+
case NULL:
|
|
136
|
+
pushToken(type, null);
|
|
137
|
+
break;
|
|
138
|
+
case FALSE:
|
|
139
|
+
pushToken(type, false);
|
|
140
|
+
break;
|
|
141
|
+
case TRUE:
|
|
142
|
+
pushToken(type, true);
|
|
143
|
+
break;
|
|
144
|
+
case NUM:
|
|
145
|
+
i += 8;
|
|
146
|
+
pushToken(type, decodeNumber(buffer.subarray(start, i)));
|
|
147
|
+
break;
|
|
148
|
+
case STR:
|
|
149
|
+
pushToken(type, readString());
|
|
150
|
+
break;
|
|
151
|
+
case ARR:
|
|
152
|
+
pushToken(type, []);
|
|
153
|
+
break;
|
|
154
|
+
case OBJ:
|
|
155
|
+
pushToken(type, {});
|
|
156
|
+
break;
|
|
157
|
+
default:
|
|
158
|
+
throw new Error(`Invalid byte ${type} at ${start}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return stack[0][0];
|
|
162
|
+
}
|
package/index.d.ts
ADDED
package/node/find.d.ts
ADDED
package/node/find.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { cmp, find } from "../util.js";
|
|
2
|
+
export function findFirst(children, target, first = 0, last = children.length) {
|
|
3
|
+
return find(children, ({ key, end }) => {
|
|
4
|
+
const keyCmp = cmp(key, target);
|
|
5
|
+
const endCmp = end && cmp(end, target);
|
|
6
|
+
if (end && keyCmp < 0 && endCmp >= 0)
|
|
7
|
+
return 0;
|
|
8
|
+
return keyCmp;
|
|
9
|
+
}, first, last);
|
|
10
|
+
}
|
|
11
|
+
export function findLast(children, end, first = 0, last = children.length) {
|
|
12
|
+
const ix = findFirst(children, end, first, last);
|
|
13
|
+
return children[ix] && cmp(children[ix].key, end) <= 0 ? ix + 1 : ix;
|
|
14
|
+
}
|
package/node/index.d.ts
ADDED
package/node/types.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function isRange(node: any): boolean;
|
|
2
|
+
export declare function isBranch(node: any): boolean;
|
|
3
|
+
export declare function isPrefix(node: any): any;
|
|
4
|
+
export declare function isLink(node: any): boolean;
|
|
5
|
+
export declare function isOlder(node: any, version: any): boolean;
|
|
6
|
+
export declare function isNewer(node: any, version: any): boolean;
|