@sanity/mutator 6.7.0-next.36 → 6.7.0-next.5
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 +1 -2
- package/lib/index.js +1795 -1652
- package/lib/index.js.map +1 -1
- package/package.json +11 -8
package/lib/index.js
CHANGED
|
@@ -2,1720 +2,1863 @@ 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 { parsePatch as parsePatch$1, applyPatches, stringifyPatches, makePatches } 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 value !== null && typeof value == "object";
|
|
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
|
-
*/
|
|
21
14
|
function arrayToJSONMatchPath(pathArray) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
let path = "";
|
|
16
|
+
return pathArray.forEach((segment, index) => {
|
|
17
|
+
path += stringifySegment(segment, index === 0);
|
|
18
|
+
}), path;
|
|
26
19
|
}
|
|
27
20
|
function stringifySegment(segment, hasLeading) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
if (typeof segment == "number")
|
|
22
|
+
return `[${segment}]`;
|
|
23
|
+
if (isRecord$1(segment)) {
|
|
24
|
+
const seg = segment;
|
|
25
|
+
return Object.keys(segment).map((key) => isPrimitiveValue(seg[key]) ? `[${key}=="${seg[key]}"]` : "").join("");
|
|
26
|
+
}
|
|
27
|
+
return typeof segment == "string" && IS_DOTTABLE.test(segment) ? hasLeading ? segment : `.${segment}` : `['${segment}']`;
|
|
34
28
|
}
|
|
35
29
|
function isPrimitiveValue(val) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
switch (typeof val) {
|
|
31
|
+
case "number":
|
|
32
|
+
case "string":
|
|
33
|
+
case "boolean":
|
|
34
|
+
return !0;
|
|
35
|
+
default:
|
|
36
|
+
return !1;
|
|
37
|
+
}
|
|
42
38
|
}
|
|
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
|
-
*/
|
|
49
39
|
function descend$1(tail) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
const [head, newTail] = splitIfPath(tail);
|
|
41
|
+
if (!head)
|
|
42
|
+
throw new Error("Head cannot be null");
|
|
43
|
+
return spreadIfUnionHead(head, newTail);
|
|
53
44
|
}
|
|
54
45
|
function splitIfPath(tail) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
nodes: nodes.slice(1)
|
|
60
|
-
}];
|
|
46
|
+
if (tail.type !== "path")
|
|
47
|
+
return [tail, null];
|
|
48
|
+
const nodes = tail.nodes;
|
|
49
|
+
return nodes.length === 0 ? [null, null] : nodes.length === 1 ? [nodes[0], null] : [nodes[0], { type: "path", nodes: nodes.slice(1) }];
|
|
61
50
|
}
|
|
62
51
|
function concatPaths(path1, path2) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
if (!path1 && !path2)
|
|
53
|
+
return null;
|
|
54
|
+
const nodes1 = path1 ? path1.nodes : [], nodes2 = path2 ? path2.nodes : [];
|
|
55
|
+
return {
|
|
56
|
+
type: "path",
|
|
57
|
+
nodes: nodes1.concat(nodes2)
|
|
58
|
+
};
|
|
69
59
|
}
|
|
70
60
|
function spreadIfUnionHead(head, tail) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
61
|
+
return head.type !== "union" ? [[head, tail]] : head.nodes.map((node) => {
|
|
62
|
+
if (node.type === "path") {
|
|
63
|
+
const [subHead, subTail] = splitIfPath(node);
|
|
64
|
+
return [subHead, concatPaths(subTail, tail)];
|
|
65
|
+
}
|
|
66
|
+
return [node, tail];
|
|
67
|
+
});
|
|
78
68
|
}
|
|
79
69
|
const digitChar = /[0-9]/, attributeCharMatcher = /^[a-zA-Z0-9_]$/, attributeFirstCharMatcher = /^[a-zA-Z_]$/, symbols = {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
comparator: [
|
|
88
|
-
">=",
|
|
89
|
-
"<=",
|
|
90
|
-
"<",
|
|
91
|
-
">",
|
|
92
|
-
"==",
|
|
93
|
-
"!="
|
|
94
|
-
],
|
|
95
|
-
keyword: ["$", "@"],
|
|
96
|
-
boolean: ["true", "false"],
|
|
97
|
-
paren: ["[", "]"]
|
|
70
|
+
// NOTE: These are compared against in order of definition,
|
|
71
|
+
// thus '==' must come before '=', '>=' before '>', etc.
|
|
72
|
+
operator: ["..", ".", ",", ":", "?"],
|
|
73
|
+
comparator: [">=", "<=", "<", ">", "==", "!="],
|
|
74
|
+
keyword: ["$", "@"],
|
|
75
|
+
boolean: ["true", "false"],
|
|
76
|
+
paren: ["[", "]"]
|
|
98
77
|
}, symbolClasses = Object.keys(symbols);
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
78
|
+
class Tokenizer {
|
|
79
|
+
source;
|
|
80
|
+
i;
|
|
81
|
+
length;
|
|
82
|
+
tokenizers;
|
|
83
|
+
constructor(path) {
|
|
84
|
+
this.source = path, this.length = path.length, this.i = 0, this.tokenizers = [
|
|
85
|
+
this.tokenizeSymbol,
|
|
86
|
+
this.tokenizeIdentifier,
|
|
87
|
+
this.tokenizeNumber,
|
|
88
|
+
this.tokenizeQuoted
|
|
89
|
+
].map((fn) => fn.bind(this));
|
|
90
|
+
}
|
|
91
|
+
tokenize() {
|
|
92
|
+
const result = [];
|
|
93
|
+
for (; !this.EOF(); ) {
|
|
94
|
+
this.chompWhitespace();
|
|
95
|
+
let token = null;
|
|
96
|
+
if (!this.tokenizers.some((tokenizer) => (token = tokenizer(), !!token)) || !token)
|
|
97
|
+
throw new Error(`Invalid tokens in jsonpath '${this.source}' @ ${this.i}`);
|
|
98
|
+
result.push(token);
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
takeWhile(fn) {
|
|
103
|
+
const start = this.i;
|
|
104
|
+
let result = "";
|
|
105
|
+
for (; !this.EOF(); ) {
|
|
106
|
+
const nextChar = fn(this.source[this.i]);
|
|
107
|
+
if (nextChar === null)
|
|
108
|
+
break;
|
|
109
|
+
result += nextChar, this.i++;
|
|
110
|
+
}
|
|
111
|
+
return this.i === start ? null : result;
|
|
112
|
+
}
|
|
113
|
+
EOF() {
|
|
114
|
+
return this.i >= this.length;
|
|
115
|
+
}
|
|
116
|
+
peek() {
|
|
117
|
+
return this.EOF() ? null : this.source[this.i];
|
|
118
|
+
}
|
|
119
|
+
consume(str) {
|
|
120
|
+
if (this.i + str.length > this.length)
|
|
121
|
+
throw new Error(`Expected ${str} at end of jsonpath`);
|
|
122
|
+
if (str === this.source.slice(this.i, this.i + str.length))
|
|
123
|
+
this.i += str.length;
|
|
124
|
+
else
|
|
125
|
+
throw new Error(`Expected "${str}", but source contained "${this.source.slice()}`);
|
|
126
|
+
}
|
|
127
|
+
// Tries to match the upcoming bit of string with the provided string. If it matches, returns
|
|
128
|
+
// the string, then advances the read pointer to the next bit. If not, returns null and nothing
|
|
129
|
+
// happens.
|
|
130
|
+
tryConsume(str) {
|
|
131
|
+
if (this.i + str.length > this.length)
|
|
132
|
+
return null;
|
|
133
|
+
if (str === this.source.slice(this.i, this.i + str.length)) {
|
|
134
|
+
if (str[0].match(attributeCharMatcher) && this.length > this.i + str.length) {
|
|
135
|
+
const nextChar = this.source[this.i + str.length];
|
|
136
|
+
if (nextChar && nextChar.match(attributeCharMatcher))
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return this.i += str.length, str;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
chompWhitespace() {
|
|
144
|
+
this.takeWhile((char) => char === " " ? "" : null);
|
|
145
|
+
}
|
|
146
|
+
tokenizeQuoted() {
|
|
147
|
+
const quote = this.peek();
|
|
148
|
+
if (quote === "'" || quote === '"') {
|
|
149
|
+
this.consume(quote);
|
|
150
|
+
let escape = !1;
|
|
151
|
+
const inner = this.takeWhile((char) => escape ? (escape = !1, char) : char === "\\" ? (escape = !0, "") : char != quote ? char : null);
|
|
152
|
+
return this.consume(quote), {
|
|
153
|
+
type: "quoted",
|
|
154
|
+
value: inner,
|
|
155
|
+
quote: quote === '"' ? "double" : "single"
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
tokenizeIdentifier() {
|
|
161
|
+
let first = !0;
|
|
162
|
+
const identifier = this.takeWhile((char) => first ? (first = !1, char.match(attributeFirstCharMatcher) ? char : null) : char.match(attributeCharMatcher) ? char : null);
|
|
163
|
+
return identifier !== null ? {
|
|
164
|
+
type: "identifier",
|
|
165
|
+
name: identifier
|
|
166
|
+
} : null;
|
|
167
|
+
}
|
|
168
|
+
tokenizeNumber() {
|
|
169
|
+
const start = this.i;
|
|
170
|
+
let dotSeen = !1, digitSeen = !1, negative = !1;
|
|
171
|
+
this.peek() === "-" && (negative = !0, this.consume("-"));
|
|
172
|
+
const number = this.takeWhile((char) => char === "." && !dotSeen && digitSeen ? (dotSeen = !0, char) : (digitSeen = !0, char.match(digitChar) ? char : null));
|
|
173
|
+
return number !== null ? {
|
|
174
|
+
type: "number",
|
|
175
|
+
value: negative ? -Number(number) : +number,
|
|
176
|
+
raw: negative ? `-${number}` : number
|
|
177
|
+
} : (this.i = start, null);
|
|
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
|
+
}
|
|
200
191
|
function tokenize(jsonpath) {
|
|
201
|
-
|
|
192
|
+
return new Tokenizer(jsonpath).tokenize();
|
|
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
|
+
}
|
|
202
385
|
}
|
|
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
|
-
};
|
|
411
386
|
function parseJsonPath(path) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
387
|
+
const parsed = new Parser(path).parse();
|
|
388
|
+
if (!parsed)
|
|
389
|
+
throw new Error(`Failed to parse JSON path "${path}"`);
|
|
390
|
+
return parsed;
|
|
415
391
|
}
|
|
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
|
-
*/
|
|
424
392
|
function toPath(expr) {
|
|
425
|
-
|
|
393
|
+
return toPathInner(expr, !1);
|
|
426
394
|
}
|
|
427
395
|
function toPathInner(expr, inUnion) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
396
|
+
switch (expr.type) {
|
|
397
|
+
case "attribute":
|
|
398
|
+
return expr.name;
|
|
399
|
+
case "alias":
|
|
400
|
+
return expr.target === "self" ? "@" : "$";
|
|
401
|
+
case "number":
|
|
402
|
+
return `${expr.value}`;
|
|
403
|
+
case "range": {
|
|
404
|
+
const result = [];
|
|
405
|
+
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("");
|
|
406
|
+
}
|
|
407
|
+
case "index":
|
|
408
|
+
return inUnion ? `${expr.value}` : `[${expr.value}]`;
|
|
409
|
+
case "constraint": {
|
|
410
|
+
const rhs = expr.rhs ? ` ${toPathInner(expr.rhs, !1)}` : "", inner = `${toPathInner(expr.lhs, !1)} ${expr.operator}${rhs}`;
|
|
411
|
+
return inUnion ? inner : `[${inner}]`;
|
|
412
|
+
}
|
|
413
|
+
case "string":
|
|
414
|
+
return JSON.stringify(expr.value);
|
|
415
|
+
case "path": {
|
|
416
|
+
const result = [], nodes = expr.nodes.slice();
|
|
417
|
+
for (; nodes.length > 0; ) {
|
|
418
|
+
const node = nodes.shift();
|
|
419
|
+
node && result.push(toPath(node));
|
|
420
|
+
const upcoming = nodes[0];
|
|
421
|
+
upcoming && toPathInner(upcoming, !1)[0] !== "[" && result.push(".");
|
|
422
|
+
}
|
|
423
|
+
return result.join("");
|
|
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
|
+
}
|
|
456
608
|
}
|
|
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
|
-
|
|
610
|
+
switch (operator) {
|
|
611
|
+
case ">":
|
|
612
|
+
return lhsValue > rhsValue;
|
|
613
|
+
case ">=":
|
|
614
|
+
return lhsValue >= rhsValue;
|
|
615
|
+
case "<":
|
|
616
|
+
return lhsValue < rhsValue;
|
|
617
|
+
case "<=":
|
|
618
|
+
return lhsValue <= rhsValue;
|
|
619
|
+
case "==":
|
|
620
|
+
return lhsValue === rhsValue;
|
|
621
|
+
case "!=":
|
|
622
|
+
return lhsValue !== rhsValue;
|
|
623
|
+
default:
|
|
624
|
+
throw new Error(`Unsupported binary operator ${operator}`);
|
|
625
|
+
}
|
|
619
626
|
}
|
|
620
627
|
function interpretNegativeIndex(index, probe) {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
628
|
+
if (index >= 0)
|
|
629
|
+
return index;
|
|
630
|
+
if (!probe)
|
|
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
|
+
}
|
|
624
814
|
}
|
|
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
|
-
};
|
|
794
815
|
function extractAccessors(path, value) {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
816
|
+
const result = [], matcher = Matcher.fromPath(path).setPayload(function(values) {
|
|
817
|
+
result.push(...values);
|
|
818
|
+
}), accessor = new PlainProbe(value);
|
|
819
|
+
return descend(matcher, accessor), result;
|
|
799
820
|
}
|
|
800
821
|
function descend(matcher, accessor) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
822
|
+
const { leads, delivery } = matcher.match(accessor);
|
|
823
|
+
leads.forEach((lead) => {
|
|
824
|
+
accessorsFromTarget(lead.target, accessor).forEach((childAccessor) => {
|
|
825
|
+
descend(lead.matcher, childAccessor);
|
|
826
|
+
});
|
|
827
|
+
}), delivery && delivery.targets.forEach((target) => {
|
|
828
|
+
typeof delivery.payload == "function" && delivery.payload(accessorsFromTarget(target, accessor));
|
|
829
|
+
});
|
|
809
830
|
}
|
|
810
831
|
function accessorsFromTarget(target, accessor) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
832
|
+
const result = [];
|
|
833
|
+
if (target.isIndexReference())
|
|
834
|
+
target.toIndicies(accessor).forEach((i) => {
|
|
835
|
+
result.push(accessor.getIndex(i));
|
|
836
|
+
});
|
|
837
|
+
else if (target.isAttributeReference())
|
|
838
|
+
result.push(accessor.getAttribute(target.name()));
|
|
839
|
+
else if (target.isSelfReference())
|
|
840
|
+
result.push(accessor);
|
|
841
|
+
else
|
|
842
|
+
throw new Error(`Unable to derive accessor for target ${target.toString()}`);
|
|
843
|
+
return compact(result);
|
|
819
844
|
}
|
|
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
|
-
*/
|
|
828
845
|
function extract(path, value) {
|
|
829
|
-
|
|
846
|
+
return extractAccessors(path, value).map((acc) => acc.get());
|
|
830
847
|
}
|
|
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
|
-
*/
|
|
839
848
|
function extractWithPath(path, value) {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
849
|
+
return extractAccessors(path, value).map((acc) => ({ path: acc.path, value: acc.get() }));
|
|
850
|
+
}
|
|
851
|
+
class ImmutableAccessor {
|
|
852
|
+
_value;
|
|
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
|
+
}
|
|
844
936
|
}
|
|
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
|
-
};
|
|
920
937
|
function isRecord(value) {
|
|
921
|
-
|
|
938
|
+
return value !== null && typeof value == "object";
|
|
922
939
|
}
|
|
923
940
|
function applyPatch(patch, oldValue) {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
941
|
+
if (typeof oldValue != "string") return oldValue;
|
|
942
|
+
const [result] = applyPatches(patch, oldValue, { allowExceedingIndices: !0 });
|
|
943
|
+
return result;
|
|
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
|
+
}
|
|
927
979
|
}
|
|
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
|
-
};
|
|
960
980
|
function performIncrement(previousValue, delta) {
|
|
961
|
-
|
|
981
|
+
return typeof previousValue != "number" || !Number.isFinite(previousValue) ? previousValue : previousValue + delta;
|
|
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
|
+
}
|
|
962
1017
|
}
|
|
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
|
-
};
|
|
995
1018
|
function targetsToIndicies(targets, accessor) {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1019
|
+
const result = [];
|
|
1020
|
+
return targets.forEach((target) => {
|
|
1021
|
+
target.isIndexReference() && result.push(...target.toIndicies(accessor));
|
|
1022
|
+
}), result.sort((a, b) => a - b);
|
|
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
|
+
}
|
|
1000
1061
|
}
|
|
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
|
-
};
|
|
1036
1062
|
function minIndex(targets, accessor) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1063
|
+
let result = min(targetsToIndicies(targets, accessor)) || 0;
|
|
1064
|
+
return targets.forEach((target) => {
|
|
1065
|
+
if (target.isRange()) {
|
|
1066
|
+
const { start } = target.expandRange();
|
|
1067
|
+
start < result && (result = start);
|
|
1068
|
+
}
|
|
1069
|
+
}), result;
|
|
1044
1070
|
}
|
|
1045
1071
|
function maxIndex(targets, accessor) {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1072
|
+
let result = max(targetsToIndicies(targets, accessor)) || 0;
|
|
1073
|
+
return targets.forEach((target) => {
|
|
1074
|
+
if (target.isRange()) {
|
|
1075
|
+
const { end } = target.expandRange();
|
|
1076
|
+
end > result && (result = end);
|
|
1077
|
+
}
|
|
1078
|
+
}), result;
|
|
1053
1079
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
class SetPatch {
|
|
1099
|
+
id;
|
|
1100
|
+
path;
|
|
1101
|
+
value;
|
|
1102
|
+
constructor(id, path, value) {
|
|
1103
|
+
this.id = id, this.path = path, this.value = value;
|
|
1104
|
+
}
|
|
1105
|
+
apply(targets, accessor) {
|
|
1106
|
+
let result = accessor;
|
|
1107
|
+
return targets.forEach((target) => {
|
|
1108
|
+
if (target.isSelfReference())
|
|
1109
|
+
result = result.set(this.value);
|
|
1110
|
+
else if (target.isIndexReference())
|
|
1111
|
+
target.toIndicies(accessor).forEach((i) => {
|
|
1112
|
+
result = result.setIndex(i, this.value);
|
|
1113
|
+
});
|
|
1114
|
+
else if (target.isAttributeReference())
|
|
1115
|
+
result.containerType() === "primitive" ? result = result.set({ [target.name()]: this.value }) : result = result.setAttribute(target.name(), this.value);
|
|
1116
|
+
else
|
|
1117
|
+
throw new Error(`Unable to apply to target ${target.toString()}`);
|
|
1118
|
+
}), result;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
class UnsetPatch {
|
|
1122
|
+
id;
|
|
1123
|
+
path;
|
|
1124
|
+
value;
|
|
1125
|
+
constructor(id, path) {
|
|
1126
|
+
this.id = id, this.path = path;
|
|
1127
|
+
}
|
|
1128
|
+
apply(targets, accessor) {
|
|
1129
|
+
let result = accessor;
|
|
1130
|
+
switch (accessor.containerType()) {
|
|
1131
|
+
case "array":
|
|
1132
|
+
result = result.unsetIndices(targetsToIndicies(targets, accessor));
|
|
1133
|
+
break;
|
|
1134
|
+
case "object":
|
|
1135
|
+
targets.forEach((target) => {
|
|
1136
|
+
result = result.unsetAttribute(target.name());
|
|
1137
|
+
});
|
|
1138
|
+
break;
|
|
1139
|
+
default:
|
|
1140
|
+
throw new Error(
|
|
1141
|
+
"Target value is neither indexable or an object. This error should potentially just be silently ignored?"
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
return result;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
function parsePatch(patch) {
|
|
1148
|
+
const result = [];
|
|
1149
|
+
if (Array.isArray(patch))
|
|
1150
|
+
return patch.reduce((r, p) => r.concat(parsePatch(p)), result);
|
|
1151
|
+
const { set, setIfMissing, unset, diffMatchPatch, inc, dec, insert } = patch;
|
|
1152
|
+
if (setIfMissing && Object.keys(setIfMissing).forEach((path) => {
|
|
1153
|
+
result.push(new SetIfMissingPatch(patch.id, path, setIfMissing[path]));
|
|
1154
|
+
}), set && Object.keys(set).forEach((path) => {
|
|
1155
|
+
result.push(new SetPatch(patch.id, path, set[path]));
|
|
1156
|
+
}), unset && unset.forEach((path) => {
|
|
1157
|
+
result.push(new UnsetPatch(patch.id, path));
|
|
1158
|
+
}), diffMatchPatch && Object.keys(diffMatchPatch).forEach((path) => {
|
|
1159
|
+
result.push(new DiffMatchPatch(patch.id, path, diffMatchPatch[path]));
|
|
1160
|
+
}), inc && Object.keys(inc).forEach((path) => {
|
|
1161
|
+
result.push(new IncPatch(patch.id, path, inc[path]));
|
|
1162
|
+
}), dec && Object.keys(dec).forEach((path) => {
|
|
1163
|
+
result.push(new IncPatch(patch.id, path, -dec[path]));
|
|
1164
|
+
}), insert) {
|
|
1165
|
+
let location, path;
|
|
1166
|
+
const spec = insert;
|
|
1167
|
+
if ("before" in spec)
|
|
1168
|
+
location = "before", path = spec.before;
|
|
1169
|
+
else if ("after" in spec)
|
|
1170
|
+
location = "after", path = spec.after;
|
|
1171
|
+
else if ("replace" in spec)
|
|
1172
|
+
location = "replace", path = spec.replace;
|
|
1173
|
+
else
|
|
1174
|
+
throw new Error("Invalid insert patch");
|
|
1175
|
+
result.push(new InsertPatch(patch.id, location, path, spec.items));
|
|
1176
|
+
}
|
|
1177
|
+
return result;
|
|
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
|
+
}
|
|
1134
1207
|
}
|
|
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
|
-
};
|
|
1152
1208
|
function process(matcher, accessor) {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1209
|
+
const isSetPatch = matcher.payload instanceof SetPatch || matcher.payload instanceof SetIfMissingPatch;
|
|
1210
|
+
let result = accessor;
|
|
1211
|
+
const { leads, delivery } = matcher.match(accessor);
|
|
1212
|
+
return leads.forEach((lead) => {
|
|
1213
|
+
if (lead.target.isIndexReference())
|
|
1214
|
+
lead.target.toIndicies().forEach((i) => {
|
|
1215
|
+
const item = result.getIndex(i);
|
|
1216
|
+
if (!item)
|
|
1217
|
+
throw new Error("Index out of bounds");
|
|
1218
|
+
result = result.setIndexAccessor(i, process(lead.matcher, item));
|
|
1219
|
+
});
|
|
1220
|
+
else if (lead.target.isAttributeReference()) {
|
|
1221
|
+
isSetPatch && result.containerType() === "primitive" && (result = result.set({}));
|
|
1222
|
+
let oldValueAccessor = result.getAttribute(lead.target.name());
|
|
1223
|
+
if (!oldValueAccessor && isSetPatch && (result = result.setAttribute(lead.target.name(), {}), oldValueAccessor = result.getAttribute(lead.target.name())), !oldValueAccessor)
|
|
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;
|
|
1168
1230
|
}
|
|
1169
1231
|
function isPatcher(payload) {
|
|
1170
|
-
|
|
1232
|
+
return !!(payload && typeof payload == "object" && payload !== null && "apply" in payload && typeof payload.apply == "function");
|
|
1171
1233
|
}
|
|
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
|
-
*/
|
|
1177
1234
|
const luid = uuid;
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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
|
-
|
|
1235
|
+
class Mutation {
|
|
1236
|
+
params;
|
|
1237
|
+
compiled;
|
|
1238
|
+
_appliesToMissingDocument;
|
|
1239
|
+
constructor(options) {
|
|
1240
|
+
this.params = options;
|
|
1241
|
+
}
|
|
1242
|
+
get transactionId() {
|
|
1243
|
+
return this.params.transactionId;
|
|
1244
|
+
}
|
|
1245
|
+
get transition() {
|
|
1246
|
+
return this.params.transition;
|
|
1247
|
+
}
|
|
1248
|
+
get identity() {
|
|
1249
|
+
return this.params.identity;
|
|
1250
|
+
}
|
|
1251
|
+
get previousRev() {
|
|
1252
|
+
return this.params.previousRev;
|
|
1253
|
+
}
|
|
1254
|
+
get resultRev() {
|
|
1255
|
+
return this.params.resultRev;
|
|
1256
|
+
}
|
|
1257
|
+
get mutations() {
|
|
1258
|
+
return this.params.mutations;
|
|
1259
|
+
}
|
|
1260
|
+
get timestamp() {
|
|
1261
|
+
if (typeof this.params.timestamp == "string")
|
|
1262
|
+
return new Date(this.params.timestamp);
|
|
1263
|
+
}
|
|
1264
|
+
get effects() {
|
|
1265
|
+
return this.params.effects;
|
|
1266
|
+
}
|
|
1267
|
+
assignRandomTransactionId() {
|
|
1268
|
+
this.params.transactionId = luid(), this.params.resultRev = this.params.transactionId;
|
|
1269
|
+
}
|
|
1270
|
+
appliesToMissingDocument() {
|
|
1271
|
+
if (typeof this._appliesToMissingDocument < "u")
|
|
1272
|
+
return this._appliesToMissingDocument;
|
|
1273
|
+
const firstMut = this.mutations[0];
|
|
1274
|
+
return firstMut ? this._appliesToMissingDocument = !!(firstMut.create || firstMut.createIfNotExists || firstMut.createOrReplace) : this._appliesToMissingDocument = !0, this._appliesToMissingDocument;
|
|
1275
|
+
}
|
|
1276
|
+
// Compiles all mutations into a handy function
|
|
1277
|
+
compile() {
|
|
1278
|
+
const operations = [], getGuaranteedCreatedAt = (doc) => doc?._createdAt || this.params.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
1279
|
+
this.mutations.forEach((mutation) => {
|
|
1280
|
+
if (mutation.create) {
|
|
1281
|
+
const create = mutation.create || {};
|
|
1282
|
+
operations.push((doc) => doc || Object.assign(create, {
|
|
1283
|
+
_createdAt: getGuaranteedCreatedAt(create)
|
|
1284
|
+
}));
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (mutation.createIfNotExists) {
|
|
1288
|
+
const createIfNotExists = mutation.createIfNotExists || {};
|
|
1289
|
+
operations.push(
|
|
1290
|
+
(doc) => doc === null ? Object.assign(createIfNotExists, {
|
|
1291
|
+
_createdAt: getGuaranteedCreatedAt(createIfNotExists)
|
|
1292
|
+
}) : doc
|
|
1293
|
+
);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
if (mutation.createOrReplace) {
|
|
1297
|
+
const createOrReplace = mutation.createOrReplace || {};
|
|
1298
|
+
operations.push(
|
|
1299
|
+
() => Object.assign(createOrReplace, {
|
|
1300
|
+
_createdAt: getGuaranteedCreatedAt(createOrReplace)
|
|
1301
|
+
})
|
|
1302
|
+
);
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
if (mutation.delete) {
|
|
1306
|
+
operations.push(() => null);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
if (mutation.patch) {
|
|
1310
|
+
if ("query" in mutation.patch)
|
|
1311
|
+
return;
|
|
1312
|
+
const patch = new Patcher(mutation.patch);
|
|
1313
|
+
operations.push((doc) => patch.apply(doc));
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
throw new Error(`Unsupported mutation ${JSON.stringify(mutation, null, 2)}`);
|
|
1317
|
+
}), typeof this.params.timestamp == "string" && operations.push((doc) => doc ? Object.assign(doc, { _updatedAt: this.params.timestamp }) : null);
|
|
1318
|
+
const prevRev = this.previousRev, rev = this.resultRev || this.transactionId;
|
|
1319
|
+
this.compiled = (doc) => {
|
|
1320
|
+
if (prevRev && doc && prevRev !== doc._rev)
|
|
1321
|
+
throw new Error(
|
|
1322
|
+
`Previous revision for this mutation was ${prevRev}, but the document revision is ${doc._rev}`
|
|
1323
|
+
);
|
|
1324
|
+
let result = doc;
|
|
1325
|
+
for (const operation of operations)
|
|
1326
|
+
result = operation(result);
|
|
1327
|
+
return result && rev && (result === doc && (result = Object.assign({}, doc)), result._rev = rev), result;
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
apply(document) {
|
|
1331
|
+
debug("Applying mutation %O to document %O", this.mutations, document), this.compiled || this.compile();
|
|
1332
|
+
const result = this.compiled(document);
|
|
1333
|
+
return debug(" => %O", result), result;
|
|
1334
|
+
}
|
|
1335
|
+
static applyAll(document, mutations) {
|
|
1336
|
+
return mutations.reduce((doc, mutation) => mutation.apply(doc), document);
|
|
1337
|
+
}
|
|
1338
|
+
// Given a number of yet-to-be-committed mutation objects, collects them into one big mutation
|
|
1339
|
+
// any metadata like transactionId is ignored and must be submitted by the client. It is assumed
|
|
1340
|
+
// that all mutations are on the same document.
|
|
1341
|
+
// TOOO: Optimize mutations, eliminating mutations that overwrite themselves!
|
|
1342
|
+
static squash(document, mutations) {
|
|
1343
|
+
const squashed = mutations.reduce(
|
|
1344
|
+
(result, mutation) => result.concat(...mutation.mutations),
|
|
1345
|
+
[]
|
|
1346
|
+
);
|
|
1347
|
+
return new Mutation({ mutations: squashed });
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
class Document {
|
|
1351
|
+
/**
|
|
1352
|
+
* Incoming patches from the server waiting to be applied to HEAD
|
|
1353
|
+
*/
|
|
1354
|
+
incoming = [];
|
|
1355
|
+
/**
|
|
1356
|
+
* Patches we know has been subitted to the server, but has not been seen yet in the return channel
|
|
1357
|
+
* so we can't be sure about the ordering yet (someone else might have slipped something between them)
|
|
1358
|
+
*/
|
|
1359
|
+
submitted = [];
|
|
1360
|
+
/**
|
|
1361
|
+
* Pending mutations
|
|
1362
|
+
*/
|
|
1363
|
+
pending = [];
|
|
1364
|
+
/**
|
|
1365
|
+
* Our model of the document according to the incoming patches from the server
|
|
1366
|
+
*/
|
|
1367
|
+
HEAD;
|
|
1368
|
+
/**
|
|
1369
|
+
* Our optimistic model of what the document will probably look like as soon as all our patches
|
|
1370
|
+
* have been processed. Updated every time we stage a new mutation, but also might revert back
|
|
1371
|
+
* to previous states if our mutations fail, or could change if unexpected mutations arrive
|
|
1372
|
+
* between our own. The `onRebase` callback will be called when EDGE changes in this manner.
|
|
1373
|
+
*/
|
|
1374
|
+
EDGE;
|
|
1375
|
+
/**
|
|
1376
|
+
* Called with the EDGE document when that document changes for a reason other than us staging
|
|
1377
|
+
* a new patch or receiving a mutation from the server while our EDGE is in sync with HEAD:
|
|
1378
|
+
* I.e. when EDGE changes because the order of mutations has changed in relation to our
|
|
1379
|
+
* optimistic predictions.
|
|
1380
|
+
*/
|
|
1381
|
+
onRebase;
|
|
1382
|
+
/**
|
|
1383
|
+
* Called when we receive a patch in the normal order of things, but the mutation is not ours
|
|
1384
|
+
*/
|
|
1385
|
+
onMutation;
|
|
1386
|
+
/**
|
|
1387
|
+
* Called when consistency state changes with the boolean value of the current consistency state
|
|
1388
|
+
*/
|
|
1389
|
+
onConsistencyChanged;
|
|
1390
|
+
/**
|
|
1391
|
+
* Called whenever a new incoming mutation comes in. These are always ordered correctly.
|
|
1392
|
+
*/
|
|
1393
|
+
onRemoteMutation;
|
|
1394
|
+
/**
|
|
1395
|
+
* We are consistent when there are no unresolved mutations of our own, and no un-applicable
|
|
1396
|
+
* incoming mutations. When this has been going on for too long, and there has been a while
|
|
1397
|
+
* since we staged a new mutation, it is time to reset your state.
|
|
1398
|
+
*/
|
|
1399
|
+
inconsistentAt = null;
|
|
1400
|
+
/**
|
|
1401
|
+
* The last time we staged a patch of our own. If we have been inconsistent for a while, but it
|
|
1402
|
+
* hasn't been long since we staged a new mutation, the reason is probably just because the user
|
|
1403
|
+
* is typing or something.
|
|
1404
|
+
*
|
|
1405
|
+
* Should be used as a guard against resetting state for inconsistency reasons.
|
|
1406
|
+
*/
|
|
1407
|
+
lastStagedAt = null;
|
|
1408
|
+
constructor(doc) {
|
|
1409
|
+
this.reset(doc), this.HEAD = doc, this.EDGE = doc;
|
|
1410
|
+
}
|
|
1411
|
+
// Reset the state of the Document, used to recover from unsavory states by reloading the document
|
|
1412
|
+
reset(doc) {
|
|
1413
|
+
this.incoming = [], this.submitted = [], this.pending = [], this.inconsistentAt = null, this.HEAD = doc, this.EDGE = doc, this.considerIncoming(), this.updateConsistencyFlag();
|
|
1414
|
+
}
|
|
1415
|
+
// Call when a mutation arrives from Sanity
|
|
1416
|
+
arrive(mutation) {
|
|
1417
|
+
this.incoming.push(mutation), this.considerIncoming(), this.updateConsistencyFlag();
|
|
1418
|
+
}
|
|
1419
|
+
// Call to signal that we are submitting a mutation. Returns a callback object with a
|
|
1420
|
+
// success and failure handler that must be called according to the outcome of our
|
|
1421
|
+
// submission.
|
|
1422
|
+
stage(mutation, silent) {
|
|
1423
|
+
if (!mutation.transactionId)
|
|
1424
|
+
throw new Error("Mutations _must_ have transactionId when submitted");
|
|
1425
|
+
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({
|
|
1426
|
+
mutation,
|
|
1427
|
+
document: this.EDGE,
|
|
1428
|
+
remote: !1
|
|
1429
|
+
});
|
|
1430
|
+
const txnId = mutation.transactionId;
|
|
1431
|
+
return this.updateConsistencyFlag(), {
|
|
1432
|
+
success: () => {
|
|
1433
|
+
this.pendingSuccessfullySubmitted(txnId), this.updateConsistencyFlag();
|
|
1434
|
+
},
|
|
1435
|
+
failure: () => {
|
|
1436
|
+
this.pendingFailed(txnId), this.updateConsistencyFlag();
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
// Call to check if everything is nice and quiet and there are no unresolved mutations.
|
|
1441
|
+
// Means this model thinks both HEAD and EDGE is up to date with what the server sees.
|
|
1442
|
+
isConsistent() {
|
|
1443
|
+
return !this.inconsistentAt;
|
|
1444
|
+
}
|
|
1445
|
+
// Private
|
|
1446
|
+
// Attempts to apply any resolvable incoming patches to HEAD. Will keep patching as long as there
|
|
1447
|
+
// are applicable patches to be applied
|
|
1448
|
+
considerIncoming() {
|
|
1449
|
+
let mustRebase = !1, nextMut;
|
|
1450
|
+
const rebaseMutations = [];
|
|
1451
|
+
if (this.HEAD && this.HEAD._updatedAt) {
|
|
1452
|
+
const updatedAt = new Date(this.HEAD._updatedAt);
|
|
1453
|
+
this.incoming.find((mut) => mut.timestamp && mut.timestamp < updatedAt) && (this.incoming = this.incoming.filter((mut) => mut.timestamp && mut.timestamp < updatedAt));
|
|
1454
|
+
}
|
|
1455
|
+
let protect = 0;
|
|
1456
|
+
do {
|
|
1457
|
+
if (this.HEAD) {
|
|
1458
|
+
const HEAD = this.HEAD;
|
|
1459
|
+
nextMut = HEAD._rev ? this.incoming.find((mut) => mut.previousRev === HEAD._rev) : void 0;
|
|
1460
|
+
} else
|
|
1461
|
+
nextMut = this.incoming.find((mut) => mut.appliesToMissingDocument());
|
|
1462
|
+
if (nextMut) {
|
|
1463
|
+
const applied = this.applyIncoming(nextMut);
|
|
1464
|
+
if (mustRebase = mustRebase || applied, mustRebase && rebaseMutations.push(nextMut), protect++ > 10)
|
|
1465
|
+
throw new Error(
|
|
1466
|
+
`Mutator stuck flushing incoming mutations. Probably stuck here: ${JSON.stringify(
|
|
1467
|
+
nextMut
|
|
1468
|
+
)}`
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
} while (nextMut);
|
|
1472
|
+
this.incoming.length > 0 && debug.enabled && debug(
|
|
1473
|
+
"Unable to apply mutations %s",
|
|
1474
|
+
this.incoming.map((mut) => mut.transactionId).join(", ")
|
|
1475
|
+
), mustRebase && this.rebase(rebaseMutations);
|
|
1476
|
+
}
|
|
1477
|
+
// check current consistency state, update flag and invoke callback if needed
|
|
1478
|
+
updateConsistencyFlag() {
|
|
1479
|
+
const wasConsistent = this.isConsistent(), isConsistent = this.pending.length === 0 && this.submitted.length === 0 && this.incoming.length === 0;
|
|
1480
|
+
isConsistent ? this.inconsistentAt = null : this.inconsistentAt || (this.inconsistentAt = /* @__PURE__ */ new Date()), wasConsistent != isConsistent && this.onConsistencyChanged && (debug(isConsistent ? "Buffered document is inconsistent" : "Buffered document is consistent"), this.onConsistencyChanged(isConsistent));
|
|
1481
|
+
}
|
|
1482
|
+
// apply an incoming patch that has been prequalified as the next in line for this document
|
|
1483
|
+
applyIncoming(mut) {
|
|
1484
|
+
if (!mut)
|
|
1485
|
+
return !1;
|
|
1486
|
+
if (!mut.transactionId)
|
|
1487
|
+
throw new Error("Received incoming mutation without a transaction ID");
|
|
1488
|
+
if (debug(
|
|
1489
|
+
"Applying mutation %s -> %s to rev %s",
|
|
1490
|
+
mut.previousRev,
|
|
1491
|
+
mut.resultRev,
|
|
1492
|
+
this.HEAD && this.HEAD._rev
|
|
1493
|
+
), this.HEAD = mut.apply(this.HEAD), this.onRemoteMutation && this.onRemoteMutation(mut), this.incoming = this.incoming.filter((m) => m.transactionId !== mut.transactionId), this.hasUnresolvedMutations()) {
|
|
1494
|
+
const needRebase = this.consumeUnresolved(mut.transactionId);
|
|
1495
|
+
return debug.enabled && (debug(
|
|
1496
|
+
`Incoming mutation ${mut.transactionId} appeared while there were pending or submitted local mutations`
|
|
1497
|
+
), 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;
|
|
1498
|
+
}
|
|
1499
|
+
return debug(
|
|
1500
|
+
"Remote mutation %s arrived w/o any pending or submitted local mutations",
|
|
1501
|
+
mut.transactionId
|
|
1502
|
+
), this.EDGE = this.HEAD, this.onMutation && this.onMutation({
|
|
1503
|
+
mutation: mut,
|
|
1504
|
+
document: this.EDGE,
|
|
1505
|
+
remote: !0
|
|
1506
|
+
}), !1;
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Returns true if there are unresolved mutations between HEAD and EDGE, meaning we have
|
|
1510
|
+
* mutations that are still waiting to be either submitted, or to be confirmed by the server.
|
|
1511
|
+
*
|
|
1512
|
+
* @returns true if there are unresolved mutations between HEAD and EDGE, false otherwise
|
|
1513
|
+
*/
|
|
1514
|
+
hasUnresolvedMutations() {
|
|
1515
|
+
return this.submitted.length > 0 || this.pending.length > 0;
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* When an incoming mutation is applied to HEAD, this is called to remove the mutation from
|
|
1519
|
+
* the unresolved state. If the newly applied patch is the next upcoming unresolved mutation,
|
|
1520
|
+
* no rebase is needed, but we might have the wrong idea about the ordering of mutations, so in
|
|
1521
|
+
* that case we are given the flag `needRebase` to tell us that this mutation arrived out of
|
|
1522
|
+
* order in terms of our optimistic version, so a rebase is needed.
|
|
1523
|
+
*
|
|
1524
|
+
* @param txnId - Transaction ID of the remote mutation
|
|
1525
|
+
* @returns true if rebase is needed, false otherwise
|
|
1526
|
+
*/
|
|
1527
|
+
consumeUnresolved(txnId) {
|
|
1528
|
+
if (this.submitted.length === 0 && this.pending.length === 0)
|
|
1529
|
+
return !1;
|
|
1530
|
+
if (this.submitted.length !== 0) {
|
|
1531
|
+
if (this.submitted[0].transactionId === txnId)
|
|
1532
|
+
return debug(
|
|
1533
|
+
"Remote mutation %s matches upcoming submitted mutation, consumed from 'submitted' buffer",
|
|
1534
|
+
txnId
|
|
1535
|
+
), this.submitted.shift(), !1;
|
|
1536
|
+
} else if (this.pending.length > 0 && this.pending[0].transactionId === txnId)
|
|
1537
|
+
return debug(
|
|
1538
|
+
"Remote mutation %s matches upcoming pending mutation, consumed from 'pending' buffer",
|
|
1539
|
+
txnId
|
|
1540
|
+
), this.pending.shift(), !1;
|
|
1541
|
+
return debug(
|
|
1542
|
+
"The mutation was not the upcoming mutation, scrubbing. Pending: %d, Submitted: %d",
|
|
1543
|
+
this.pending.length,
|
|
1544
|
+
this.submitted.length
|
|
1545
|
+
), 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;
|
|
1546
|
+
}
|
|
1547
|
+
pendingSuccessfullySubmitted(pendingTxnId) {
|
|
1548
|
+
if (this.pending.length === 0)
|
|
1549
|
+
return;
|
|
1550
|
+
const first = this.pending[0];
|
|
1551
|
+
if (first.transactionId === pendingTxnId) {
|
|
1552
|
+
this.pending.shift(), this.submitted.push(first);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
let justSubmitted;
|
|
1556
|
+
const stillPending = [];
|
|
1557
|
+
this.pending.forEach((mutation) => {
|
|
1558
|
+
if (mutation.transactionId === pendingTxnId) {
|
|
1559
|
+
justSubmitted = mutation;
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
stillPending.push(mutation);
|
|
1563
|
+
}), justSubmitted && this.submitted.push(justSubmitted), this.pending = stillPending, this.rebase([]);
|
|
1564
|
+
}
|
|
1565
|
+
pendingFailed(pendingTxnId) {
|
|
1566
|
+
this.pending = this.pending.filter((mutation) => mutation.transactionId !== pendingTxnId), this.rebase([]);
|
|
1567
|
+
}
|
|
1568
|
+
rebase(incomingMutations) {
|
|
1569
|
+
const oldEdge = this.EDGE;
|
|
1570
|
+
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);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
class SquashingBuffer {
|
|
1574
|
+
/**
|
|
1575
|
+
* The document forming the basis of this squash
|
|
1576
|
+
*/
|
|
1577
|
+
BASIS;
|
|
1578
|
+
/**
|
|
1579
|
+
* The document after the out-Mutation has been applied, but before the staged
|
|
1580
|
+
* operations are committed.
|
|
1581
|
+
*/
|
|
1582
|
+
PRESTAGE;
|
|
1583
|
+
/**
|
|
1584
|
+
* setOperations contain the latest set operation by path. If the set-operations are
|
|
1585
|
+
* updating strings to new strings, they are rewritten as diffMatchPatch operations,
|
|
1586
|
+
* any new set operations on the same paths overwrites any older set operations.
|
|
1587
|
+
* Only set-operations assigning plain values to plain values gets optimized like this.
|
|
1588
|
+
*/
|
|
1589
|
+
setOperations;
|
|
1590
|
+
/**
|
|
1591
|
+
* `documentPresent` is true whenever we know that the document must be present due
|
|
1592
|
+
* to preceeding mutations. `false` implies that it may or may not already exist.
|
|
1593
|
+
*/
|
|
1594
|
+
documentPresent;
|
|
1595
|
+
/**
|
|
1596
|
+
* The operations in the out-Mutation are not able to be optimized any further
|
|
1597
|
+
*/
|
|
1598
|
+
out = [];
|
|
1599
|
+
/**
|
|
1600
|
+
* Staged mutation operations
|
|
1601
|
+
*/
|
|
1602
|
+
staged;
|
|
1603
|
+
constructor(doc) {
|
|
1604
|
+
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;
|
|
1605
|
+
}
|
|
1606
|
+
add(mut) {
|
|
1607
|
+
mut.mutations.forEach((op) => this.addOperation(op));
|
|
1608
|
+
}
|
|
1609
|
+
hasChanges() {
|
|
1610
|
+
return this.out.length > 0 || Object.keys(this.setOperations).length > 0;
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Extracts the mutations in this buffer.
|
|
1614
|
+
* After this is done, the buffer lifecycle is over and the client should
|
|
1615
|
+
* create an new one with the new, updated BASIS.
|
|
1616
|
+
*
|
|
1617
|
+
* @param txnId - Transaction ID
|
|
1618
|
+
* @returns A `Mutation` instance if we had outgoing mutations pending, null otherwise
|
|
1619
|
+
*/
|
|
1620
|
+
purge(txnId) {
|
|
1621
|
+
this.stashStagedOperations();
|
|
1622
|
+
let result = null;
|
|
1623
|
+
return this.out.length > 0 && (debug("Purged mutation buffer"), result = new Mutation({
|
|
1624
|
+
mutations: this.out,
|
|
1625
|
+
resultRev: txnId,
|
|
1626
|
+
transactionId: txnId
|
|
1627
|
+
})), this.out = [], this.documentPresent = !1, result;
|
|
1628
|
+
}
|
|
1629
|
+
addOperation(op) {
|
|
1630
|
+
if (op.patch && op.patch.set && "id" in op.patch && op.patch.id === this.PRESTAGE?._id && Object.keys(op.patch).length === 2) {
|
|
1631
|
+
const setPatch = op.patch.set, unoptimizable = {};
|
|
1632
|
+
for (const path of Object.keys(setPatch))
|
|
1633
|
+
setPatch.hasOwnProperty(path) && (this.optimiseSetOperation(path, setPatch[path]) || (unoptimizable[path] = setPatch[path]));
|
|
1634
|
+
Object.keys(unoptimizable).length > 0 && (debug("Unoptimizable set-operation detected, purging optimization buffer"), this.staged.push({ patch: { id: this.PRESTAGE._id, set: unoptimizable } }), this.stashStagedOperations());
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
if (op.createIfNotExists && this.PRESTAGE && op.createIfNotExists._id === this.PRESTAGE._id) {
|
|
1638
|
+
this.documentPresent || (this.staged.push(op), this.documentPresent = !0, this.stashStagedOperations());
|
|
1639
|
+
return;
|
|
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
|
+
}
|
|
1583
1709
|
const mutReducerFn = (acc, mut) => acc.concat(mut.mutations);
|
|
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
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1710
|
+
class BufferedDocument {
|
|
1711
|
+
mutations;
|
|
1712
|
+
/**
|
|
1713
|
+
* The Document we are wrapping
|
|
1714
|
+
*/
|
|
1715
|
+
document;
|
|
1716
|
+
/**
|
|
1717
|
+
* The Document with local changes applied
|
|
1718
|
+
*/
|
|
1719
|
+
LOCAL;
|
|
1720
|
+
/**
|
|
1721
|
+
* Commits that are waiting to be delivered to the server
|
|
1722
|
+
*/
|
|
1723
|
+
commits;
|
|
1724
|
+
/**
|
|
1725
|
+
* Local mutations that are not scheduled to be committed yet
|
|
1726
|
+
*/
|
|
1727
|
+
buffer;
|
|
1728
|
+
/**
|
|
1729
|
+
* Assignable event handler for when the buffered document applies a mutation
|
|
1730
|
+
*/
|
|
1731
|
+
onMutation;
|
|
1732
|
+
/**
|
|
1733
|
+
* Assignable event handler for when a remote mutation happened
|
|
1734
|
+
*/
|
|
1735
|
+
onRemoteMutation;
|
|
1736
|
+
/**
|
|
1737
|
+
* Assignable event handler for when the buffered document rebased
|
|
1738
|
+
*/
|
|
1739
|
+
onRebase;
|
|
1740
|
+
/**
|
|
1741
|
+
* Assignable event handler for when the document is deleted
|
|
1742
|
+
*/
|
|
1743
|
+
onDelete;
|
|
1744
|
+
/**
|
|
1745
|
+
* Assignable event handler for when the state of consistency changed
|
|
1746
|
+
*/
|
|
1747
|
+
onConsistencyChanged;
|
|
1748
|
+
/**
|
|
1749
|
+
* Assignable event handler for when the buffered document should commit changes
|
|
1750
|
+
*/
|
|
1751
|
+
commitHandler;
|
|
1752
|
+
/**
|
|
1753
|
+
* Whether or not we are currently commiting
|
|
1754
|
+
*/
|
|
1755
|
+
committerRunning = !1;
|
|
1756
|
+
constructor(doc) {
|
|
1757
|
+
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 = [];
|
|
1758
|
+
}
|
|
1759
|
+
// Used to reset the state of the local document model. If the model has been inconsistent
|
|
1760
|
+
// for too long, it has probably missed a notification, and should reload the document from the server
|
|
1761
|
+
reset(doc) {
|
|
1762
|
+
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());
|
|
1763
|
+
}
|
|
1764
|
+
// Add a change to the buffer
|
|
1765
|
+
add(mutation) {
|
|
1766
|
+
this.onConsistencyChanged && this.onConsistencyChanged(!1), debug("Staged local mutation"), this.buffer.add(mutation);
|
|
1767
|
+
const oldLocal = this.LOCAL;
|
|
1768
|
+
this.LOCAL = mutation.apply(this.LOCAL), this.onMutation && oldLocal !== this.LOCAL && (debug("onMutation fired"), this.onMutation({
|
|
1769
|
+
mutation,
|
|
1770
|
+
document: this.LOCAL,
|
|
1771
|
+
remote: !1
|
|
1772
|
+
}), this.LOCAL === null && this.onDelete && this.onDelete(this.LOCAL));
|
|
1773
|
+
}
|
|
1774
|
+
// Call when a mutation arrives from Sanity
|
|
1775
|
+
arrive(mutation) {
|
|
1776
|
+
if (debug("Remote mutation arrived %s -> %s", mutation.previousRev, mutation.resultRev), mutation.previousRev === mutation.resultRev)
|
|
1777
|
+
throw new Error(
|
|
1778
|
+
`Mutation ${mutation.transactionId} has previousRev === resultRev (${mutation.previousRev})`
|
|
1779
|
+
);
|
|
1780
|
+
return this.document.arrive(mutation);
|
|
1781
|
+
}
|
|
1782
|
+
// Submit all mutations in the buffer to be committed
|
|
1783
|
+
commit() {
|
|
1784
|
+
return new Promise((resolve, reject) => {
|
|
1785
|
+
if (!this.buffer.hasChanges()) {
|
|
1786
|
+
resolve();
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
debug("Committing local changes");
|
|
1790
|
+
const pendingMutations = this.buffer.purge();
|
|
1791
|
+
this.commits.push(new Commit(pendingMutations ? [pendingMutations] : [], { resolve, reject })), this.buffer = new SquashingBuffer(this.LOCAL), this.performCommits();
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
// Starts the committer that will try to committ all staged commits to the database
|
|
1795
|
+
// by calling the commitHandler. Will keep running until all commits are successfully
|
|
1796
|
+
// committed.
|
|
1797
|
+
performCommits() {
|
|
1798
|
+
if (!this.commitHandler)
|
|
1799
|
+
throw new Error("No commitHandler configured for this BufferedDocument");
|
|
1800
|
+
this.committerRunning || this._cycleCommitter();
|
|
1801
|
+
}
|
|
1802
|
+
// TODO: Error handling, right now retries after every error
|
|
1803
|
+
_cycleCommitter() {
|
|
1804
|
+
const commit = this.commits.shift();
|
|
1805
|
+
if (!commit) {
|
|
1806
|
+
this.committerRunning = !1;
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
this.committerRunning = !0;
|
|
1810
|
+
const squashed = commit.squash(this.LOCAL), docResponder = this.document.stage(squashed, !0), responder = {
|
|
1811
|
+
success: () => {
|
|
1812
|
+
debug("Commit succeeded"), docResponder.success(), commit.resolve(), this._cycleCommitter();
|
|
1813
|
+
},
|
|
1814
|
+
failure: () => {
|
|
1815
|
+
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, ONE_MINUTE));
|
|
1816
|
+
},
|
|
1817
|
+
cancel: (error) => {
|
|
1818
|
+
this.commits.forEach((comm) => comm.reject(error)), this.commits = [], this.reset(this.document.HEAD), this.buffer = new SquashingBuffer(this.LOCAL), this.committerRunning = !1;
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
debug("Posting commit"), this.commitHandler && this.commitHandler({
|
|
1822
|
+
mutation: squashed,
|
|
1823
|
+
success: responder.success,
|
|
1824
|
+
failure: responder.failure,
|
|
1825
|
+
cancel: responder.cancel
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
handleDocRebase(edge, remoteMutations, localMutations) {
|
|
1829
|
+
this.rebase(remoteMutations, localMutations);
|
|
1830
|
+
}
|
|
1831
|
+
handleDocumentDeleted() {
|
|
1832
|
+
debug("Document deleted"), this.LOCAL !== null && this.onDelete && this.onDelete(this.LOCAL), this.commits = [], this.mutations = [];
|
|
1833
|
+
}
|
|
1834
|
+
handleDocMutation(msg) {
|
|
1835
|
+
if (this.commits.length === 0 && !this.buffer.hasChanges()) {
|
|
1836
|
+
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);
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
debug("Document mutated from remote with local changes"), this.document.EDGE === null && this.handleDocumentDeleted(), this.rebase([msg.mutation], []);
|
|
1840
|
+
}
|
|
1841
|
+
rebase(remoteMutations, localMutations) {
|
|
1842
|
+
debug("Rebasing document"), this.document.EDGE === null && this.handleDocumentDeleted();
|
|
1843
|
+
const oldLocal = this.LOCAL;
|
|
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
|
|
1718
1863
|
};
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
//# sourceMappingURL=index.js.map
|
|
1864
|
+
//# sourceMappingURL=index.js.map
|