tinybase
Version:
A reactive data store and sync engine.
195 lines (184 loc) • 5.95 kB
JavaScript
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,
};