UNPKG

@sanity/desk-tool

Version:

Tool for managing all sorts of content in a structured manner

331 lines (320 loc) • 14.2 kB
"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; }