UNPKG

tinybase

Version:

A reactive data store and sync engine.

195 lines (184 loc) 5.95 kB
const EMPTY_STRING = ''; const strSplit = (str, separator = EMPTY_STRING, limit) => str.split(separator, limit); const GLOBAL = globalThis; const math = Math; const mathMax = math.max; const mathFloor = math.floor; const isInstanceOf = (thing, cls) => thing instanceof cls; const isUndefined = (thing) => thing == void 0; const ifNotUndefined = (value, then, otherwise) => isUndefined(value) ? otherwise?.() : then(value); const arrayForEach = (array, cb) => array.forEach(cb); const arrayMap = (array, cb) => array.map(cb); const arrayReduce = (array, cb, initial) => array.reduce(cb, initial); const object = Object; const objEntries = object.entries; const mapNew = (entries) => new Map(entries); const mapGet = (map, key) => map?.get(key); const MASK6 = 63; const ENCODE = /* @__PURE__ */ strSplit( '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz', ); const DECODE = /* @__PURE__ */ mapNew( /* @__PURE__ */ arrayMap(ENCODE, (char, index) => [char, index]), ); const encode = (num) => ENCODE[num & MASK6]; const decode = (str, pos) => mapGet(DECODE, str[pos]) ?? 0; const getRandomValues = GLOBAL.crypto ? (array) => GLOBAL.crypto.getRandomValues(array) : /* istanbul ignore next */ (array) => arrayMap(array, () => mathFloor(math.random() * 256)); const getUniqueId = (length = 16) => arrayReduce( getRandomValues(new Uint8Array(length)), (uniqueId, number) => uniqueId + encode(number), '', ); const jsonString = JSON.stringify; const jsonStringWithMap = (obj) => jsonString(obj, (_key, value) => isInstanceOf(value, Map) ? object.fromEntries([...value]) : value, ); const textEncoder = /* @__PURE__ */ new GLOBAL.TextEncoder(); const getHash = (string) => { let hash = 2166136261; arrayForEach(textEncoder.encode(string), (char) => { hash ^= char; hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); }); return hash >>> 0; }; const addOrRemoveHash = (hash1, hash2) => (hash1 ^ hash2) >>> 0; const getValuesHash = (valueHashes) => arrayReduce( objEntries(valueHashes), (valuesHash, [valueId, valueHash]) => addOrRemoveHash( valuesHash, getValueInValuesHash(valueId, valueHash) ^ getValueInValuesHash(valueId, 0), // legacy v5; remove in v7 ), 0, // legacy v5; valuesHlc in v7? ); const getValueInValuesHash = (valueId, valueHash) => getHash(valueId + ':' + valueHash); const getValueHash = (value, valueHlc) => getHash(jsonStringWithMap(value ?? null) + ':' + valueHlc); const getCellHash = getValueHash; const getCellInRowHash = getValueInValuesHash; const getRowHash = getValuesHash; const getRowInTableHash = getValueInValuesHash; const getTableHash = (rowHashes) => // alias to getValuesHash in v7 arrayReduce( objEntries(rowHashes), (valuesHash, [rowId, rowHash]) => addOrRemoveHash(valuesHash, getValueInValuesHash(rowId, rowHash)), 0, // legacy v5; rowHlc in v7? ); const getTableInTablesHash = getValueInValuesHash; const getTablesHash = getTableHash; const SHIFT36 = 2 ** 36; const SHIFT30 = 2 ** 30; const SHIFT24 = 2 ** 24; const SHIFT18 = 2 ** 18; const SHIFT12 = 2 ** 12; const SHIFT6 = 2 ** 6; const getClientIdFromUniqueId = (uniqueId) => { const clientHash30 = getHash(uniqueId); return ( encode(clientHash30 / SHIFT24) + encode(clientHash30 / SHIFT18) + encode(clientHash30 / SHIFT12) + encode(clientHash30 / SHIFT6) + encode(clientHash30) ); }; const getHlcFunctions = (uniqueId, getNow = Date.now) => { let lastLogicalTime = 0; let lastCounter = -1; const thisClientId = ifNotUndefined(uniqueId, getClientIdFromUniqueId, () => getUniqueId(5), ); const getNextHlc = () => { seenHlc(); return encodeHlc(lastLogicalTime, ++lastCounter); }; const seenHlc = (hlc) => { const previousLogicalTime = lastLogicalTime; const [remoteLogicalTime, remoteCounter] = isUndefined(hlc) || hlc == '' ? [0, 0] : decodeHlc(hlc); lastLogicalTime = mathMax(previousLogicalTime, remoteLogicalTime, getNow()); lastCounter = lastLogicalTime == previousLogicalTime ? lastLogicalTime == remoteLogicalTime ? mathMax(lastCounter, remoteCounter) : lastCounter : lastLogicalTime == remoteLogicalTime ? remoteCounter : -1; }; const encodeHlc = (logicalTime42, counter24, clientId) => encode(logicalTime42 / SHIFT36) + encode(logicalTime42 / SHIFT30) + encode(logicalTime42 / SHIFT24) + encode(logicalTime42 / SHIFT18) + encode(logicalTime42 / SHIFT12) + encode(logicalTime42 / SHIFT6) + encode(logicalTime42) + encode(counter24 / SHIFT18) + encode(counter24 / SHIFT12) + encode(counter24 / SHIFT6) + encode(counter24) + (isUndefined(clientId) ? thisClientId : getClientIdFromUniqueId(clientId)); const decodeHlc = (hlc16) => [ decode(hlc16, 0) * SHIFT36 + decode(hlc16, 1) * SHIFT30 + decode(hlc16, 2) * SHIFT24 + decode(hlc16, 3) * SHIFT18 + decode(hlc16, 4) * SHIFT12 + decode(hlc16, 5) * SHIFT6 + decode(hlc16, 6), decode(hlc16, 7) * SHIFT18 + decode(hlc16, 8) * SHIFT12 + decode(hlc16, 9) * SHIFT6 + decode(hlc16, 10), hlc16.slice(11), ]; const getLastLogicalTime = () => lastLogicalTime; const getLastCounter = () => lastCounter; const getClientId = () => thisClientId; return [ getNextHlc, seenHlc, encodeHlc, decodeHlc, getLastLogicalTime, getLastCounter, getClientId, ]; }; const defaultSorter = (sortKey1, sortKey2) => (sortKey1 ?? 0) < (sortKey2 ?? 0) ? -1 : 1; export { addOrRemoveHash, defaultSorter, getCellHash, getCellInRowHash, getHash, getHlcFunctions, getRowHash, getRowInTableHash, getTableHash, getTableInTablesHash, getTablesHash, getUniqueId, getValueHash, getValueInValuesHash, getValuesHash, };