@sanity/desk-tool
Version:
Tool for managing all sorts of content in a structured manner
331 lines (320 loc) • 14.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Timeline = void 0;
var _mendoza = require("mendoza");
var _diffValue = require("./diffValue");
var _TwoEndedArray = require("./TwoEndedArray");
var _chunker = require("./chunker");
var _utils = require("./utils");
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
/**
* Timeline maintains information about the history of a document:
* Grouping raw translog entries into sensible groups, replaying and
* reconstructing different versions and abstract other details.
*
* Note that this class by itself is not capable of _fetching_ information,
* but will only organize and structure the incoming translog entries.
*/
class Timeline {
constructor(opts) {
_defineProperty(this, "reachedEarliestEntry", false);
_defineProperty(this, "publishedId", void 0);
_defineProperty(this, "draftId", void 0);
_defineProperty(this, "_transactions", new _TwoEndedArray.TwoEndedArray());
_defineProperty(this, "_chunks", new _TwoEndedArray.TwoEndedArray());
// These two properties are here to handle the case
_defineProperty(this, "_possiblePendingTransactions", new Map());
_defineProperty(this, "_recreateTransactionsFrom", void 0);
_defineProperty(this, "_trace", void 0);
this.publishedId = opts.publishedId;
this.draftId = "drafts.".concat(opts.publishedId);
if (opts.enableTrace) {
this._trace = [];
this._trace.push({
type: 'initial',
publishedId: opts.publishedId
});
window.__sanityTimelineTrace = this._trace;
}
}
get chunkCount() {
return this._chunks.length;
}
/** Maps over the chunk from newest to oldest. */
mapChunks(mapper) {
var result = [];
var firstIdx = this._chunks.firstIdx;
var lastIdx = this._chunks.lastIdx;
for (var _idx = lastIdx; _idx >= firstIdx; _idx--) {
result.push(mapper(this._chunks.get(_idx), _idx));
}
return result;
}
reset() {
this._transactions = new _TwoEndedArray.TwoEndedArray();
this._chunks = new _TwoEndedArray.TwoEndedArray();
this._possiblePendingTransactions = new Map();
this._recreateTransactionsFrom = undefined;
this.reachedEarliestEntry = false;
}
/**
* Adds a remote mutation to the timeline. This methods assumes that the remote mutations
* come in correct order for their respective version, but has no ordering requirements
* across draft/published.
*
* Example: [D1, D2, P1] (where D1 and P1 were mutations done to the draft and published
* version in the same transaction) is a valid input. [P1, D2, D1] is _not_ valid since
* the mutation for the draft is out of order.
*/
addRemoteMutation(entry) {
if (this._trace) this._trace.push({
type: 'addRemoteMutation',
event: entry
});
var pending = this._possiblePendingTransactions.get(entry.transactionId);
var transaction = pending ? pending.transaction : {
index: 0,
id: entry.transactionId,
timestamp: entry.timestamp.toISOString(),
author: entry.author
};
if (entry.version === 'draft') {
transaction.draftEffect = entry.effects;
} else {
transaction.publishedEffect = entry.effects;
}
if (pending) {
this._possiblePendingTransactions.delete(entry.transactionId);
this._invalidateTransactionFrom(pending.idx);
} else {
this._transactions.addToEnd(transaction);
this._possiblePendingTransactions.set(entry.transactionId, {
transaction,
idx: this._transactions.lastIdx
});
}
}
addTranslogEntry(event) {
if (this._trace) this._trace.push({
type: 'addTranslogEntry',
event
});
this._transactions.addToBeginning({
index: 0,
id: event.id,
author: event.author,
timestamp: event.timestamp,
draftEffect: event.effects[this.draftId],
publishedEffect: event.effects[this.publishedId]
});
}
/** Mark that we've reached the earliest entry. */
didReachEarliestEntry() {
if (this._trace) this._trace.push({
type: 'didReachEarliestEntry'
});
this.reachedEarliestEntry = true;
}
/**
* updateChunks synchronizes the chunks to match the current state
* of the transactions array. After calling this method you need
* to invalidate all Chunks.
*/
updateChunks() {
if (this._trace) this._trace.push({
type: 'updateChunks'
});
this._removeInvalidatedChunks();
this._addChunksFromTransactions();
this._createInitialChunk();
}
_removeInvalidatedChunks() {
if (this._recreateTransactionsFrom) {
while (this._chunks.length > 0) {
var _chunk = this._chunks.last;
if (this._recreateTransactionsFrom < _chunk.end) {
this._chunks.removeFromEnd();
} else {
break;
}
}
this._recreateTransactionsFrom = undefined;
}
}
_addChunksFromTransactions() {
var firstIdx = this._transactions.firstIdx;
var lastIdx = this._transactions.lastIdx;
// Add transactions at the end:
var nextTransactionToChunk = this._chunks.length > 0 ? this._chunks.last.end : firstIdx;
for (var _idx2 = nextTransactionToChunk; _idx2 <= lastIdx; _idx2++) {
var transaction = this._transactions.get(_idx2);
this._chunks.mergeAtEnd((0, _chunker.chunkFromTransaction)(transaction), _chunker.mergeChunk);
}
// Add transactions at the beginning:
if (this._chunks.length == 0) return;
var firstTransactionChunked = this._chunks.first.start;
for (var _idx3 = firstTransactionChunked - 1; _idx3 >= firstIdx; _idx3--) {
var _transaction = this._transactions.get(_idx3);
this._chunks.mergeAtBeginning((0, _chunker.chunkFromTransaction)(_transaction), _chunker.mergeChunk);
}
}
_invalidateTransactionFrom(idx) {
if (this._recreateTransactionsFrom === undefined || idx < this._recreateTransactionsFrom) {
this._recreateTransactionsFrom = idx;
}
}
_createInitialChunk() {
if (this.reachedEarliestEntry) {
var _this$_chunks$first;
if (((_this$_chunks$first = this._chunks.first) === null || _this$_chunks$first === void 0 ? void 0 : _this$_chunks$first.type) === 'initial') return;
var firstTx = this._transactions.first;
if (!firstTx) return;
var initialChunk = (0, _chunker.chunkFromTransaction)(firstTx);
initialChunk.type = 'initial';
initialChunk.id = '@initial';
initialChunk.end = initialChunk.start;
this._chunks.addToBeginning(initialChunk);
}
}
/**
* Resolves a time reference.
*
* Note that the chunk returned is only valid if the timeline stays constant.
* Once the timeline is updated, you must re-parse all references.
*/
parseTimeId(id) {
if (this._chunks.length === 0) {
return this.reachedEarliestEntry ? 'invalid' : 'loading';
}
var _id$split = id.split('/', 3),
_id$split2 = _slicedToArray(_id$split, 2),
timestampStr = _id$split2[0],
chunkId = _id$split2[1];
var timestamp = Number(timestampStr);
for (var _idx4 = this._chunks.lastIdx; _idx4 >= this._chunks.firstIdx; _idx4--) {
var _chunk2 = this._chunks.get(_idx4);
if (_chunk2.id === chunkId) {
return _chunk2;
}
if (Date.parse(_chunk2.endTimestamp) + 60 * 60 * 1000 < timestamp) {
// The chunk ended _before_ the timestamp we're asking for. This means that there
// is no point in looking further and the chunk is invalid.
// We add 1 hour to allow some slack since transactions are not guaranteed to be in order.
return 'invalid';
}
}
return this.reachedEarliestEntry ? 'invalid' : 'loading';
}
findLastPublishedBefore(chunk) {
for (var chunkIdx = chunk ? chunk.index - 1 : this._chunks.lastIdx; chunkIdx >= this._chunks.firstIdx; chunkIdx--) {
var currentChunk = this._chunks.get(chunkIdx);
if (currentChunk.type === 'publish' || currentChunk.type === 'initial') {
return currentChunk;
}
}
if (!this.reachedEarliestEntry) return 'loading';
return this._chunks.first;
}
isLatestChunk(chunk) {
return chunk === this._chunks.last;
}
// eslint-disable-next-line class-methods-use-this
createTimeId(chunk) {
return "".concat(chunk.endTimestamp.valueOf(), "/").concat(chunk.id);
}
lastChunk() {
return this._chunks.last;
}
transactionByIndex(idx) {
if (!this._transactions.has(idx)) return null;
return this._transactions.get(idx);
}
chunkByTransactionIndex(idx) {
var startChunkIdx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var chunkIdx = startChunkIdx;
for (;;) {
var _chunk3 = this._chunks.get(chunkIdx);
if (!_chunk3) throw new Error('transaction does not belong in any chunk');
if (idx >= _chunk3.end) {
chunkIdx++;
} else if (idx < _chunk3.start) {
chunkIdx--;
} else {
return _chunk3;
}
}
}
replayBackwardsBetween(firstIdx, lastIdx, doc) {
var draft = doc.draft;
var published = doc.published;
for (var _idx5 = lastIdx; _idx5 >= firstIdx; _idx5--) {
var transaction = this._transactions.get(_idx5);
if (transaction.draftEffect) {
draft = (0, _mendoza.applyPatch)(draft, transaction.draftEffect.revert);
}
if (transaction.publishedEffect) {
published = (0, _mendoza.applyPatch)(published, transaction.publishedEffect.revert);
}
}
return {
draft,
published
};
}
replayBackwardsUntil(firstIdx, doc) {
return this.replayBackwardsBetween(firstIdx, this._transactions.lastIdx, doc);
}
calculateDiff(initialDoc, finalDoc, firstIdx, lastIdx) {
var draftValue = _mendoza.incremental.wrap(initialDoc.draft, null);
var publishedValue = _mendoza.incremental.wrap(initialDoc.published, null);
var initialValue = getValue(draftValue, publishedValue);
var initialAttributes = (0, _utils.getAttrs)(initialDoc);
var firstChunk = null;
// Loop over all of the chunks:
for (var chunkIdx = firstIdx; chunkIdx <= lastIdx; chunkIdx++) {
var _chunk4 = this._chunks.get(chunkIdx);
if (!firstChunk) firstChunk = _chunk4;
for (var _idx6 = _chunk4.start; _idx6 < _chunk4.end; _idx6++) {
var transaction = this._transactions.get(_idx6);
var meta = {
chunk: _chunk4,
transactionIndex: _idx6
};
var preDraftValue = draftValue;
var prePublishedValue = publishedValue;
if (transaction.draftEffect) {
draftValue = _mendoza.incremental.applyPatch(draftValue, transaction.draftEffect.apply, meta);
}
if (transaction.publishedEffect) {
publishedValue = _mendoza.incremental.applyPatch(publishedValue, transaction.publishedEffect.apply, meta);
}
var didHaveDriaft = _mendoza.incremental.getType(preDraftValue) !== 'null';
var haveDraft = _mendoza.incremental.getType(draftValue) !== 'null';
var havePublished = _mendoza.incremental.getType(publishedValue) !== 'null';
if (havePublished && !haveDraft) {
publishedValue = _mendoza.incremental.rebaseValue(preDraftValue, publishedValue);
}
if (haveDraft && !didHaveDriaft) {
draftValue = _mendoza.incremental.rebaseValue(prePublishedValue, draftValue);
}
}
}
var finalValue = _mendoza.incremental.getType(draftValue) === 'null' ? publishedValue : draftValue;
var finalAttributes = (0, _utils.getAttrs)(finalDoc);
return (0, _diffValue.diffValue)(this, firstChunk, initialValue, initialAttributes, finalValue, finalAttributes);
}
}
exports.Timeline = Timeline;
function getValue(draftValue, publishedValue) {
return _mendoza.incremental.getType(draftValue) === 'null' ? publishedValue : draftValue;
}