diffusion
Version:
Diffusion JavaScript client
593 lines (592 loc) • 22.9 kB
JavaScript
"use strict";
/**
* @module diffusion.datatypes.RecordV2
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.RecordV2Impl = void 0;
var errors_1 = require("./../../../errors/errors");
var binary_delta_type_impl_1 = require("./../../data/binary/binary-delta-type-impl");
var bytes_impl_1 = require("./../../data/bytes-impl");
var record_model_impl_1 = require("./../../data/record/model/record-model-impl");
var recordv2_delta_impl_1 = require("./../../data/record/recordv2-delta-impl");
var recordv2_parser_1 = require("./../../data/record/recordv2-parser");
var recordv2_utils_1 = require("./../../data/record/recordv2-utils");
var buffer_output_stream_1 = require("./../../io/buffer-output-stream");
var function_1 = require("./../../util/function");
var uint8array_1 = require("./../../util/uint8array");
/**
* The result of a difference operation between two RecordV2 instances
*/
var DiffResult = /** @class */ (function () {
function DiffResult() {
/**
* A stream recording the delta between the two records
*/
this.delta = new buffer_output_stream_1.BufferOutputStream();
/**
* A flag indicating whether there are changes between the records
*/
this.changed = false;
/**
* The record changes
*/
this.recordChanges = null;
/**
* The field changes
*/
this.fieldChanges = {};
}
/**
* Record a change
*
* @param buffer the buffer of the change to record or a single number
* @param offset the offset in the buffer
* @param length the amount of bytes to write from the provided buffer
*/
DiffResult.prototype.write = function (buffer, offset, length) {
if (offset !== undefined && length !== undefined) {
this.delta.writeMany(buffer, offset, length);
}
else {
this.delta.write(buffer);
}
};
/**
* Get the delta
*
* @return the delta inside a buffer
*/
DiffResult.prototype.getDelta = function () {
return this.delta.getBuffer();
};
/**
* The size of the delta
*
* @return the number of bytes recorded
*/
DiffResult.prototype.size = function () {
return this.delta.count;
};
/**
* Check if there are any changes
*
* @return `true` if a change has been recorded
*/
DiffResult.prototype.hasChanges = function () {
return this.changed;
};
/**
* Record a change
*/
DiffResult.prototype.setChanged = function () {
this.changed = true;
};
/**
* Record a changed record
*
* @param added a flag indicating if the record was added or removed
* @param index the record index
*/
DiffResult.prototype.setRecordsChanged = function (added, index) {
this.recordChanges = [added, index];
this.changed = true;
};
/**
* Get the recorded record changes
*
* @return the record changes or `null` if there were no changes
*/
DiffResult.prototype.getRecordChanges = function () {
return this.recordChanges;
};
/**
* Record an added field
*
* @param recordIndex the index of the record
* @param fieldIndex the index of the field
*/
DiffResult.prototype.setFieldsAdded = function (recordIndex, fieldIndex) {
this.fieldChanges[recordIndex] = [true, fieldIndex];
this.changed = true;
};
/**
* Record a removed field
*
* @param recordIndex the index of the record
* @param fieldIndex the index of the field
*/
DiffResult.prototype.setFieldsRemoved = function (recordIndex, fieldIndex) {
this.fieldChanges[recordIndex] = [false, fieldIndex];
this.changed = true;
};
/**
* Get the recorded field changes
*
* @return the field changes
*/
DiffResult.prototype.getFieldChanges = function () {
return this.fieldChanges;
};
return DiffResult;
}());
/**
* Calculate the differences between two records
*
* @param oldBytes the buffer of the old record
* @param oldOffset the offset in the buffer of the old record
* @param oldLength the number of bytes in the old record
* @param newBytes the buffer of the new record
* @param newOffset the offset in the buffer of the new record
* @param newLength the number of bytes in the new record
* @return the difference between the buffers
*/
function diffRecords(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength) {
var result = new DiffResult();
var oldCount = recordv2_utils_1.recordCount(oldBytes, oldOffset, oldLength);
var newCount = recordv2_utils_1.recordCount(newBytes, newOffset, newLength);
var minCount = Math.min(oldCount, newCount);
var oldStart = oldOffset;
var oldEnd = oldOffset;
var newStart = newOffset;
var newEnd = newOffset;
for (var i = 0; i < minCount; ++i) {
oldEnd = recordv2_utils_1.findDelimiter(oldBytes, oldEnd, oldOffset + oldLength, recordv2_utils_1.RECORD_DELIMITER);
newEnd = recordv2_utils_1.findDelimiter(newBytes, newEnd, newOffset + newLength, recordv2_utils_1.RECORD_DELIMITER);
if (i > 0) {
result.write(recordv2_utils_1.RECORD_DELIMITER);
}
diffRecord(i, oldBytes, oldStart, oldEnd - oldStart, newBytes, newStart, newEnd - newStart, result);
oldStart = ++oldEnd;
newStart = ++newEnd;
}
if (newCount !== oldCount) {
if (newCount > oldCount) {
result.write(newBytes, newStart - 1, newOffset + newLength - newStart + 1);
}
result.setRecordsChanged(newCount > oldCount, minCount);
}
return result;
}
/**
* Calculate the differences between two record entries
*
* @param recordIndex the index of the record
* @param oldBytes the buffer of the old record
* @param oldOffset the offset in the buffer of the old record
* @param oldLength the number of bytes in the old record
* @param newBytes the buffer of the new record
* @param newOffset the offset in the buffer of the new record
* @param newLength the number of bytes in the new record
* @param result the result recording the difference between the buffers
*/
function diffRecord(recordIndex, oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result) {
if (newLength === 0) {
if (oldLength !== 0) {
result.setFieldsRemoved(recordIndex, 0);
}
return;
}
if (recordv2_utils_1.recordIsSingleEmptyField(newBytes, newOffset, newLength)) {
diffWhenNewIsSingleEmptyField(recordIndex, oldBytes, oldOffset, oldLength, result);
return;
}
if (oldLength === 0) {
result.write(newBytes, newOffset, newLength);
result.setFieldsAdded(recordIndex, 0);
return;
}
if (recordv2_utils_1.recordIsSingleEmptyField(oldBytes, oldOffset, oldLength)) {
diffWhenOldIsSingleEmptyField(recordIndex, newBytes, newOffset, newLength, result);
return;
}
diffRecordFields(recordIndex, oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result);
}
/**
* Calculate the differences between two record entries when the new record
* contains a single empty field
*
* @param recordIndex the index of the record
* @param newBytes the buffer of the new record
* @param newOffset the offset in the buffer of the new record
* @param newLength the number of bytes in the new record
* @param result the result recording the difference between the buffers
*/
function diffWhenNewIsSingleEmptyField(recordIndex, oldBytes, oldOffset, oldLength, result) {
if (recordv2_utils_1.recordIsSingleEmptyField(oldBytes, oldOffset, oldLength)) {
result.write(recordv2_utils_1.FIELD_MU);
}
else {
var oldCount = recordv2_utils_1.fieldCount(oldBytes, oldOffset, oldLength);
if (oldCount > 1) {
result.setFieldsRemoved(recordIndex, 1);
}
result.write(recordv2_utils_1.EMPTY_FIELD);
result.setChanged();
}
}
/**
* Calculate the differences between two record entries when the old record
* contains a single empty field
*
* @param recordIndex the index of the record
* @param oldBytes the buffer of the old record
* @param oldOffset the offset in the buffer of the old record
* @param oldLength the number of bytes in the old record
* @param result the result recording the difference between the buffers
*/
function diffWhenOldIsSingleEmptyField(recordIndex, newBytes, newOffset, newLength, result) {
var b = newBytes[newOffset];
if (b === recordv2_utils_1.EMPTY_FIELD) {
result.write(newBytes, newOffset + 1, newLength - 1);
}
else {
result.write(newBytes, newOffset, newLength);
if (b !== recordv2_utils_1.FIELD_DELIMITER) {
result.setChanged();
}
}
if (recordv2_utils_1.fieldCount(newBytes, newOffset, newLength) > 1) {
result.setFieldsAdded(recordIndex, 1);
}
}
/**
* Calculate the differences between the fields of two record entries
*
* @param recordIndex the index of the record
* @param oldBytes the buffer of the old record
* @param oldOffset the offset in the buffer of the old record
* @param oldLength the number of bytes in the old record
* @param newBytes the buffer of the new record
* @param newOffset the offset in the buffer of the new record
* @param newLength the number of bytes in the new record
* @param result the result recording the difference between the buffers
*/
function diffRecordFields(recordIndex, oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result) {
var oldCount = recordv2_utils_1.fieldCount(oldBytes, oldOffset, oldLength);
var newCount = recordv2_utils_1.fieldCount(newBytes, newOffset, newLength);
var same = Math.min(oldCount, newCount);
var oldStart = oldOffset;
var oldEnd = oldOffset;
var newStart = newOffset;
var newEnd = newOffset;
var startSize = result.size();
for (var i = 0; i < same; ++i) {
oldEnd = recordv2_utils_1.findDelimiter(oldBytes, oldEnd, oldOffset + oldLength, recordv2_utils_1.FIELD_DELIMITER);
newEnd = recordv2_utils_1.findDelimiter(newBytes, newEnd, newOffset + newLength, recordv2_utils_1.FIELD_DELIMITER);
if (i > 0) {
result.write(recordv2_utils_1.FIELD_DELIMITER);
}
diffField(oldBytes, oldStart, oldEnd - oldStart, newBytes, newStart, newEnd - newStart, result);
oldStart = ++oldEnd;
newStart = ++newEnd;
}
if (newCount !== oldCount) {
if (newCount > oldCount) {
result.write(newBytes, newStart - 1, newOffset + newLength - newStart + 1);
result.setFieldsAdded(recordIndex, same);
}
else {
result.setFieldsRemoved(recordIndex, same);
}
}
if (newCount === 1 && startSize === result.size()) {
result.write(recordv2_utils_1.FIELD_MU);
}
}
/**
* Calculate the differences between two fields
*
* @param oldBytes the buffer of the old record
* @param oldOffset the offset in the buffer of the old record
* @param oldLength the number of bytes in the old record
* @param newBytes the buffer of the new record
* @param newOffset the offset in the buffer of the new record
* @param newLength the number of bytes in the new record
* @param result the result recording the difference between the buffers
*/
function diffField(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength, result) {
if (fieldsAreEqual(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength)) {
return;
}
if (newLength === 0) {
result.write(recordv2_utils_1.EMPTY_FIELD);
}
else {
result.write(newBytes, newOffset, newLength);
}
result.setChanged();
}
/**
* Check if two fields are equal. The fields may be empty.
*
* @param oldBytes the buffer of the old record
* @param oldOffset the offset in the buffer of the old record
* @param oldLength the number of bytes in the old record
* @param newBytes the buffer of the new record
* @param newOffset the offset in the buffer of the new record
* @param newLength the number of bytes in the new record
* @return `true` if the two fields are equal
*/
function fieldsAreEqual(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength) {
if (oldLength === newLength) {
if (oldLength === 0 || fieldEquals(oldBytes, oldOffset, newBytes, newOffset, oldLength)) {
return true;
}
}
else if (oldLength === 1 && oldBytes[oldOffset] === recordv2_utils_1.EMPTY_FIELD && newLength === 0) {
return true;
}
else if (newLength === 1 && newBytes[newOffset] === recordv2_utils_1.EMPTY_FIELD && oldLength === 0) {
return true;
}
return false;
}
/**
* Check if two non-empty fields are equal
*
* @param oldBytes the buffer of the old record
* @param oldOffset the offset in the buffer of the old record
* @param oldLength the number of bytes in the old record
* @param newBytes the buffer of the new record
* @param newOffset the offset in the buffer of the new record
* @param newLength the number of bytes in the new record
* @return `true` if the two fields are equal
*/
function fieldEquals(oldBytes, oldOffset, newBytes, newOffset, length) {
for (var i = 0; i < length; ++i) {
if (oldBytes[oldOffset + i] !== newBytes[newOffset + i]) {
return false;
}
}
return true;
}
/**
* Create a new RecordV2DeltaImpl for an empty value
*
* @return a new RecordV2DeltaImpl
*/
function deltaForEmptyNewValue() {
return new recordv2_delta_impl_1.RecordV2DeltaImpl(Uint8Array.from([]), 0, 0, [false, 0], {});
}
/**
* Create a new RecordV2DeltaImpl for a single record with no fields
*
* @return a new RecordV2DeltaImpl
*/
function deltaForSingleEmptyRecordNewValue(oldRecordCount) {
switch (oldRecordCount) {
case 0:
return new recordv2_delta_impl_1.RecordV2DeltaImpl(Uint8Array.from([recordv2_utils_1.RECORD_MU]), 0, 1, [true, 0], {});
case 1:
return new recordv2_delta_impl_1.RecordV2DeltaImpl(Uint8Array.from([recordv2_utils_1.RECORD_MU]), 0, 1, null, { 0: [false, 0] });
default:
return new recordv2_delta_impl_1.RecordV2DeltaImpl(Uint8Array.from([recordv2_utils_1.RECORD_MU]), 0, 1, [false, 1], { 0: [false, 0] });
}
}
/**
* Create a RecordV2DeltaImpl that contains only new records
*
* @param newBytes the buffer of the new record
* @param newOffset the offset in the buffer of the new record
* @param newLength the number of bytes in the new record
* @return a new RecordV2DeltaImpl
*/
function deltaForAllNewRecords(newBytes, newOffset, newLength) {
return new recordv2_delta_impl_1.RecordV2DeltaImpl(newBytes, newOffset, newLength, [true, 0], {});
}
/**
* Return a delta for when the old value is a single empty record.
* This assumes that the new record is neither empty nor a single empty
* record.
*
* @param newBytes the buffer of the new record
* @param newOffset the offset in the buffer of the new record
* @param newLength the number of bytes in the new record
* @return a new RecordV2DeltaImpl
*/
function deltaForOldSingleEmptyRecord(newBytes, newOffset, newLength) {
var newRecordCount = recordv2_utils_1.recordCount(newBytes, newOffset, newLength);
if (newRecordCount === 1) {
// single record so fields added to first
return new recordv2_delta_impl_1.RecordV2DeltaImpl(newBytes, newOffset, newLength, null, { 0: [true, 0] });
}
else {
// records added
return new recordv2_delta_impl_1.RecordV2DeltaImpl(newBytes, newOffset, newLength, [true, 1], newBytes[0] === recordv2_utils_1.RECORD_DELIMITER ? {} : { 0: [true, 0] });
}
}
/**
* Create a new RecordV2 object from a buffer or {@link RecordV2Impl}. If a
* {@link RecordV2Impl} is passed as argument it is simply returned.
*
* @param value the object to create a record from
* @return the record created
* @throws an {@link SchemaViolationError} if the value is invalid
*/
function fromValue(value) {
// tslint:disable-next-line:no-use-before-declare
if (value instanceof RecordV2Impl) {
return value;
}
else if (uint8array_1.isUint8Array(value)) {
// tslint:disable-next-line:no-use-before-declare
return new RecordV2Impl(value);
}
else {
throw new errors_1.SchemaViolationError("Unable to read RecordV2 value from: " + value);
}
}
/**
* Implementation of {@link RecordV2Delta}
*/
var RecordV2Impl = /** @class */ (function (_super) {
__extends(RecordV2Impl, _super);
/**
* Create a RecordV2DeltaImpl instance
*
* @param buffer the internal buffer
* @param offset the offset of the slice
* @param length the number of bytes in the slice
* @param recordChanges an array containing a record index at position 0
* and a boolean at position 1 indicating if a record
* has changed
* @param fieldChanges an array of arrays containing a field index at
* position 0 and a boolean at position 1 indicating
* which fields have changed
*/
function RecordV2Impl(buffer, offset, length) {
if (offset === void 0) { offset = 0; }
if (length === void 0) { length = buffer.length; }
return _super.call(this, buffer, offset, length) || this;
}
/**
* Return the name of the data type
*
* @return `RecordV2Impl`
*/
RecordV2Impl.toString = function () {
return 'RecordV2Impl';
};
/**
* Create a new RecordV2 object from a buffer or {@link RecordV2Impl}. If a
* {@link RecordV2Impl} is passed as argument it is simply returned.
*
* @param value the object to create a record from
* @return the record created
*/
RecordV2Impl.from = function (value) {
return fromValue(value);
};
/**
* Apply a delta and return a buffer with containing a modified record.
*
* If the delta does not contain any changes `this` is returned.
*
* @param delta the delta to apply
* @return the modified record
* @throws an {@link InvalidDataError} if the delta is invalid
*/
RecordV2Impl.prototype.apply = function (delta) {
// tslint:disable-next-line:no-use-before-declare
return BINARY_DELTA_TYPE.apply(this, delta);
};
/**
* Convert the objet to a string
*
* @return a string representation of the RecordV2Impl
*/
RecordV2Impl.prototype.toString = function () {
return JSON.stringify(this.asRecords());
};
/**
* @inheritdoc
*/
RecordV2Impl.prototype.asRecords = function () {
return recordv2_parser_1.parse(this.$buffer, this.$offset, this.$length);
};
/**
* @inheritdoc
*/
RecordV2Impl.prototype.asFields = function () {
return this.asRecords().reduce(function (fields, record) {
return fields.concat(record);
}, []);
};
/**
* @inheritdoc
*/
RecordV2Impl.prototype.asModel = function (schema) {
return new record_model_impl_1.RecordModelImpl(RecordV2Impl, schema, this.asRecords(), false);
};
/**
* @inheritdoc
*/
RecordV2Impl.prototype.asValidatedModel = function (schema) {
return new record_model_impl_1.RecordModelImpl(RecordV2Impl, schema, this.asRecords(), true);
};
/**
* Calculate the binary difference between the record and another buffer.
*/
RecordV2Impl.prototype.binaryDiff = function (original) {
// tslint:disable-next-line:no-use-before-declare
return BINARY_DELTA_TYPE.diff(original, this);
};
/**
* @inheritdoc
*/
RecordV2Impl.prototype.diff = function (original) {
var oldBytes = original.$buffer;
var oldOffset = original.$offset;
var oldLength = original.$length;
var newBytes = this.$buffer;
var newOffset = this.$offset;
var newLength = this.$length;
if (newLength === 0) {
if (oldLength === 0) {
return recordv2_delta_impl_1.NO_CHANGE;
}
return deltaForEmptyNewValue();
}
else if (recordv2_utils_1.isSingleEmptyRecord(newBytes, newOffset, newLength)) {
if (recordv2_utils_1.isSingleEmptyRecord(oldBytes, oldOffset, oldLength)) {
return recordv2_delta_impl_1.NO_CHANGE;
}
return deltaForSingleEmptyRecordNewValue(recordv2_utils_1.recordCount(oldBytes, oldOffset, oldLength));
}
if (oldLength === 0) {
return deltaForAllNewRecords(newBytes, newOffset, newLength);
}
else if (recordv2_utils_1.isSingleEmptyRecord(oldBytes, oldOffset, oldLength)) {
return deltaForOldSingleEmptyRecord(newBytes, newOffset, newLength);
}
var result = diffRecords(oldBytes, oldOffset, oldLength, newBytes, newOffset, newLength);
if (result.hasChanges()) {
var delta = result.getDelta();
return new recordv2_delta_impl_1.RecordV2DeltaImpl(delta, 0, delta.length, result.getRecordChanges(), result.getFieldChanges());
}
else {
return recordv2_delta_impl_1.NO_CHANGE;
}
};
return RecordV2Impl;
}(bytes_impl_1.BytesImpl));
exports.RecordV2Impl = RecordV2Impl;
/**
* A unique binary delta type instance to calculate deltas of {@link RecordV2Impl}
*/
var BINARY_DELTA_TYPE = new binary_delta_type_impl_1.BinaryDeltaTypeImpl(RecordV2Impl, fromValue, function_1.identity);