UNPKG

diffusion

Version:

Diffusion JavaScript client

593 lines (592 loc) 22.9 kB
"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);