acebase-core
Version:
Shared AceBase core components, no need to install manually
463 lines • 20.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getGlobalObject = exports.defer = exports.getChildValues = exports.getMutations = exports.compareValues = exports.ObjectDifferences = exports.valuesAreEqual = exports.cloneObject = exports.concatTypedArrays = exports.decodeString = exports.encodeString = exports.bytesToBigint = exports.bigintToBytes = exports.bytesToNumber = exports.numberToBytes = void 0;
const path_reference_1 = require("./path-reference");
const process_1 = require("./process");
const partial_array_1 = require("./partial-array");
function numberToBytes(number) {
const bytes = new Uint8Array(8);
const view = new DataView(bytes.buffer);
view.setFloat64(0, number);
return new Array(...bytes);
}
exports.numberToBytes = numberToBytes;
function bytesToNumber(bytes) {
const length = Array.isArray(bytes) ? bytes.length : bytes.byteLength;
if (length !== 8) {
throw new TypeError('must be 8 bytes');
}
const bin = new Uint8Array(bytes);
const view = new DataView(bin.buffer);
const nr = view.getFloat64(0);
return nr;
}
exports.bytesToNumber = bytesToNumber;
const hasBigIntSupport = (() => {
try {
return typeof BigInt(0) === 'bigint';
}
catch (err) {
return false;
}
})();
const noBigIntError = 'BigInt is not supported on this platform';
const bigIntFunctions = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
bigintToBytes(number) { throw new Error(noBigIntError); },
// eslint-disable-next-line @typescript-eslint/no-unused-vars
bytesToBigint(bytes) { throw new Error(noBigIntError); },
};
if (hasBigIntSupport) {
const big = {
zero: BigInt(0),
one: BigInt(1),
two: BigInt(2),
eight: BigInt(8),
ff: BigInt(0xff),
};
bigIntFunctions.bigintToBytes = function bigintToBytes(number) {
if (typeof number !== 'bigint') {
throw new Error('number must be a bigint');
}
const bytes = [];
const negative = number < big.zero;
do {
const byte = Number(number & big.ff); // NOTE: bits are inverted on negative numbers
bytes.push(byte);
number = number >> big.eight;
} while (number !== (negative ? -big.one : big.zero));
bytes.reverse(); // little-endian
if (negative ? bytes[0] < 128 : bytes[0] >= 128) {
bytes.unshift(negative ? 255 : 0); // extra sign byte needed
}
return bytes;
};
bigIntFunctions.bytesToBigint = function bytesToBigint(bytes) {
const negative = bytes[0] >= 128;
let number = big.zero;
for (let b of bytes) {
if (negative) {
b = ~b & 0xff;
} // Invert the bits
number = (number << big.eight) + BigInt(b);
}
if (negative) {
number = -(number + big.one);
}
return number;
};
}
exports.bigintToBytes = bigIntFunctions.bigintToBytes;
exports.bytesToBigint = bigIntFunctions.bytesToBigint;
/**
* Converts a string to a utf-8 encoded Uint8Array
*/
function encodeString(str) {
if (typeof TextEncoder !== 'undefined') {
// Modern browsers, Node.js v11.0.0+ (or v8.3.0+ with util.TextEncoder)
const encoder = new TextEncoder();
return encoder.encode(str);
}
else if (typeof Buffer === 'function') {
// Node.js
const buf = Buffer.from(str, 'utf-8');
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
}
else {
// Older browsers. Manually encode
const arr = [];
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code > 128) {
// Attempt simple UTF-8 conversion. See https://en.wikipedia.org/wiki/UTF-8
if ((code & 0xd800) === 0xd800) {
// code starts with 1101 10...: this is a 2-part utf-16 char code
const nextCode = str.charCodeAt(i + 1);
if ((nextCode & 0xdc00) !== 0xdc00) {
// next code must start with 1101 11...
throw new Error('follow-up utf-16 character does not start with 0xDC00');
}
i++;
const p1 = code & 0x3ff; // Only use last 10 bits
const p2 = nextCode & 0x3ff;
// Create code point from these 2: (see https://en.wikipedia.org/wiki/UTF-16)
code = 0x10000 | (p1 << 10) | p2;
}
if (code < 2048) {
// Use 2 bytes for 11 bit value, first byte starts with 110xxxxx (0xc0), 2nd byte with 10xxxxxx (0x80)
const b1 = 0xc0 | ((code >> 6) & 0x1f); // 0xc0 = 11000000, 0x1f = 11111
const b2 = 0x80 | (code & 0x3f); // 0x80 = 10000000, 0x3f = 111111
arr.push(b1, b2);
}
else if (code < 65536) {
// Use 3 bytes for 16-bit value, bits per byte: 4, 6, 6
const b1 = 0xe0 | ((code >> 12) & 0xf); // 0xe0 = 11100000, 0xf = 1111
const b2 = 0x80 | ((code >> 6) & 0x3f); // 0x80 = 10000000, 0x3f = 111111
const b3 = 0x80 | (code & 0x3f);
arr.push(b1, b2, b3);
}
else if (code < 2097152) {
// Use 4 bytes for 21-bit value, bits per byte: 3, 6, 6, 6
const b1 = 0xf0 | ((code >> 18) & 0x7); // 0xf0 = 11110000, 0x7 = 111
const b2 = 0x80 | ((code >> 12) & 0x3f); // 0x80 = 10000000, 0x3f = 111111
const b3 = 0x80 | ((code >> 6) & 0x3f); // 0x80 = 10000000, 0x3f = 111111
const b4 = 0x80 | (code & 0x3f);
arr.push(b1, b2, b3, b4);
}
else {
throw new Error(`Cannot convert character ${str.charAt(i)} (code ${code}) to utf-8`);
}
}
else {
arr.push(code < 128 ? code : 63); // 63 = ?
}
}
return new Uint8Array(arr);
}
}
exports.encodeString = encodeString;
/**
* Converts a utf-8 encoded buffer to string
*/
function decodeString(buffer) {
if (typeof TextDecoder !== 'undefined') {
// Modern browsers, Node.js v11.0.0+ (or v8.3.0+ with util.TextDecoder)
const decoder = new TextDecoder();
if (buffer instanceof Uint8Array) {
return decoder.decode(buffer);
}
const buf = Uint8Array.from(buffer);
return decoder.decode(buf);
}
else if (typeof Buffer === 'function') {
// Node.js (v10 and below)
if (buffer instanceof Array) {
buffer = Uint8Array.from(buffer); // convert to typed array
}
if (!(buffer instanceof Buffer) && 'buffer' in buffer && buffer.buffer instanceof ArrayBuffer) {
const typedArray = buffer;
buffer = Buffer.from(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); // Convert typed array to node.js Buffer
}
if (!(buffer instanceof Buffer)) {
throw new Error('Unsupported buffer argument');
}
return buffer.toString('utf-8');
}
else {
// Older browsers. Manually decode!
if (!(buffer instanceof Uint8Array) && 'buffer' in buffer && buffer['buffer'] instanceof ArrayBuffer) {
// Convert TypedArray to Uint8Array
const typedArray = buffer;
buffer = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);
}
if (buffer instanceof Buffer || buffer instanceof Array || buffer instanceof Uint8Array) {
let str = '';
for (let i = 0; i < buffer.length; i++) {
let code = buffer[i];
if (code > 128) {
// Decode Unicode character
if ((code & 0xf0) === 0xf0) {
// 4 byte char
const b1 = code, b2 = buffer[i + 1], b3 = buffer[i + 2], b4 = buffer[i + 3];
code = ((b1 & 0x7) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f);
i += 3;
}
else if ((code & 0xe0) === 0xe0) {
// 3 byte char
const b1 = code, b2 = buffer[i + 1], b3 = buffer[i + 2];
code = ((b1 & 0xf) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f);
i += 2;
}
else if ((code & 0xc0) === 0xc0) {
// 2 byte char
const b1 = code, b2 = buffer[i + 1];
code = ((b1 & 0x1f) << 6) | (b2 & 0x3f);
i++;
}
else {
throw new Error('invalid utf-8 data');
}
}
if (code >= 65536) {
// Split into 2-part utf-16 char codes
code ^= 0x10000;
const p1 = 0xd800 | (code >> 10);
const p2 = 0xdc00 | (code & 0x3ff);
str += String.fromCharCode(p1);
str += String.fromCharCode(p2);
}
else {
str += String.fromCharCode(code);
}
}
return str;
}
else {
throw new Error('Unsupported buffer argument');
}
}
}
exports.decodeString = decodeString;
function concatTypedArrays(a, b) {
const c = new a.constructor(a.length + b.length);
c.set(a);
c.set(b, a.length);
return c;
}
exports.concatTypedArrays = concatTypedArrays;
function cloneObject(original, stack) {
var _a;
if (((_a = original === null || original === void 0 ? void 0 : original.constructor) === null || _a === void 0 ? void 0 : _a.name) === 'DataSnapshot') {
throw new TypeError(`Object to clone is a DataSnapshot (path "${original.ref.path}")`);
}
const checkAndFixTypedArray = (obj) => {
if (obj !== null && typeof obj === 'object'
&& typeof obj.constructor === 'function' && typeof obj.constructor.name === 'string'
&& ['Buffer', 'Uint8Array', 'Int8Array', 'Uint16Array', 'Int16Array', 'Uint32Array', 'Int32Array', 'BigUint64Array', 'BigInt64Array'].includes(obj.constructor.name)) {
// FIX for typed array being converted to objects with numeric properties:
// Convert Buffer or TypedArray to ArrayBuffer
obj = obj.buffer.slice(obj.byteOffset, obj.byteOffset + obj.byteLength);
}
return obj;
};
original = checkAndFixTypedArray(original);
if (typeof original !== 'object' || original === null || original instanceof Date || original instanceof ArrayBuffer || original instanceof path_reference_1.PathReference || original instanceof RegExp) {
return original;
}
const cloneValue = (val) => {
if (stack.indexOf(val) >= 0) {
throw new ReferenceError('object contains a circular reference');
}
val = checkAndFixTypedArray(val);
if (val === null || val instanceof Date || val instanceof ArrayBuffer || val instanceof path_reference_1.PathReference || val instanceof RegExp) { // || val instanceof ID
return val;
}
else if (typeof val === 'object') {
stack.push(val);
val = cloneObject(val, stack);
stack.pop();
return val;
}
else {
return val; // Anything other can just be copied
}
};
if (typeof stack === 'undefined') {
stack = [original];
}
const clone = original instanceof Array ? [] : original instanceof partial_array_1.PartialArray ? new partial_array_1.PartialArray() : {};
Object.keys(original).forEach(key => {
const val = original[key];
if (typeof val === 'function') {
return; // skip functions
}
clone[key] = cloneValue(val);
});
return clone;
}
exports.cloneObject = cloneObject;
const isTypedArray = (val) => typeof val === 'object' && ['ArrayBuffer', 'Buffer', 'Uint8Array', 'Uint16Array', 'Uint32Array', 'Int8Array', 'Int16Array', 'Int32Array'].includes(val.constructor.name);
// CONSIDER: updating isTypedArray to: const isTypedArray = val => typeof val === 'object' && 'buffer' in val && 'byteOffset' in val && 'byteLength' in val;
function valuesAreEqual(val1, val2) {
if (val1 === val2) {
return true;
}
if (typeof val1 !== typeof val2) {
return false;
}
if (typeof val1 === 'object' || typeof val2 === 'object') {
if (val1 === null || val2 === null) {
return false;
}
if (val1 instanceof path_reference_1.PathReference || val2 instanceof path_reference_1.PathReference) {
return val1 instanceof path_reference_1.PathReference && val2 instanceof path_reference_1.PathReference && val1.path === val2.path;
}
if (val1 instanceof Date || val2 instanceof Date) {
return val1 instanceof Date && val2 instanceof Date && val1.getTime() === val2.getTime();
}
if (val1 instanceof Array || val2 instanceof Array) {
return val1 instanceof Array && val2 instanceof Array && val1.length === val2.length && val1.every((item, i) => valuesAreEqual(val1[i], val2[i]));
}
if (isTypedArray(val1) || isTypedArray(val2)) {
if (!isTypedArray(val1) || !isTypedArray(val2) || val1.byteLength === val2.byteLength) {
return false;
}
const typed1 = val1 instanceof ArrayBuffer ? new Uint8Array(val1) : new Uint8Array(val1.buffer, val1.byteOffset, val1.byteLength), typed2 = val2 instanceof ArrayBuffer ? new Uint8Array(val2) : new Uint8Array(val2.buffer, val2.byteOffset, val2.byteLength);
return typed1.every((val, i) => typed2[i] === val);
}
const keys1 = Object.keys(val1), keys2 = Object.keys(val2);
return keys1.length === keys2.length && keys1.every(key => keys2.includes(key)) && keys1.every(key => valuesAreEqual(val1[key], val2[key]));
}
return false;
}
exports.valuesAreEqual = valuesAreEqual;
class ObjectDifferences {
constructor(added, removed, changed) {
this.added = added;
this.removed = removed;
this.changed = changed;
}
forChild(key) {
if (this.added.includes(key)) {
return 'added';
}
if (this.removed.includes(key)) {
return 'removed';
}
const changed = this.changed.find(ch => ch.key === key);
return changed ? changed.change : 'identical';
}
}
exports.ObjectDifferences = ObjectDifferences;
function compareValues(oldVal, newVal, sortedResults = false) {
const voids = [undefined, null];
if (oldVal === newVal) {
return 'identical';
}
else if (voids.indexOf(oldVal) >= 0 && voids.indexOf(newVal) < 0) {
return 'added';
}
else if (voids.indexOf(oldVal) < 0 && voids.indexOf(newVal) >= 0) {
return 'removed';
}
else if (typeof oldVal !== typeof newVal) {
return 'changed';
}
else if (isTypedArray(oldVal) || isTypedArray(newVal)) {
// One or both values are typed arrays.
if (!isTypedArray(oldVal) || !isTypedArray(newVal)) {
return 'changed';
}
// Both are typed. Compare lengths and byte content of typed arrays
const typed1 = oldVal instanceof Uint8Array ? oldVal : oldVal instanceof ArrayBuffer ? new Uint8Array(oldVal) : new Uint8Array(oldVal.buffer, oldVal.byteOffset, oldVal.byteLength);
const typed2 = newVal instanceof Uint8Array ? newVal : newVal instanceof ArrayBuffer ? new Uint8Array(newVal) : new Uint8Array(newVal.buffer, newVal.byteOffset, newVal.byteLength);
return typed1.byteLength === typed2.byteLength && typed1.every((val, i) => typed2[i] === val) ? 'identical' : 'changed';
}
else if (oldVal instanceof Date || newVal instanceof Date) {
return oldVal instanceof Date && newVal instanceof Date && oldVal.getTime() === newVal.getTime() ? 'identical' : 'changed';
}
else if (oldVal instanceof path_reference_1.PathReference || newVal instanceof path_reference_1.PathReference) {
return oldVal instanceof path_reference_1.PathReference && newVal instanceof path_reference_1.PathReference && oldVal.path === newVal.path ? 'identical' : 'changed';
}
else if (typeof oldVal === 'object') {
// Do key-by-key comparison of objects
const isArray = oldVal instanceof Array;
const getKeys = (obj) => {
let keys = Object.keys(obj).filter(key => !voids.includes(obj[key]));
if (isArray) {
keys = keys.map((v) => parseInt(v));
}
return keys;
};
const oldKeys = getKeys(oldVal);
const newKeys = getKeys(newVal);
const removedKeys = oldKeys.filter(key => !newKeys.includes(key));
const addedKeys = newKeys.filter(key => !oldKeys.includes(key));
const changedKeys = newKeys.reduce((changed, key) => {
if (oldKeys.includes(key)) {
const val1 = oldVal[key];
const val2 = newVal[key];
const c = compareValues(val1, val2);
if (c !== 'identical') {
changed.push({ key, change: c });
}
}
return changed;
}, []);
if (addedKeys.length === 0 && removedKeys.length === 0 && changedKeys.length === 0) {
return 'identical';
}
else {
return new ObjectDifferences(addedKeys, removedKeys, sortedResults ? changedKeys.sort((a, b) => a.key < b.key ? -1 : 1) : changedKeys);
}
}
return 'changed';
}
exports.compareValues = compareValues;
function getMutations(oldVal, newVal, sortedResults = false) {
const process = (target, compareResult, prev, val) => {
switch (compareResult) {
case 'identical': return [];
case 'changed': return [{ target, prev, val }];
case 'added': return [{ target, prev: null, val }];
case 'removed': return [{ target, prev, val: null }];
default: {
let changes = [];
compareResult.added.forEach(key => changes.push({ target: target.concat(key), prev: null, val: val[key] }));
compareResult.removed.forEach(key => changes.push({ target: target.concat(key), prev: prev[key], val: null }));
compareResult.changed.forEach(item => {
const childChanges = process(target.concat(item.key), item.change, prev[item.key], val[item.key]);
changes = changes.concat(childChanges);
});
return changes;
}
}
};
const compareResult = compareValues(oldVal, newVal, sortedResults);
return process([], compareResult, oldVal, newVal);
}
exports.getMutations = getMutations;
function getChildValues(childKey, oldValue, newValue) {
oldValue = oldValue === null ? null : oldValue[childKey];
if (typeof oldValue === 'undefined') {
oldValue = null;
}
newValue = newValue === null ? null : newValue[childKey];
if (typeof newValue === 'undefined') {
newValue = null;
}
return { oldValue, newValue };
}
exports.getChildValues = getChildValues;
function defer(fn) {
process_1.default.nextTick(fn);
}
exports.defer = defer;
function getGlobalObject() {
var _a;
if (typeof globalThis !== 'undefined') {
return globalThis;
}
if (typeof global !== 'undefined') {
return global;
}
if (typeof window !== 'undefined') {
return window;
}
if (typeof self !== 'undefined') {
return self;
}
return (_a = (function () { return this; }())) !== null && _a !== void 0 ? _a : Function('return this')();
}
exports.getGlobalObject = getGlobalObject;
//# sourceMappingURL=utils.js.map