diffusion
Version:
Diffusion JavaScript client
341 lines (340 loc) • 11.6 kB
JavaScript
"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;