UNPKG

diffusion

Version:

Diffusion JavaScript client

341 lines (340 loc) 11.6 kB
"use strict"; /** * @module diffusion.datatypes */ Object.defineProperty(exports, "__esModule", { value: true }); exports.BinaryDeltaTypeImpl = exports.NO_CHANGE = void 0; var decoder_1 = require("./../../cbor/decoder"); var encoder_1 = require("./../../cbor/encoder"); var binary_delta_impl_1 = require("./../../data/binary/binary-delta-impl"); var myers_binary_diff_1 = require("./../../data/diff/myers-binary-diff"); var buffer_output_stream_1 = require("./../../io/buffer-output-stream"); var uint8array_1 = require("./../../util/uint8array"); /** * Global CBOR encoder */ var encoder = new encoder_1.Encoder(); /** * Global no-change constant */ exports.NO_CHANGE = new binary_delta_impl_1.BinaryDeltaImpl(Uint8Array.from([-10]), 0, 1); /** * Create a delta that contains only the new value * * @param value the buffer containing the value * @return the resulting delta */ function replace(value) { encoder.encode(value.$buffer, value.$offset, value.$length); return new binary_delta_impl_1.BinaryDeltaImpl(encoder.flush()); } /** * The CBOR cost of an unsigned integer in bytes. It's cheaper to * calculate than flush the encoder and check the number of written * bytes. * * @param i the integer to check * @returns byte cost */ function cborCost(i) { if (i < 24) { return 1; } else if (i < 0xFF) { return 2; } else if (i <= 0xFFFF) { return 3; } return 5; } /** * Check if a match should be conflated with an insert * * @param matchStart the starting position of the match * @param matchLength the number of bytes in the match * @param insertLength the number of bytes to be instered * * @return true if it is worth conflating a match with an adjacent insert */ function conflatableMatch(matchStart, matchLength, insertLength) { var matchCost = cborCost(matchStart) + 1; var insertCost = cborCost(matchLength + insertLength) - cborCost(insertLength) + matchLength; return insertCost <= matchCost; } /** * A script for Myers Binary Diff algorithm to process the differences between two * pieces of CBOR data */ var BinaryDeltaScript = /** @class */ (function () { /** * Create a new Script instance * * @param encoder the CBOR encoder * @param buffer the buffer containing the data * @param offset the offset in the buffer to encode from * @param blowsBudget a function that indicates whether or not to send the * new value instead of * the difference, depending on * the byte cost. */ function BinaryDeltaScript(cborEncoder, buffer, offset, blowsBudget) { /** * If insertStart is not -1, there is an unflushed insert of insertLength bytes. */ this.insertStart = -1; /** * If matchStart is not -1, there is an unflushed/uncoalesced match * of matchLength bytes. If insertStart is also not -1, the match * follows the insert; they could not be coalesced, but that might * change if the next insert is small. */ this.matchStart = -1; this.cborEncoder = cborEncoder; this.buffer = buffer; this.offset = offset; this.blowsBudget = blowsBudget; } /** * Process an insert operation * * @param bStart the starting position of the insert * @param length the number of bytes of the insert * @return a flag indicating success */ BinaryDeltaScript.prototype.insert = function (bStart, length) { if (this.matchStart !== -1) { if (conflatableMatch(this.matchStart, this.matchLength, length)) { this.matchStart = -1; if (this.insertStart === -1) { this.insertStart = bStart - this.matchLength; this.insertLength = this.matchLength + length; } else { // else triple play!: insert, match, insert -> insert this.insertLength += this.matchLength + length; } return true; } if (!this.flush()) { return false; } } if (this.insertStart === -1) { this.insertStart = bStart; this.insertLength = length; } else { this.insertLength += length; } return true; }; /** * Process a match operation * * @param bStart the starting position of the match * @param length the number of bytes of the match * @return a flag indicating success */ BinaryDeltaScript.prototype.match = function (aStart, length) { if (this.matchStart !== -1) { if (!this.flush()) { return false; } } else if (this.insertStart !== -1 && conflatableMatch(aStart, length, this.insertLength)) { this.insertLength += length; return true; } this.matchStart = aStart; this.matchLength = length; return true; }; /** * Process a delete operation * * @param bStart the starting position of the delete * @param length the number of bytes of the delete * @return a flag indicating success */ BinaryDeltaScript.prototype.delete = function (aStart, length) { return true; }; /** * Close the script and flush all pending operations * * @return a flag indicating success */ BinaryDeltaScript.prototype.close = function () { return this.flush(); }; /** * Flush all pending operations * * @return a flag indicating success */ BinaryDeltaScript.prototype.flush = function () { return (this.insertStart === -1 || this.writeInsert()) && (this.matchStart === -1 || this.writeMatch()); }; /** * Write an insert to the encoder * * @param start the starting position of the insert * @param length the number of bytes of the insert * @return a flag indicating success */ BinaryDeltaScript.prototype.writeInsert = function () { if (this.blowsBudget(this.insertLength + cborCost(this.insertLength))) { return false; } encoder.encode(this.buffer, this.offset + this.insertStart, this.insertLength); this.insertStart = -1; return true; }; /** * Write a match to the encoder * * @param start the starting position of the match * @param length the number of bytes of the match * @return a flag indicating success */ BinaryDeltaScript.prototype.writeMatch = function () { if (this.blowsBudget(cborCost(this.matchStart) + cborCost(this.matchLength))) { return false; } this.cborEncoder.encode(this.matchStart); this.cborEncoder.encode(this.matchLength); this.matchStart = -1; return true; }; return BinaryDeltaScript; }()); /** * Implementation of {@link DeltaType} * * @inheritdoc */ var BinaryDeltaTypeImpl = /** @class */ (function () { /** * Create a new instance of BinaryDeltaTypeImpl * * @param implementation constructor function to create new instances of the type `T` * @param cToV conversion function to calculate the value from a CBOR buffer * @param vToC conversion function to calculate a CBOR buffer from the value */ function BinaryDeltaTypeImpl(implementation, vToC, cToV) { /** * The Myers Binary Difference algorithm to calculate the differences */ this.binaryDiff = new myers_binary_diff_1.MyersBinaryDiff(); this.implementation = implementation; this.cToV = cToV; this.vToC = vToC; } /** * @inheritdoc */ BinaryDeltaTypeImpl.prototype.name = function () { return 'binary'; }; /** * @inheritdoc */ BinaryDeltaTypeImpl.prototype.diff = function (oldValueObj, newValueObj) { var oldValue = this.vToC(oldValueObj); var newValue = this.vToC(newValueObj); var buffer = newValue.$buffer; var offset = newValue.$offset; var length = newValue.$length; var budget = length; var script = new BinaryDeltaScript(encoder, buffer, offset, function (cost) { return ((budget -= cost) <= 0); }); // perform diff var result = this.binaryDiff.diff(oldValue.$buffer, oldValue.$offset, oldValue.$length, buffer, offset, length, script); // flush will reset global encoder var delta = encoder.flush(); switch (result) { case myers_binary_diff_1.REPLACE: return replace(newValue); case myers_binary_diff_1.NO_CHANGE: return exports.NO_CHANGE; default: return this.readDelta(delta); } }; /** * @inheritdoc */ BinaryDeltaTypeImpl.prototype.apply = function (oldValueObj, delta) { return this.applyImpl(oldValueObj, delta).value; }; /** * @inheritdoc */ BinaryDeltaTypeImpl.prototype.applyImpl = function (oldValueObj, delta, oldBuffer) { var deltaImpl = delta; var oldValue = oldBuffer || this.vToC(oldValueObj); if (!delta || !delta.hasChanges()) { return { value: this.cToV(oldValue), cbor: oldValue }; } var decoder = new decoder_1.Decoder(deltaImpl.$buffer); var bos = new buffer_output_stream_1.BufferOutputStream(); while (decoder.hasRemaining()) { var start = decoder.nextValue(); if (uint8array_1.isUint8Array(start)) { bos.writeMany(start); } else if (typeof start === 'number') { bos.writeMany(oldValue.$buffer, oldValue.$offset + start, decoder.nextValue()); } } var impl = new this.implementation(bos.getBuffer()); return { value: this.cToV(impl), cbor: impl }; }; /** * @inheritdoc */ BinaryDeltaTypeImpl.prototype.readDelta = function (buffer, offset, length) { var delta = new binary_delta_impl_1.BinaryDeltaImpl(buffer, offset, length); // because JS handles object equality solely by reference, check (unique) properties if (delta.$length === 1 && buffer[delta.$offset] === exports.NO_CHANGE.$buffer[0]) { return exports.NO_CHANGE; } return delta; }; /** * @inheritdoc * * @deprecated since 6.11 */ BinaryDeltaTypeImpl.prototype.writeDelta = function (delta) { return Buffer.from(this.writeDeltaToArray(delta).buffer); }; /** * @inheritdoc */ BinaryDeltaTypeImpl.prototype.writeDeltaToArray = function (delta) { var deltaImpl = delta; return deltaImpl.$buffer.subarray(deltaImpl.$offset, deltaImpl.$offset + deltaImpl.$length); }; /** * @inheritdoc */ BinaryDeltaTypeImpl.prototype.noChange = function () { return exports.NO_CHANGE; }; /** * @inheritdoc */ BinaryDeltaTypeImpl.prototype.isValueCheaper = function (value, delta) { return value.$length <= delta.$length; }; return BinaryDeltaTypeImpl; }()); exports.BinaryDeltaTypeImpl = BinaryDeltaTypeImpl;