@sanity/mutator
Version:
A set of models to make it easier to utilize the powerful real time collaborative features of Sanity
1,402 lines • 68.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: !0 });
var isEqual = require("lodash/isEqual.js"), debugIt = require("debug"), flatten = require("lodash/flatten.js"), diffMatchPatch = require("@sanity/diff-match-patch"), max = require("lodash/max.js"), min = require("lodash/min.js"), uuid = require("@sanity/uuid"), compact = require("lodash/compact.js");
function _interopDefaultCompat(e) {
return e && typeof e == "object" && "default" in e ? e : { default: e };
}
var isEqual__default = /* @__PURE__ */ _interopDefaultCompat(isEqual), debugIt__default = /* @__PURE__ */ _interopDefaultCompat(debugIt), flatten__default = /* @__PURE__ */ _interopDefaultCompat(flatten), max__default = /* @__PURE__ */ _interopDefaultCompat(max), min__default = /* @__PURE__ */ _interopDefaultCompat(min), compact__default = /* @__PURE__ */ _interopDefaultCompat(compact);
const debug = debugIt__default.default("mutator-document");
class ImmutableAccessor {
_value;
path;
constructor(value, path) {
this._value = value, this.path = path || [];
}
containerType() {
return Array.isArray(this._value) ? "array" : this._value !== null && typeof this._value == "object" ? "object" : "primitive";
}
// Common reader, supported by all containers
get() {
return this._value;
}
// Array reader
length() {
if (!Array.isArray(this._value))
throw new Error("Won't return length of non-indexable _value");
return this._value.length;
}
getIndex(i) {
return Array.isArray(this._value) ? i >= this.length() ? null : new ImmutableAccessor(this._value[i], this.path.concat(i)) : !1;
}
// Object reader
hasAttribute(key) {
return isRecord$1(this._value) ? this._value.hasOwnProperty(key) : !1;
}
attributeKeys() {
return isRecord$1(this._value) ? Object.keys(this._value) : [];
}
getAttribute(key) {
if (!isRecord$1(this._value))
throw new Error("getAttribute only applies to plain objects");
return this.hasAttribute(key) ? new ImmutableAccessor(this._value[key], this.path.concat(key)) : null;
}
// Common writer, supported by all containers
set(value) {
return value === this._value ? this : new ImmutableAccessor(value, this.path);
}
// array writer interface
setIndex(i, value) {
if (!Array.isArray(this._value))
throw new Error("setIndex only applies to arrays");
if (Object.is(value, this._value[i]))
return this;
const nextValue = this._value.slice();
return nextValue[i] = value, new ImmutableAccessor(nextValue, this.path);
}
setIndexAccessor(i, accessor) {
return this.setIndex(i, accessor.get());
}
unsetIndices(indices) {
if (!Array.isArray(this._value))
throw new Error("unsetIndices only applies to arrays");
const length = this._value.length, nextValue = [];
for (let i = 0; i < length; i++)
indices.indexOf(i) === -1 && nextValue.push(this._value[i]);
return new ImmutableAccessor(nextValue, this.path);
}
insertItemsAt(pos, items) {
if (!Array.isArray(this._value))
throw new Error("insertItemsAt only applies to arrays");
let nextValue;
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);
}
// Object writer interface
setAttribute(key, value) {
if (!isRecord$1(this._value))
throw new Error("Unable to set attribute of non-object container");
if (Object.is(value, this._value[key]))
return this;
const nextValue = Object.assign({}, this._value, { [key]: value });
return new ImmutableAccessor(nextValue, this.path);
}
setAttributeAccessor(key, accessor) {
return this.setAttribute(key, accessor.get());
}
unsetAttribute(key) {
if (!isRecord$1(this._value))
throw new Error("Unable to unset attribute of non-object container");
const nextValue = Object.assign({}, this._value);
return delete nextValue[key], new ImmutableAccessor(nextValue, this.path);
}
}
function isRecord$1(value) {
return value !== null && typeof value == "object";
}
function isRecord(value) {
return value !== null && typeof value == "object";
}
const IS_DOTTABLE = /^[a-z_$]+/;
function arrayToJSONMatchPath(pathArray) {
let path = "";
return pathArray.forEach((segment, index) => {
path += stringifySegment(segment, index === 0);
}), path;
}
function stringifySegment(segment, hasLeading) {
if (typeof segment == "number")
return `[${segment}]`;
if (isRecord(segment)) {
const seg = segment;
return Object.keys(segment).map((key) => isPrimitiveValue(seg[key]) ? `[${key}=="${seg[key]}"]` : "").join("");
}
return typeof segment == "string" && IS_DOTTABLE.test(segment) ? hasLeading ? segment : `.${segment}` : `['${segment}']`;
}
function isPrimitiveValue(val) {
switch (typeof val) {
case "number":
case "string":
case "boolean":
return !0;
default:
return !1;
}
}
function descend$1(tail) {
const [head, newTail] = splitIfPath(tail);
if (!head)
throw new Error("Head cannot be null");
return spreadIfUnionHead(head, newTail);
}
function splitIfPath(tail) {
if (tail.type !== "path")
return [tail, null];
const nodes = tail.nodes;
return nodes.length === 0 ? [null, null] : nodes.length === 1 ? [nodes[0], null] : [nodes[0], { type: "path", nodes: nodes.slice(1) }];
}
function concatPaths(path1, path2) {
if (!path1 && !path2)
return null;
const nodes1 = path1 ? path1.nodes : [], nodes2 = path2 ? path2.nodes : [];
return {
type: "path",
nodes: nodes1.concat(nodes2)
};
}
function spreadIfUnionHead(head, tail) {
return head.type !== "union" ? [[head, tail]] : head.nodes.map((node) => {
if (node.type === "path") {
const [subHead, subTail] = splitIfPath(node);
return [subHead, concatPaths(subTail, tail)];
}
return [node, tail];
});
}
const digitChar = /[0-9]/, attributeCharMatcher = /^[a-zA-Z0-9_]$/, attributeFirstCharMatcher = /^[a-zA-Z_]$/, symbols = {
// NOTE: These are compared against in order of definition,
// thus '==' must come before '=', '>=' before '>', etc.
operator: ["..", ".", ",", ":", "?"],
comparator: [">=", "<=", "<", ">", "==", "!="],
keyword: ["$", "@"],
boolean: ["true", "false"],
paren: ["[", "]"]
}, symbolClasses = Object.keys(symbols);
class Tokenizer {
source;
i;
length;
tokenizers;
constructor(path) {
this.source = path, this.length = path.length, this.i = 0, this.tokenizers = [
this.tokenizeSymbol,
this.tokenizeIdentifier,
this.tokenizeNumber,
this.tokenizeQuoted
].map((fn) => fn.bind(this));
}
tokenize() {
const result = [];
for (; !this.EOF(); ) {
this.chompWhitespace();
let token = null;
if (!this.tokenizers.some((tokenizer) => (token = tokenizer(), !!token)) || !token)
throw new Error(`Invalid tokens in jsonpath '${this.source}' @ ${this.i}`);
result.push(token);
}
return result;
}
takeWhile(fn) {
const start = this.i;
let result = "";
for (; !this.EOF(); ) {
const nextChar = fn(this.source[this.i]);
if (nextChar === null)
break;
result += nextChar, this.i++;
}
return this.i === start ? null : result;
}
EOF() {
return this.i >= this.length;
}
peek() {
return this.EOF() ? null : this.source[this.i];
}
consume(str) {
if (this.i + str.length > this.length)
throw new Error(`Expected ${str} at end of jsonpath`);
if (str === this.source.slice(this.i, this.i + str.length))
this.i += str.length;
else
throw new Error(`Expected "${str}", but source contained "${this.source.slice()}`);
}
// Tries to match the upcoming bit of string with the provided string. If it matches, returns
// the string, then advances the read pointer to the next bit. If not, returns null and nothing
// happens.
tryConsume(str) {
if (this.i + str.length > this.length)
return null;
if (str === this.source.slice(this.i, this.i + str.length)) {
if (str[0].match(attributeCharMatcher) && this.length > this.i + str.length) {
const nextChar = this.source[this.i + str.length];
if (nextChar && nextChar.match(attributeCharMatcher))
return null;
}
return this.i += str.length, str;
}
return null;
}
chompWhitespace() {
this.takeWhile((char) => char === " " ? "" : null);
}
tokenizeQuoted() {
const quote = this.peek();
if (quote === "'" || quote === '"') {
this.consume(quote);
let escape = !1;
const inner = this.takeWhile((char) => escape ? (escape = !1, char) : char === "\\" ? (escape = !0, "") : char != quote ? char : null);
return this.consume(quote), {
type: "quoted",
value: inner,
quote: quote === '"' ? "double" : "single"
};
}
return null;
}
tokenizeIdentifier() {
let first = !0;
const identifier = this.takeWhile((char) => first ? (first = !1, char.match(attributeFirstCharMatcher) ? char : null) : char.match(attributeCharMatcher) ? char : null);
return identifier !== null ? {
type: "identifier",
name: identifier
} : null;
}
tokenizeNumber() {
const start = this.i;
let dotSeen = !1, digitSeen = !1, negative = !1;
this.peek() === "-" && (negative = !0, this.consume("-"));
const number = this.takeWhile((char) => char === "." && !dotSeen && digitSeen ? (dotSeen = !0, char) : (digitSeen = !0, char.match(digitChar) ? char : null));
return number !== null ? {
type: "number",
value: negative ? -number : +number,
raw: negative ? `-${number}` : number
} : (this.i = start, null);
}
tokenizeSymbol() {
for (const symbolClass of symbolClasses) {
const symbol = symbols[symbolClass].find((pattern) => this.tryConsume(pattern));
if (symbol)
return {
type: symbolClass,
symbol
};
}
return null;
}
}
function tokenize(jsonpath) {
return new Tokenizer(jsonpath).tokenize();
}
class Parser {
tokens;
length;
i;
constructor(path) {
this.tokens = tokenize(path), this.length = this.tokens.length, this.i = 0;
}
parse() {
return this.parsePath();
}
EOF() {
return this.i >= this.length;
}
// Look at upcoming token
peek() {
return this.EOF() ? null : this.tokens[this.i];
}
consume() {
const result = this.peek();
return this.i += 1, result;
}
// Return next token if it matches the pattern
probe(pattern) {
const token = this.peek();
if (!token)
return null;
const record = token;
return Object.keys(pattern).every((key) => key in token && pattern[key] === record[key]) ? token : null;
}
// Return and consume next token if it matches the pattern
match(pattern) {
return this.probe(pattern) ? this.consume() : null;
}
parseAttribute() {
const token = this.match({ type: "identifier" });
if (token && token.type === "identifier")
return {
type: "attribute",
name: token.name
};
const quoted = this.match({ type: "quoted", quote: "single" });
return quoted && quoted.type === "quoted" ? {
type: "attribute",
name: quoted.value || ""
} : null;
}
parseAlias() {
return this.match({ type: "keyword", symbol: "@" }) || this.match({ type: "keyword", symbol: "$" }) ? {
type: "alias",
target: "self"
} : null;
}
parseNumber() {
const token = this.match({ type: "number" });
return token && token.type === "number" ? {
type: "number",
value: token.value
} : null;
}
parseNumberValue() {
const expr = this.parseNumber();
return expr ? expr.value : null;
}
parseSliceSelector() {
const start = this.i, rangeStart = this.parseNumberValue();
if (!this.match({ type: "operator", symbol: ":" }))
return rangeStart === null ? (this.i = start, null) : { type: "index", value: rangeStart };
const result = {
type: "range",
start: rangeStart,
end: this.parseNumberValue()
};
return this.match({ type: "operator", symbol: ":" }) && (result.step = this.parseNumberValue()), result.start === null && result.end === null ? (this.i = start, null) : result;
}
parseValueReference() {
return this.parseAttribute() || this.parseSliceSelector();
}
parseLiteralValue() {
const literalString = this.match({ type: "quoted", quote: "double" });
if (literalString && literalString.type === "quoted")
return {
type: "string",
value: literalString.value || ""
};
const literalBoolean = this.match({ type: "boolean" });
return literalBoolean && literalBoolean.type === "boolean" ? {
type: "boolean",
value: literalBoolean.symbol === "true"
} : this.parseNumber();
}
// TODO: Reorder constraints so that literal value is always on rhs, and variable is always
// on lhs.
parseFilterExpression() {
const start = this.i, expr = this.parseAttribute() || this.parseAlias();
if (!expr)
return null;
if (this.match({ type: "operator", symbol: "?" }))
return {
type: "constraint",
operator: "?",
lhs: expr
};
const binOp = this.match({ type: "comparator" });
if (!binOp || binOp.type !== "comparator")
return this.i = start, null;
const lhs = expr, rhs = this.parseLiteralValue();
if (!rhs)
throw new Error(`Operator ${binOp.symbol} needs a literal value at the right hand side`);
return {
type: "constraint",
operator: binOp.symbol,
lhs,
rhs
};
}
parseExpression() {
return this.parseFilterExpression() || this.parseValueReference();
}
parseUnion() {
if (!this.match({ type: "paren", symbol: "[" }))
return null;
const terms = [];
let expr = this.parseFilterExpression() || this.parsePath() || this.parseValueReference();
for (; expr && (terms.push(expr), !this.match({ type: "paren", symbol: "]" })); ) {
if (!this.match({ type: "operator", symbol: "," }))
throw new Error("Expected ]");
if (expr = this.parseFilterExpression() || this.parsePath() || this.parseValueReference(), !expr)
throw new Error("Expected expression following ','");
}
return {
type: "union",
nodes: terms
};
}
parseRecursive() {
if (!this.match({ type: "operator", symbol: ".." }))
return null;
const subpath = this.parsePath();
if (!subpath)
throw new Error("Expected path following '..' operator");
return {
type: "recursive",
term: subpath
};
}
parsePath() {
const nodes = [], expr = this.parseAttribute() || this.parseUnion() || this.parseRecursive();
if (!expr)
return null;
for (nodes.push(expr); !this.EOF(); )
if (this.match({ type: "operator", symbol: "." })) {
const attr = this.parseAttribute();
if (!attr)
throw new Error("Expected attribute name following '.");
nodes.push(attr);
continue;
} else if (this.probe({ type: "paren", symbol: "[" })) {
const union = this.parseUnion();
if (!union)
throw new Error("Expected union following '['");
nodes.push(union);
} else {
const recursive = this.parseRecursive();
recursive && nodes.push(recursive);
break;
}
return nodes.length === 1 ? nodes[0] : {
type: "path",
nodes
};
}
}
function parseJsonPath(path) {
const parsed = new Parser(path).parse();
if (!parsed)
throw new Error(`Failed to parse JSON path "${path}"`);
return parsed;
}
function toPath(expr) {
return toPathInner(expr, !1);
}
function toPathInner(expr, inUnion) {
switch (expr.type) {
case "attribute":
return expr.name;
case "alias":
return expr.target === "self" ? "@" : "$";
case "number":
return `${expr.value}`;
case "range": {
const result = [];
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("");
}
case "index":
return inUnion ? `${expr.value}` : `[${expr.value}]`;
case "constraint": {
const rhs = expr.rhs ? ` ${toPathInner(expr.rhs, !1)}` : "", inner = `${toPathInner(expr.lhs, !1)} ${expr.operator}${rhs}`;
return inUnion ? inner : `[${inner}]`;
}
case "string":
return JSON.stringify(expr.value);
case "path": {
const result = [], nodes = expr.nodes.slice();
for (; nodes.length > 0; ) {
const node = nodes.shift();
node && result.push(toPath(node));
const upcoming = nodes[0];
upcoming && toPathInner(upcoming, !1)[0] !== "[" && result.push(".");
}
return result.join("");
}
case "union":
return `[${expr.nodes.map((e) => toPathInner(e, !0)).join(",")}]`;
default:
throw new Error(`Unknown node type ${expr.type}`);
case "recursive":
return `..${toPathInner(expr.term, !1)}`;
}
}
class Expression {
expr;
constructor(expr) {
if (!expr)
throw new Error("Attempted to create Expression from null-value");
if ("expr" in expr ? this.expr = expr.expr : this.expr = expr, !("type" in this.expr))
throw new Error("Attempt to create Expression for expression with no type");
}
isPath() {
return this.expr.type === "path";
}
isUnion() {
return this.expr.type === "union";
}
isCollection() {
return this.isPath() || this.isUnion();
}
isConstraint() {
return this.expr.type === "constraint";
}
isRecursive() {
return this.expr.type === "recursive";
}
isExistenceConstraint() {
return this.expr.type === "constraint" && this.expr.operator === "?";
}
isIndex() {
return this.expr.type === "index";
}
isRange() {
return this.expr.type === "range";
}
expandRange(probe) {
const probeLength = () => {
if (!probe)
throw new Error("expandRange() required a probe that was not passed");
return probe.length();
};
let start = "start" in this.expr && this.expr.start || 0;
start = interpretNegativeIndex(start, probe);
let end = "end" in this.expr && this.expr.end || probeLength();
end = interpretNegativeIndex(end, probe);
const step = "step" in this.expr && this.expr.step || 1;
return { start, end, step };
}
isAttributeReference() {
return this.expr.type === "attribute";
}
// Is a range or index -> something referencing indexes
isIndexReference() {
return this.isIndex() || this.isRange();
}
name() {
return "name" in this.expr ? this.expr.name : "";
}
isSelfReference() {
return this.expr.type === "alias" && this.expr.target === "self";
}
constraintTargetIsSelf() {
return this.expr.type === "constraint" && this.expr.lhs.type === "alias" && this.expr.lhs.target === "self";
}
constraintTargetIsAttribute() {
return this.expr.type === "constraint" && this.expr.lhs.type === "attribute";
}
testConstraint(probe) {
const expr = this.expr;
if (expr.type === "constraint" && expr.lhs.type === "alias" && expr.lhs.target === "self") {
if (probe.containerType() !== "primitive")
return !1;
if (expr.type === "constraint" && expr.operator === "?")
return !0;
const lhs2 = probe.get(), rhs2 = expr.rhs && "value" in expr.rhs ? expr.rhs.value : void 0;
return testBinaryOperator(lhs2, expr.operator, rhs2);
}
if (expr.type !== "constraint")
return !1;
const lhs = expr.lhs;
if (!lhs)
throw new Error("No LHS of expression");
if (lhs.type !== "attribute")
throw new Error(`Constraint target ${lhs.type} not supported`);
if (probe.containerType() !== "object")
return !1;
const lhsValue = probe.getAttribute(lhs.name);
if (lhsValue == null || lhsValue.containerType() !== "primitive")
return !1;
if (this.isExistenceConstraint())
return !0;
const rhs = expr.rhs && "value" in expr.rhs ? expr.rhs.value : void 0;
return testBinaryOperator(lhsValue.get(), expr.operator, rhs);
}
pathNodes() {
return this.expr.type === "path" ? this.expr.nodes : [this.expr];
}
prepend(node) {
return node ? new Expression({
type: "path",
nodes: node.pathNodes().concat(this.pathNodes())
}) : this;
}
concat(other) {
return other ? other.prepend(this) : this;
}
descend() {
return descend$1(this.expr).map((headTail) => {
const [head, tail] = headTail;
return {
head: head ? new Expression(head) : null,
tail: tail ? new Expression(tail) : null
};
});
}
unwrapRecursive() {
if (this.expr.type !== "recursive")
throw new Error(`Attempt to unwrap recursive on type ${this.expr.type}`);
return new Expression(this.expr.term);
}
toIndicies(probe) {
if (this.expr.type !== "index" && this.expr.type !== "range")
throw new Error("Node cannot be converted to indexes");
if (this.expr.type === "index")
return [interpretNegativeIndex(this.expr.value, probe)];
const result = [], range = this.expandRange(probe);
let { start, end } = range;
range.step < 0 && ([start, end] = [end, start]);
for (let i = start; i < end; i++)
result.push(i);
return result;
}
toFieldReferences() {
if (this.isIndexReference())
return this.toIndicies();
if (this.expr.type === "attribute")
return [this.expr.name];
throw new Error(`Can't convert ${this.expr.type} to field references`);
}
toString() {
return toPath(this.expr);
}
static fromPath(path) {
const parsed = parseJsonPath(path);
if (!parsed)
throw new Error(`Failed to parse path "${path}"`);
return new Expression(parsed);
}
static attributeReference(name) {
return new Expression({
type: "attribute",
name
});
}
static indexReference(i) {
return new Expression({
type: "index",
value: i
});
}
}
function testBinaryOperator(lhsValue, operator, rhsValue) {
switch (operator) {
case ">":
return lhsValue > rhsValue;
case ">=":
return lhsValue >= rhsValue;
case "<":
return lhsValue < rhsValue;
case "<=":
return lhsValue <= rhsValue;
case "==":
return lhsValue === rhsValue;
case "!=":
return lhsValue !== rhsValue;
default:
throw new Error(`Unsupported binary operator ${operator}`);
}
}
function interpretNegativeIndex(index, probe) {
if (index >= 0)
return index;
if (!probe)
throw new Error("interpretNegativeIndex() must have a probe when < 0");
return index + probe.length();
}
class Descender {
head;
tail;
constructor(head, tail) {
this.head = head, this.tail = tail;
}
// Iterate this descender once processing any constraints that are
// resolvable on the current value. Returns an array of new descenders
// that are guaranteed to be without constraints in the head
iterate(probe) {
let result = [this];
if (this.head && this.head.isConstraint()) {
let anyConstraints = !0;
for (; anyConstraints; )
result = flatten__default.default(
result.map((descender) => descender.iterateConstraints(probe))
), anyConstraints = result.some((descender) => descender.head && descender.head.isConstraint());
}
return result;
}
isRecursive() {
return !!(this.head && this.head.isRecursive());
}
hasArrived() {
return this.head === null && this.tail === null;
}
extractRecursives() {
if (this.head && this.head.isRecursive()) {
const term = this.head.unwrapRecursive();
return new Descender(null, term.concat(this.tail)).descend();
}
return [];
}
iterateConstraints(probe) {
const head = this.head;
if (head === null || !head.isConstraint())
return [this];
const result = [];
if (probe.containerType() === "primitive" && head.constraintTargetIsSelf())
return head.testConstraint(probe) && result.push(...this.descend()), result;
if (probe.containerType() === "array") {
const length = probe.length();
for (let i = 0; i < length; i++) {
const constraint = probe.getIndex(i);
constraint && head.testConstraint(constraint) && result.push(new Descender(new Expression({ type: "index", value: i }), this.tail));
}
return result;
}
return probe.containerType() === "object" ? head.constraintTargetIsSelf() ? [] : head.testConstraint(probe) ? this.descend() : result : result;
}
descend() {
return this.tail ? this.tail.descend().map((ht) => new Descender(ht.head, ht.tail)) : [new Descender(null, null)];
}
toString() {
const result = ["<"];
return this.head && result.push(this.head.toString()), result.push("|"), this.tail && result.push(this.tail.toString()), result.push(">"), result.join("");
}
}
class Matcher {
active;
recursives;
payload;
constructor(active, parent) {
this.active = active || [], parent ? (this.recursives = parent.recursives, this.payload = parent.payload) : this.recursives = [], this.extractRecursives();
}
setPayload(payload) {
return this.payload = payload, this;
}
// Moves any recursive descenders onto the recursive track, removing them from
// the active set
extractRecursives() {
this.active = this.active.filter((descender) => descender.isRecursive() ? (this.recursives.push(...descender.extractRecursives()), !1) : !0);
}
// Find recursives that are relevant now and should be considered part of the active set
activeRecursives(probe) {
return this.recursives.filter((descender) => {
const head = descender.head;
return head ? head.isConstraint() || probe.containerType() === "array" && head.isIndexReference() ? !0 : probe.containerType() === "object" ? head.isAttributeReference() && probe.hasAttribute(head.name()) : !1 : !1;
});
}
match(probe) {
return this.iterate(probe).extractMatches(probe);
}
iterate(probe) {
const newActiveSet = [];
return this.active.concat(this.activeRecursives(probe)).forEach((descender) => {
newActiveSet.push(...descender.iterate(probe));
}), new Matcher(newActiveSet, this);
}
// Returns true if any of the descenders in the active or recursive set
// consider the current state a final destination
isDestination() {
return this.active.some((descender) => descender.hasArrived());
}
hasRecursives() {
return this.recursives.length > 0;
}
// Returns any payload delivieries and leads that needs to be followed to complete
// the process.
extractMatches(probe) {
const leads = [], targets = [];
if (this.active.forEach((descender) => {
if (descender.hasArrived()) {
targets.push(
new Expression({
type: "alias",
target: "self"
})
);
return;
}
const descenderHead = descender.head;
if (descenderHead && !(probe.containerType() === "array" && !descenderHead.isIndexReference()) && !(probe.containerType() === "object" && !descenderHead.isAttributeReference()))
if (descender.tail) {
const matcher = new Matcher(descender.descend(), this);
descenderHead.toFieldReferences().forEach(() => {
leads.push({
target: descenderHead,
matcher
});
});
} else
targets.push(descenderHead);
}), this.hasRecursives()) {
const recursivesMatcher = new Matcher([], this);
if (probe.containerType() === "array") {
const length = probe.length();
for (let i = 0; i < length; i++)
leads.push({
target: Expression.indexReference(i),
matcher: recursivesMatcher
});
} else probe.containerType() === "object" && probe.attributeKeys().forEach((name) => {
leads.push({
target: Expression.attributeReference(name),
matcher: recursivesMatcher
});
});
}
return targets.length > 0 ? { leads, delivery: { targets, payload: this.payload } } : { leads };
}
static fromPath(jsonpath) {
const path = parseJsonPath(jsonpath);
if (!path)
throw new Error(`Failed to parse path from "${jsonpath}"`);
const descender = new Descender(null, new Expression(path));
return new Matcher(descender.descend());
}
}
class PlainProbe {
_value;
path;
constructor(value, path) {
this._value = value, this.path = path || [];
}
containerType() {
return Array.isArray(this._value) ? "array" : this._value !== null && typeof this._value == "object" ? "object" : "primitive";
}
length() {
if (!Array.isArray(this._value))
throw new Error("Won't return length of non-indexable _value");
return this._value.length;
}
getIndex(i) {
return Array.isArray(this._value) ? i >= this.length() ? null : new PlainProbe(this._value[i], this.path.concat(i)) : !1;
}
hasAttribute(key) {
return isRecord(this._value) ? this._value.hasOwnProperty(key) : !1;
}
attributeKeys() {
return isRecord(this._value) ? Object.keys(this._value) : [];
}
getAttribute(key) {
if (!isRecord(this._value))
throw new Error("getAttribute only applies to plain objects");
return this.hasAttribute(key) ? new PlainProbe(this._value[key], this.path.concat(key)) : null;
}
get() {
return this._value;
}
}
function extractAccessors(path, value) {
const result = [], matcher = Matcher.fromPath(path).setPayload(function(values) {
result.push(...values);
}), accessor = new PlainProbe(value);
return descend(matcher, accessor), result;
}
function descend(matcher, accessor) {
const { leads, delivery } = matcher.match(accessor);
leads.forEach((lead) => {
accessorsFromTarget(lead.target, accessor).forEach((childAccessor) => {
descend(lead.matcher, childAccessor);
});
}), delivery && delivery.targets.forEach((target) => {
typeof delivery.payload == "function" && delivery.payload(accessorsFromTarget(target, accessor));
});
}
function accessorsFromTarget(target, accessor) {
const result = [];
if (target.isIndexReference())
target.toIndicies(accessor).forEach((i) => {
result.push(accessor.getIndex(i));
});
else if (target.isAttributeReference())
result.push(accessor.getAttribute(target.name()));
else if (target.isSelfReference())
result.push(accessor);
else
throw new Error(`Unable to derive accessor for target ${target.toString()}`);
return compact__default.default(result);
}
function extract(path, value) {
return extractAccessors(path, value).map((acc) => acc.get());
}
function extractWithPath(path, value) {
return extractAccessors(path, value).map((acc) => ({ path: acc.path, value: acc.get() }));
}
function applyPatch(patch, oldValue) {
if (typeof oldValue != "string") return oldValue;
const [result] = diffMatchPatch.applyPatches(patch, oldValue, { allowExceedingIndices: !0 });
return result;
}
class DiffMatchPatch {
path;
dmpPatch;
id;
constructor(id, path, dmpPatchSrc) {
this.id = id, this.path = path, this.dmpPatch = diffMatchPatch.parsePatch(dmpPatchSrc);
}
apply(targets, accessor) {
let result = accessor;
if (result.containerType() === "primitive")
return result;
for (const target of targets) {
if (target.isIndexReference()) {
for (const index of target.toIndicies(accessor)) {
const item = result.getIndex(index);
if (!item)
continue;
const oldValue = item.get(), nextValue = applyPatch(this.dmpPatch, oldValue);
result = result.setIndex(index, nextValue);
}
continue;
}
if (target.isAttributeReference() && result.hasAttribute(target.name())) {
const attribute = result.getAttribute(target.name());
if (!attribute)
continue;
const oldValue = attribute.get(), nextValue = applyPatch(this.dmpPatch, oldValue);
result = result.setAttribute(target.name(), nextValue);
continue;
}
throw new Error(`Unable to apply diffMatchPatch to target ${target.toString()}`);
}
return result;
}
}
function performIncrement(previousValue, delta) {
return typeof previousValue != "number" || !Number.isFinite(previousValue) ? previousValue : previousValue + delta;
}
class IncPatch {
path;
value;
id;
constructor(id, path, value) {
this.path = path, this.value = value, this.id = id;
}
apply(targets, accessor) {
let result = accessor;
if (result.containerType() === "primitive")
return result;
for (const target of targets) {
if (target.isIndexReference()) {
for (const index of target.toIndicies(accessor)) {
const item = result.getIndex(index);
if (!item)
continue;
const previousValue = item.get();
result = result.setIndex(index, performIncrement(previousValue, this.value));
}
continue;
}
if (target.isAttributeReference()) {
const attribute = result.getAttribute(target.name());
if (!attribute)
continue;
const previousValue = attribute.get();
result = result.setAttribute(target.name(), performIncrement(previousValue, this.value));
continue;
}
throw new Error(`Unable to apply to target ${target.toString()}`);
}
return result;
}
}
function targetsToIndicies(targets, accessor) {
const result = [];
return targets.forEach((target) => {
target.isIndexReference() && result.push(...target.toIndicies(accessor));
}), result.sort();
}
class InsertPatch {
location;
path;
items;
id;
constructor(id, location, path, items) {
this.id = id, this.location = location, this.path = path, this.items = items;
}
apply(targets, accessor) {
let result = accessor;
if (accessor.containerType() !== "array")
throw new Error("Attempt to apply insert patch to non-array value");
switch (this.location) {
case "before": {
const pos = minIndex(targets, accessor);
result = result.insertItemsAt(pos, this.items);
break;
}
case "after": {
const pos = maxIndex(targets, accessor);
result = result.insertItemsAt(pos + 1, this.items);
break;
}
case "replace": {
const indicies = targetsToIndicies(targets, accessor);
result = result.unsetIndices(indicies), result = result.insertItemsAt(indicies[0], this.items);
break;
}
default:
throw new Error(`Unsupported location atm: ${this.location}`);
}
return result;
}
}
function minIndex(targets, accessor) {
let result = min__default.default(targetsToIndicies(targets, accessor)) || 0;
return targets.forEach((target) => {
if (target.isRange()) {
const { start } = target.expandRange();
start < result && (result = start);
}
}), result;
}
function maxIndex(targets, accessor) {
let result = max__default.default(targetsToIndicies(targets, accessor)) || 0;
return targets.forEach((target) => {
if (target.isRange()) {
const { end } = target.expandRange();
end > result && (result = end);
}
}), result;
}
class SetIfMissingPatch {
id;
path;
value;
constructor(id, path, value) {
this.id = id, this.path = path, this.value = value;
}
apply(targets, accessor) {
let result = accessor;
return targets.forEach((target) => {
if (!target.isIndexReference())
if (target.isAttributeReference())
result.containerType() === "primitive" ? result = result.set({ [target.name()]: this.value }) : result.hasAttribute(target.name()) || (result = accessor.setAttribute(target.name(), this.value));
else
throw new Error(`Unable to apply to target ${target.toString()}`);
}), result;
}
}
class SetPatch {
id;
path;
value;
constructor(id, path, value) {
this.id = id, this.path = path, this.value = value;
}
apply(targets, accessor) {
let result = accessor;
return targets.forEach((target) => {
if (target.isSelfReference())
result = result.set(this.value);
else if (target.isIndexReference())
target.toIndicies(accessor).forEach((i) => {
result = result.setIndex(i, this.value);
});
else if (target.isAttributeReference())
result.containerType() === "primitive" ? result = result.set({ [target.name()]: this.value }) : result = result.setAttribute(target.name(), this.value);
else
throw new Error(`Unable to apply to target ${target.toString()}`);
}), result;
}
}
class UnsetPatch {
id;
path;
value;
constructor(id, path) {
this.id = id, this.path = path;
}
// eslint-disable-next-line class-methods-use-this
apply(targets, accessor) {
let result = accessor;
switch (accessor.containerType()) {
case "array":
result = result.unsetIndices(targetsToIndicies(targets, accessor));
break;
case "object":
targets.forEach((target) => {
result = result.unsetAttribute(target.name());
});
break;
default:
throw new Error(
"Target value is neither indexable or an object. This error should potentially just be silently ignored?"
);
}
return result;
}
}
function parsePatch(patch) {
const result = [];
if (Array.isArray(patch))
return patch.reduce((r, p) => r.concat(parsePatch(p)), result);
const { set, setIfMissing, unset, diffMatchPatch: diffMatchPatch2, inc, dec, insert } = patch;
if (setIfMissing && Object.keys(setIfMissing).forEach((path) => {
result.push(new SetIfMissingPatch(patch.id, path, setIfMissing[path]));
}), set && Object.keys(set).forEach((path) => {
result.push(new SetPatch(patch.id, path, set[path]));
}), unset && unset.forEach((path) => {
result.push(new UnsetPatch(patch.id, path));
}), diffMatchPatch2 && Object.keys(diffMatchPatch2).forEach((path) => {
result.push(new DiffMatchPatch(patch.id, path, diffMatchPatch2[path]));
}), inc && Object.keys(inc).forEach((path) => {
result.push(new IncPatch(patch.id, path, inc[path]));
}), dec && Object.keys(dec).forEach((path) => {
result.push(new IncPatch(patch.id, path, -dec[path]));
}), insert) {
let location, path;
const spec = insert;
if ("before" in spec)
location = "before", path = spec.before;
else if ("after" in spec)
location = "after", path = spec.after;
else if ("replace" in spec)
location = "replace", path = spec.replace;
else
throw new Error("Invalid insert patch");
result.push(new InsertPatch(patch.id, location, path, spec.items));
}
return result;
}
class Patcher {
patches;
constructor(patch) {
this.patches = parsePatch(patch);
}
apply(value) {
const accessor = new ImmutableAccessor(value);
return this.applyViaAccessor(accessor).get();
}
// If you want to use your own accessor implementation, you can use this method
// to invoke the patcher. Since all subsequent accessors for children of this accessor
// are obtained through the methods in the accessors, you retain full control of the
// implementation throguhgout the application. Have a look in ImmutableAccessor
// to see an example of how accessors are implemented.
applyViaAccessor(accessor) {
let result = accessor;
const idAccessor = accessor.getAttribute("_id");
if (!idAccessor)
throw new Error("Cannot apply patch to document with no _id");
const id = idAccessor.get();
for (const patch of this.patches) {
if (patch.id !== id)
continue;
const matcher = Matcher.fromPath(patch.path).setPayload(patch);
result = process(matcher, result);
}
return result;
}
}
function process(matcher, accessor) {
const isSetPatch = matcher.payload instanceof SetPatch || matcher.payload instanceof SetIfMissingPatch;
let result = accessor;
const { leads, delivery } = matcher.match(accessor);
return leads.forEach((lead) => {
if (lead.target.isIndexReference())
lead.target.toIndicies().forEach((i) => {
const item = result.getIndex(i);
if (!item)
throw new Error("Index out of bounds");
result = result.setIndexAccessor(i, process(lead.matcher, item));
});
else if (lead.target.isAttributeReference()) {
isSetPatch && result.containerType() === "primitive" && (result = result.set({}));
let oldValueAccessor = result.getAttribute(lead.target.name());
if (!oldValueAccessor && isSetPatch && (result = result.setAttribute(lead.target.name(), {}), oldValueAccessor = result.getAttribute(lead.target.name())), !oldValueAccessor)
return;
const newValueAccessor = process(lead.matcher, oldValueAccessor);
oldValueAccessor !== newValueAccessor && (result = result.setAttributeAccessor(lead.target.name(), newValueAccessor));
} else
throw new Error(`Unable to handle target ${lead.target.toString()}`);
}), delivery && isPatcher(delivery.payload) && (result = delivery.payload.apply(delivery.targets, result)), result;
}
function isPatcher(payload) {
return !!(payload && typeof payload == "object" && payload !== null && "apply" in payload && typeof payload.apply == "function");
}
const luid = uuid.uuid;
class Mutation {
params;
compiled;
_appliesToMissingDocument;
constructor(options) {
this.params = options;
}
get transactionId() {
return this.params.transactionId;
}
get transition() {
return this.params.transition;
}
get identity() {
return this.params.identity;
}
get previousRev() {
return this.params.previousRev;
}
get resultRev() {
return this.params.resultRev;
}
get mutations() {
return this.params.mutations;
}
get timestamp() {
if (typeof this.params.timestamp == "string")
return new Date(this.params.timestamp);
}
get effects() {
return this.params.effects;
}
assignRandomTransactionId() {
this.params.transactionId = luid(), this.params.resultRev = this.params.transactionId;
}
appliesToMissingDocument() {
if (typeof this._appliesToMissingDocument < "u")
return this._appliesToMissingDocument;
const firstMut = this.mutations[0];
return firstMut ? this._appliesToMissingDocument = !!(firstMut.create || firstMut.createIfNotExists || firstMut.createOrReplace) : this._appliesToMissingDocument = !0, this._appliesToMissingDocument;
}
// Compiles all mutations into a handy function
compile() {
const operations = [], getGuaranteedCreatedAt = (doc) => doc?._createdAt || this.params.timestamp || (/* @__PURE__ */ new Date()).toISOString();
this.mutations.forEach((mutation) => {
if (mutation.create) {
const create = mutation.create || {};
operations.push((doc) => doc || Object.assign(create, {
_createdAt: getGuaranteedCreatedAt(create)
}));
return;
}
if (mutation.createIfNotExists) {
const createIfNotExists = mutation.createIfNotExists || {};
operations.push(
(doc) => doc === null ? Object.assign(createIfNotExists, {
_createdAt: getGuaranteedCreatedAt(createIfNotExists)
}) : doc
);
return;
}
if (mutation.createOrReplace) {
const createOrReplace = mutation.createOrReplace || {};
operations.push(
() => Object.assign(createOrReplace, {
_createdAt: getGuaranteedCreatedAt(createOrReplace)
})
);
return;
}
if (mutation.delete) {
operations.push(() => null);
return;
}
if (mutation.patch) {
if ("query" in mutation.patch)
return;
const patch = new Patcher(mutation.patch);
operations.push((doc) => patch.apply(doc));
return;
}
throw new Error(`Unsupported mutation ${JSON.stringify(mutation, null, 2)}`);
}), typeof this.params.timestamp == "string" && operations.push((doc) => doc ? Object.assign(doc, { _updatedAt: this.params.timestamp }) : null);
const prevRev = this.previousRev, rev = this.resultRev || this.transactionId;
this.compiled = (doc) => {
if (prevRev && doc && prevRev !== doc._rev)
throw new Error(
`Previous revision for this mutation was ${prevRev}, but the document revision is ${doc._rev}`
);
let result = doc;
for (const operation of operations)
result = operation(result);
return result && rev && (result === doc && (result = Object.assign({}, doc)), result._rev = rev), result;
};
}
apply(document) {
debug("Applying mutation %O to document %O", this.mutations, document), this.compiled || this.compile();
const result = this.compiled(document);
return debug(" => %O", result), result;
}
static applyAll(document, mutations) {
return mutations.reduce((doc, mutation) => mutation.apply(doc), document);
}
// Given a number of yet-to-be-committed mutation objects, collects them into one big mutation
// any metadata like transactionId is ignored and must be submitted by the client. It is assumed
// that all mutations are on the same document.
// TOOO: Optimize mutations, eliminating mutations that overwrite themselves!
static squash(document, mutations) {
const squashed = mutations.reduce(
(result, mutation) => result.concat(...mutation.mutations),
[]
);
return new Mutation({ mutations: squashed });
}
}
class Document {
/**
* Incoming patches from the server waiting to be applied to HEAD
*/
incoming = [];
/**
* Patches we know has been subitted to the server, but has not been seen yet in the return channel
* so we can't be sure about the ordering yet (someone else might have slipped something between them)
*/
submitted = [];
/**
* Pending mutations
*/
pending = [];
/**
* Our model of the document according to the incoming patches from the server
*/
HEAD;
/**
* Our optimistic model of what the document will probably look like as soon as all our patches
* have been processed. Updated every time we stage a new mutation, but also might revert back
* to previous states if our mutations fail, or could change if unexpected mutations arrive
* between our own. The `onRebase` callback will be called when EDGE changes in this manner.
*/
EDGE;
/**
* Called with the EDGE document when that document changes for a reason other than us staging
* a new patch or receiving a mutation from the server while our EDGE is in sync with HEAD:
* I.e. when EDGE changes because the order of mutations has changed in relation to our
* optimistic predictions.
*/
onRebase;
/**
* Called when we receive a patch in the normal order of things, but the mutation is not ours
*/
onMutation;
/**
* Called when consistency state changes with the boolean value of the current consistency state
*/
onConsistencyChanged;
/**
* Called whenever a new incoming mutation comes in. These are always ordered correctly.
*/
onRemoteMutation;
/**
* We are consistent when there are no unresolved mutations of our own, and no un-applicable
* incoming mutations. When this has been going on for too long, and there has been a while
* since we staged a new mutation, it is time to reset your state.
*/
inconsistentAt = null;
/**
* The last time we staged a patch of our own. If we have been inconsistent for a while, but it
* hasn't been long since we staged a new mutation, the reason is probably just because the user
* is typing or something.
*
* Should be used as a guard against resetting state for inconsistency reasons.
*/
lastStagedAt = null;
constructor(doc) {
this.reset(doc), this.HEAD = doc, this.EDGE = doc;
}
// Reset the state of the Document, used to recover from unsavory states by reloading the document
reset(doc) {
this.incoming = [], this.submitted = [], this.pending = [], this.inconsistentAt = null, this.HEAD = doc, this.EDGE = doc, this.considerIncoming(), this.updateConsistencyFlag();
}
// Call when a mutation arrives from Sanity
arrive(mutation) {
this.incoming.push(mutation), this.considerIncoming(), this.updateConsistencyFlag();
}
// Call to signal that we are submitting a mutation. Returns a callback object with a
// success and failure handler that must be called according to the outcome of our
// submission.
stage(mutation, silent) {
if (!mutation.transactionId)
throw new Error("Mutations _must_ have transactionId when submitted");
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({
mutation,
document: this.EDGE,
remote: !1
});
const txnId = mutation.transactionId;
return this.updateConsistencyFlag(), {
success: () => {
this.pendingSuccessfullySubmitted(txnId), this.updateConsistencyFlag();
},
failure: () => {
this.pendingFailed(txnId), this.updateConsistencyFlag();
}
};
}
// Call to check if everything is nice and quiet and there are no unresolved mutations.
// Means this model thinks both HEAD and EDGE is up to date with what the server sees.
isConsistent() {
return !this.inconsistentAt;
}
// Private
// Attempts to apply any resolvable incoming patches to HEAD. Will keep patching as long as there
//