UNPKG

acebase-core

Version:

Shared AceBase core components, no need to install manually

463 lines 20.4 kB
"use strict"; 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