@sanity/mutator 6.7.0-next.5 → 6.7.0-next.50
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/lib/index.d.ts +2 -1
- package/lib/index.js +1652 -1795
- package/lib/index.js.map +1 -1
- package/package.json +9 -14
package/lib/index.js
CHANGED
|
@@ -2,1863 +2,1720 @@ import isEqual from "lodash-es/isEqual.js";
|
|
|
2
2
|
import debugIt from "debug";
|
|
3
3
|
import compact from "lodash-es/compact.js";
|
|
4
4
|
import flatten from "lodash-es/flatten.js";
|
|
5
|
-
import {
|
|
5
|
+
import { applyPatches, makePatches, parsePatch, stringifyPatches } from "@sanity/diff-match-patch";
|
|
6
6
|
import max from "lodash-es/max.js";
|
|
7
7
|
import min from "lodash-es/min.js";
|
|
8
8
|
import { uuid } from "@sanity/uuid";
|
|
9
9
|
const debug = debugIt("mutator-document");
|
|
10
10
|
function isRecord$1(value) {
|
|
11
|
-
|
|
11
|
+
return typeof value == "object" && !!value;
|
|
12
12
|
}
|
|
13
13
|
const IS_DOTTABLE = /^[a-z_$]+/;
|
|
14
|
+
/**
|
|
15
|
+
* Converts a path in array form to a JSONPath string
|
|
16
|
+
*
|
|
17
|
+
* @param pathArray - Array of path segments
|
|
18
|
+
* @returns String representation of the path
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
14
21
|
function arrayToJSONMatchPath(pathArray) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
let path = "";
|
|
23
|
+
return pathArray.forEach((segment, index) => {
|
|
24
|
+
path += stringifySegment(segment, index === 0);
|
|
25
|
+
}), path;
|
|
19
26
|
}
|
|
20
27
|
function stringifySegment(segment, hasLeading) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return typeof segment == "string" && IS_DOTTABLE.test(segment) ? hasLeading ? segment : `.${segment}` : `['${segment}']`;
|
|
28
|
+
if (typeof segment == "number") return `[${segment}]`;
|
|
29
|
+
if (isRecord$1(segment)) {
|
|
30
|
+
let seg = segment;
|
|
31
|
+
return Object.keys(segment).map((key) => isPrimitiveValue(seg[key]) ? `[${key}=="${seg[key]}"]` : "").join("");
|
|
32
|
+
}
|
|
33
|
+
return typeof segment == "string" && IS_DOTTABLE.test(segment) ? hasLeading ? segment : `.${segment}` : `['${segment}']`;
|
|
28
34
|
}
|
|
29
35
|
function isPrimitiveValue(val) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return !1;
|
|
37
|
-
}
|
|
36
|
+
switch (typeof val) {
|
|
37
|
+
case "number":
|
|
38
|
+
case "string":
|
|
39
|
+
case "boolean": return !0;
|
|
40
|
+
default: return !1;
|
|
41
|
+
}
|
|
38
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Splits an expression into a set of heads, tails. A head is the next leaf node to
|
|
45
|
+
* check for matches, and a tail is everything that follows it. Matching is done by
|
|
46
|
+
* matching heads, then proceedint to the matching value, splitting the tail into
|
|
47
|
+
* heads and tails and checking the heads against the new value, and so on.
|
|
48
|
+
*/
|
|
39
49
|
function descend$1(tail) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return spreadIfUnionHead(head, newTail);
|
|
50
|
+
let [head, newTail] = splitIfPath(tail);
|
|
51
|
+
if (!head) throw Error("Head cannot be null");
|
|
52
|
+
return spreadIfUnionHead(head, newTail);
|
|
44
53
|
}
|
|
45
54
|
function splitIfPath(tail) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
if (tail.type !== "path") return [tail, null];
|
|
56
|
+
let nodes = tail.nodes;
|
|
57
|
+
return nodes.length === 0 ? [null, null] : nodes.length === 1 ? [nodes[0], null] : [nodes[0], {
|
|
58
|
+
type: "path",
|
|
59
|
+
nodes: nodes.slice(1)
|
|
60
|
+
}];
|
|
50
61
|
}
|
|
51
62
|
function concatPaths(path1, path2) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
};
|
|
63
|
+
if (!path1 && !path2) return null;
|
|
64
|
+
let nodes1 = path1 ? path1.nodes : [], nodes2 = path2 ? path2.nodes : [];
|
|
65
|
+
return {
|
|
66
|
+
type: "path",
|
|
67
|
+
nodes: nodes1.concat(nodes2)
|
|
68
|
+
};
|
|
59
69
|
}
|
|
60
70
|
function spreadIfUnionHead(head, tail) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
return head.type === "union" ? head.nodes.map((node) => {
|
|
72
|
+
if (node.type === "path") {
|
|
73
|
+
let [subHead, subTail] = splitIfPath(node);
|
|
74
|
+
return [subHead, concatPaths(subTail, tail)];
|
|
75
|
+
}
|
|
76
|
+
return [node, tail];
|
|
77
|
+
}) : [[head, tail]];
|
|
68
78
|
}
|
|
69
79
|
const digitChar = /[0-9]/, attributeCharMatcher = /^[a-zA-Z0-9_]$/, attributeFirstCharMatcher = /^[a-zA-Z_]$/, symbols = {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
operator: [
|
|
81
|
+
"..",
|
|
82
|
+
".",
|
|
83
|
+
",",
|
|
84
|
+
":",
|
|
85
|
+
"?"
|
|
86
|
+
],
|
|
87
|
+
comparator: [
|
|
88
|
+
">=",
|
|
89
|
+
"<=",
|
|
90
|
+
"<",
|
|
91
|
+
">",
|
|
92
|
+
"==",
|
|
93
|
+
"!="
|
|
94
|
+
],
|
|
95
|
+
keyword: ["$", "@"],
|
|
96
|
+
boolean: ["true", "false"],
|
|
97
|
+
paren: ["[", "]"]
|
|
77
98
|
}, symbolClasses = Object.keys(symbols);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
tokenizeSymbol() {
|
|
180
|
-
for (const symbolClass of symbolClasses) {
|
|
181
|
-
const symbol = symbols[symbolClass].find((pattern) => this.tryConsume(pattern));
|
|
182
|
-
if (symbol)
|
|
183
|
-
return {
|
|
184
|
-
type: symbolClass,
|
|
185
|
-
symbol
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
99
|
+
/**
|
|
100
|
+
* Tokenizes a jsonpath2 expression
|
|
101
|
+
*/
|
|
102
|
+
var Tokenizer = class {
|
|
103
|
+
source;
|
|
104
|
+
i;
|
|
105
|
+
length;
|
|
106
|
+
tokenizers;
|
|
107
|
+
constructor(path) {
|
|
108
|
+
this.source = path, this.length = path.length, this.i = 0, this.tokenizers = [
|
|
109
|
+
this.tokenizeSymbol,
|
|
110
|
+
this.tokenizeIdentifier,
|
|
111
|
+
this.tokenizeNumber,
|
|
112
|
+
this.tokenizeQuoted
|
|
113
|
+
].map((fn) => fn.bind(this));
|
|
114
|
+
}
|
|
115
|
+
tokenize() {
|
|
116
|
+
let result = [];
|
|
117
|
+
for (; !this.EOF();) {
|
|
118
|
+
this.chompWhitespace();
|
|
119
|
+
let token = null;
|
|
120
|
+
if (!this.tokenizers.some((tokenizer) => (token = tokenizer(), !!token)) || !token) throw Error(`Invalid tokens in jsonpath '${this.source}' @ ${this.i}`);
|
|
121
|
+
result.push(token);
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
takeWhile(fn) {
|
|
126
|
+
let start = this.i, result = "";
|
|
127
|
+
for (; !this.EOF();) {
|
|
128
|
+
let nextChar = fn(this.source[this.i]);
|
|
129
|
+
if (nextChar === null) break;
|
|
130
|
+
result += nextChar, this.i++;
|
|
131
|
+
}
|
|
132
|
+
return this.i === start ? null : result;
|
|
133
|
+
}
|
|
134
|
+
EOF() {
|
|
135
|
+
return this.i >= this.length;
|
|
136
|
+
}
|
|
137
|
+
peek() {
|
|
138
|
+
return this.EOF() ? null : this.source[this.i];
|
|
139
|
+
}
|
|
140
|
+
consume(str) {
|
|
141
|
+
if (this.i + str.length > this.length) throw Error(`Expected ${str} at end of jsonpath`);
|
|
142
|
+
if (str === this.source.slice(this.i, this.i + str.length)) this.i += str.length;
|
|
143
|
+
else throw Error(`Expected "${str}", but source contained "${this.source.slice()}`);
|
|
144
|
+
}
|
|
145
|
+
tryConsume(str) {
|
|
146
|
+
if (this.i + str.length > this.length) return null;
|
|
147
|
+
if (str === this.source.slice(this.i, this.i + str.length)) {
|
|
148
|
+
if (str[0].match(attributeCharMatcher) && this.length > this.i + str.length) {
|
|
149
|
+
let nextChar = this.source[this.i + str.length];
|
|
150
|
+
if (nextChar && nextChar.match(attributeCharMatcher)) return null;
|
|
151
|
+
}
|
|
152
|
+
return this.i += str.length, str;
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
chompWhitespace() {
|
|
157
|
+
this.takeWhile((char) => char === " " ? "" : null);
|
|
158
|
+
}
|
|
159
|
+
tokenizeQuoted() {
|
|
160
|
+
let quote = this.peek();
|
|
161
|
+
if (quote === "'" || quote === "\"") {
|
|
162
|
+
this.consume(quote);
|
|
163
|
+
let escape = !1, inner = this.takeWhile((char) => escape ? (escape = !1, char) : char === "\\" ? (escape = !0, "") : char == quote ? null : char);
|
|
164
|
+
return this.consume(quote), {
|
|
165
|
+
type: "quoted",
|
|
166
|
+
value: inner,
|
|
167
|
+
quote: quote === "\"" ? "double" : "single"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
tokenizeIdentifier() {
|
|
173
|
+
let first = !0, identifier = this.takeWhile((char) => first ? (first = !1, char.match(attributeFirstCharMatcher) ? char : null) : char.match(attributeCharMatcher) ? char : null);
|
|
174
|
+
return identifier === null ? null : {
|
|
175
|
+
type: "identifier",
|
|
176
|
+
name: identifier
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
tokenizeNumber() {
|
|
180
|
+
let start = this.i, dotSeen = !1, digitSeen = !1, negative = !1;
|
|
181
|
+
this.peek() === "-" && (negative = !0, this.consume("-"));
|
|
182
|
+
let number = this.takeWhile((char) => char === "." && !dotSeen && digitSeen ? (dotSeen = !0, char) : (digitSeen = !0, char.match(digitChar) ? char : null));
|
|
183
|
+
return number === null ? (this.i = start, null) : {
|
|
184
|
+
type: "number",
|
|
185
|
+
value: negative ? -Number(number) : +number,
|
|
186
|
+
raw: negative ? `-${number}` : number
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
tokenizeSymbol() {
|
|
190
|
+
for (let symbolClass of symbolClasses) {
|
|
191
|
+
let symbol = symbols[symbolClass].find((pattern) => this.tryConsume(pattern));
|
|
192
|
+
if (symbol) return {
|
|
193
|
+
type: symbolClass,
|
|
194
|
+
symbol
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
191
200
|
function tokenize(jsonpath) {
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
class Parser {
|
|
195
|
-
tokens;
|
|
196
|
-
length;
|
|
197
|
-
i;
|
|
198
|
-
constructor(path) {
|
|
199
|
-
this.tokens = tokenize(path), this.length = this.tokens.length, this.i = 0;
|
|
200
|
-
}
|
|
201
|
-
parse() {
|
|
202
|
-
return this.parsePath();
|
|
203
|
-
}
|
|
204
|
-
EOF() {
|
|
205
|
-
return this.i >= this.length;
|
|
206
|
-
}
|
|
207
|
-
// Look at upcoming token
|
|
208
|
-
peek() {
|
|
209
|
-
return this.EOF() ? null : this.tokens[this.i];
|
|
210
|
-
}
|
|
211
|
-
consume() {
|
|
212
|
-
const result = this.peek();
|
|
213
|
-
return this.i += 1, result;
|
|
214
|
-
}
|
|
215
|
-
// Return next token if it matches the pattern
|
|
216
|
-
probe(pattern) {
|
|
217
|
-
const token = this.peek();
|
|
218
|
-
if (!token)
|
|
219
|
-
return null;
|
|
220
|
-
const record = token;
|
|
221
|
-
return Object.keys(pattern).every((key) => key in token && pattern[key] === record[key]) ? token : null;
|
|
222
|
-
}
|
|
223
|
-
// Return and consume next token if it matches the pattern
|
|
224
|
-
match(pattern) {
|
|
225
|
-
return this.probe(pattern) ? this.consume() : null;
|
|
226
|
-
}
|
|
227
|
-
parseAttribute() {
|
|
228
|
-
const token = this.match({ type: "identifier" });
|
|
229
|
-
if (token && token.type === "identifier")
|
|
230
|
-
return {
|
|
231
|
-
type: "attribute",
|
|
232
|
-
name: token.name
|
|
233
|
-
};
|
|
234
|
-
const quoted = this.match({ type: "quoted", quote: "single" });
|
|
235
|
-
return quoted && quoted.type === "quoted" ? {
|
|
236
|
-
type: "attribute",
|
|
237
|
-
name: quoted.value || ""
|
|
238
|
-
} : null;
|
|
239
|
-
}
|
|
240
|
-
parseAlias() {
|
|
241
|
-
return this.match({ type: "keyword", symbol: "@" }) || this.match({ type: "keyword", symbol: "$" }) ? {
|
|
242
|
-
type: "alias",
|
|
243
|
-
target: "self"
|
|
244
|
-
} : null;
|
|
245
|
-
}
|
|
246
|
-
parseNumber() {
|
|
247
|
-
const token = this.match({ type: "number" });
|
|
248
|
-
return token && token.type === "number" ? {
|
|
249
|
-
type: "number",
|
|
250
|
-
value: token.value
|
|
251
|
-
} : null;
|
|
252
|
-
}
|
|
253
|
-
parseNumberValue() {
|
|
254
|
-
const expr = this.parseNumber();
|
|
255
|
-
return expr ? expr.value : null;
|
|
256
|
-
}
|
|
257
|
-
parseSliceSelector() {
|
|
258
|
-
const start = this.i, rangeStart = this.parseNumberValue();
|
|
259
|
-
if (!this.match({ type: "operator", symbol: ":" }))
|
|
260
|
-
return rangeStart === null ? (this.i = start, null) : { type: "index", value: rangeStart };
|
|
261
|
-
const result = {
|
|
262
|
-
type: "range",
|
|
263
|
-
start: rangeStart,
|
|
264
|
-
end: this.parseNumberValue()
|
|
265
|
-
};
|
|
266
|
-
return this.match({ type: "operator", symbol: ":" }) && (result.step = this.parseNumberValue()), result.start === null && result.end === null ? (this.i = start, null) : result;
|
|
267
|
-
}
|
|
268
|
-
parseValueReference() {
|
|
269
|
-
return this.parseAttribute() || this.parseSliceSelector();
|
|
270
|
-
}
|
|
271
|
-
parseLiteralValue() {
|
|
272
|
-
const literalString = this.match({ type: "quoted", quote: "double" });
|
|
273
|
-
if (literalString && literalString.type === "quoted")
|
|
274
|
-
return {
|
|
275
|
-
type: "string",
|
|
276
|
-
value: literalString.value || ""
|
|
277
|
-
};
|
|
278
|
-
const literalBoolean = this.match({ type: "boolean" });
|
|
279
|
-
return literalBoolean && literalBoolean.type === "boolean" ? {
|
|
280
|
-
type: "boolean",
|
|
281
|
-
value: literalBoolean.symbol === "true"
|
|
282
|
-
} : this.parseNumber();
|
|
283
|
-
}
|
|
284
|
-
// The LHS of a filter constraint: a single attribute or a dotted chain
|
|
285
|
-
// (`asset._ref`). Only `.attribute` continuations are accepted; a `[union]` or
|
|
286
|
-
// `..recursive` segment would make the rewind in `parseFilterExpression()`
|
|
287
|
-
// ambiguous.
|
|
288
|
-
parseAttributePath() {
|
|
289
|
-
const first = this.parseAttribute();
|
|
290
|
-
if (!first)
|
|
291
|
-
return null;
|
|
292
|
-
const nodes = [first];
|
|
293
|
-
for (; this.match({ type: "operator", symbol: "." }); ) {
|
|
294
|
-
const next = this.parseAttribute();
|
|
295
|
-
if (!next)
|
|
296
|
-
throw new Error("Expected attribute name following '.'");
|
|
297
|
-
nodes.push(next);
|
|
298
|
-
}
|
|
299
|
-
return nodes.length === 1 ? nodes[0] : {
|
|
300
|
-
type: "path",
|
|
301
|
-
nodes
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
// TODO: Reorder constraints so that literal value is always on rhs, and variable is always
|
|
305
|
-
// on lhs.
|
|
306
|
-
parseFilterExpression() {
|
|
307
|
-
const start = this.i, expr = this.parseAttributePath() || this.parseAlias();
|
|
308
|
-
if (!expr)
|
|
309
|
-
return null;
|
|
310
|
-
if (this.match({ type: "operator", symbol: "?" }))
|
|
311
|
-
return {
|
|
312
|
-
type: "constraint",
|
|
313
|
-
operator: "?",
|
|
314
|
-
lhs: expr
|
|
315
|
-
};
|
|
316
|
-
const binOp = this.match({ type: "comparator" });
|
|
317
|
-
if (!binOp || binOp.type !== "comparator")
|
|
318
|
-
return this.i = start, null;
|
|
319
|
-
const lhs = expr, rhs = this.parseLiteralValue();
|
|
320
|
-
if (!rhs)
|
|
321
|
-
throw new Error(`Operator ${binOp.symbol} needs a literal value at the right hand side`);
|
|
322
|
-
return {
|
|
323
|
-
type: "constraint",
|
|
324
|
-
operator: binOp.symbol,
|
|
325
|
-
lhs,
|
|
326
|
-
rhs
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
parseExpression() {
|
|
330
|
-
return this.parseFilterExpression() || this.parseValueReference();
|
|
331
|
-
}
|
|
332
|
-
parseUnion() {
|
|
333
|
-
if (!this.match({ type: "paren", symbol: "[" }))
|
|
334
|
-
return null;
|
|
335
|
-
const terms = [];
|
|
336
|
-
let expr = this.parseFilterExpression() || this.parsePath() || this.parseValueReference();
|
|
337
|
-
for (; expr && (terms.push(expr), !this.match({ type: "paren", symbol: "]" })); ) {
|
|
338
|
-
if (!this.match({ type: "operator", symbol: "," }))
|
|
339
|
-
throw new Error("Expected ]");
|
|
340
|
-
if (expr = this.parseFilterExpression() || this.parsePath() || this.parseValueReference(), !expr)
|
|
341
|
-
throw new Error("Expected expression following ','");
|
|
342
|
-
}
|
|
343
|
-
return {
|
|
344
|
-
type: "union",
|
|
345
|
-
nodes: terms
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
parseRecursive() {
|
|
349
|
-
if (!this.match({ type: "operator", symbol: ".." }))
|
|
350
|
-
return null;
|
|
351
|
-
const subpath = this.parsePath();
|
|
352
|
-
if (!subpath)
|
|
353
|
-
throw new Error("Expected path following '..' operator");
|
|
354
|
-
return {
|
|
355
|
-
type: "recursive",
|
|
356
|
-
term: subpath
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
parsePath() {
|
|
360
|
-
const nodes = [], expr = this.parseAttribute() || this.parseUnion() || this.parseRecursive();
|
|
361
|
-
if (!expr)
|
|
362
|
-
return null;
|
|
363
|
-
for (nodes.push(expr); !this.EOF(); )
|
|
364
|
-
if (this.match({ type: "operator", symbol: "." })) {
|
|
365
|
-
const attr = this.parseAttribute();
|
|
366
|
-
if (!attr)
|
|
367
|
-
throw new Error("Expected attribute name following '.");
|
|
368
|
-
nodes.push(attr);
|
|
369
|
-
continue;
|
|
370
|
-
} else if (this.probe({ type: "paren", symbol: "[" })) {
|
|
371
|
-
const union = this.parseUnion();
|
|
372
|
-
if (!union)
|
|
373
|
-
throw new Error("Expected union following '['");
|
|
374
|
-
nodes.push(union);
|
|
375
|
-
} else {
|
|
376
|
-
const recursive = this.parseRecursive();
|
|
377
|
-
recursive && nodes.push(recursive);
|
|
378
|
-
break;
|
|
379
|
-
}
|
|
380
|
-
return nodes.length === 1 ? nodes[0] : {
|
|
381
|
-
type: "path",
|
|
382
|
-
nodes
|
|
383
|
-
};
|
|
384
|
-
}
|
|
201
|
+
return new Tokenizer(jsonpath).tokenize();
|
|
385
202
|
}
|
|
203
|
+
var Parser = class {
|
|
204
|
+
tokens;
|
|
205
|
+
length;
|
|
206
|
+
i;
|
|
207
|
+
constructor(path) {
|
|
208
|
+
this.tokens = tokenize(path), this.length = this.tokens.length, this.i = 0;
|
|
209
|
+
}
|
|
210
|
+
parse() {
|
|
211
|
+
return this.parsePath();
|
|
212
|
+
}
|
|
213
|
+
EOF() {
|
|
214
|
+
return this.i >= this.length;
|
|
215
|
+
}
|
|
216
|
+
peek() {
|
|
217
|
+
return this.EOF() ? null : this.tokens[this.i];
|
|
218
|
+
}
|
|
219
|
+
consume() {
|
|
220
|
+
let result = this.peek();
|
|
221
|
+
return this.i += 1, result;
|
|
222
|
+
}
|
|
223
|
+
probe(pattern) {
|
|
224
|
+
let token = this.peek();
|
|
225
|
+
if (!token) return null;
|
|
226
|
+
let record = token;
|
|
227
|
+
return Object.keys(pattern).every((key) => key in token && pattern[key] === record[key]) ? token : null;
|
|
228
|
+
}
|
|
229
|
+
match(pattern) {
|
|
230
|
+
return this.probe(pattern) ? this.consume() : null;
|
|
231
|
+
}
|
|
232
|
+
parseAttribute() {
|
|
233
|
+
let token = this.match({ type: "identifier" });
|
|
234
|
+
if (token && token.type === "identifier") return {
|
|
235
|
+
type: "attribute",
|
|
236
|
+
name: token.name
|
|
237
|
+
};
|
|
238
|
+
let quoted = this.match({
|
|
239
|
+
type: "quoted",
|
|
240
|
+
quote: "single"
|
|
241
|
+
});
|
|
242
|
+
return quoted && quoted.type === "quoted" ? {
|
|
243
|
+
type: "attribute",
|
|
244
|
+
name: quoted.value || ""
|
|
245
|
+
} : null;
|
|
246
|
+
}
|
|
247
|
+
parseAlias() {
|
|
248
|
+
return this.match({
|
|
249
|
+
type: "keyword",
|
|
250
|
+
symbol: "@"
|
|
251
|
+
}) || this.match({
|
|
252
|
+
type: "keyword",
|
|
253
|
+
symbol: "$"
|
|
254
|
+
}) ? {
|
|
255
|
+
type: "alias",
|
|
256
|
+
target: "self"
|
|
257
|
+
} : null;
|
|
258
|
+
}
|
|
259
|
+
parseNumber() {
|
|
260
|
+
let token = this.match({ type: "number" });
|
|
261
|
+
return token && token.type === "number" ? {
|
|
262
|
+
type: "number",
|
|
263
|
+
value: token.value
|
|
264
|
+
} : null;
|
|
265
|
+
}
|
|
266
|
+
parseNumberValue() {
|
|
267
|
+
let expr = this.parseNumber();
|
|
268
|
+
return expr ? expr.value : null;
|
|
269
|
+
}
|
|
270
|
+
parseSliceSelector() {
|
|
271
|
+
let start = this.i, rangeStart = this.parseNumberValue();
|
|
272
|
+
if (!this.match({
|
|
273
|
+
type: "operator",
|
|
274
|
+
symbol: ":"
|
|
275
|
+
})) return rangeStart === null ? (this.i = start, null) : {
|
|
276
|
+
type: "index",
|
|
277
|
+
value: rangeStart
|
|
278
|
+
};
|
|
279
|
+
let result = {
|
|
280
|
+
type: "range",
|
|
281
|
+
start: rangeStart,
|
|
282
|
+
end: this.parseNumberValue()
|
|
283
|
+
};
|
|
284
|
+
return this.match({
|
|
285
|
+
type: "operator",
|
|
286
|
+
symbol: ":"
|
|
287
|
+
}) && (result.step = this.parseNumberValue()), result.start === null && result.end === null ? (this.i = start, null) : result;
|
|
288
|
+
}
|
|
289
|
+
parseValueReference() {
|
|
290
|
+
return this.parseAttribute() || this.parseSliceSelector();
|
|
291
|
+
}
|
|
292
|
+
parseLiteralValue() {
|
|
293
|
+
let literalString = this.match({
|
|
294
|
+
type: "quoted",
|
|
295
|
+
quote: "double"
|
|
296
|
+
});
|
|
297
|
+
if (literalString && literalString.type === "quoted") return {
|
|
298
|
+
type: "string",
|
|
299
|
+
value: literalString.value || ""
|
|
300
|
+
};
|
|
301
|
+
let literalBoolean = this.match({ type: "boolean" });
|
|
302
|
+
return literalBoolean && literalBoolean.type === "boolean" ? {
|
|
303
|
+
type: "boolean",
|
|
304
|
+
value: literalBoolean.symbol === "true"
|
|
305
|
+
} : this.parseNumber();
|
|
306
|
+
}
|
|
307
|
+
parseAttributePath() {
|
|
308
|
+
let first = this.parseAttribute();
|
|
309
|
+
if (!first) return null;
|
|
310
|
+
let nodes = [first];
|
|
311
|
+
for (; this.match({
|
|
312
|
+
type: "operator",
|
|
313
|
+
symbol: "."
|
|
314
|
+
});) {
|
|
315
|
+
let next = this.parseAttribute();
|
|
316
|
+
if (!next) throw Error("Expected attribute name following '.'");
|
|
317
|
+
nodes.push(next);
|
|
318
|
+
}
|
|
319
|
+
return nodes.length === 1 ? nodes[0] : {
|
|
320
|
+
type: "path",
|
|
321
|
+
nodes
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
parseFilterExpression() {
|
|
325
|
+
let start = this.i, expr = this.parseAttributePath() || this.parseAlias();
|
|
326
|
+
if (!expr) return null;
|
|
327
|
+
if (this.match({
|
|
328
|
+
type: "operator",
|
|
329
|
+
symbol: "?"
|
|
330
|
+
})) return {
|
|
331
|
+
type: "constraint",
|
|
332
|
+
operator: "?",
|
|
333
|
+
lhs: expr
|
|
334
|
+
};
|
|
335
|
+
let binOp = this.match({ type: "comparator" });
|
|
336
|
+
if (!binOp || binOp.type !== "comparator") return this.i = start, null;
|
|
337
|
+
let lhs = expr, rhs = this.parseLiteralValue();
|
|
338
|
+
if (!rhs) throw Error(`Operator ${binOp.symbol} needs a literal value at the right hand side`);
|
|
339
|
+
return {
|
|
340
|
+
type: "constraint",
|
|
341
|
+
operator: binOp.symbol,
|
|
342
|
+
lhs,
|
|
343
|
+
rhs
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
parseExpression() {
|
|
347
|
+
return this.parseFilterExpression() || this.parseValueReference();
|
|
348
|
+
}
|
|
349
|
+
parseUnion() {
|
|
350
|
+
if (!this.match({
|
|
351
|
+
type: "paren",
|
|
352
|
+
symbol: "["
|
|
353
|
+
})) return null;
|
|
354
|
+
let terms = [], expr = this.parseFilterExpression() || this.parsePath() || this.parseValueReference();
|
|
355
|
+
for (; expr && (terms.push(expr), !this.match({
|
|
356
|
+
type: "paren",
|
|
357
|
+
symbol: "]"
|
|
358
|
+
}));) {
|
|
359
|
+
if (!this.match({
|
|
360
|
+
type: "operator",
|
|
361
|
+
symbol: ","
|
|
362
|
+
})) throw Error("Expected ]");
|
|
363
|
+
if (expr = this.parseFilterExpression() || this.parsePath() || this.parseValueReference(), !expr) throw Error("Expected expression following ','");
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
type: "union",
|
|
367
|
+
nodes: terms
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
parseRecursive() {
|
|
371
|
+
if (!this.match({
|
|
372
|
+
type: "operator",
|
|
373
|
+
symbol: ".."
|
|
374
|
+
})) return null;
|
|
375
|
+
let subpath = this.parsePath();
|
|
376
|
+
if (!subpath) throw Error("Expected path following '..' operator");
|
|
377
|
+
return {
|
|
378
|
+
type: "recursive",
|
|
379
|
+
term: subpath
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
parsePath() {
|
|
383
|
+
let nodes = [], expr = this.parseAttribute() || this.parseUnion() || this.parseRecursive();
|
|
384
|
+
if (!expr) return null;
|
|
385
|
+
for (nodes.push(expr); !this.EOF();) if (this.match({
|
|
386
|
+
type: "operator",
|
|
387
|
+
symbol: "."
|
|
388
|
+
})) {
|
|
389
|
+
let attr = this.parseAttribute();
|
|
390
|
+
if (!attr) throw Error("Expected attribute name following '.");
|
|
391
|
+
nodes.push(attr);
|
|
392
|
+
continue;
|
|
393
|
+
} else if (this.probe({
|
|
394
|
+
type: "paren",
|
|
395
|
+
symbol: "["
|
|
396
|
+
})) {
|
|
397
|
+
let union = this.parseUnion();
|
|
398
|
+
if (!union) throw Error("Expected union following '['");
|
|
399
|
+
nodes.push(union);
|
|
400
|
+
} else {
|
|
401
|
+
let recursive = this.parseRecursive();
|
|
402
|
+
recursive && nodes.push(recursive);
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
return nodes.length === 1 ? nodes[0] : {
|
|
406
|
+
type: "path",
|
|
407
|
+
nodes
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
};
|
|
386
411
|
function parseJsonPath(path) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return parsed;
|
|
412
|
+
let parsed = new Parser(path).parse();
|
|
413
|
+
if (!parsed) throw Error(`Failed to parse JSON path "${path}"`);
|
|
414
|
+
return parsed;
|
|
391
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Converts a parsed expression back into jsonpath2, roughly -
|
|
418
|
+
* mostly for use with tests.
|
|
419
|
+
*
|
|
420
|
+
* @param expr - Expression to convert to path
|
|
421
|
+
* @returns a string representation of the path
|
|
422
|
+
* @internal
|
|
423
|
+
*/
|
|
392
424
|
function toPath(expr) {
|
|
393
|
-
|
|
425
|
+
return toPathInner(expr, !1);
|
|
394
426
|
}
|
|
395
427
|
function toPathInner(expr, inUnion) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
case "union":
|
|
426
|
-
return `[${expr.nodes.map((e) => toPathInner(e, !0)).join(",")}]`;
|
|
427
|
-
default:
|
|
428
|
-
throw new Error(`Unknown node type ${expr.type}`);
|
|
429
|
-
case "recursive":
|
|
430
|
-
return `..${toPathInner(expr.term, !1)}`;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
class Expression {
|
|
434
|
-
expr;
|
|
435
|
-
constructor(expr) {
|
|
436
|
-
if (!expr)
|
|
437
|
-
throw new Error("Attempted to create Expression from null-value");
|
|
438
|
-
if ("expr" in expr ? this.expr = expr.expr : this.expr = expr, !("type" in this.expr))
|
|
439
|
-
throw new Error("Attempt to create Expression for expression with no type");
|
|
440
|
-
}
|
|
441
|
-
isPath() {
|
|
442
|
-
return this.expr.type === "path";
|
|
443
|
-
}
|
|
444
|
-
isUnion() {
|
|
445
|
-
return this.expr.type === "union";
|
|
446
|
-
}
|
|
447
|
-
isCollection() {
|
|
448
|
-
return this.isPath() || this.isUnion();
|
|
449
|
-
}
|
|
450
|
-
isConstraint() {
|
|
451
|
-
return this.expr.type === "constraint";
|
|
452
|
-
}
|
|
453
|
-
isRecursive() {
|
|
454
|
-
return this.expr.type === "recursive";
|
|
455
|
-
}
|
|
456
|
-
isExistenceConstraint() {
|
|
457
|
-
return this.expr.type === "constraint" && this.expr.operator === "?";
|
|
458
|
-
}
|
|
459
|
-
isIndex() {
|
|
460
|
-
return this.expr.type === "index";
|
|
461
|
-
}
|
|
462
|
-
isRange() {
|
|
463
|
-
return this.expr.type === "range";
|
|
464
|
-
}
|
|
465
|
-
expandRange(probe) {
|
|
466
|
-
const probeLength = () => {
|
|
467
|
-
if (!probe)
|
|
468
|
-
throw new Error("expandRange() required a probe that was not passed");
|
|
469
|
-
return probe.length();
|
|
470
|
-
};
|
|
471
|
-
let start = "start" in this.expr && this.expr.start || 0;
|
|
472
|
-
start = interpretNegativeIndex(start, probe);
|
|
473
|
-
let end = "end" in this.expr && this.expr.end || probeLength();
|
|
474
|
-
end = interpretNegativeIndex(end, probe);
|
|
475
|
-
const step = "step" in this.expr && this.expr.step || 1;
|
|
476
|
-
return { start, end, step };
|
|
477
|
-
}
|
|
478
|
-
isAttributeReference() {
|
|
479
|
-
return this.expr.type === "attribute";
|
|
480
|
-
}
|
|
481
|
-
// Is a range or index -> something referencing indexes
|
|
482
|
-
isIndexReference() {
|
|
483
|
-
return this.isIndex() || this.isRange();
|
|
484
|
-
}
|
|
485
|
-
name() {
|
|
486
|
-
return "name" in this.expr ? this.expr.name : "";
|
|
487
|
-
}
|
|
488
|
-
isSelfReference() {
|
|
489
|
-
return this.expr.type === "alias" && this.expr.target === "self";
|
|
490
|
-
}
|
|
491
|
-
constraintTargetIsSelf() {
|
|
492
|
-
return this.expr.type === "constraint" && this.expr.lhs.type === "alias" && this.expr.lhs.target === "self";
|
|
493
|
-
}
|
|
494
|
-
constraintTargetIsAttribute() {
|
|
495
|
-
return this.expr.type === "constraint" && this.expr.lhs.type === "attribute";
|
|
496
|
-
}
|
|
497
|
-
testConstraint(probe) {
|
|
498
|
-
const expr = this.expr;
|
|
499
|
-
if (expr.type === "constraint" && expr.lhs.type === "alias" && expr.lhs.target === "self") {
|
|
500
|
-
if (probe.containerType() !== "primitive")
|
|
501
|
-
return !1;
|
|
502
|
-
if (expr.type === "constraint" && expr.operator === "?")
|
|
503
|
-
return !0;
|
|
504
|
-
const lhs2 = probe.get(), rhs2 = expr.rhs && "value" in expr.rhs ? expr.rhs.value : void 0;
|
|
505
|
-
return testBinaryOperator(lhs2, expr.operator, rhs2);
|
|
506
|
-
}
|
|
507
|
-
if (expr.type !== "constraint")
|
|
508
|
-
return !1;
|
|
509
|
-
const lhs = expr.lhs;
|
|
510
|
-
if (!lhs)
|
|
511
|
-
throw new Error("No LHS of expression");
|
|
512
|
-
if (lhs.type !== "attribute" && lhs.type !== "path")
|
|
513
|
-
throw new Error(`Constraint target ${lhs.type} not supported`);
|
|
514
|
-
if (probe.containerType() !== "object")
|
|
515
|
-
return !1;
|
|
516
|
-
const attrNames = lhs.type === "attribute" ? [lhs.name] : lhs.nodes.map((node) => {
|
|
517
|
-
if (node.type !== "attribute")
|
|
518
|
-
throw new Error(
|
|
519
|
-
`Constraint target path with non-attribute segment '${node.type}' not supported`
|
|
520
|
-
);
|
|
521
|
-
return node.name;
|
|
522
|
-
});
|
|
523
|
-
let cursor = probe;
|
|
524
|
-
for (let i = 0; i < attrNames.length - 1; i++) {
|
|
525
|
-
if (cursor.containerType() !== "object")
|
|
526
|
-
return !1;
|
|
527
|
-
const next = cursor.getAttribute(attrNames[i]);
|
|
528
|
-
if (next === null)
|
|
529
|
-
return !1;
|
|
530
|
-
cursor = next;
|
|
531
|
-
}
|
|
532
|
-
if (cursor.containerType() !== "object")
|
|
533
|
-
return !1;
|
|
534
|
-
const lhsValue = cursor.getAttribute(attrNames[attrNames.length - 1]);
|
|
535
|
-
if (lhsValue == null || lhsValue.containerType() !== "primitive")
|
|
536
|
-
return !1;
|
|
537
|
-
if (this.isExistenceConstraint())
|
|
538
|
-
return !0;
|
|
539
|
-
const rhs = expr.rhs && "value" in expr.rhs ? expr.rhs.value : void 0;
|
|
540
|
-
return testBinaryOperator(lhsValue.get(), expr.operator, rhs);
|
|
541
|
-
}
|
|
542
|
-
pathNodes() {
|
|
543
|
-
return this.expr.type === "path" ? this.expr.nodes : [this.expr];
|
|
544
|
-
}
|
|
545
|
-
prepend(node) {
|
|
546
|
-
return node ? new Expression({
|
|
547
|
-
type: "path",
|
|
548
|
-
nodes: node.pathNodes().concat(this.pathNodes())
|
|
549
|
-
}) : this;
|
|
550
|
-
}
|
|
551
|
-
concat(other) {
|
|
552
|
-
return other ? other.prepend(this) : this;
|
|
553
|
-
}
|
|
554
|
-
descend() {
|
|
555
|
-
return descend$1(this.expr).map((headTail) => {
|
|
556
|
-
const [head, tail] = headTail;
|
|
557
|
-
return {
|
|
558
|
-
head: head ? new Expression(head) : null,
|
|
559
|
-
tail: tail ? new Expression(tail) : null
|
|
560
|
-
};
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
unwrapRecursive() {
|
|
564
|
-
if (this.expr.type !== "recursive")
|
|
565
|
-
throw new Error(`Attempt to unwrap recursive on type ${this.expr.type}`);
|
|
566
|
-
return new Expression(this.expr.term);
|
|
567
|
-
}
|
|
568
|
-
toIndicies(probe) {
|
|
569
|
-
if (this.expr.type !== "index" && this.expr.type !== "range")
|
|
570
|
-
throw new Error("Node cannot be converted to indexes");
|
|
571
|
-
if (this.expr.type === "index")
|
|
572
|
-
return [interpretNegativeIndex(this.expr.value, probe)];
|
|
573
|
-
const result = [], range = this.expandRange(probe);
|
|
574
|
-
let { start, end } = range;
|
|
575
|
-
range.step < 0 && ([start, end] = [end, start]);
|
|
576
|
-
for (let i = start; i < end; i++)
|
|
577
|
-
result.push(i);
|
|
578
|
-
return result;
|
|
579
|
-
}
|
|
580
|
-
toFieldReferences() {
|
|
581
|
-
if (this.isIndexReference())
|
|
582
|
-
return this.toIndicies();
|
|
583
|
-
if (this.expr.type === "attribute")
|
|
584
|
-
return [this.expr.name];
|
|
585
|
-
throw new Error(`Can't convert ${this.expr.type} to field references`);
|
|
586
|
-
}
|
|
587
|
-
toString() {
|
|
588
|
-
return toPath(this.expr);
|
|
589
|
-
}
|
|
590
|
-
static fromPath(path) {
|
|
591
|
-
const parsed = parseJsonPath(path);
|
|
592
|
-
if (!parsed)
|
|
593
|
-
throw new Error(`Failed to parse path "${path}"`);
|
|
594
|
-
return new Expression(parsed);
|
|
595
|
-
}
|
|
596
|
-
static attributeReference(name) {
|
|
597
|
-
return new Expression({
|
|
598
|
-
type: "attribute",
|
|
599
|
-
name
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
static indexReference(i) {
|
|
603
|
-
return new Expression({
|
|
604
|
-
type: "index",
|
|
605
|
-
value: i
|
|
606
|
-
});
|
|
607
|
-
}
|
|
428
|
+
switch (expr.type) {
|
|
429
|
+
case "attribute": return expr.name;
|
|
430
|
+
case "alias": return expr.target === "self" ? "@" : "$";
|
|
431
|
+
case "number": return `${expr.value}`;
|
|
432
|
+
case "range": {
|
|
433
|
+
let result = [];
|
|
434
|
+
return inUnion || result.push("["), expr.start && result.push(`${expr.start}`), result.push(":"), expr.end && result.push(`${expr.end}`), expr.step && result.push(`:${expr.step}`), inUnion || result.push("]"), result.join("");
|
|
435
|
+
}
|
|
436
|
+
case "index": return inUnion ? `${expr.value}` : `[${expr.value}]`;
|
|
437
|
+
case "constraint": {
|
|
438
|
+
let rhs = expr.rhs ? ` ${toPathInner(expr.rhs, !1)}` : "", inner = `${toPathInner(expr.lhs, !1)} ${expr.operator}${rhs}`;
|
|
439
|
+
return inUnion ? inner : `[${inner}]`;
|
|
440
|
+
}
|
|
441
|
+
case "string": return JSON.stringify(expr.value);
|
|
442
|
+
case "path": {
|
|
443
|
+
let result = [], nodes = expr.nodes.slice();
|
|
444
|
+
for (; nodes.length > 0;) {
|
|
445
|
+
let node = nodes.shift();
|
|
446
|
+
node && result.push(toPath(node));
|
|
447
|
+
let upcoming = nodes[0];
|
|
448
|
+
upcoming && toPathInner(upcoming, !1)[0] !== "[" && result.push(".");
|
|
449
|
+
}
|
|
450
|
+
return result.join("");
|
|
451
|
+
}
|
|
452
|
+
case "union": return `[${expr.nodes.map((e) => toPathInner(e, !0)).join(",")}]`;
|
|
453
|
+
default: throw Error(`Unknown node type ${expr.type}`);
|
|
454
|
+
case "recursive": return `..${toPathInner(expr.term, !1)}`;
|
|
455
|
+
}
|
|
608
456
|
}
|
|
457
|
+
var Expression = class Expression {
|
|
458
|
+
expr;
|
|
459
|
+
constructor(expr) {
|
|
460
|
+
if (!expr) throw Error("Attempted to create Expression from null-value");
|
|
461
|
+
if ("expr" in expr ? this.expr = expr.expr : this.expr = expr, !("type" in this.expr)) throw Error("Attempt to create Expression for expression with no type");
|
|
462
|
+
}
|
|
463
|
+
isPath() {
|
|
464
|
+
return this.expr.type === "path";
|
|
465
|
+
}
|
|
466
|
+
isUnion() {
|
|
467
|
+
return this.expr.type === "union";
|
|
468
|
+
}
|
|
469
|
+
isCollection() {
|
|
470
|
+
return this.isPath() || this.isUnion();
|
|
471
|
+
}
|
|
472
|
+
isConstraint() {
|
|
473
|
+
return this.expr.type === "constraint";
|
|
474
|
+
}
|
|
475
|
+
isRecursive() {
|
|
476
|
+
return this.expr.type === "recursive";
|
|
477
|
+
}
|
|
478
|
+
isExistenceConstraint() {
|
|
479
|
+
return this.expr.type === "constraint" && this.expr.operator === "?";
|
|
480
|
+
}
|
|
481
|
+
isIndex() {
|
|
482
|
+
return this.expr.type === "index";
|
|
483
|
+
}
|
|
484
|
+
isRange() {
|
|
485
|
+
return this.expr.type === "range";
|
|
486
|
+
}
|
|
487
|
+
expandRange(probe) {
|
|
488
|
+
let probeLength = () => {
|
|
489
|
+
if (!probe) throw Error("expandRange() required a probe that was not passed");
|
|
490
|
+
return probe.length();
|
|
491
|
+
}, start = "start" in this.expr && this.expr.start || 0;
|
|
492
|
+
start = interpretNegativeIndex(start, probe);
|
|
493
|
+
let end = "end" in this.expr && this.expr.end || probeLength();
|
|
494
|
+
end = interpretNegativeIndex(end, probe);
|
|
495
|
+
let step = "step" in this.expr && this.expr.step || 1;
|
|
496
|
+
return {
|
|
497
|
+
start,
|
|
498
|
+
end,
|
|
499
|
+
step
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
isAttributeReference() {
|
|
503
|
+
return this.expr.type === "attribute";
|
|
504
|
+
}
|
|
505
|
+
isIndexReference() {
|
|
506
|
+
return this.isIndex() || this.isRange();
|
|
507
|
+
}
|
|
508
|
+
name() {
|
|
509
|
+
return "name" in this.expr ? this.expr.name : "";
|
|
510
|
+
}
|
|
511
|
+
isSelfReference() {
|
|
512
|
+
return this.expr.type === "alias" && this.expr.target === "self";
|
|
513
|
+
}
|
|
514
|
+
constraintTargetIsSelf() {
|
|
515
|
+
return this.expr.type === "constraint" && this.expr.lhs.type === "alias" && this.expr.lhs.target === "self";
|
|
516
|
+
}
|
|
517
|
+
constraintTargetIsAttribute() {
|
|
518
|
+
return this.expr.type === "constraint" && this.expr.lhs.type === "attribute";
|
|
519
|
+
}
|
|
520
|
+
testConstraint(probe) {
|
|
521
|
+
let expr = this.expr;
|
|
522
|
+
if (expr.type === "constraint" && expr.lhs.type === "alias" && expr.lhs.target === "self") {
|
|
523
|
+
if (probe.containerType() !== "primitive") return !1;
|
|
524
|
+
if (expr.type === "constraint" && expr.operator === "?") return !0;
|
|
525
|
+
let lhs = probe.get(), rhs = expr.rhs && "value" in expr.rhs ? expr.rhs.value : void 0;
|
|
526
|
+
return testBinaryOperator(lhs, expr.operator, rhs);
|
|
527
|
+
}
|
|
528
|
+
if (expr.type !== "constraint") return !1;
|
|
529
|
+
let lhs = expr.lhs;
|
|
530
|
+
if (!lhs) throw Error("No LHS of expression");
|
|
531
|
+
if (lhs.type !== "attribute" && lhs.type !== "path") throw Error(`Constraint target ${lhs.type} not supported`);
|
|
532
|
+
if (probe.containerType() !== "object") return !1;
|
|
533
|
+
let attrNames = lhs.type === "attribute" ? [lhs.name] : lhs.nodes.map((node) => {
|
|
534
|
+
if (node.type !== "attribute") throw Error(`Constraint target path with non-attribute segment '${node.type}' not supported`);
|
|
535
|
+
return node.name;
|
|
536
|
+
}), cursor = probe;
|
|
537
|
+
for (let i = 0; i < attrNames.length - 1; i++) {
|
|
538
|
+
if (cursor.containerType() !== "object") return !1;
|
|
539
|
+
let next = cursor.getAttribute(attrNames[i]);
|
|
540
|
+
if (next === null) return !1;
|
|
541
|
+
cursor = next;
|
|
542
|
+
}
|
|
543
|
+
if (cursor.containerType() !== "object") return !1;
|
|
544
|
+
let lhsValue = cursor.getAttribute(attrNames[attrNames.length - 1]);
|
|
545
|
+
if (lhsValue == null || lhsValue.containerType() !== "primitive") return !1;
|
|
546
|
+
if (this.isExistenceConstraint()) return !0;
|
|
547
|
+
let rhs = expr.rhs && "value" in expr.rhs ? expr.rhs.value : void 0;
|
|
548
|
+
return testBinaryOperator(lhsValue.get(), expr.operator, rhs);
|
|
549
|
+
}
|
|
550
|
+
pathNodes() {
|
|
551
|
+
return this.expr.type === "path" ? this.expr.nodes : [this.expr];
|
|
552
|
+
}
|
|
553
|
+
prepend(node) {
|
|
554
|
+
return node ? new Expression({
|
|
555
|
+
type: "path",
|
|
556
|
+
nodes: node.pathNodes().concat(this.pathNodes())
|
|
557
|
+
}) : this;
|
|
558
|
+
}
|
|
559
|
+
concat(other) {
|
|
560
|
+
return other ? other.prepend(this) : this;
|
|
561
|
+
}
|
|
562
|
+
descend() {
|
|
563
|
+
return descend$1(this.expr).map((headTail) => {
|
|
564
|
+
let [head, tail] = headTail;
|
|
565
|
+
return {
|
|
566
|
+
head: head ? new Expression(head) : null,
|
|
567
|
+
tail: tail ? new Expression(tail) : null
|
|
568
|
+
};
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
unwrapRecursive() {
|
|
572
|
+
if (this.expr.type !== "recursive") throw Error(`Attempt to unwrap recursive on type ${this.expr.type}`);
|
|
573
|
+
return new Expression(this.expr.term);
|
|
574
|
+
}
|
|
575
|
+
toIndicies(probe) {
|
|
576
|
+
if (this.expr.type !== "index" && this.expr.type !== "range") throw Error("Node cannot be converted to indexes");
|
|
577
|
+
if (this.expr.type === "index") return [interpretNegativeIndex(this.expr.value, probe)];
|
|
578
|
+
let result = [], range = this.expandRange(probe), { start, end } = range;
|
|
579
|
+
range.step < 0 && ([start, end] = [end, start]);
|
|
580
|
+
for (let i = start; i < end; i++) result.push(i);
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
toFieldReferences() {
|
|
584
|
+
if (this.isIndexReference()) return this.toIndicies();
|
|
585
|
+
if (this.expr.type === "attribute") return [this.expr.name];
|
|
586
|
+
throw Error(`Can't convert ${this.expr.type} to field references`);
|
|
587
|
+
}
|
|
588
|
+
toString() {
|
|
589
|
+
return toPath(this.expr);
|
|
590
|
+
}
|
|
591
|
+
static fromPath(path) {
|
|
592
|
+
let parsed = parseJsonPath(path);
|
|
593
|
+
if (!parsed) throw Error(`Failed to parse path "${path}"`);
|
|
594
|
+
return new Expression(parsed);
|
|
595
|
+
}
|
|
596
|
+
static attributeReference(name) {
|
|
597
|
+
return new Expression({
|
|
598
|
+
type: "attribute",
|
|
599
|
+
name
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
static indexReference(i) {
|
|
603
|
+
return new Expression({
|
|
604
|
+
type: "index",
|
|
605
|
+
value: i
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
609
|
function testBinaryOperator(lhsValue, operator, rhsValue) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
case "==":
|
|
620
|
-
return lhsValue === rhsValue;
|
|
621
|
-
case "!=":
|
|
622
|
-
return lhsValue !== rhsValue;
|
|
623
|
-
default:
|
|
624
|
-
throw new Error(`Unsupported binary operator ${operator}`);
|
|
625
|
-
}
|
|
610
|
+
switch (operator) {
|
|
611
|
+
case ">": return lhsValue > rhsValue;
|
|
612
|
+
case ">=": return lhsValue >= rhsValue;
|
|
613
|
+
case "<": return lhsValue < rhsValue;
|
|
614
|
+
case "<=": return lhsValue <= rhsValue;
|
|
615
|
+
case "==": return lhsValue === rhsValue;
|
|
616
|
+
case "!=": return lhsValue !== rhsValue;
|
|
617
|
+
default: throw Error(`Unsupported binary operator ${operator}`);
|
|
618
|
+
}
|
|
626
619
|
}
|
|
627
620
|
function interpretNegativeIndex(index, probe) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
throw new Error("interpretNegativeIndex() must have a probe when < 0");
|
|
632
|
-
return index + probe.length();
|
|
633
|
-
}
|
|
634
|
-
class Descender {
|
|
635
|
-
head;
|
|
636
|
-
tail;
|
|
637
|
-
constructor(head, tail) {
|
|
638
|
-
this.head = head, this.tail = tail;
|
|
639
|
-
}
|
|
640
|
-
// Iterate this descender once processing any constraints that are
|
|
641
|
-
// resolvable on the current value. Returns an array of new descenders
|
|
642
|
-
// that are guaranteed to be without constraints in the head
|
|
643
|
-
iterate(probe) {
|
|
644
|
-
let result = [this];
|
|
645
|
-
if (this.head && this.head.isConstraint()) {
|
|
646
|
-
let anyConstraints = !0;
|
|
647
|
-
for (; anyConstraints; )
|
|
648
|
-
result = flatten(
|
|
649
|
-
result.map((descender) => descender.iterateConstraints(probe))
|
|
650
|
-
), anyConstraints = result.some((descender) => descender.head && descender.head.isConstraint());
|
|
651
|
-
}
|
|
652
|
-
return result;
|
|
653
|
-
}
|
|
654
|
-
isRecursive() {
|
|
655
|
-
return !!(this.head && this.head.isRecursive());
|
|
656
|
-
}
|
|
657
|
-
hasArrived() {
|
|
658
|
-
return this.head === null && this.tail === null;
|
|
659
|
-
}
|
|
660
|
-
extractRecursives() {
|
|
661
|
-
if (this.head && this.head.isRecursive()) {
|
|
662
|
-
const term = this.head.unwrapRecursive();
|
|
663
|
-
return new Descender(null, term.concat(this.tail)).descend();
|
|
664
|
-
}
|
|
665
|
-
return [];
|
|
666
|
-
}
|
|
667
|
-
iterateConstraints(probe) {
|
|
668
|
-
const head = this.head;
|
|
669
|
-
if (head === null || !head.isConstraint())
|
|
670
|
-
return [this];
|
|
671
|
-
const result = [];
|
|
672
|
-
if (probe.containerType() === "primitive" && head.constraintTargetIsSelf())
|
|
673
|
-
return head.testConstraint(probe) && result.push(...this.descend()), result;
|
|
674
|
-
if (probe.containerType() === "array") {
|
|
675
|
-
const length = probe.length();
|
|
676
|
-
for (let i = 0; i < length; i++) {
|
|
677
|
-
const constraint = probe.getIndex(i);
|
|
678
|
-
constraint && head.testConstraint(constraint) && result.push(new Descender(new Expression({ type: "index", value: i }), this.tail));
|
|
679
|
-
}
|
|
680
|
-
return result;
|
|
681
|
-
}
|
|
682
|
-
return probe.containerType() === "object" ? head.constraintTargetIsSelf() ? [] : head.testConstraint(probe) ? this.descend() : result : result;
|
|
683
|
-
}
|
|
684
|
-
descend() {
|
|
685
|
-
return this.tail ? this.tail.descend().map((ht) => new Descender(ht.head, ht.tail)) : [new Descender(null, null)];
|
|
686
|
-
}
|
|
687
|
-
toString() {
|
|
688
|
-
const result = ["<"];
|
|
689
|
-
return this.head && result.push(this.head.toString()), result.push("|"), this.tail && result.push(this.tail.toString()), result.push(">"), result.join("");
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
class Matcher {
|
|
693
|
-
active;
|
|
694
|
-
recursives;
|
|
695
|
-
payload;
|
|
696
|
-
constructor(active, parent) {
|
|
697
|
-
this.active = active || [], parent ? (this.recursives = parent.recursives, this.payload = parent.payload) : this.recursives = [], this.extractRecursives();
|
|
698
|
-
}
|
|
699
|
-
setPayload(payload) {
|
|
700
|
-
return this.payload = payload, this;
|
|
701
|
-
}
|
|
702
|
-
// Moves any recursive descenders onto the recursive track, removing them from
|
|
703
|
-
// the active set
|
|
704
|
-
extractRecursives() {
|
|
705
|
-
this.active = this.active.filter((descender) => descender.isRecursive() ? (this.recursives.push(...descender.extractRecursives()), !1) : !0);
|
|
706
|
-
}
|
|
707
|
-
// Find recursives that are relevant now and should be considered part of the active set
|
|
708
|
-
activeRecursives(probe) {
|
|
709
|
-
return this.recursives.filter((descender) => {
|
|
710
|
-
const head = descender.head;
|
|
711
|
-
return head ? head.isConstraint() || probe.containerType() === "array" && head.isIndexReference() ? !0 : probe.containerType() === "object" ? head.isAttributeReference() && probe.hasAttribute(head.name()) : !1 : !1;
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
match(probe) {
|
|
715
|
-
return this.iterate(probe).extractMatches(probe);
|
|
716
|
-
}
|
|
717
|
-
iterate(probe) {
|
|
718
|
-
const newActiveSet = [];
|
|
719
|
-
return this.active.concat(this.activeRecursives(probe)).forEach((descender) => {
|
|
720
|
-
newActiveSet.push(...descender.iterate(probe));
|
|
721
|
-
}), new Matcher(newActiveSet, this);
|
|
722
|
-
}
|
|
723
|
-
// Returns true if any of the descenders in the active or recursive set
|
|
724
|
-
// consider the current state a final destination
|
|
725
|
-
isDestination() {
|
|
726
|
-
return this.active.some((descender) => descender.hasArrived());
|
|
727
|
-
}
|
|
728
|
-
hasRecursives() {
|
|
729
|
-
return this.recursives.length > 0;
|
|
730
|
-
}
|
|
731
|
-
// Returns any payload delivieries and leads that needs to be followed to complete
|
|
732
|
-
// the process.
|
|
733
|
-
extractMatches(probe) {
|
|
734
|
-
const leads = [], targets = [];
|
|
735
|
-
if (this.active.forEach((descender) => {
|
|
736
|
-
if (descender.hasArrived()) {
|
|
737
|
-
targets.push(
|
|
738
|
-
new Expression({
|
|
739
|
-
type: "alias",
|
|
740
|
-
target: "self"
|
|
741
|
-
})
|
|
742
|
-
);
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
const descenderHead = descender.head;
|
|
746
|
-
if (descenderHead && !(probe.containerType() === "array" && !descenderHead.isIndexReference()) && !(probe.containerType() === "object" && !descenderHead.isAttributeReference()))
|
|
747
|
-
if (descender.tail) {
|
|
748
|
-
const matcher = new Matcher(descender.descend(), this);
|
|
749
|
-
descenderHead.toFieldReferences().forEach(() => {
|
|
750
|
-
leads.push({
|
|
751
|
-
target: descenderHead,
|
|
752
|
-
matcher
|
|
753
|
-
});
|
|
754
|
-
});
|
|
755
|
-
} else
|
|
756
|
-
targets.push(descenderHead);
|
|
757
|
-
}), this.hasRecursives()) {
|
|
758
|
-
const recursivesMatcher = new Matcher([], this);
|
|
759
|
-
if (probe.containerType() === "array") {
|
|
760
|
-
const length = probe.length();
|
|
761
|
-
for (let i = 0; i < length; i++)
|
|
762
|
-
leads.push({
|
|
763
|
-
target: Expression.indexReference(i),
|
|
764
|
-
matcher: recursivesMatcher
|
|
765
|
-
});
|
|
766
|
-
} else probe.containerType() === "object" && probe.attributeKeys().forEach((name) => {
|
|
767
|
-
leads.push({
|
|
768
|
-
target: Expression.attributeReference(name),
|
|
769
|
-
matcher: recursivesMatcher
|
|
770
|
-
});
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
return targets.length > 0 ? { leads, delivery: { targets, payload: this.payload } } : { leads };
|
|
774
|
-
}
|
|
775
|
-
static fromPath(jsonpath) {
|
|
776
|
-
const path = parseJsonPath(jsonpath);
|
|
777
|
-
if (!path)
|
|
778
|
-
throw new Error(`Failed to parse path from "${jsonpath}"`);
|
|
779
|
-
const descender = new Descender(null, new Expression(path));
|
|
780
|
-
return new Matcher(descender.descend());
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
class PlainProbe {
|
|
784
|
-
_value;
|
|
785
|
-
path;
|
|
786
|
-
constructor(value, path) {
|
|
787
|
-
this._value = value, this.path = path || [];
|
|
788
|
-
}
|
|
789
|
-
containerType() {
|
|
790
|
-
return Array.isArray(this._value) ? "array" : this._value !== null && typeof this._value == "object" ? "object" : "primitive";
|
|
791
|
-
}
|
|
792
|
-
length() {
|
|
793
|
-
if (!Array.isArray(this._value))
|
|
794
|
-
throw new Error("Won't return length of non-indexable _value");
|
|
795
|
-
return this._value.length;
|
|
796
|
-
}
|
|
797
|
-
getIndex(i) {
|
|
798
|
-
return Array.isArray(this._value) ? i >= this.length() ? null : new PlainProbe(this._value[i], this.path.concat(i)) : !1;
|
|
799
|
-
}
|
|
800
|
-
hasAttribute(key) {
|
|
801
|
-
return isRecord$1(this._value) ? this._value.hasOwnProperty(key) : !1;
|
|
802
|
-
}
|
|
803
|
-
attributeKeys() {
|
|
804
|
-
return isRecord$1(this._value) ? Object.keys(this._value) : [];
|
|
805
|
-
}
|
|
806
|
-
getAttribute(key) {
|
|
807
|
-
if (!isRecord$1(this._value))
|
|
808
|
-
throw new Error("getAttribute only applies to plain objects");
|
|
809
|
-
return this.hasAttribute(key) ? new PlainProbe(this._value[key], this.path.concat(key)) : null;
|
|
810
|
-
}
|
|
811
|
-
get() {
|
|
812
|
-
return this._value;
|
|
813
|
-
}
|
|
621
|
+
if (index >= 0) return index;
|
|
622
|
+
if (!probe) throw Error("interpretNegativeIndex() must have a probe when < 0");
|
|
623
|
+
return index + probe.length();
|
|
814
624
|
}
|
|
625
|
+
/**
|
|
626
|
+
* Descender models the state of one partial jsonpath evaluation. Head is the
|
|
627
|
+
* next thing to match, tail is the upcoming things once the head is matched.
|
|
628
|
+
*/
|
|
629
|
+
var Descender = class Descender {
|
|
630
|
+
head;
|
|
631
|
+
tail;
|
|
632
|
+
constructor(head, tail) {
|
|
633
|
+
this.head = head, this.tail = tail;
|
|
634
|
+
}
|
|
635
|
+
iterate(probe) {
|
|
636
|
+
let result = [this];
|
|
637
|
+
if (this.head && this.head.isConstraint()) {
|
|
638
|
+
let anyConstraints = !0;
|
|
639
|
+
for (; anyConstraints;) result = flatten(result.map((descender) => descender.iterateConstraints(probe))), anyConstraints = result.some((descender) => descender.head && descender.head.isConstraint());
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
isRecursive() {
|
|
644
|
+
return !!(this.head && this.head.isRecursive());
|
|
645
|
+
}
|
|
646
|
+
hasArrived() {
|
|
647
|
+
return this.head === null && this.tail === null;
|
|
648
|
+
}
|
|
649
|
+
extractRecursives() {
|
|
650
|
+
if (this.head && this.head.isRecursive()) {
|
|
651
|
+
let term = this.head.unwrapRecursive();
|
|
652
|
+
return new Descender(null, term.concat(this.tail)).descend();
|
|
653
|
+
}
|
|
654
|
+
return [];
|
|
655
|
+
}
|
|
656
|
+
iterateConstraints(probe) {
|
|
657
|
+
let head = this.head;
|
|
658
|
+
if (head === null || !head.isConstraint()) return [this];
|
|
659
|
+
let result = [];
|
|
660
|
+
if (probe.containerType() === "primitive" && head.constraintTargetIsSelf()) return head.testConstraint(probe) && result.push(...this.descend()), result;
|
|
661
|
+
if (probe.containerType() === "array") {
|
|
662
|
+
let length = probe.length();
|
|
663
|
+
for (let i = 0; i < length; i++) {
|
|
664
|
+
let constraint = probe.getIndex(i);
|
|
665
|
+
constraint && head.testConstraint(constraint) && result.push(new Descender(new Expression({
|
|
666
|
+
type: "index",
|
|
667
|
+
value: i
|
|
668
|
+
}), this.tail));
|
|
669
|
+
}
|
|
670
|
+
return result;
|
|
671
|
+
}
|
|
672
|
+
return probe.containerType() === "object" ? head.constraintTargetIsSelf() ? [] : head.testConstraint(probe) ? this.descend() : result : result;
|
|
673
|
+
}
|
|
674
|
+
descend() {
|
|
675
|
+
return this.tail ? this.tail.descend().map((ht) => new Descender(ht.head, ht.tail)) : [new Descender(null, null)];
|
|
676
|
+
}
|
|
677
|
+
toString() {
|
|
678
|
+
let result = ["<"];
|
|
679
|
+
return this.head && result.push(this.head.toString()), result.push("|"), this.tail && result.push(this.tail.toString()), result.push(">"), result.join("");
|
|
680
|
+
}
|
|
681
|
+
}, Matcher = class Matcher {
|
|
682
|
+
active;
|
|
683
|
+
recursives;
|
|
684
|
+
payload;
|
|
685
|
+
constructor(active, parent) {
|
|
686
|
+
this.active = active || [], parent ? (this.recursives = parent.recursives, this.payload = parent.payload) : this.recursives = [], this.extractRecursives();
|
|
687
|
+
}
|
|
688
|
+
setPayload(payload) {
|
|
689
|
+
return this.payload = payload, this;
|
|
690
|
+
}
|
|
691
|
+
extractRecursives() {
|
|
692
|
+
this.active = this.active.filter((descender) => descender.isRecursive() ? (this.recursives.push(...descender.extractRecursives()), !1) : !0);
|
|
693
|
+
}
|
|
694
|
+
activeRecursives(probe) {
|
|
695
|
+
return this.recursives.filter((descender) => {
|
|
696
|
+
let head = descender.head;
|
|
697
|
+
return head ? head.isConstraint() || probe.containerType() === "array" && head.isIndexReference() ? !0 : probe.containerType() === "object" && head.isAttributeReference() && probe.hasAttribute(head.name()) : !1;
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
match(probe) {
|
|
701
|
+
return this.iterate(probe).extractMatches(probe);
|
|
702
|
+
}
|
|
703
|
+
iterate(probe) {
|
|
704
|
+
let newActiveSet = [];
|
|
705
|
+
return this.active.concat(this.activeRecursives(probe)).forEach((descender) => {
|
|
706
|
+
newActiveSet.push(...descender.iterate(probe));
|
|
707
|
+
}), new Matcher(newActiveSet, this);
|
|
708
|
+
}
|
|
709
|
+
isDestination() {
|
|
710
|
+
return this.active.some((descender) => descender.hasArrived());
|
|
711
|
+
}
|
|
712
|
+
hasRecursives() {
|
|
713
|
+
return this.recursives.length > 0;
|
|
714
|
+
}
|
|
715
|
+
extractMatches(probe) {
|
|
716
|
+
let leads = [], targets = [];
|
|
717
|
+
if (this.active.forEach((descender) => {
|
|
718
|
+
if (descender.hasArrived()) {
|
|
719
|
+
targets.push(new Expression({
|
|
720
|
+
type: "alias",
|
|
721
|
+
target: "self"
|
|
722
|
+
}));
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
let descenderHead = descender.head;
|
|
726
|
+
if (descenderHead && !(probe.containerType() === "array" && !descenderHead.isIndexReference()) && !(probe.containerType() === "object" && !descenderHead.isAttributeReference())) if (descender.tail) {
|
|
727
|
+
let matcher = new Matcher(descender.descend(), this);
|
|
728
|
+
descenderHead.toFieldReferences().forEach(() => {
|
|
729
|
+
leads.push({
|
|
730
|
+
target: descenderHead,
|
|
731
|
+
matcher
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
} else targets.push(descenderHead);
|
|
735
|
+
}), this.hasRecursives()) {
|
|
736
|
+
let recursivesMatcher = new Matcher([], this);
|
|
737
|
+
if (probe.containerType() === "array") {
|
|
738
|
+
let length = probe.length();
|
|
739
|
+
for (let i = 0; i < length; i++) leads.push({
|
|
740
|
+
target: Expression.indexReference(i),
|
|
741
|
+
matcher: recursivesMatcher
|
|
742
|
+
});
|
|
743
|
+
} else probe.containerType() === "object" && probe.attributeKeys().forEach((name) => {
|
|
744
|
+
leads.push({
|
|
745
|
+
target: Expression.attributeReference(name),
|
|
746
|
+
matcher: recursivesMatcher
|
|
747
|
+
});
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
return targets.length > 0 ? {
|
|
751
|
+
leads,
|
|
752
|
+
delivery: {
|
|
753
|
+
targets,
|
|
754
|
+
payload: this.payload
|
|
755
|
+
}
|
|
756
|
+
} : { leads };
|
|
757
|
+
}
|
|
758
|
+
static fromPath(jsonpath) {
|
|
759
|
+
let path = parseJsonPath(jsonpath);
|
|
760
|
+
if (!path) throw Error(`Failed to parse path from "${jsonpath}"`);
|
|
761
|
+
let descender = new Descender(null, new Expression(path));
|
|
762
|
+
return new Matcher(descender.descend());
|
|
763
|
+
}
|
|
764
|
+
}, PlainProbe = class PlainProbe {
|
|
765
|
+
_value;
|
|
766
|
+
path;
|
|
767
|
+
constructor(value, path) {
|
|
768
|
+
this._value = value, this.path = path || [];
|
|
769
|
+
}
|
|
770
|
+
containerType() {
|
|
771
|
+
return Array.isArray(this._value) ? "array" : this._value !== null && typeof this._value == "object" ? "object" : "primitive";
|
|
772
|
+
}
|
|
773
|
+
length() {
|
|
774
|
+
if (!Array.isArray(this._value)) throw Error("Won't return length of non-indexable _value");
|
|
775
|
+
return this._value.length;
|
|
776
|
+
}
|
|
777
|
+
getIndex(i) {
|
|
778
|
+
return Array.isArray(this._value) ? i >= this.length() ? null : new PlainProbe(this._value[i], this.path.concat(i)) : !1;
|
|
779
|
+
}
|
|
780
|
+
hasAttribute(key) {
|
|
781
|
+
return isRecord$1(this._value) ? this._value.hasOwnProperty(key) : !1;
|
|
782
|
+
}
|
|
783
|
+
attributeKeys() {
|
|
784
|
+
return isRecord$1(this._value) ? Object.keys(this._value) : [];
|
|
785
|
+
}
|
|
786
|
+
getAttribute(key) {
|
|
787
|
+
if (!isRecord$1(this._value)) throw Error("getAttribute only applies to plain objects");
|
|
788
|
+
return this.hasAttribute(key) ? new PlainProbe(this._value[key], this.path.concat(key)) : null;
|
|
789
|
+
}
|
|
790
|
+
get() {
|
|
791
|
+
return this._value;
|
|
792
|
+
}
|
|
793
|
+
};
|
|
815
794
|
function extractAccessors(path, value) {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
795
|
+
let result = [];
|
|
796
|
+
return descend(Matcher.fromPath(path).setPayload(function(values) {
|
|
797
|
+
result.push(...values);
|
|
798
|
+
}), new PlainProbe(value)), result;
|
|
820
799
|
}
|
|
821
800
|
function descend(matcher, accessor) {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
801
|
+
let { leads, delivery } = matcher.match(accessor);
|
|
802
|
+
leads.forEach((lead) => {
|
|
803
|
+
accessorsFromTarget(lead.target, accessor).forEach((childAccessor) => {
|
|
804
|
+
descend(lead.matcher, childAccessor);
|
|
805
|
+
});
|
|
806
|
+
}), delivery && delivery.targets.forEach((target) => {
|
|
807
|
+
typeof delivery.payload == "function" && delivery.payload(accessorsFromTarget(target, accessor));
|
|
808
|
+
});
|
|
830
809
|
}
|
|
831
810
|
function accessorsFromTarget(target, accessor) {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
result.push(accessor);
|
|
841
|
-
else
|
|
842
|
-
throw new Error(`Unable to derive accessor for target ${target.toString()}`);
|
|
843
|
-
return compact(result);
|
|
811
|
+
let result = [];
|
|
812
|
+
if (target.isIndexReference()) target.toIndicies(accessor).forEach((i) => {
|
|
813
|
+
result.push(accessor.getIndex(i));
|
|
814
|
+
});
|
|
815
|
+
else if (target.isAttributeReference()) result.push(accessor.getAttribute(target.name()));
|
|
816
|
+
else if (target.isSelfReference()) result.push(accessor);
|
|
817
|
+
else throw Error(`Unable to derive accessor for target ${target.toString()}`);
|
|
818
|
+
return compact(result);
|
|
844
819
|
}
|
|
820
|
+
/**
|
|
821
|
+
* Extracts values matching the given JsonPath
|
|
822
|
+
*
|
|
823
|
+
* @param path - Path to extract
|
|
824
|
+
* @param value - Value to extract from
|
|
825
|
+
* @returns An array of values matching the given path
|
|
826
|
+
* @public
|
|
827
|
+
*/
|
|
845
828
|
function extract(path, value) {
|
|
846
|
-
|
|
829
|
+
return extractAccessors(path, value).map((acc) => acc.get());
|
|
847
830
|
}
|
|
831
|
+
/**
|
|
832
|
+
* Extracts a value for the given JsonPath, and includes the specific path of where it was found
|
|
833
|
+
*
|
|
834
|
+
* @param path - Path to extract
|
|
835
|
+
* @param value - Value to extract from
|
|
836
|
+
* @returns An array of objects with `path` and `value` keys
|
|
837
|
+
* @internal
|
|
838
|
+
*/
|
|
848
839
|
function extractWithPath(path, value) {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
path;
|
|
854
|
-
constructor(value, path) {
|
|
855
|
-
this._value = value, this.path = path || [];
|
|
856
|
-
}
|
|
857
|
-
containerType() {
|
|
858
|
-
return Array.isArray(this._value) ? "array" : this._value !== null && typeof this._value == "object" ? "object" : "primitive";
|
|
859
|
-
}
|
|
860
|
-
valueType() {
|
|
861
|
-
return this._value === null ? "null" : Array.isArray(this._value) ? "array" : typeof this._value;
|
|
862
|
-
}
|
|
863
|
-
// Common reader, supported by all containers
|
|
864
|
-
get() {
|
|
865
|
-
return this._value;
|
|
866
|
-
}
|
|
867
|
-
// Array reader
|
|
868
|
-
length() {
|
|
869
|
-
if (!Array.isArray(this._value))
|
|
870
|
-
throw new Error("Won't return length of non-indexable _value");
|
|
871
|
-
return this._value.length;
|
|
872
|
-
}
|
|
873
|
-
getIndex(i) {
|
|
874
|
-
return Array.isArray(this._value) ? i >= this.length() ? null : new ImmutableAccessor(this._value[i], this.path.concat(i)) : !1;
|
|
875
|
-
}
|
|
876
|
-
// Object reader
|
|
877
|
-
hasAttribute(key) {
|
|
878
|
-
return isRecord(this._value) ? this._value.hasOwnProperty(key) : !1;
|
|
879
|
-
}
|
|
880
|
-
attributeKeys() {
|
|
881
|
-
return isRecord(this._value) ? Object.keys(this._value) : [];
|
|
882
|
-
}
|
|
883
|
-
getAttribute(key) {
|
|
884
|
-
if (!isRecord(this._value))
|
|
885
|
-
throw new Error("getAttribute only applies to plain objects");
|
|
886
|
-
return this.hasAttribute(key) ? new ImmutableAccessor(this._value[key], this.path.concat(key)) : null;
|
|
887
|
-
}
|
|
888
|
-
// Common writer, supported by all containers
|
|
889
|
-
set(value) {
|
|
890
|
-
return value === this._value ? this : new ImmutableAccessor(value, this.path);
|
|
891
|
-
}
|
|
892
|
-
// array writer interface
|
|
893
|
-
setIndex(i, value) {
|
|
894
|
-
if (!Array.isArray(this._value))
|
|
895
|
-
throw new Error("setIndex only applies to arrays");
|
|
896
|
-
if (Object.is(value, this._value[i]))
|
|
897
|
-
return this;
|
|
898
|
-
const nextValue = this._value.slice();
|
|
899
|
-
return nextValue[i] = value, new ImmutableAccessor(nextValue, this.path);
|
|
900
|
-
}
|
|
901
|
-
setIndexAccessor(i, accessor) {
|
|
902
|
-
return this.setIndex(i, accessor.get());
|
|
903
|
-
}
|
|
904
|
-
unsetIndices(indices) {
|
|
905
|
-
if (!Array.isArray(this._value))
|
|
906
|
-
throw new Error("unsetIndices only applies to arrays");
|
|
907
|
-
const length = this._value.length, nextValue = [];
|
|
908
|
-
for (let i = 0; i < length; i++)
|
|
909
|
-
indices.indexOf(i) === -1 && nextValue.push(this._value[i]);
|
|
910
|
-
return new ImmutableAccessor(nextValue, this.path);
|
|
911
|
-
}
|
|
912
|
-
insertItemsAt(pos, items) {
|
|
913
|
-
if (!Array.isArray(this._value))
|
|
914
|
-
throw new Error("insertItemsAt only applies to arrays");
|
|
915
|
-
let nextValue;
|
|
916
|
-
return this._value.length === 0 && pos === 0 ? nextValue = items : nextValue = this._value.slice(0, pos).concat(items).concat(this._value.slice(pos)), new ImmutableAccessor(nextValue, this.path);
|
|
917
|
-
}
|
|
918
|
-
// Object writer interface
|
|
919
|
-
setAttribute(key, value) {
|
|
920
|
-
if (!isRecord(this._value))
|
|
921
|
-
throw new Error("Unable to set attribute of non-object container");
|
|
922
|
-
if (Object.is(value, this._value[key]))
|
|
923
|
-
return this;
|
|
924
|
-
const nextValue = Object.assign({}, this._value, { [key]: value });
|
|
925
|
-
return new ImmutableAccessor(nextValue, this.path);
|
|
926
|
-
}
|
|
927
|
-
setAttributeAccessor(key, accessor) {
|
|
928
|
-
return this.setAttribute(key, accessor.get());
|
|
929
|
-
}
|
|
930
|
-
unsetAttribute(key) {
|
|
931
|
-
if (!isRecord(this._value))
|
|
932
|
-
throw new Error("Unable to unset attribute of non-object container");
|
|
933
|
-
const nextValue = Object.assign({}, this._value);
|
|
934
|
-
return delete nextValue[key], new ImmutableAccessor(nextValue, this.path);
|
|
935
|
-
}
|
|
840
|
+
return extractAccessors(path, value).map((acc) => ({
|
|
841
|
+
path: acc.path,
|
|
842
|
+
value: acc.get()
|
|
843
|
+
}));
|
|
936
844
|
}
|
|
845
|
+
/**
|
|
846
|
+
* An immutable probe/writer for plain JS objects that will never mutate
|
|
847
|
+
* the provided _value in place. Each setter returns a new (wrapped) version
|
|
848
|
+
* of the value.
|
|
849
|
+
*/
|
|
850
|
+
var ImmutableAccessor = class ImmutableAccessor {
|
|
851
|
+
_value;
|
|
852
|
+
path;
|
|
853
|
+
constructor(value, path) {
|
|
854
|
+
this._value = value, this.path = path || [];
|
|
855
|
+
}
|
|
856
|
+
containerType() {
|
|
857
|
+
return Array.isArray(this._value) ? "array" : this._value !== null && typeof this._value == "object" ? "object" : "primitive";
|
|
858
|
+
}
|
|
859
|
+
valueType() {
|
|
860
|
+
return this._value === null ? "null" : Array.isArray(this._value) ? "array" : typeof this._value;
|
|
861
|
+
}
|
|
862
|
+
get() {
|
|
863
|
+
return this._value;
|
|
864
|
+
}
|
|
865
|
+
length() {
|
|
866
|
+
if (!Array.isArray(this._value)) throw Error("Won't return length of non-indexable _value");
|
|
867
|
+
return this._value.length;
|
|
868
|
+
}
|
|
869
|
+
getIndex(i) {
|
|
870
|
+
return Array.isArray(this._value) ? i >= this.length() ? null : new ImmutableAccessor(this._value[i], this.path.concat(i)) : !1;
|
|
871
|
+
}
|
|
872
|
+
hasAttribute(key) {
|
|
873
|
+
return isRecord(this._value) ? this._value.hasOwnProperty(key) : !1;
|
|
874
|
+
}
|
|
875
|
+
attributeKeys() {
|
|
876
|
+
return isRecord(this._value) ? Object.keys(this._value) : [];
|
|
877
|
+
}
|
|
878
|
+
getAttribute(key) {
|
|
879
|
+
if (!isRecord(this._value)) throw Error("getAttribute only applies to plain objects");
|
|
880
|
+
return this.hasAttribute(key) ? new ImmutableAccessor(this._value[key], this.path.concat(key)) : null;
|
|
881
|
+
}
|
|
882
|
+
set(value) {
|
|
883
|
+
return value === this._value ? this : new ImmutableAccessor(value, this.path);
|
|
884
|
+
}
|
|
885
|
+
setIndex(i, value) {
|
|
886
|
+
if (!Array.isArray(this._value)) throw Error("setIndex only applies to arrays");
|
|
887
|
+
if (Object.is(value, this._value[i])) return this;
|
|
888
|
+
let nextValue = this._value.slice();
|
|
889
|
+
return nextValue[i] = value, new ImmutableAccessor(nextValue, this.path);
|
|
890
|
+
}
|
|
891
|
+
setIndexAccessor(i, accessor) {
|
|
892
|
+
return this.setIndex(i, accessor.get());
|
|
893
|
+
}
|
|
894
|
+
unsetIndices(indices) {
|
|
895
|
+
if (!Array.isArray(this._value)) throw Error("unsetIndices only applies to arrays");
|
|
896
|
+
let length = this._value.length, nextValue = [];
|
|
897
|
+
for (let i = 0; i < length; i++) indices.indexOf(i) === -1 && nextValue.push(this._value[i]);
|
|
898
|
+
return new ImmutableAccessor(nextValue, this.path);
|
|
899
|
+
}
|
|
900
|
+
insertItemsAt(pos, items) {
|
|
901
|
+
if (!Array.isArray(this._value)) throw Error("insertItemsAt only applies to arrays");
|
|
902
|
+
let nextValue;
|
|
903
|
+
return nextValue = this._value.length === 0 && pos === 0 ? items : this._value.slice(0, pos).concat(items).concat(this._value.slice(pos)), new ImmutableAccessor(nextValue, this.path);
|
|
904
|
+
}
|
|
905
|
+
setAttribute(key, value) {
|
|
906
|
+
if (!isRecord(this._value)) throw Error("Unable to set attribute of non-object container");
|
|
907
|
+
if (Object.is(value, this._value[key])) return this;
|
|
908
|
+
let nextValue = Object.assign({}, this._value, { [key]: value });
|
|
909
|
+
return new ImmutableAccessor(nextValue, this.path);
|
|
910
|
+
}
|
|
911
|
+
setAttributeAccessor(key, accessor) {
|
|
912
|
+
return this.setAttribute(key, accessor.get());
|
|
913
|
+
}
|
|
914
|
+
unsetAttribute(key) {
|
|
915
|
+
if (!isRecord(this._value)) throw Error("Unable to unset attribute of non-object container");
|
|
916
|
+
let nextValue = Object.assign({}, this._value);
|
|
917
|
+
return delete nextValue[key], new ImmutableAccessor(nextValue, this.path);
|
|
918
|
+
}
|
|
919
|
+
};
|
|
937
920
|
function isRecord(value) {
|
|
938
|
-
|
|
921
|
+
return typeof value == "object" && !!value;
|
|
939
922
|
}
|
|
940
923
|
function applyPatch(patch, oldValue) {
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
}
|
|
945
|
-
class DiffMatchPatch {
|
|
946
|
-
path;
|
|
947
|
-
dmpPatch;
|
|
948
|
-
id;
|
|
949
|
-
constructor(id, path, dmpPatchSrc) {
|
|
950
|
-
this.id = id, this.path = path, this.dmpPatch = parsePatch$1(dmpPatchSrc);
|
|
951
|
-
}
|
|
952
|
-
apply(targets, accessor) {
|
|
953
|
-
let result = accessor;
|
|
954
|
-
if (result.containerType() === "primitive")
|
|
955
|
-
return result;
|
|
956
|
-
for (const target of targets) {
|
|
957
|
-
if (target.isIndexReference()) {
|
|
958
|
-
for (const index of target.toIndicies(accessor)) {
|
|
959
|
-
const item = result.getIndex(index);
|
|
960
|
-
if (!item)
|
|
961
|
-
continue;
|
|
962
|
-
const oldValue = item.get(), nextValue = applyPatch(this.dmpPatch, oldValue);
|
|
963
|
-
result = result.setIndex(index, nextValue);
|
|
964
|
-
}
|
|
965
|
-
continue;
|
|
966
|
-
}
|
|
967
|
-
if (target.isAttributeReference() && result.hasAttribute(target.name())) {
|
|
968
|
-
const attribute = result.getAttribute(target.name());
|
|
969
|
-
if (!attribute)
|
|
970
|
-
continue;
|
|
971
|
-
const oldValue = attribute.get(), nextValue = applyPatch(this.dmpPatch, oldValue);
|
|
972
|
-
result = result.setAttribute(target.name(), nextValue);
|
|
973
|
-
continue;
|
|
974
|
-
}
|
|
975
|
-
throw new Error(`Unable to apply diffMatchPatch to target ${target.toString()}`);
|
|
976
|
-
}
|
|
977
|
-
return result;
|
|
978
|
-
}
|
|
924
|
+
if (typeof oldValue != "string") return oldValue;
|
|
925
|
+
let [result] = applyPatches(patch, oldValue, { allowExceedingIndices: !0 });
|
|
926
|
+
return result;
|
|
979
927
|
}
|
|
928
|
+
var DiffMatchPatch = class {
|
|
929
|
+
path;
|
|
930
|
+
dmpPatch;
|
|
931
|
+
id;
|
|
932
|
+
constructor(id, path, dmpPatchSrc) {
|
|
933
|
+
this.id = id, this.path = path, this.dmpPatch = parsePatch(dmpPatchSrc);
|
|
934
|
+
}
|
|
935
|
+
apply(targets, accessor) {
|
|
936
|
+
let result = accessor;
|
|
937
|
+
if (result.containerType() === "primitive") return result;
|
|
938
|
+
for (let target of targets) {
|
|
939
|
+
if (target.isIndexReference()) {
|
|
940
|
+
for (let index of target.toIndicies(accessor)) {
|
|
941
|
+
let item = result.getIndex(index);
|
|
942
|
+
if (!item) continue;
|
|
943
|
+
let oldValue = item.get(), nextValue = applyPatch(this.dmpPatch, oldValue);
|
|
944
|
+
result = result.setIndex(index, nextValue);
|
|
945
|
+
}
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
if (target.isAttributeReference() && result.hasAttribute(target.name())) {
|
|
949
|
+
let attribute = result.getAttribute(target.name());
|
|
950
|
+
if (!attribute) continue;
|
|
951
|
+
let oldValue = attribute.get(), nextValue = applyPatch(this.dmpPatch, oldValue);
|
|
952
|
+
result = result.setAttribute(target.name(), nextValue);
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
throw Error(`Unable to apply diffMatchPatch to target ${target.toString()}`);
|
|
956
|
+
}
|
|
957
|
+
return result;
|
|
958
|
+
}
|
|
959
|
+
};
|
|
980
960
|
function performIncrement(previousValue, delta) {
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
class IncPatch {
|
|
984
|
-
path;
|
|
985
|
-
value;
|
|
986
|
-
id;
|
|
987
|
-
constructor(id, path, value) {
|
|
988
|
-
this.path = path, this.value = value, this.id = id;
|
|
989
|
-
}
|
|
990
|
-
apply(targets, accessor) {
|
|
991
|
-
let result = accessor;
|
|
992
|
-
if (result.containerType() === "primitive")
|
|
993
|
-
return result;
|
|
994
|
-
for (const target of targets) {
|
|
995
|
-
if (target.isIndexReference()) {
|
|
996
|
-
for (const index of target.toIndicies(accessor)) {
|
|
997
|
-
const item = result.getIndex(index);
|
|
998
|
-
if (!item)
|
|
999
|
-
continue;
|
|
1000
|
-
const previousValue = item.get();
|
|
1001
|
-
result = result.setIndex(index, performIncrement(previousValue, this.value));
|
|
1002
|
-
}
|
|
1003
|
-
continue;
|
|
1004
|
-
}
|
|
1005
|
-
if (target.isAttributeReference()) {
|
|
1006
|
-
const attribute = result.getAttribute(target.name());
|
|
1007
|
-
if (!attribute)
|
|
1008
|
-
continue;
|
|
1009
|
-
const previousValue = attribute.get();
|
|
1010
|
-
result = result.setAttribute(target.name(), performIncrement(previousValue, this.value));
|
|
1011
|
-
continue;
|
|
1012
|
-
}
|
|
1013
|
-
throw new Error(`Unable to apply to target ${target.toString()}`);
|
|
1014
|
-
}
|
|
1015
|
-
return result;
|
|
1016
|
-
}
|
|
961
|
+
return typeof previousValue != "number" || !Number.isFinite(previousValue) ? previousValue : previousValue + delta;
|
|
1017
962
|
}
|
|
963
|
+
var IncPatch = class {
|
|
964
|
+
path;
|
|
965
|
+
value;
|
|
966
|
+
id;
|
|
967
|
+
constructor(id, path, value) {
|
|
968
|
+
this.path = path, this.value = value, this.id = id;
|
|
969
|
+
}
|
|
970
|
+
apply(targets, accessor) {
|
|
971
|
+
let result = accessor;
|
|
972
|
+
if (result.containerType() === "primitive") return result;
|
|
973
|
+
for (let target of targets) {
|
|
974
|
+
if (target.isIndexReference()) {
|
|
975
|
+
for (let index of target.toIndicies(accessor)) {
|
|
976
|
+
let item = result.getIndex(index);
|
|
977
|
+
if (!item) continue;
|
|
978
|
+
let previousValue = item.get();
|
|
979
|
+
result = result.setIndex(index, performIncrement(previousValue, this.value));
|
|
980
|
+
}
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (target.isAttributeReference()) {
|
|
984
|
+
let attribute = result.getAttribute(target.name());
|
|
985
|
+
if (!attribute) continue;
|
|
986
|
+
let previousValue = attribute.get();
|
|
987
|
+
result = result.setAttribute(target.name(), performIncrement(previousValue, this.value));
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
throw Error(`Unable to apply to target ${target.toString()}`);
|
|
991
|
+
}
|
|
992
|
+
return result;
|
|
993
|
+
}
|
|
994
|
+
};
|
|
1018
995
|
function targetsToIndicies(targets, accessor) {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}
|
|
1024
|
-
class InsertPatch {
|
|
1025
|
-
location;
|
|
1026
|
-
path;
|
|
1027
|
-
items;
|
|
1028
|
-
id;
|
|
1029
|
-
constructor(id, location, path, items) {
|
|
1030
|
-
this.id = id, this.location = location, this.path = path, this.items = items;
|
|
1031
|
-
}
|
|
1032
|
-
apply(targets, accessor) {
|
|
1033
|
-
let result = accessor;
|
|
1034
|
-
if (accessor.containerType() !== "array") {
|
|
1035
|
-
const valueType = accessor.valueType();
|
|
1036
|
-
throw new Error(
|
|
1037
|
-
`Attempt to apply insert patch to value of type "${valueType}" at path "${accessor.path.join(" \u2192 ")}"`
|
|
1038
|
-
);
|
|
1039
|
-
}
|
|
1040
|
-
switch (this.location) {
|
|
1041
|
-
case "before": {
|
|
1042
|
-
const pos = minIndex(targets, accessor);
|
|
1043
|
-
result = result.insertItemsAt(pos, this.items);
|
|
1044
|
-
break;
|
|
1045
|
-
}
|
|
1046
|
-
case "after": {
|
|
1047
|
-
const pos = maxIndex(targets, accessor);
|
|
1048
|
-
result = result.insertItemsAt(pos + 1, this.items);
|
|
1049
|
-
break;
|
|
1050
|
-
}
|
|
1051
|
-
case "replace": {
|
|
1052
|
-
const indicies = targetsToIndicies(targets, accessor);
|
|
1053
|
-
result = result.unsetIndices(indicies), result = result.insertItemsAt(indicies[0], this.items);
|
|
1054
|
-
break;
|
|
1055
|
-
}
|
|
1056
|
-
default:
|
|
1057
|
-
throw new Error(`Unsupported location atm: ${this.location}`);
|
|
1058
|
-
}
|
|
1059
|
-
return result;
|
|
1060
|
-
}
|
|
996
|
+
let result = [];
|
|
997
|
+
return targets.forEach((target) => {
|
|
998
|
+
target.isIndexReference() && result.push(...target.toIndicies(accessor));
|
|
999
|
+
}), result.sort((a, b) => a - b);
|
|
1061
1000
|
}
|
|
1001
|
+
var InsertPatch = class {
|
|
1002
|
+
location;
|
|
1003
|
+
path;
|
|
1004
|
+
items;
|
|
1005
|
+
id;
|
|
1006
|
+
constructor(id, location, path, items) {
|
|
1007
|
+
this.id = id, this.location = location, this.path = path, this.items = items;
|
|
1008
|
+
}
|
|
1009
|
+
apply(targets, accessor) {
|
|
1010
|
+
let result = accessor;
|
|
1011
|
+
if (accessor.containerType() !== "array") {
|
|
1012
|
+
let valueType = accessor.valueType();
|
|
1013
|
+
throw Error(`Attempt to apply insert patch to value of type "${valueType}" at path "${accessor.path.join(" → ")}"`);
|
|
1014
|
+
}
|
|
1015
|
+
switch (this.location) {
|
|
1016
|
+
case "before": {
|
|
1017
|
+
let pos = minIndex(targets, accessor);
|
|
1018
|
+
result = result.insertItemsAt(pos, this.items);
|
|
1019
|
+
break;
|
|
1020
|
+
}
|
|
1021
|
+
case "after": {
|
|
1022
|
+
let pos = maxIndex(targets, accessor);
|
|
1023
|
+
result = result.insertItemsAt(pos + 1, this.items);
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
case "replace": {
|
|
1027
|
+
let indicies = targetsToIndicies(targets, accessor);
|
|
1028
|
+
result = result.unsetIndices(indicies), result = result.insertItemsAt(indicies[0], this.items);
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
default: throw Error(`Unsupported location atm: ${this.location}`);
|
|
1032
|
+
}
|
|
1033
|
+
return result;
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1062
1036
|
function minIndex(targets, accessor) {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1037
|
+
let result = min(targetsToIndicies(targets, accessor)) || 0;
|
|
1038
|
+
return targets.forEach((target) => {
|
|
1039
|
+
if (target.isRange()) {
|
|
1040
|
+
let { start } = target.expandRange();
|
|
1041
|
+
start < result && (result = start);
|
|
1042
|
+
}
|
|
1043
|
+
}), result;
|
|
1070
1044
|
}
|
|
1071
1045
|
function maxIndex(targets, accessor) {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
}
|
|
1080
|
-
class SetIfMissingPatch {
|
|
1081
|
-
id;
|
|
1082
|
-
path;
|
|
1083
|
-
value;
|
|
1084
|
-
constructor(id, path, value) {
|
|
1085
|
-
this.id = id, this.path = path, this.value = value;
|
|
1086
|
-
}
|
|
1087
|
-
apply(targets, accessor) {
|
|
1088
|
-
let result = accessor;
|
|
1089
|
-
return targets.forEach((target) => {
|
|
1090
|
-
if (!target.isIndexReference())
|
|
1091
|
-
if (target.isAttributeReference())
|
|
1092
|
-
result.containerType() === "primitive" ? result = result.set({ [target.name()]: this.value }) : result.hasAttribute(target.name()) || (result = accessor.setAttribute(target.name(), this.value));
|
|
1093
|
-
else
|
|
1094
|
-
throw new Error(`Unable to apply to target ${target.toString()}`);
|
|
1095
|
-
}), result;
|
|
1096
|
-
}
|
|
1046
|
+
let result = max(targetsToIndicies(targets, accessor)) || 0;
|
|
1047
|
+
return targets.forEach((target) => {
|
|
1048
|
+
if (target.isRange()) {
|
|
1049
|
+
let { end } = target.expandRange();
|
|
1050
|
+
end > result && (result = end);
|
|
1051
|
+
}
|
|
1052
|
+
}), result;
|
|
1097
1053
|
}
|
|
1098
|
-
class
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
}
|
|
1179
|
-
class Patcher {
|
|
1180
|
-
patches;
|
|
1181
|
-
constructor(patch) {
|
|
1182
|
-
this.patches = parsePatch(patch);
|
|
1183
|
-
}
|
|
1184
|
-
apply(value) {
|
|
1185
|
-
const accessor = new ImmutableAccessor(value);
|
|
1186
|
-
return this.applyViaAccessor(accessor).get();
|
|
1187
|
-
}
|
|
1188
|
-
// If you want to use your own accessor implementation, you can use this method
|
|
1189
|
-
// to invoke the patcher. Since all subsequent accessors for children of this accessor
|
|
1190
|
-
// are obtained through the methods in the accessors, you retain full control of the
|
|
1191
|
-
// implementation throguhgout the application. Have a look in ImmutableAccessor
|
|
1192
|
-
// to see an example of how accessors are implemented.
|
|
1193
|
-
applyViaAccessor(accessor) {
|
|
1194
|
-
let result = accessor;
|
|
1195
|
-
const idAccessor = accessor.getAttribute("_id");
|
|
1196
|
-
if (!idAccessor)
|
|
1197
|
-
throw new Error("Cannot apply patch to document with no _id");
|
|
1198
|
-
const id = idAccessor.get();
|
|
1199
|
-
for (const patch of this.patches) {
|
|
1200
|
-
if (patch.id !== id)
|
|
1201
|
-
continue;
|
|
1202
|
-
const matcher = Matcher.fromPath(patch.path).setPayload(patch);
|
|
1203
|
-
result = process(matcher, result);
|
|
1204
|
-
}
|
|
1205
|
-
return result;
|
|
1206
|
-
}
|
|
1054
|
+
var SetIfMissingPatch = class {
|
|
1055
|
+
id;
|
|
1056
|
+
path;
|
|
1057
|
+
value;
|
|
1058
|
+
constructor(id, path, value) {
|
|
1059
|
+
this.id = id, this.path = path, this.value = value;
|
|
1060
|
+
}
|
|
1061
|
+
apply(targets, accessor) {
|
|
1062
|
+
let result = accessor;
|
|
1063
|
+
return targets.forEach((target) => {
|
|
1064
|
+
if (!target.isIndexReference()) if (target.isAttributeReference()) result.containerType() === "primitive" ? result = result.set({ [target.name()]: this.value }) : result.hasAttribute(target.name()) || (result = accessor.setAttribute(target.name(), this.value));
|
|
1065
|
+
else throw Error(`Unable to apply to target ${target.toString()}`);
|
|
1066
|
+
}), result;
|
|
1067
|
+
}
|
|
1068
|
+
}, SetPatch = class {
|
|
1069
|
+
id;
|
|
1070
|
+
path;
|
|
1071
|
+
value;
|
|
1072
|
+
constructor(id, path, value) {
|
|
1073
|
+
this.id = id, this.path = path, this.value = value;
|
|
1074
|
+
}
|
|
1075
|
+
apply(targets, accessor) {
|
|
1076
|
+
let result = accessor;
|
|
1077
|
+
return targets.forEach((target) => {
|
|
1078
|
+
if (target.isSelfReference()) result = result.set(this.value);
|
|
1079
|
+
else if (target.isIndexReference()) target.toIndicies(accessor).forEach((i) => {
|
|
1080
|
+
result = result.setIndex(i, this.value);
|
|
1081
|
+
});
|
|
1082
|
+
else if (target.isAttributeReference()) result = result.containerType() === "primitive" ? result.set({ [target.name()]: this.value }) : result.setAttribute(target.name(), this.value);
|
|
1083
|
+
else throw Error(`Unable to apply to target ${target.toString()}`);
|
|
1084
|
+
}), result;
|
|
1085
|
+
}
|
|
1086
|
+
}, UnsetPatch = class {
|
|
1087
|
+
id;
|
|
1088
|
+
path;
|
|
1089
|
+
value;
|
|
1090
|
+
constructor(id, path) {
|
|
1091
|
+
this.id = id, this.path = path;
|
|
1092
|
+
}
|
|
1093
|
+
apply(targets, accessor) {
|
|
1094
|
+
let result = accessor;
|
|
1095
|
+
switch (accessor.containerType()) {
|
|
1096
|
+
case "array":
|
|
1097
|
+
result = result.unsetIndices(targetsToIndicies(targets, accessor));
|
|
1098
|
+
break;
|
|
1099
|
+
case "object":
|
|
1100
|
+
targets.forEach((target) => {
|
|
1101
|
+
result = result.unsetAttribute(target.name());
|
|
1102
|
+
});
|
|
1103
|
+
break;
|
|
1104
|
+
default: throw Error("Target value is neither indexable or an object. This error should potentially just be silently ignored?");
|
|
1105
|
+
}
|
|
1106
|
+
return result;
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
function parsePatch$1(patch) {
|
|
1110
|
+
let result = [];
|
|
1111
|
+
if (Array.isArray(patch)) return patch.reduce((r, p) => r.concat(parsePatch$1(p)), result);
|
|
1112
|
+
let { set, setIfMissing, unset, diffMatchPatch, inc, dec, insert } = patch;
|
|
1113
|
+
if (setIfMissing && Object.keys(setIfMissing).forEach((path) => {
|
|
1114
|
+
result.push(new SetIfMissingPatch(patch.id, path, setIfMissing[path]));
|
|
1115
|
+
}), set && Object.keys(set).forEach((path) => {
|
|
1116
|
+
result.push(new SetPatch(patch.id, path, set[path]));
|
|
1117
|
+
}), unset && unset.forEach((path) => {
|
|
1118
|
+
result.push(new UnsetPatch(patch.id, path));
|
|
1119
|
+
}), diffMatchPatch && Object.keys(diffMatchPatch).forEach((path) => {
|
|
1120
|
+
result.push(new DiffMatchPatch(patch.id, path, diffMatchPatch[path]));
|
|
1121
|
+
}), inc && Object.keys(inc).forEach((path) => {
|
|
1122
|
+
result.push(new IncPatch(patch.id, path, inc[path]));
|
|
1123
|
+
}), dec && Object.keys(dec).forEach((path) => {
|
|
1124
|
+
result.push(new IncPatch(patch.id, path, -dec[path]));
|
|
1125
|
+
}), insert) {
|
|
1126
|
+
let location, path, spec = insert;
|
|
1127
|
+
if ("before" in spec) location = "before", path = spec.before;
|
|
1128
|
+
else if ("after" in spec) location = "after", path = spec.after;
|
|
1129
|
+
else if ("replace" in spec) location = "replace", path = spec.replace;
|
|
1130
|
+
else throw Error("Invalid insert patch");
|
|
1131
|
+
result.push(new InsertPatch(patch.id, location, path, spec.items));
|
|
1132
|
+
}
|
|
1133
|
+
return result;
|
|
1207
1134
|
}
|
|
1135
|
+
var Patcher = class {
|
|
1136
|
+
patches;
|
|
1137
|
+
constructor(patch) {
|
|
1138
|
+
this.patches = parsePatch$1(patch);
|
|
1139
|
+
}
|
|
1140
|
+
apply(value) {
|
|
1141
|
+
let accessor = new ImmutableAccessor(value);
|
|
1142
|
+
return this.applyViaAccessor(accessor).get();
|
|
1143
|
+
}
|
|
1144
|
+
applyViaAccessor(accessor) {
|
|
1145
|
+
let result = accessor, idAccessor = accessor.getAttribute("_id");
|
|
1146
|
+
if (!idAccessor) throw Error("Cannot apply patch to document with no _id");
|
|
1147
|
+
let id = idAccessor.get();
|
|
1148
|
+
for (let patch of this.patches) patch.id === id && (result = process(Matcher.fromPath(patch.path).setPayload(patch), result));
|
|
1149
|
+
return result;
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1208
1152
|
function process(matcher, accessor) {
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
return;
|
|
1225
|
-
const newValueAccessor = process(lead.matcher, oldValueAccessor);
|
|
1226
|
-
oldValueAccessor !== newValueAccessor && (result = result.setAttributeAccessor(lead.target.name(), newValueAccessor));
|
|
1227
|
-
} else
|
|
1228
|
-
throw new Error(`Unable to handle target ${lead.target.toString()}`);
|
|
1229
|
-
}), delivery && isPatcher(delivery.payload) && (result = delivery.payload.apply(delivery.targets, result)), result;
|
|
1153
|
+
let isSetPatch = matcher.payload instanceof SetPatch || matcher.payload instanceof SetIfMissingPatch, result = accessor, { leads, delivery } = matcher.match(accessor);
|
|
1154
|
+
return leads.forEach((lead) => {
|
|
1155
|
+
if (lead.target.isIndexReference()) lead.target.toIndicies().forEach((i) => {
|
|
1156
|
+
let item = result.getIndex(i);
|
|
1157
|
+
if (!item) throw Error("Index out of bounds");
|
|
1158
|
+
result = result.setIndexAccessor(i, process(lead.matcher, item));
|
|
1159
|
+
});
|
|
1160
|
+
else if (lead.target.isAttributeReference()) {
|
|
1161
|
+
isSetPatch && result.containerType() === "primitive" && (result = result.set({}));
|
|
1162
|
+
let oldValueAccessor = result.getAttribute(lead.target.name());
|
|
1163
|
+
if (!oldValueAccessor && isSetPatch && (result = result.setAttribute(lead.target.name(), {}), oldValueAccessor = result.getAttribute(lead.target.name())), !oldValueAccessor) return;
|
|
1164
|
+
let newValueAccessor = process(lead.matcher, oldValueAccessor);
|
|
1165
|
+
oldValueAccessor !== newValueAccessor && (result = result.setAttributeAccessor(lead.target.name(), newValueAccessor));
|
|
1166
|
+
} else throw Error(`Unable to handle target ${lead.target.toString()}`);
|
|
1167
|
+
}), delivery && isPatcher(delivery.payload) && (result = delivery.payload.apply(delivery.targets, result)), result;
|
|
1230
1168
|
}
|
|
1231
1169
|
function isPatcher(payload) {
|
|
1232
|
-
|
|
1170
|
+
return !!(payload && typeof payload == "object" && payload && "apply" in payload && typeof payload.apply == "function");
|
|
1233
1171
|
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Locally unique id's. We use this to generate transaction ids, and they don't have to be
|
|
1174
|
+
* cryptographically unique, as the worst that can happen is that they get rejected because
|
|
1175
|
+
* of a collision, and then we should just retry with a new id.
|
|
1176
|
+
*/
|
|
1234
1177
|
const luid = uuid;
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
}
|
|
1641
|
-
debug("Unoptimizable mutation detected, purging optimization buffer"), this.staged.push(op), this.stashStagedOperations();
|
|
1642
|
-
}
|
|
1643
|
-
/**
|
|
1644
|
-
* Attempt to perform one single set operation in an optimised manner, return value
|
|
1645
|
-
* reflects whether or not the operation could be performed.
|
|
1646
|
-
|
|
1647
|
-
* @param path - The JSONPath to the set operation in question
|
|
1648
|
-
* @param nextValue - The value to be set
|
|
1649
|
-
* @returns True of optimized, false otherwise
|
|
1650
|
-
*/
|
|
1651
|
-
optimiseSetOperation(path, nextValue) {
|
|
1652
|
-
if (typeof nextValue == "object")
|
|
1653
|
-
return !1;
|
|
1654
|
-
const matches = extractWithPath(path, this.PRESTAGE);
|
|
1655
|
-
if (matches.length !== 1)
|
|
1656
|
-
return !1;
|
|
1657
|
-
const match = matches[0];
|
|
1658
|
-
if (typeof match.value == "object" || !this.PRESTAGE)
|
|
1659
|
-
return !1;
|
|
1660
|
-
let op = null;
|
|
1661
|
-
if (match.value === nextValue)
|
|
1662
|
-
op = null;
|
|
1663
|
-
else if (typeof match.value == "string" && typeof nextValue == "string")
|
|
1664
|
-
try {
|
|
1665
|
-
const patch = stringifyPatches(makePatches(match.value, nextValue));
|
|
1666
|
-
op = { patch: { id: this.PRESTAGE._id, diffMatchPatch: { [path]: patch } } };
|
|
1667
|
-
} catch {
|
|
1668
|
-
return !1;
|
|
1669
|
-
}
|
|
1670
|
-
else
|
|
1671
|
-
op = { patch: { id: this.PRESTAGE._id, set: { [path]: nextValue } } };
|
|
1672
|
-
const canonicalPath = arrayToJSONMatchPath(match.path);
|
|
1673
|
-
return op ? this.setOperations[canonicalPath] = op : delete this.setOperations[canonicalPath], !0;
|
|
1674
|
-
}
|
|
1675
|
-
stashStagedOperations() {
|
|
1676
|
-
const nextOps = [];
|
|
1677
|
-
Object.keys(this.setOperations).forEach((key) => {
|
|
1678
|
-
const op = this.setOperations[key];
|
|
1679
|
-
op && nextOps.push(op);
|
|
1680
|
-
}), nextOps.push(...this.staged), nextOps.length > 0 && (this.PRESTAGE = new Mutation({ mutations: nextOps }).apply(this.PRESTAGE), this.staged = [], this.setOperations = {}), this.out.push(...nextOps);
|
|
1681
|
-
}
|
|
1682
|
-
/**
|
|
1683
|
-
* Rebases given the new base-document
|
|
1684
|
-
*
|
|
1685
|
-
* @param newBasis - New base document to rebase on
|
|
1686
|
-
* @returns New "edge" document with buffered changes integrated
|
|
1687
|
-
*/
|
|
1688
|
-
rebase(newBasis) {
|
|
1689
|
-
return this.stashStagedOperations(), newBasis === null ? (this.out = [], this.BASIS = newBasis, this.PRESTAGE = newBasis, this.documentPresent = !1) : (this.BASIS = newBasis, this.out ? this.PRESTAGE = new Mutation({ mutations: this.out }).apply(this.BASIS) : this.PRESTAGE = this.BASIS), this.PRESTAGE;
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
const ONE_MINUTE = 1e3 * 60;
|
|
1693
|
-
class Commit {
|
|
1694
|
-
mutations;
|
|
1695
|
-
tries;
|
|
1696
|
-
resolve;
|
|
1697
|
-
reject;
|
|
1698
|
-
constructor(mutations, { resolve, reject }) {
|
|
1699
|
-
this.mutations = mutations, this.tries = 0, this.resolve = resolve, this.reject = reject;
|
|
1700
|
-
}
|
|
1701
|
-
apply(doc) {
|
|
1702
|
-
return Mutation.applyAll(doc, this.mutations);
|
|
1703
|
-
}
|
|
1704
|
-
squash(doc) {
|
|
1705
|
-
const result = Mutation.squash(doc, this.mutations);
|
|
1706
|
-
return result.assignRandomTransactionId(), result;
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1178
|
+
/**
|
|
1179
|
+
* A mutation describing a number of operations on a single document.
|
|
1180
|
+
* This should be considered an immutable structure. Mutations are compiled
|
|
1181
|
+
* on first application, and any changes in properties will not effectively
|
|
1182
|
+
* change its behavior after that.
|
|
1183
|
+
*
|
|
1184
|
+
* @internal
|
|
1185
|
+
*/
|
|
1186
|
+
var Mutation = class Mutation {
|
|
1187
|
+
params;
|
|
1188
|
+
compiled;
|
|
1189
|
+
_appliesToMissingDocument;
|
|
1190
|
+
constructor(options) {
|
|
1191
|
+
this.params = options;
|
|
1192
|
+
}
|
|
1193
|
+
get transactionId() {
|
|
1194
|
+
return this.params.transactionId;
|
|
1195
|
+
}
|
|
1196
|
+
get transition() {
|
|
1197
|
+
return this.params.transition;
|
|
1198
|
+
}
|
|
1199
|
+
get identity() {
|
|
1200
|
+
return this.params.identity;
|
|
1201
|
+
}
|
|
1202
|
+
get previousRev() {
|
|
1203
|
+
return this.params.previousRev;
|
|
1204
|
+
}
|
|
1205
|
+
get resultRev() {
|
|
1206
|
+
return this.params.resultRev;
|
|
1207
|
+
}
|
|
1208
|
+
get mutations() {
|
|
1209
|
+
return this.params.mutations;
|
|
1210
|
+
}
|
|
1211
|
+
get timestamp() {
|
|
1212
|
+
if (typeof this.params.timestamp == "string") return new Date(this.params.timestamp);
|
|
1213
|
+
}
|
|
1214
|
+
get effects() {
|
|
1215
|
+
return this.params.effects;
|
|
1216
|
+
}
|
|
1217
|
+
assignRandomTransactionId() {
|
|
1218
|
+
this.params.transactionId = luid(), this.params.resultRev = this.params.transactionId;
|
|
1219
|
+
}
|
|
1220
|
+
appliesToMissingDocument() {
|
|
1221
|
+
if (this._appliesToMissingDocument !== void 0) return this._appliesToMissingDocument;
|
|
1222
|
+
let firstMut = this.mutations[0];
|
|
1223
|
+
return firstMut ? this._appliesToMissingDocument = !!(firstMut.create || firstMut.createIfNotExists || firstMut.createOrReplace) : this._appliesToMissingDocument = !0, this._appliesToMissingDocument;
|
|
1224
|
+
}
|
|
1225
|
+
compile() {
|
|
1226
|
+
let operations = [], getGuaranteedCreatedAt = (doc) => doc?._createdAt || this.params.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
1227
|
+
this.mutations.forEach((mutation) => {
|
|
1228
|
+
if (mutation.create) {
|
|
1229
|
+
let create = mutation.create || {};
|
|
1230
|
+
operations.push((doc) => doc || Object.assign(create, { _createdAt: getGuaranteedCreatedAt(create) }));
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (mutation.createIfNotExists) {
|
|
1234
|
+
let createIfNotExists = mutation.createIfNotExists || {};
|
|
1235
|
+
operations.push((doc) => doc === null ? Object.assign(createIfNotExists, { _createdAt: getGuaranteedCreatedAt(createIfNotExists) }) : doc);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (mutation.createOrReplace) {
|
|
1239
|
+
let createOrReplace = mutation.createOrReplace || {};
|
|
1240
|
+
operations.push(() => Object.assign(createOrReplace, { _createdAt: getGuaranteedCreatedAt(createOrReplace) }));
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (mutation.delete) {
|
|
1244
|
+
operations.push(() => null);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (mutation.patch) {
|
|
1248
|
+
if ("query" in mutation.patch) return;
|
|
1249
|
+
let patch = new Patcher(mutation.patch);
|
|
1250
|
+
operations.push((doc) => patch.apply(doc));
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
throw Error(`Unsupported mutation ${JSON.stringify(mutation, null, 2)}`);
|
|
1254
|
+
}), typeof this.params.timestamp == "string" && operations.push((doc) => doc ? Object.assign(doc, { _updatedAt: this.params.timestamp }) : null);
|
|
1255
|
+
let prevRev = this.previousRev, rev = this.resultRev || this.transactionId;
|
|
1256
|
+
this.compiled = (doc) => {
|
|
1257
|
+
if (prevRev && doc && prevRev !== doc._rev) throw Error(`Previous revision for this mutation was ${prevRev}, but the document revision is ${doc._rev}`);
|
|
1258
|
+
let result = doc;
|
|
1259
|
+
for (let operation of operations) result = operation(result);
|
|
1260
|
+
return result && rev && (result === doc && (result = Object.assign({}, doc)), result._rev = rev), result;
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
apply(document) {
|
|
1264
|
+
debug("Applying mutation %O to document %O", this.mutations, document), this.compiled || this.compile();
|
|
1265
|
+
let result = this.compiled(document);
|
|
1266
|
+
return debug(" => %O", result), result;
|
|
1267
|
+
}
|
|
1268
|
+
static applyAll(document, mutations) {
|
|
1269
|
+
return mutations.reduce((doc, mutation) => mutation.apply(doc), document);
|
|
1270
|
+
}
|
|
1271
|
+
static squash(document, mutations) {
|
|
1272
|
+
let squashed = mutations.reduce((result, mutation) => result.concat(...mutation.mutations), []);
|
|
1273
|
+
return new Mutation({ mutations: squashed });
|
|
1274
|
+
}
|
|
1275
|
+
}, Document = class {
|
|
1276
|
+
/**
|
|
1277
|
+
* Incoming patches from the server waiting to be applied to HEAD
|
|
1278
|
+
*/
|
|
1279
|
+
incoming = [];
|
|
1280
|
+
/**
|
|
1281
|
+
* Patches we know has been subitted to the server, but has not been seen yet in the return channel
|
|
1282
|
+
* so we can't be sure about the ordering yet (someone else might have slipped something between them)
|
|
1283
|
+
*/
|
|
1284
|
+
submitted = [];
|
|
1285
|
+
/**
|
|
1286
|
+
* Pending mutations
|
|
1287
|
+
*/
|
|
1288
|
+
pending = [];
|
|
1289
|
+
/**
|
|
1290
|
+
* Our model of the document according to the incoming patches from the server
|
|
1291
|
+
*/
|
|
1292
|
+
HEAD;
|
|
1293
|
+
/**
|
|
1294
|
+
* Our optimistic model of what the document will probably look like as soon as all our patches
|
|
1295
|
+
* have been processed. Updated every time we stage a new mutation, but also might revert back
|
|
1296
|
+
* to previous states if our mutations fail, or could change if unexpected mutations arrive
|
|
1297
|
+
* between our own. The `onRebase` callback will be called when EDGE changes in this manner.
|
|
1298
|
+
*/
|
|
1299
|
+
EDGE;
|
|
1300
|
+
/**
|
|
1301
|
+
* Called with the EDGE document when that document changes for a reason other than us staging
|
|
1302
|
+
* a new patch or receiving a mutation from the server while our EDGE is in sync with HEAD:
|
|
1303
|
+
* I.e. when EDGE changes because the order of mutations has changed in relation to our
|
|
1304
|
+
* optimistic predictions.
|
|
1305
|
+
*/
|
|
1306
|
+
onRebase;
|
|
1307
|
+
/**
|
|
1308
|
+
* Called when we receive a patch in the normal order of things, but the mutation is not ours
|
|
1309
|
+
*/
|
|
1310
|
+
onMutation;
|
|
1311
|
+
/**
|
|
1312
|
+
* Called when consistency state changes with the boolean value of the current consistency state
|
|
1313
|
+
*/
|
|
1314
|
+
onConsistencyChanged;
|
|
1315
|
+
/**
|
|
1316
|
+
* Called whenever a new incoming mutation comes in. These are always ordered correctly.
|
|
1317
|
+
*/
|
|
1318
|
+
onRemoteMutation;
|
|
1319
|
+
/**
|
|
1320
|
+
* We are consistent when there are no unresolved mutations of our own, and no un-applicable
|
|
1321
|
+
* incoming mutations. When this has been going on for too long, and there has been a while
|
|
1322
|
+
* since we staged a new mutation, it is time to reset your state.
|
|
1323
|
+
*/
|
|
1324
|
+
inconsistentAt = null;
|
|
1325
|
+
/**
|
|
1326
|
+
* The last time we staged a patch of our own. If we have been inconsistent for a while, but it
|
|
1327
|
+
* hasn't been long since we staged a new mutation, the reason is probably just because the user
|
|
1328
|
+
* is typing or something.
|
|
1329
|
+
*
|
|
1330
|
+
* Should be used as a guard against resetting state for inconsistency reasons.
|
|
1331
|
+
*/
|
|
1332
|
+
lastStagedAt = null;
|
|
1333
|
+
constructor(doc) {
|
|
1334
|
+
this.reset(doc), this.HEAD = doc, this.EDGE = doc;
|
|
1335
|
+
}
|
|
1336
|
+
reset(doc) {
|
|
1337
|
+
this.incoming = [], this.submitted = [], this.pending = [], this.inconsistentAt = null, this.HEAD = doc, this.EDGE = doc, this.considerIncoming(), this.updateConsistencyFlag();
|
|
1338
|
+
}
|
|
1339
|
+
arrive(mutation) {
|
|
1340
|
+
this.incoming.push(mutation), this.considerIncoming(), this.updateConsistencyFlag();
|
|
1341
|
+
}
|
|
1342
|
+
stage(mutation, silent) {
|
|
1343
|
+
if (!mutation.transactionId) throw Error("Mutations _must_ have transactionId when submitted");
|
|
1344
|
+
this.lastStagedAt = /* @__PURE__ */ new Date(), debug("Staging mutation %s (pushed to pending)", mutation.transactionId), this.pending.push(mutation), this.EDGE = mutation.apply(this.EDGE), this.onMutation && !silent && this.onMutation({
|
|
1345
|
+
mutation,
|
|
1346
|
+
document: this.EDGE,
|
|
1347
|
+
remote: !1
|
|
1348
|
+
});
|
|
1349
|
+
let txnId = mutation.transactionId;
|
|
1350
|
+
return this.updateConsistencyFlag(), {
|
|
1351
|
+
success: () => {
|
|
1352
|
+
this.pendingSuccessfullySubmitted(txnId), this.updateConsistencyFlag();
|
|
1353
|
+
},
|
|
1354
|
+
failure: () => {
|
|
1355
|
+
this.pendingFailed(txnId), this.updateConsistencyFlag();
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
isConsistent() {
|
|
1360
|
+
return !this.inconsistentAt;
|
|
1361
|
+
}
|
|
1362
|
+
considerIncoming() {
|
|
1363
|
+
let mustRebase = !1, nextMut, rebaseMutations = [];
|
|
1364
|
+
if (this.HEAD && this.HEAD._updatedAt) {
|
|
1365
|
+
let updatedAt = new Date(this.HEAD._updatedAt);
|
|
1366
|
+
this.incoming.find((mut) => mut.timestamp && mut.timestamp < updatedAt) && (this.incoming = this.incoming.filter((mut) => mut.timestamp && mut.timestamp < updatedAt));
|
|
1367
|
+
}
|
|
1368
|
+
let protect = 0;
|
|
1369
|
+
do {
|
|
1370
|
+
if (this.HEAD) {
|
|
1371
|
+
let HEAD = this.HEAD;
|
|
1372
|
+
nextMut = HEAD._rev ? this.incoming.find((mut) => mut.previousRev === HEAD._rev) : void 0;
|
|
1373
|
+
} else nextMut = this.incoming.find((mut) => mut.appliesToMissingDocument());
|
|
1374
|
+
if (nextMut) {
|
|
1375
|
+
let applied = this.applyIncoming(nextMut);
|
|
1376
|
+
if (mustRebase ||= applied, mustRebase && rebaseMutations.push(nextMut), protect++ > 10) throw Error(`Mutator stuck flushing incoming mutations. Probably stuck here: ${JSON.stringify(nextMut)}`);
|
|
1377
|
+
}
|
|
1378
|
+
} while (nextMut);
|
|
1379
|
+
this.incoming.length > 0 && debug.enabled && debug("Unable to apply mutations %s", this.incoming.map((mut) => mut.transactionId).join(", ")), mustRebase && this.rebase(rebaseMutations);
|
|
1380
|
+
}
|
|
1381
|
+
updateConsistencyFlag() {
|
|
1382
|
+
let wasConsistent = this.isConsistent(), isConsistent = this.pending.length === 0 && this.submitted.length === 0 && this.incoming.length === 0;
|
|
1383
|
+
isConsistent ? this.inconsistentAt = null : this.inconsistentAt ||= /* @__PURE__ */ new Date(), wasConsistent != isConsistent && this.onConsistencyChanged && (debug(isConsistent ? "Buffered document is inconsistent" : "Buffered document is consistent"), this.onConsistencyChanged(isConsistent));
|
|
1384
|
+
}
|
|
1385
|
+
applyIncoming(mut) {
|
|
1386
|
+
if (!mut) return !1;
|
|
1387
|
+
if (!mut.transactionId) throw Error("Received incoming mutation without a transaction ID");
|
|
1388
|
+
if (debug("Applying mutation %s -> %s to rev %s", mut.previousRev, mut.resultRev, this.HEAD && this.HEAD._rev), this.HEAD = mut.apply(this.HEAD), this.onRemoteMutation && this.onRemoteMutation(mut), this.incoming = this.incoming.filter((m) => m.transactionId !== mut.transactionId), this.hasUnresolvedMutations()) {
|
|
1389
|
+
let needRebase = this.consumeUnresolved(mut.transactionId);
|
|
1390
|
+
return debug.enabled && (debug(`Incoming mutation ${mut.transactionId} appeared while there were pending or submitted local mutations`), debug(`Submitted txnIds: ${this.submitted.map((m) => m.transactionId).join(", ")}`), debug(`Pending txnIds: ${this.pending.map((m) => m.transactionId).join(", ")}`), debug("needRebase === %s", needRebase)), needRebase;
|
|
1391
|
+
}
|
|
1392
|
+
return debug("Remote mutation %s arrived w/o any pending or submitted local mutations", mut.transactionId), this.EDGE = this.HEAD, this.onMutation && this.onMutation({
|
|
1393
|
+
mutation: mut,
|
|
1394
|
+
document: this.EDGE,
|
|
1395
|
+
remote: !0
|
|
1396
|
+
}), !1;
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Returns true if there are unresolved mutations between HEAD and EDGE, meaning we have
|
|
1400
|
+
* mutations that are still waiting to be either submitted, or to be confirmed by the server.
|
|
1401
|
+
*
|
|
1402
|
+
* @returns true if there are unresolved mutations between HEAD and EDGE, false otherwise
|
|
1403
|
+
*/
|
|
1404
|
+
hasUnresolvedMutations() {
|
|
1405
|
+
return this.submitted.length > 0 || this.pending.length > 0;
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* When an incoming mutation is applied to HEAD, this is called to remove the mutation from
|
|
1409
|
+
* the unresolved state. If the newly applied patch is the next upcoming unresolved mutation,
|
|
1410
|
+
* no rebase is needed, but we might have the wrong idea about the ordering of mutations, so in
|
|
1411
|
+
* that case we are given the flag `needRebase` to tell us that this mutation arrived out of
|
|
1412
|
+
* order in terms of our optimistic version, so a rebase is needed.
|
|
1413
|
+
*
|
|
1414
|
+
* @param txnId - Transaction ID of the remote mutation
|
|
1415
|
+
* @returns true if rebase is needed, false otherwise
|
|
1416
|
+
*/
|
|
1417
|
+
consumeUnresolved(txnId) {
|
|
1418
|
+
if (this.submitted.length === 0 && this.pending.length === 0) return !1;
|
|
1419
|
+
if (this.submitted.length !== 0) {
|
|
1420
|
+
if (this.submitted[0].transactionId === txnId) return debug("Remote mutation %s matches upcoming submitted mutation, consumed from 'submitted' buffer", txnId), this.submitted.shift(), !1;
|
|
1421
|
+
} else if (this.pending.length > 0 && this.pending[0].transactionId === txnId) return debug("Remote mutation %s matches upcoming pending mutation, consumed from 'pending' buffer", txnId), this.pending.shift(), !1;
|
|
1422
|
+
return debug("The mutation was not the upcoming mutation, scrubbing. Pending: %d, Submitted: %d", this.pending.length, this.submitted.length), this.submitted = this.submitted.filter((mut) => mut.transactionId !== txnId), this.pending = this.pending.filter((mut) => mut.transactionId !== txnId), debug("After scrubbing: Pending: %d, Submitted: %d", this.pending.length, this.submitted.length), !0;
|
|
1423
|
+
}
|
|
1424
|
+
pendingSuccessfullySubmitted(pendingTxnId) {
|
|
1425
|
+
if (this.pending.length === 0) return;
|
|
1426
|
+
let first = this.pending[0];
|
|
1427
|
+
if (first.transactionId === pendingTxnId) {
|
|
1428
|
+
this.pending.shift(), this.submitted.push(first);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
let justSubmitted, stillPending = [];
|
|
1432
|
+
this.pending.forEach((mutation) => {
|
|
1433
|
+
if (mutation.transactionId === pendingTxnId) {
|
|
1434
|
+
justSubmitted = mutation;
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
stillPending.push(mutation);
|
|
1438
|
+
}), justSubmitted && this.submitted.push(justSubmitted), this.pending = stillPending, this.rebase([]);
|
|
1439
|
+
}
|
|
1440
|
+
pendingFailed(pendingTxnId) {
|
|
1441
|
+
this.pending = this.pending.filter((mutation) => mutation.transactionId !== pendingTxnId), this.rebase([]);
|
|
1442
|
+
}
|
|
1443
|
+
rebase(incomingMutations) {
|
|
1444
|
+
let oldEdge = this.EDGE;
|
|
1445
|
+
this.EDGE = Mutation.applyAll(this.HEAD, this.submitted.concat(this.pending)), oldEdge !== null && this.EDGE !== null && (oldEdge._rev = this.EDGE._rev), !isEqual(this.EDGE, oldEdge) && this.onRebase && this.onRebase(this.EDGE, incomingMutations, this.pending);
|
|
1446
|
+
}
|
|
1447
|
+
}, SquashingBuffer = class {
|
|
1448
|
+
/**
|
|
1449
|
+
* The document forming the basis of this squash
|
|
1450
|
+
*/
|
|
1451
|
+
BASIS;
|
|
1452
|
+
/**
|
|
1453
|
+
* The document after the out-Mutation has been applied, but before the staged
|
|
1454
|
+
* operations are committed.
|
|
1455
|
+
*/
|
|
1456
|
+
PRESTAGE;
|
|
1457
|
+
/**
|
|
1458
|
+
* setOperations contain the latest set operation by path. If the set-operations are
|
|
1459
|
+
* updating strings to new strings, they are rewritten as diffMatchPatch operations,
|
|
1460
|
+
* any new set operations on the same paths overwrites any older set operations.
|
|
1461
|
+
* Only set-operations assigning plain values to plain values gets optimized like this.
|
|
1462
|
+
*/
|
|
1463
|
+
setOperations;
|
|
1464
|
+
/**
|
|
1465
|
+
* `documentPresent` is true whenever we know that the document must be present due
|
|
1466
|
+
* to preceeding mutations. `false` implies that it may or may not already exist.
|
|
1467
|
+
*/
|
|
1468
|
+
documentPresent;
|
|
1469
|
+
/**
|
|
1470
|
+
* The operations in the out-Mutation are not able to be optimized any further
|
|
1471
|
+
*/
|
|
1472
|
+
out = [];
|
|
1473
|
+
/**
|
|
1474
|
+
* Staged mutation operations
|
|
1475
|
+
*/
|
|
1476
|
+
staged;
|
|
1477
|
+
constructor(doc) {
|
|
1478
|
+
doc ? debug("Reset mutation buffer to rev %s", doc._rev) : debug("Reset mutation buffer state to document being deleted"), this.staged = [], this.setOperations = {}, this.documentPresent = !1, this.BASIS = doc, this.PRESTAGE = doc;
|
|
1479
|
+
}
|
|
1480
|
+
add(mut) {
|
|
1481
|
+
mut.mutations.forEach((op) => this.addOperation(op));
|
|
1482
|
+
}
|
|
1483
|
+
hasChanges() {
|
|
1484
|
+
return this.out.length > 0 || Object.keys(this.setOperations).length > 0;
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Extracts the mutations in this buffer.
|
|
1488
|
+
* After this is done, the buffer lifecycle is over and the client should
|
|
1489
|
+
* create an new one with the new, updated BASIS.
|
|
1490
|
+
*
|
|
1491
|
+
* @param txnId - Transaction ID
|
|
1492
|
+
* @returns A `Mutation` instance if we had outgoing mutations pending, null otherwise
|
|
1493
|
+
*/
|
|
1494
|
+
purge(txnId) {
|
|
1495
|
+
this.stashStagedOperations();
|
|
1496
|
+
let result = null;
|
|
1497
|
+
return this.out.length > 0 && (debug("Purged mutation buffer"), result = new Mutation({
|
|
1498
|
+
mutations: this.out,
|
|
1499
|
+
resultRev: txnId,
|
|
1500
|
+
transactionId: txnId
|
|
1501
|
+
})), this.out = [], this.documentPresent = !1, result;
|
|
1502
|
+
}
|
|
1503
|
+
addOperation(op) {
|
|
1504
|
+
if (op.patch && op.patch.set && "id" in op.patch && op.patch.id === this.PRESTAGE?._id && Object.keys(op.patch).length === 2) {
|
|
1505
|
+
let setPatch = op.patch.set, unoptimizable = {};
|
|
1506
|
+
for (let path of Object.keys(setPatch)) setPatch.hasOwnProperty(path) && (this.optimiseSetOperation(path, setPatch[path]) || (unoptimizable[path] = setPatch[path]));
|
|
1507
|
+
Object.keys(unoptimizable).length > 0 && (debug("Unoptimizable set-operation detected, purging optimization buffer"), this.staged.push({ patch: {
|
|
1508
|
+
id: this.PRESTAGE._id,
|
|
1509
|
+
set: unoptimizable
|
|
1510
|
+
} }), this.stashStagedOperations());
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
if (op.createIfNotExists && this.PRESTAGE && op.createIfNotExists._id === this.PRESTAGE._id) {
|
|
1514
|
+
this.documentPresent || (this.staged.push(op), this.documentPresent = !0, this.stashStagedOperations());
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
debug("Unoptimizable mutation detected, purging optimization buffer"), this.staged.push(op), this.stashStagedOperations();
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Attempt to perform one single set operation in an optimised manner, return value
|
|
1521
|
+
* reflects whether or not the operation could be performed.
|
|
1522
|
+
|
|
1523
|
+
* @param path - The JSONPath to the set operation in question
|
|
1524
|
+
* @param nextValue - The value to be set
|
|
1525
|
+
* @returns True of optimized, false otherwise
|
|
1526
|
+
*/
|
|
1527
|
+
optimiseSetOperation(path, nextValue) {
|
|
1528
|
+
if (typeof nextValue == "object") return !1;
|
|
1529
|
+
let matches = extractWithPath(path, this.PRESTAGE);
|
|
1530
|
+
if (matches.length !== 1) return !1;
|
|
1531
|
+
let match = matches[0];
|
|
1532
|
+
if (typeof match.value == "object" || !this.PRESTAGE) return !1;
|
|
1533
|
+
let op = null;
|
|
1534
|
+
if (match.value === nextValue) op = null;
|
|
1535
|
+
else if (typeof match.value == "string" && typeof nextValue == "string") try {
|
|
1536
|
+
let patch = stringifyPatches(makePatches(match.value, nextValue));
|
|
1537
|
+
op = { patch: {
|
|
1538
|
+
id: this.PRESTAGE._id,
|
|
1539
|
+
diffMatchPatch: { [path]: patch }
|
|
1540
|
+
} };
|
|
1541
|
+
} catch {
|
|
1542
|
+
return !1;
|
|
1543
|
+
}
|
|
1544
|
+
else op = { patch: {
|
|
1545
|
+
id: this.PRESTAGE._id,
|
|
1546
|
+
set: { [path]: nextValue }
|
|
1547
|
+
} };
|
|
1548
|
+
let canonicalPath = arrayToJSONMatchPath(match.path);
|
|
1549
|
+
return op ? this.setOperations[canonicalPath] = op : delete this.setOperations[canonicalPath], !0;
|
|
1550
|
+
}
|
|
1551
|
+
stashStagedOperations() {
|
|
1552
|
+
let nextOps = [];
|
|
1553
|
+
Object.keys(this.setOperations).forEach((key) => {
|
|
1554
|
+
let op = this.setOperations[key];
|
|
1555
|
+
op && nextOps.push(op);
|
|
1556
|
+
}), nextOps.push(...this.staged), nextOps.length > 0 && (this.PRESTAGE = new Mutation({ mutations: nextOps }).apply(this.PRESTAGE), this.staged = [], this.setOperations = {}), this.out.push(...nextOps);
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Rebases given the new base-document
|
|
1560
|
+
*
|
|
1561
|
+
* @param newBasis - New base document to rebase on
|
|
1562
|
+
* @returns New "edge" document with buffered changes integrated
|
|
1563
|
+
*/
|
|
1564
|
+
rebase(newBasis) {
|
|
1565
|
+
return this.stashStagedOperations(), newBasis === null ? (this.out = [], this.BASIS = newBasis, this.PRESTAGE = newBasis, this.documentPresent = !1) : (this.BASIS = newBasis, this.out ? this.PRESTAGE = new Mutation({ mutations: this.out }).apply(this.BASIS) : this.PRESTAGE = this.BASIS), this.PRESTAGE;
|
|
1566
|
+
}
|
|
1567
|
+
}, Commit = class {
|
|
1568
|
+
mutations;
|
|
1569
|
+
tries;
|
|
1570
|
+
resolve;
|
|
1571
|
+
reject;
|
|
1572
|
+
constructor(mutations, { resolve, reject }) {
|
|
1573
|
+
this.mutations = mutations, this.tries = 0, this.resolve = resolve, this.reject = reject;
|
|
1574
|
+
}
|
|
1575
|
+
apply(doc) {
|
|
1576
|
+
return Mutation.applyAll(doc, this.mutations);
|
|
1577
|
+
}
|
|
1578
|
+
squash(doc) {
|
|
1579
|
+
let result = Mutation.squash(doc, this.mutations);
|
|
1580
|
+
return result.assignRandomTransactionId(), result;
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1709
1583
|
const mutReducerFn = (acc, mut) => acc.concat(mut.mutations);
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
this.LOCAL = this.commits.reduce((doc, commit) => commit.apply(doc), this.document.EDGE), this.LOCAL = this.buffer.rebase(this.LOCAL), oldLocal !== null && this.LOCAL !== null && (oldLocal._rev = this.LOCAL._rev), !isEqual(this.LOCAL, oldLocal) && this.onRebase && this.onRebase(
|
|
1845
|
-
this.LOCAL,
|
|
1846
|
-
remoteMutations.reduce(mutReducerFn, []),
|
|
1847
|
-
localMutations.reduce(mutReducerFn, [])
|
|
1848
|
-
);
|
|
1849
|
-
}
|
|
1850
|
-
handleDocConsistencyChanged(isConsistent) {
|
|
1851
|
-
if (!this.onConsistencyChanged)
|
|
1852
|
-
return;
|
|
1853
|
-
const hasLocalChanges = this.commits.length > 0 || this.buffer.hasChanges();
|
|
1854
|
-
isConsistent && !hasLocalChanges && this.onConsistencyChanged(!0), isConsistent || this.onConsistencyChanged(!1);
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
export {
|
|
1858
|
-
BufferedDocument,
|
|
1859
|
-
Mutation,
|
|
1860
|
-
arrayToJSONMatchPath,
|
|
1861
|
-
extract,
|
|
1862
|
-
extractWithPath
|
|
1584
|
+
/**
|
|
1585
|
+
* @internal
|
|
1586
|
+
*/
|
|
1587
|
+
var BufferedDocument = class {
|
|
1588
|
+
mutations;
|
|
1589
|
+
/**
|
|
1590
|
+
* The Document we are wrapping
|
|
1591
|
+
*/
|
|
1592
|
+
document;
|
|
1593
|
+
/**
|
|
1594
|
+
* The Document with local changes applied
|
|
1595
|
+
*/
|
|
1596
|
+
LOCAL;
|
|
1597
|
+
/**
|
|
1598
|
+
* Commits that are waiting to be delivered to the server
|
|
1599
|
+
*/
|
|
1600
|
+
commits;
|
|
1601
|
+
/**
|
|
1602
|
+
* Local mutations that are not scheduled to be committed yet
|
|
1603
|
+
*/
|
|
1604
|
+
buffer;
|
|
1605
|
+
/**
|
|
1606
|
+
* Assignable event handler for when the buffered document applies a mutation
|
|
1607
|
+
*/
|
|
1608
|
+
onMutation;
|
|
1609
|
+
/**
|
|
1610
|
+
* Assignable event handler for when a remote mutation happened
|
|
1611
|
+
*/
|
|
1612
|
+
onRemoteMutation;
|
|
1613
|
+
/**
|
|
1614
|
+
* Assignable event handler for when the buffered document rebased
|
|
1615
|
+
*/
|
|
1616
|
+
onRebase;
|
|
1617
|
+
/**
|
|
1618
|
+
* Assignable event handler for when the document is deleted
|
|
1619
|
+
*/
|
|
1620
|
+
onDelete;
|
|
1621
|
+
/**
|
|
1622
|
+
* Assignable event handler for when the state of consistency changed
|
|
1623
|
+
*/
|
|
1624
|
+
onConsistencyChanged;
|
|
1625
|
+
/**
|
|
1626
|
+
* Assignable event handler for when the buffered document should commit changes
|
|
1627
|
+
*/
|
|
1628
|
+
commitHandler;
|
|
1629
|
+
/**
|
|
1630
|
+
* Whether or not we are currently commiting
|
|
1631
|
+
*/
|
|
1632
|
+
committerRunning = !1;
|
|
1633
|
+
constructor(doc) {
|
|
1634
|
+
this.buffer = new SquashingBuffer(doc), this.document = new Document(doc), this.document.onMutation = (msg) => this.handleDocMutation(msg), this.document.onRemoteMutation = (mut) => this.onRemoteMutation && this.onRemoteMutation(mut), this.document.onRebase = (edge, remoteMutations, localMutations) => this.handleDocRebase(edge, remoteMutations, localMutations), this.document.onConsistencyChanged = (msg) => this.handleDocConsistencyChanged(msg), this.LOCAL = doc, this.mutations = [], this.commits = [];
|
|
1635
|
+
}
|
|
1636
|
+
reset(doc) {
|
|
1637
|
+
doc ? debug("Document state reset to revision %s", doc._rev) : debug("Document state reset to being deleted"), this.document.reset(doc), this.rebase([], []), this.handleDocConsistencyChanged(this.document.isConsistent());
|
|
1638
|
+
}
|
|
1639
|
+
add(mutation) {
|
|
1640
|
+
this.onConsistencyChanged && this.onConsistencyChanged(!1), debug("Staged local mutation"), this.buffer.add(mutation);
|
|
1641
|
+
let oldLocal = this.LOCAL;
|
|
1642
|
+
this.LOCAL = mutation.apply(this.LOCAL), this.onMutation && oldLocal !== this.LOCAL && (debug("onMutation fired"), this.onMutation({
|
|
1643
|
+
mutation,
|
|
1644
|
+
document: this.LOCAL,
|
|
1645
|
+
remote: !1
|
|
1646
|
+
}), this.LOCAL === null && this.onDelete && this.onDelete(this.LOCAL));
|
|
1647
|
+
}
|
|
1648
|
+
arrive(mutation) {
|
|
1649
|
+
if (debug("Remote mutation arrived %s -> %s", mutation.previousRev, mutation.resultRev), mutation.previousRev === mutation.resultRev) throw Error(`Mutation ${mutation.transactionId} has previousRev === resultRev (${mutation.previousRev})`);
|
|
1650
|
+
return this.document.arrive(mutation);
|
|
1651
|
+
}
|
|
1652
|
+
commit() {
|
|
1653
|
+
return new Promise((resolve, reject) => {
|
|
1654
|
+
if (!this.buffer.hasChanges()) {
|
|
1655
|
+
resolve();
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
debug("Committing local changes");
|
|
1659
|
+
let pendingMutations = this.buffer.purge();
|
|
1660
|
+
this.commits.push(new Commit(pendingMutations ? [pendingMutations] : [], {
|
|
1661
|
+
resolve,
|
|
1662
|
+
reject
|
|
1663
|
+
})), this.buffer = new SquashingBuffer(this.LOCAL), this.performCommits();
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
performCommits() {
|
|
1667
|
+
if (!this.commitHandler) throw Error("No commitHandler configured for this BufferedDocument");
|
|
1668
|
+
this.committerRunning || this._cycleCommitter();
|
|
1669
|
+
}
|
|
1670
|
+
_cycleCommitter() {
|
|
1671
|
+
let commit = this.commits.shift();
|
|
1672
|
+
if (!commit) {
|
|
1673
|
+
this.committerRunning = !1;
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
this.committerRunning = !0;
|
|
1677
|
+
let squashed = commit.squash(this.LOCAL), docResponder = this.document.stage(squashed, !0), responder = {
|
|
1678
|
+
success: () => {
|
|
1679
|
+
debug("Commit succeeded"), docResponder.success(), commit.resolve(), this._cycleCommitter();
|
|
1680
|
+
},
|
|
1681
|
+
failure: () => {
|
|
1682
|
+
debug("Commit failed"), commit.tries += 1, this.LOCAL !== null && this.commits.unshift(commit), docResponder.failure(), commit.tries < 200 && setTimeout(() => this._cycleCommitter(), Math.min(commit.tries * 1e3, 6e4));
|
|
1683
|
+
},
|
|
1684
|
+
cancel: (error) => {
|
|
1685
|
+
this.commits.forEach((comm) => comm.reject(error)), this.commits = [], this.reset(this.document.HEAD), this.buffer = new SquashingBuffer(this.LOCAL), this.committerRunning = !1;
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
debug("Posting commit"), this.commitHandler && this.commitHandler({
|
|
1689
|
+
mutation: squashed,
|
|
1690
|
+
success: responder.success,
|
|
1691
|
+
failure: responder.failure,
|
|
1692
|
+
cancel: responder.cancel
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
handleDocRebase(edge, remoteMutations, localMutations) {
|
|
1696
|
+
this.rebase(remoteMutations, localMutations);
|
|
1697
|
+
}
|
|
1698
|
+
handleDocumentDeleted() {
|
|
1699
|
+
debug("Document deleted"), this.LOCAL !== null && this.onDelete && this.onDelete(this.LOCAL), this.commits = [], this.mutations = [];
|
|
1700
|
+
}
|
|
1701
|
+
handleDocMutation(msg) {
|
|
1702
|
+
if (this.commits.length === 0 && !this.buffer.hasChanges()) {
|
|
1703
|
+
debug("Document mutated from remote with no local changes"), this.LOCAL = this.document.EDGE, this.buffer = new SquashingBuffer(this.LOCAL), this.onMutation && this.onMutation(msg);
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
debug("Document mutated from remote with local changes"), this.document.EDGE === null && this.handleDocumentDeleted(), this.rebase([msg.mutation], []);
|
|
1707
|
+
}
|
|
1708
|
+
rebase(remoteMutations, localMutations) {
|
|
1709
|
+
debug("Rebasing document"), this.document.EDGE === null && this.handleDocumentDeleted();
|
|
1710
|
+
let oldLocal = this.LOCAL;
|
|
1711
|
+
this.LOCAL = this.commits.reduce((doc, commit) => commit.apply(doc), this.document.EDGE), this.LOCAL = this.buffer.rebase(this.LOCAL), oldLocal !== null && this.LOCAL !== null && (oldLocal._rev = this.LOCAL._rev), !isEqual(this.LOCAL, oldLocal) && this.onRebase && this.onRebase(this.LOCAL, remoteMutations.reduce(mutReducerFn, []), localMutations.reduce(mutReducerFn, []));
|
|
1712
|
+
}
|
|
1713
|
+
handleDocConsistencyChanged(isConsistent) {
|
|
1714
|
+
if (!this.onConsistencyChanged) return;
|
|
1715
|
+
let hasLocalChanges = this.commits.length > 0 || this.buffer.hasChanges();
|
|
1716
|
+
isConsistent && !hasLocalChanges && this.onConsistencyChanged(!0), isConsistent || this.onConsistencyChanged(!1);
|
|
1717
|
+
}
|
|
1863
1718
|
};
|
|
1864
|
-
|
|
1719
|
+
export { BufferedDocument, Mutation, arrayToJSONMatchPath, extract, extractWithPath };
|
|
1720
|
+
|
|
1721
|
+
//# sourceMappingURL=index.js.map
|