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