UNPKG

tinybase

Version:

A reactive data store and sync engine.

1,558 lines (1,537 loc) 120 kB
const getTypeOf = (thing) => typeof thing; const EMPTY_STRING = ''; const STRING = getTypeOf(EMPTY_STRING); const BOOLEAN = getTypeOf(true); const NUMBER = getTypeOf(0); const FUNCTION = getTypeOf(getTypeOf); const TYPE = 'type'; const DEFAULT = 'default'; const SUM = 'sum'; const AVG = 'avg'; const MIN = 'min'; const MAX = 'max'; const LISTENER = 'Listener'; const RESULT = 'Result'; const GET = 'get'; const SET = 'set'; const ADD = 'add'; const DEL = 'del'; const HAS = 'Has'; const IDS = 'Ids'; const TABLE = 'Table'; const TABLES = TABLE + 's'; const TABLE_IDS = TABLE + IDS; const ROW = 'Row'; const ROW_COUNT = ROW + 'Count'; const ROW_IDS = ROW + IDS; const SORTED_ROW_IDS = 'Sorted' + ROW + IDS; const CELL = 'Cell'; const CELL_IDS = CELL + IDS; const VALUE = 'Value'; const VALUES = VALUE + 's'; const VALUE_IDS = VALUE + IDS; const TRANSACTION = 'Transaction'; const id = (key) => EMPTY_STRING + key; const strStartsWith = (str, prefix) => str.startsWith(prefix); const strEndsWith = (str, suffix) => str.endsWith(suffix); const strSplit = (str, separator = EMPTY_STRING, limit) => str.split(separator, limit); const GLOBAL = globalThis; const math = Math; const mathMax = math.max; const mathMin = math.min; const mathFloor = math.floor; const isFiniteNumber = isFinite; const isInstanceOf = (thing, cls) => thing instanceof cls; const isUndefined = (thing) => thing == void 0; const ifNotUndefined = (value, then, otherwise) => isUndefined(value) ? otherwise?.() : then(value); const isTypeStringOrBoolean = (type) => type == STRING || type == BOOLEAN; const isString = (thing) => getTypeOf(thing) == STRING; const isFunction = (thing) => getTypeOf(thing) == FUNCTION; const isArray = (thing) => Array.isArray(thing); const slice = (arrayOrString, start, end) => arrayOrString.slice(start, end); const size = (arrayOrString) => arrayOrString.length; const test = (regex, subject) => regex.test(subject); const getUndefined = () => void 0; const arrayHas = (array, value) => array.includes(value); const arrayEvery = (array, cb) => array.every(cb); const arrayIsEqual = (array1, array2) => size(array1) === size(array2) && arrayEvery(array1, (value1, index) => array2[index] === value1); const arrayIsSorted = (array, sorter) => arrayEvery( array, (value, index) => index == 0 || sorter(array[index - 1], value) <= 0, ); const arraySort = (array, sorter) => array.sort(sorter); const arrayForEach = (array, cb) => array.forEach(cb); const arrayMap = (array, cb) => array.map(cb); const arraySum = (array) => arrayReduce(array, (i, j) => i + j, 0); const arrayIsEmpty = (array) => size(array) == 0; const arrayReduce = (array, cb, initial) => array.reduce(cb, initial); const arrayClear = (array, to) => array.splice(0, to); const arrayPush = (array, ...values) => array.push(...values); const arrayPop = (array) => array.pop(); const arrayUnshift = (array, ...values) => array.unshift(...values); const arrayShift = (array) => array.shift(); const getCellOrValueType = (cellOrValue) => { const type = getTypeOf(cellOrValue); return isTypeStringOrBoolean(type) || (type == NUMBER && isFiniteNumber(cellOrValue)) ? type : void 0; }; const isCellOrValueOrNullOrUndefined = (cellOrValue) => isUndefined(cellOrValue) || !isUndefined(getCellOrValueType(cellOrValue)); const setOrDelCell = (store, tableId, rowId, cellId, cell) => isUndefined(cell) ? store.delCell(tableId, rowId, cellId, true) : store.setCell(tableId, rowId, cellId, cell); const setOrDelValue = (store, valueId, value) => isUndefined(value) ? store.delValue(valueId) : store.setValue(valueId, value); const collSizeN = (collSizer) => (coll) => arrayReduce(collValues(coll), (total, coll2) => total + collSizer(coll2), 0); const collSize = (coll) => coll?.size ?? 0; const collSize2 = collSizeN(collSize); const collSize3 = collSizeN(collSize2); const collSize4 = collSizeN(collSize3); const collHas = (coll, keyOrValue) => coll?.has(keyOrValue) ?? false; const collIsEmpty = (coll) => isUndefined(coll) || collSize(coll) == 0; const collValues = (coll) => [...(coll?.values() ?? [])]; const collClear = (coll) => coll.clear(); const collForEach = (coll, cb) => coll?.forEach(cb); const collDel = (coll, keyOrValue) => coll?.delete(keyOrValue); const object = Object; const getPrototypeOf = (obj) => object.getPrototypeOf(obj); const objEntries = object.entries; const objFrozen = object.isFrozen; const isObject = (obj) => !isUndefined(obj) && ifNotUndefined( getPrototypeOf(obj), (objPrototype) => objPrototype == object.prototype || isUndefined(getPrototypeOf(objPrototype)), /* istanbul ignore next */ () => true, ); const objIds = object.keys; const objFreeze = object.freeze; const objNew = (entries = []) => object.fromEntries(entries); const objHas = (obj, id) => id in obj; const objDel = (obj, id) => { delete obj[id]; return obj; }; const objForEach = (obj, cb) => arrayForEach(objEntries(obj), ([id, value]) => cb(value, id)); const objToArray = (obj, cb) => arrayMap(objEntries(obj), ([id, value]) => cb(value, id)); const objMap = (obj, cb) => objNew(objToArray(obj, (value, id) => [id, cb(value, id)])); const objSize = (obj) => size(objIds(obj)); const objIsEmpty = (obj) => isObject(obj) && objSize(obj) == 0; const objEnsure = (obj, id, getDefaultValue) => { if (!objHas(obj, id)) { obj[id] = getDefaultValue(); } return obj[id]; }; const objValidate = (obj, validateChild, onInvalidObj, emptyIsValid = 0) => { if ( isUndefined(obj) || !isObject(obj) || (!emptyIsValid && objIsEmpty(obj)) || objFrozen(obj) ) { onInvalidObj?.(); return false; } objForEach(obj, (child, id) => { if (!validateChild(child, id)) { objDel(obj, id); } }); return emptyIsValid ? true : !objIsEmpty(obj); }; const mapNew = (entries) => new Map(entries); const mapKeys = (map) => [...(map?.keys() ?? [])]; const mapGet = (map, key) => map?.get(key); const mapForEach = (map, cb) => collForEach(map, (value, key) => cb(key, value)); const mapMap = (coll, cb) => arrayMap([...(coll?.entries() ?? [])], ([key, value]) => cb(value, key)); const mapSet = (map, key, value) => isUndefined(value) ? (collDel(map, key), map) : map?.set(key, value); const mapEnsure = (map, key, getDefaultValue, hadExistingValue) => { if (!collHas(map, key)) { mapSet(map, key, getDefaultValue()); } else { hadExistingValue?.(mapGet(map, key)); } return mapGet(map, key); }; const mapMatch = (map, obj, set, del = mapSet) => { objMap(obj, (value, id) => set(map, id, value)); mapForEach(map, (id) => (objHas(obj, id) ? 0 : del(map, id))); return map; }; const mapToObj = (map, valueMapper, excludeMapValue, excludeObjValue) => { const obj = {}; collForEach(map, (mapValue, id) => { if (!excludeMapValue?.(mapValue, id)) { const objValue = valueMapper ? valueMapper(mapValue, id) : mapValue; if (!excludeObjValue?.(objValue)) { obj[id] = objValue; } } }); return obj; }; const mapToObj2 = (map, valueMapper, excludeMapValue) => mapToObj( map, (childMap) => mapToObj(childMap, valueMapper, excludeMapValue), collIsEmpty, objIsEmpty, ); const mapToObj3 = (map, valueMapper, excludeMapValue) => mapToObj( map, (childMap) => mapToObj2(childMap, valueMapper, excludeMapValue), collIsEmpty, objIsEmpty, ); const mapClone = (map, mapValue) => { const map2 = mapNew(); collForEach(map, (value, key) => map2.set(key, mapValue?.(value) ?? value)); return map2; }; const mapClone2 = (map) => mapClone(map, mapClone); const mapClone3 = (map) => mapClone(map, mapClone2); const visitTree = (node, path, ensureLeaf, pruneLeaf, p = 0) => ifNotUndefined( (ensureLeaf ? mapEnsure : mapGet)( node, path[p], p > size(path) - 2 ? ensureLeaf : mapNew, ), (nodeOrLeaf) => { if (p > size(path) - 2) { if (pruneLeaf?.(nodeOrLeaf)) { mapSet(node, path[p]); } return nodeOrLeaf; } const leaf = visitTree(nodeOrLeaf, path, ensureLeaf, pruneLeaf, p + 1); if (collIsEmpty(nodeOrLeaf)) { mapSet(node, path[p]); } return leaf; }, ); const setNew = (entryOrEntries) => new Set( isArray(entryOrEntries) || isUndefined(entryOrEntries) ? entryOrEntries : [entryOrEntries], ); const setAdd = (set, value) => set?.add(value); const getDefinableFunctions = ( store, getDefaultThing, validateRowValue, addListener, callListeners, ) => { const hasRow = store.hasRow; const tableIds = mapNew(); const things = mapNew(); const thingIdListeners = mapNew(); const allRowValues = mapNew(); const allSortKeys = mapNew(); const storeListenerIds = mapNew(); const getStore = () => store; const getThingIds = () => mapKeys(tableIds); const forEachThing = (cb) => mapForEach(things, cb); const hasThing = (id) => collHas(things, id); const getTableId = (id) => mapGet(tableIds, id); const getThing = (id) => mapGet(things, id); const setThing = (id, thing) => mapSet(things, id, thing); const addStoreListeners = (id, andCall, ...listenerIds) => { const set = mapEnsure(storeListenerIds, id, setNew); arrayForEach( listenerIds, (listenerId) => setAdd(set, listenerId) && andCall && store.callListener(listenerId), ); return listenerIds; }; const delStoreListeners = (id, ...listenerIds) => ifNotUndefined(mapGet(storeListenerIds, id), (allListenerIds) => { arrayForEach( arrayIsEmpty(listenerIds) ? collValues(allListenerIds) : listenerIds, (listenerId) => { store.delListener(listenerId); collDel(allListenerIds, listenerId); }, ); if (collIsEmpty(allListenerIds)) { mapSet(storeListenerIds, id); } }); const setDefinition = (id, tableId) => { mapSet(tableIds, id, tableId); if (!collHas(things, id)) { mapSet(things, id, getDefaultThing()); mapSet(allRowValues, id, mapNew()); mapSet(allSortKeys, id, mapNew()); callListeners(thingIdListeners); } }; const setDefinitionAndListen = ( id, tableId, onChanged, getRowValue, getSortKey, ) => { setDefinition(id, tableId); const changedRowValues = mapNew(); const changedSortKeys = mapNew(); const rowValues = mapGet(allRowValues, id); const sortKeys = mapGet(allSortKeys, id); const processRow = (rowId) => { const getCell = (cellId) => store.getCell(tableId, rowId, cellId); const oldRowValue = mapGet(rowValues, rowId); const newRowValue = hasRow(tableId, rowId) ? validateRowValue(getRowValue(getCell, rowId)) : void 0; if ( !( oldRowValue === newRowValue || (isArray(oldRowValue) && isArray(newRowValue) && arrayIsEqual(oldRowValue, newRowValue)) ) ) { mapSet(changedRowValues, rowId, [oldRowValue, newRowValue]); } if (!isUndefined(getSortKey)) { const oldSortKey = mapGet(sortKeys, rowId); const newSortKey = hasRow(tableId, rowId) ? getSortKey(getCell, rowId) : void 0; if (oldSortKey != newSortKey) { mapSet(changedSortKeys, rowId, newSortKey); } } }; const processTable = (force) => { onChanged( () => { collForEach(changedRowValues, ([, newRowValue], rowId) => mapSet(rowValues, rowId, newRowValue), ); collForEach(changedSortKeys, (newSortKey, rowId) => mapSet(sortKeys, rowId, newSortKey), ); }, changedRowValues, changedSortKeys, rowValues, sortKeys, force, ); collClear(changedRowValues); collClear(changedSortKeys); }; mapForEach(rowValues, processRow); if (store.hasTable(tableId)) { arrayForEach(store.getRowIds(tableId), (rowId) => { if (!collHas(rowValues, rowId)) { processRow(rowId); } }); } processTable(true); delStoreListeners(id); addStoreListeners( id, 0, store.addRowListener(tableId, null, (_store, _tableId, rowId) => processRow(rowId), ), store.addTableListener(tableId, () => processTable()), ); }; const delDefinition = (id) => { mapSet(tableIds, id); mapSet(things, id); mapSet(allRowValues, id); mapSet(allSortKeys, id); delStoreListeners(id); callListeners(thingIdListeners); }; const addThingIdsListener = (listener) => addListener(listener, thingIdListeners); const destroy = () => mapForEach(storeListenerIds, delDefinition); return [ getStore, getThingIds, forEachThing, hasThing, getTableId, getThing, setThing, setDefinition, setDefinitionAndListen, delDefinition, addThingIdsListener, destroy, addStoreListeners, delStoreListeners, ]; }; const getRowCellFunction = (getRowCell, defaultCellValue) => isString(getRowCell) ? (getCell) => getCell(getRowCell) : (getRowCell ?? (() => defaultCellValue ?? EMPTY_STRING)); const getCreateFunction = (getFunction, initFunction) => { const thingsByStore = /* @__PURE__ */ new WeakMap(); return (store) => { if (!thingsByStore.has(store)) { thingsByStore.set(store, getFunction(store)); } const thing = thingsByStore.get(store); initFunction?.(thing); return thing; }; }; const INTEGER = /^\d+$/; const getPoolFunctions = () => { const pool = []; let nextId = 0; return [ (reuse) => (reuse ? arrayShift(pool) : null) ?? EMPTY_STRING + nextId++, (id) => { if (test(INTEGER, id) && size(pool) < 1e3) { arrayPush(pool, id); } }, ]; }; const getWildcardedLeaves = (deepIdSet, path = [EMPTY_STRING]) => { const leaves = []; const deep = (node, p) => p == size(path) ? arrayPush(leaves, node) : path[p] === null ? collForEach(node, (node2) => deep(node2, p + 1)) : arrayForEach([path[p], null], (id) => deep(mapGet(node, id), p + 1)); deep(deepIdSet, 0); return leaves; }; const getListenerFunctions = (getThing) => { let thing; const [getId, releaseId] = getPoolFunctions(); const allListeners = mapNew(); const addListener = ( listener, idSetNode, path, pathGetters = [], extraArgsGetter = () => [], ) => { thing ??= getThing(); const id = getId(1); mapSet(allListeners, id, [ listener, idSetNode, path, pathGetters, extraArgsGetter, ]); setAdd(visitTree(idSetNode, path ?? [EMPTY_STRING], setNew), id); return id; }; const callListeners = (idSetNode, ids, ...extraArgs) => arrayForEach(getWildcardedLeaves(idSetNode, ids), (set) => collForEach(set, (id) => mapGet(allListeners, id)[0](thing, ...(ids ?? []), ...extraArgs), ), ); const delListener = (id) => ifNotUndefined(mapGet(allListeners, id), ([, idSetNode, idOrNulls]) => { visitTree(idSetNode, idOrNulls ?? [EMPTY_STRING], void 0, (idSet) => { collDel(idSet, id); return collIsEmpty(idSet) ? 1 : 0; }); mapSet(allListeners, id); releaseId(id); return idOrNulls; }); const callListener = (id) => ifNotUndefined( mapGet(allListeners, id), ([listener, , path = [], pathGetters, extraArgsGetter]) => { const callWithIds = (...ids) => { const index = size(ids); if (index == size(path)) { listener(thing, ...ids, ...extraArgsGetter(ids)); } else if (isUndefined(path[index])) { arrayForEach(pathGetters[index]?.(...ids) ?? [], (id2) => callWithIds(...ids, id2), ); } else { callWithIds(...ids, path[index]); } }; callWithIds(); }, ); return [addListener, callListeners, delListener, callListener]; }; const createCheckpoints = getCreateFunction( (store) => { let backwardIdsSize = 100; let currentId; let cellsDelta = mapNew(); let valuesDelta = mapNew(); let listening = 1; let nextCheckpointId; let checkpointsChanged; const checkpointIdsListeners = mapNew(); const checkpointListeners = mapNew(); const [addListener, callListeners, delListenerImpl] = getListenerFunctions( () => checkpoints, ); const deltas = mapNew(); const labels = mapNew(); const backwardIds = []; const forwardIds = []; const updateStore = (oldOrNew, checkpointId) => { listening = 0; store.transaction(() => { const [cellsDelta2, valuesDelta2] = mapGet(deltas, checkpointId); collForEach(cellsDelta2, (table, tableId) => collForEach(table, (row, rowId) => collForEach(row, (oldNew, cellId) => setOrDelCell(store, tableId, rowId, cellId, oldNew[oldOrNew]), ), ), ); collForEach(valuesDelta2, (oldNew, valueId) => setOrDelValue(store, valueId, oldNew[oldOrNew]), ); }); listening = 1; }; const clearCheckpointId = (checkpointId) => { mapSet(deltas, checkpointId); mapSet(labels, checkpointId); callListeners(checkpointListeners, [checkpointId]); }; const clearCheckpointIds = (checkpointIds, to) => arrayForEach( arrayClear(checkpointIds, to ?? size(checkpointIds)), clearCheckpointId, ); const trimBackwardsIds = () => clearCheckpointIds(backwardIds, size(backwardIds) - backwardIdsSize); const storeChanged = () => ifNotUndefined(currentId, () => { arrayPush(backwardIds, currentId); trimBackwardsIds(); clearCheckpointIds(forwardIds); currentId = void 0; checkpointsChanged = 1; }); const storeUnchanged = () => { currentId = arrayPop(backwardIds); checkpointsChanged = 1; }; let cellListenerId; let valueListenerId; const addCheckpointImpl = (label = EMPTY_STRING) => { if (isUndefined(currentId)) { currentId = EMPTY_STRING + nextCheckpointId++; mapSet(deltas, currentId, [cellsDelta, valuesDelta]); setCheckpoint(currentId, label); cellsDelta = mapNew(); valuesDelta = mapNew(); checkpointsChanged = 1; } return currentId; }; const goBackwardImpl = () => { if (!arrayIsEmpty(backwardIds)) { arrayUnshift(forwardIds, addCheckpointImpl()); updateStore(0, currentId); currentId = arrayPop(backwardIds); checkpointsChanged = 1; } }; const goForwardImpl = () => { if (!arrayIsEmpty(forwardIds)) { arrayPush(backwardIds, currentId); currentId = arrayShift(forwardIds); updateStore(1, currentId); checkpointsChanged = 1; } }; const callListenersIfChanged = () => { if (checkpointsChanged) { callListeners(checkpointIdsListeners); checkpointsChanged = 0; } }; const setSize = (size2) => { backwardIdsSize = size2; trimBackwardsIds(); return checkpoints; }; const addCheckpoint = (label) => { const id = addCheckpointImpl(label); callListenersIfChanged(); return id; }; const setCheckpoint = (checkpointId, label) => { if ( hasCheckpoint(checkpointId) && mapGet(labels, checkpointId) !== label ) { mapSet(labels, checkpointId, label); callListeners(checkpointListeners, [checkpointId]); } return checkpoints; }; const getStore = () => store; const getCheckpointIds = () => [ [...backwardIds], currentId, [...forwardIds], ]; const forEachCheckpoint = (checkpointCallback) => mapForEach(labels, checkpointCallback); const hasCheckpoint = (checkpointId) => collHas(deltas, checkpointId); const getCheckpoint = (checkpointId) => mapGet(labels, checkpointId); const goBackward = () => { goBackwardImpl(); callListenersIfChanged(); return checkpoints; }; const goForward = () => { goForwardImpl(); callListenersIfChanged(); return checkpoints; }; const goTo = (checkpointId) => { const action = arrayHas(backwardIds, checkpointId) ? goBackwardImpl : arrayHas(forwardIds, checkpointId) ? goForwardImpl : null; while (!isUndefined(action) && checkpointId != currentId) { action(); } callListenersIfChanged(); return checkpoints; }; const addCheckpointIdsListener = (listener) => addListener(listener, checkpointIdsListeners); const addCheckpointListener = (checkpointId, listener) => addListener(listener, checkpointListeners, [checkpointId]); const delListener = (listenerId) => { delListenerImpl(listenerId); return checkpoints; }; const clear = () => { clearCheckpointIds(backwardIds); clearCheckpointIds(forwardIds); if (!isUndefined(currentId)) { clearCheckpointId(currentId); } currentId = void 0; nextCheckpointId = 0; addCheckpoint(); return checkpoints; }; const clearForward = () => { if (!arrayIsEmpty(forwardIds)) { clearCheckpointIds(forwardIds); callListeners(checkpointIdsListeners); } return checkpoints; }; const destroy = () => { store.delListener(cellListenerId); store.delListener(valueListenerId); }; const getListenerStats = () => ({ checkpointIds: collSize2(checkpointIdsListeners), checkpoint: collSize2(checkpointListeners), }); const _registerListeners = () => { cellListenerId = store.addCellListener( null, null, null, (_store, tableId, rowId, cellId, newCell, oldCell) => { if (listening) { storeChanged(); const table = mapEnsure(cellsDelta, tableId, mapNew); const row = mapEnsure(table, rowId, mapNew); const oldNew = mapEnsure(row, cellId, () => [oldCell, void 0]); oldNew[1] = newCell; if ( oldNew[0] === newCell && collIsEmpty(mapSet(row, cellId)) && collIsEmpty(mapSet(table, rowId)) && collIsEmpty(mapSet(cellsDelta, tableId)) ) { storeUnchanged(); } callListenersIfChanged(); } }, ); valueListenerId = store.addValueListener( null, (_store, valueId, newValue, oldValue) => { if (listening) { storeChanged(); const oldNew = mapEnsure(valuesDelta, valueId, () => [ oldValue, void 0, ]); oldNew[1] = newValue; if ( oldNew[0] === newValue && collIsEmpty(mapSet(valuesDelta, valueId)) ) { storeUnchanged(); } callListenersIfChanged(); } }, ); }; const checkpoints = { setSize, addCheckpoint, setCheckpoint, getStore, getCheckpointIds, forEachCheckpoint, hasCheckpoint, getCheckpoint, goBackward, goForward, goTo, addCheckpointIdsListener, addCheckpointListener, delListener, clear, clearForward, destroy, getListenerStats, _registerListeners, }; return objFreeze(checkpoints.clear()); }, (checkpoints) => checkpoints._registerListeners(), ); 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 defaultSorter = (sortKey1, sortKey2) => (sortKey1 ?? 0) < (sortKey2 ?? 0) ? -1 : 1; const getUniqueId = (length = 16) => arrayReduce( getRandomValues(new Uint8Array(length)), (uniqueId, number) => uniqueId + encode(number), '', ); const createIndexes = getCreateFunction((store) => { const sliceIdsListeners = mapNew(); const sliceRowIdsListeners = mapNew(); const [addListener, callListeners, delListenerImpl] = getListenerFunctions( () => indexes, ); const [ getStore, getIndexIds, forEachIndexImpl, hasIndex, getTableId, getIndex, setIndex, , setDefinitionAndListen, delDefinition, addIndexIdsListener, destroy, ] = getDefinableFunctions( store, mapNew, (value) => isUndefined(value) ? EMPTY_STRING : isArray(value) ? arrayMap(value, id) : id(value), addListener, callListeners, ); const hasSlice = (indexId, sliceId) => collHas(getIndex(indexId), sliceId); const setIndexDefinition = ( indexId, tableId, getSliceIdOrIds, getSortKey, sliceIdSorter, rowIdSorter = defaultSorter, ) => { const sliceIdArraySorter = isUndefined(sliceIdSorter) ? void 0 : ([id1], [id2]) => sliceIdSorter(id1, id2); setDefinitionAndListen( indexId, tableId, ( change, changedSliceIds, changedSortKeys, sliceIdOrIdsByRowId, sortKeys, force, ) => { let sliceIdsChanged = 0; const changedSlices = setNew(); const unsortedSlices = setNew(); const index = getIndex(indexId); collForEach( changedSliceIds, ([oldSliceIdOrIds, newSliceIdOrIds], rowId) => { const oldSliceIds = setNew(oldSliceIdOrIds); const newSliceIds = setNew(newSliceIdOrIds); collForEach(oldSliceIds, (oldSliceId) => collDel(newSliceIds, oldSliceId) ? collDel(oldSliceIds, oldSliceId) : 0, ); collForEach(oldSliceIds, (oldSliceId) => { setAdd(changedSlices, oldSliceId); ifNotUndefined(mapGet(index, oldSliceId), (oldSlice) => { collDel(oldSlice, rowId); if (collIsEmpty(oldSlice)) { mapSet(index, oldSliceId); sliceIdsChanged = 1; } }); }); collForEach(newSliceIds, (newSliceId) => { setAdd(changedSlices, newSliceId); if (!collHas(index, newSliceId)) { mapSet(index, newSliceId, setNew()); sliceIdsChanged = 1; } setAdd(mapGet(index, newSliceId), rowId); if (!isUndefined(getSortKey)) { setAdd(unsortedSlices, newSliceId); } }); }, ); change(); if (!collIsEmpty(sortKeys)) { if (force) { mapForEach(index, (sliceId) => setAdd(unsortedSlices, sliceId)); } else { mapForEach(changedSortKeys, (rowId) => ifNotUndefined( mapGet(sliceIdOrIdsByRowId, rowId), (sliceIdOrIds) => arrayForEach( isArray(sliceIdOrIds) ? sliceIdOrIds : [sliceIdOrIds], (sliceId) => setAdd(unsortedSlices, sliceId), ), ), ); } collForEach(unsortedSlices, (sliceId) => { const rowIdArraySorter = (rowId1, rowId2) => rowIdSorter( mapGet(sortKeys, rowId1), mapGet(sortKeys, rowId2), sliceId, ); const sliceArray = [...mapGet(index, sliceId)]; if (!arrayIsSorted(sliceArray, rowIdArraySorter)) { mapSet( index, sliceId, setNew(arraySort(sliceArray, rowIdArraySorter)), ); setAdd(changedSlices, sliceId); } }); } if (sliceIdsChanged || force) { if (!isUndefined(sliceIdArraySorter)) { const indexArray = [...index]; if (!arrayIsSorted(indexArray, sliceIdArraySorter)) { setIndex( indexId, mapNew(arraySort(indexArray, sliceIdArraySorter)), ); sliceIdsChanged = 1; } } } if (sliceIdsChanged) { callListeners(sliceIdsListeners, [indexId]); } collForEach(changedSlices, (sliceId) => callListeners(sliceRowIdsListeners, [indexId, sliceId]), ); }, getRowCellFunction(getSliceIdOrIds), ifNotUndefined(getSortKey, getRowCellFunction), ); return indexes; }; const forEachIndex = (indexCallback) => forEachIndexImpl((indexId, slices) => indexCallback(indexId, (sliceCallback) => forEachSliceImpl(indexId, sliceCallback, slices), ), ); const forEachSlice = (indexId, sliceCallback) => forEachSliceImpl(indexId, sliceCallback, getIndex(indexId)); const forEachSliceImpl = (indexId, sliceCallback, slices) => { const tableId = getTableId(indexId); collForEach(slices, (rowIds, sliceId) => sliceCallback(sliceId, (rowCallback) => collForEach(rowIds, (rowId) => rowCallback(rowId, (cellCallback) => store.forEachCell(tableId, rowId, cellCallback), ), ), ), ); }; const delIndexDefinition = (indexId) => { delDefinition(indexId); return indexes; }; const getSliceIds = (indexId) => mapKeys(getIndex(indexId)); const getSliceRowIds = (indexId, sliceId) => collValues(mapGet(getIndex(indexId), sliceId)); const addSliceIdsListener = (indexId, listener) => addListener(listener, sliceIdsListeners, [indexId]); const addSliceRowIdsListener = (indexId, sliceId, listener) => addListener(listener, sliceRowIdsListeners, [indexId, sliceId]); const delListener = (listenerId) => { delListenerImpl(listenerId); return indexes; }; const getListenerStats = () => ({ sliceIds: collSize2(sliceIdsListeners), sliceRowIds: collSize3(sliceRowIdsListeners), }); const indexes = { setIndexDefinition, delIndexDefinition, getStore, getIndexIds, forEachIndex, forEachSlice, hasIndex, hasSlice, getTableId, getSliceIds, getSliceRowIds, addIndexIdsListener, addSliceIdsListener, addSliceRowIdsListener, delListener, destroy, getListenerStats, }; return objFreeze(indexes); }); const textEncoder = /* @__PURE__ */ new GLOBAL.TextEncoder(); const getHash = (value) => { let hash = 2166136261; arrayForEach(textEncoder.encode(value), (char) => { hash ^= char; hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); }); return hash >>> 0; }; 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 encodeTimeAndCounter = (logicalTime42, counter24) => 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); const decodeTimeAndCounter = (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), ]; const getHlcFunctions = (uniqueId) => { let logicalTime = 0; let lastCounter = -1; const clientPart = ifNotUndefined( uniqueId, (uniqueId2) => { const clientHash30 = getHash(uniqueId2); return ( encode(clientHash30 / SHIFT24) + encode(clientHash30 / SHIFT18) + encode(clientHash30 / SHIFT12) + encode(clientHash30 / SHIFT6) + encode(clientHash30) ); }, () => getUniqueId(5), ); const getHlc = () => { seenHlc(); return encodeTimeAndCounter(logicalTime, ++lastCounter) + clientPart; }; const seenHlc = (hlc) => { const previousLogicalTime = logicalTime; const [remoteLogicalTime, remoteCounter] = isUndefined(hlc) || hlc == '' ? [0, 0] : decodeTimeAndCounter(hlc); logicalTime = mathMax( previousLogicalTime, remoteLogicalTime, GLOBAL.HLC_TIME ?? Date.now(), ); lastCounter = logicalTime == previousLogicalTime ? logicalTime == remoteLogicalTime ? mathMax(lastCounter, remoteCounter) : lastCounter : logicalTime == remoteLogicalTime ? remoteCounter : -1; }; return [getHlc, seenHlc]; }; const jsonString = JSON.stringify; const jsonParse = JSON.parse; const jsonStringWithMap = (obj) => jsonString(obj, (_key, value) => isInstanceOf(value, Map) ? object.fromEntries([...value]) : value, ); const stampClone = ([value, time]) => stampNew(value, time); const stampCloneWithHash = ([value, time, hash]) => [value, time, hash]; const stampNew = (value, time) => (time ? [value, time] : [value]); const stampNewWithHash = (value, time, hash) => [value, time, hash]; const getStampHash = (stamp) => stamp[2]; const hashIdAndHash = (id, hash) => getHash(id + ':' + hash); const replaceTimeHash = (oldTime, newTime) => newTime > oldTime ? (oldTime ? getHash(oldTime) : 0) ^ getHash(newTime) : 0; const getLatestTime = (time1, time2) => /* istanbul ignore next */ ((time1 ?? '') > (time2 ?? '') ? time1 : time2) ?? ''; const stampUpdate = (stamp, time, hash) => { if (time > stamp[1]) { stamp[1] = time; } stamp[2] = hash >>> 0; }; const stampNewObj = (time = EMPTY_STRING) => stampNew(objNew(), time); const stampNewMap = (time = EMPTY_STRING) => [mapNew(), time, 0]; const stampMapToObjWithHash = ( [map, time, hash], mapper = stampCloneWithHash, ) => [mapToObj(map, mapper), time, hash]; const stampMapToObjWithoutHash = ([map, time], mapper = stampClone) => stampNew(mapToObj(map, mapper), time); const stampValidate = (stamp, validateThing) => isArray(stamp) && size(stamp) == 3 && isString(stamp[1]) && getTypeOf(stamp[2]) == NUMBER && isFiniteNumber(stamp[2]) && validateThing(stamp[0]); const pairNew = (value) => [value, value]; const pairCollSize2 = (pair, func = collSize2) => func(pair[0]) + func(pair[1]); const pairNewMap = () => [mapNew(), mapNew()]; const pairClone = (array) => [...array]; const pairIsEqual = ([entry1, entry2]) => entry1 === entry2; const idsChanged = (changedIds, id2, addedOrRemoved) => mapSet( changedIds, id2, mapGet(changedIds, id2) == -addedOrRemoved ? void 0 : addedOrRemoved, ); const createStore = () => { let hasTablesSchema; let hasValuesSchema; let hadTables = false; let hadValues = false; let transactions = 0; let internalListeners = []; const changedTableIds = mapNew(); const changedTableCellIds = mapNew(); const changedRowCount = mapNew(); const changedRowIds = mapNew(); const changedCellIds = mapNew(); const changedCells = mapNew(); const changedValueIds = mapNew(); const changedValues = mapNew(); const invalidCells = mapNew(); const invalidValues = mapNew(); const tablesSchemaMap = mapNew(); const tablesSchemaRowCache = mapNew(); const valuesSchemaMap = mapNew(); const valuesDefaulted = mapNew(); const valuesNonDefaulted = setNew(); const tablePoolFunctions = mapNew(); const tableCellIds = mapNew(); const tablesMap = mapNew(); const valuesMap = mapNew(); const hasTablesListeners = pairNewMap(); const tablesListeners = pairNewMap(); const tableIdsListeners = pairNewMap(); const hasTableListeners = pairNewMap(); const tableListeners = pairNewMap(); const tableCellIdsListeners = pairNewMap(); const hasTableCellListeners = pairNewMap(); const rowCountListeners = pairNewMap(); const rowIdsListeners = pairNewMap(); const sortedRowIdsListeners = pairNewMap(); const hasRowListeners = pairNewMap(); const rowListeners = pairNewMap(); const cellIdsListeners = pairNewMap(); const hasCellListeners = pairNewMap(); const cellListeners = pairNewMap(); const invalidCellListeners = pairNewMap(); const invalidValueListeners = pairNewMap(); const hasValuesListeners = pairNewMap(); const valuesListeners = pairNewMap(); const valueIdsListeners = pairNewMap(); const hasValueListeners = pairNewMap(); const valueListeners = pairNewMap(); const startTransactionListeners = mapNew(); const finishTransactionListeners = pairNewMap(); const [addListener, callListeners, delListenerImpl, callListenerImpl] = getListenerFunctions(() => store); const validateTablesSchema = (tableSchema) => objValidate(tableSchema, (tableSchema2) => objValidate(tableSchema2, validateCellOrValueSchema), ); const validateValuesSchema = (valuesSchema) => objValidate(valuesSchema, validateCellOrValueSchema); const validateCellOrValueSchema = (schema) => { if (!objValidate(schema, (_child, id2) => arrayHas([TYPE, DEFAULT], id2))) { return false; } const type = schema[TYPE]; if (!isTypeStringOrBoolean(type) && type != NUMBER) { return false; } if (getCellOrValueType(schema[DEFAULT]) != type) { objDel(schema, DEFAULT); } return true; }; const validateContent = isArray; const validateTables = (tables) => objValidate(tables, validateTable, cellInvalid); const validateTable = (table, tableId) => (!hasTablesSchema || collHas(tablesSchemaMap, tableId) || /* istanbul ignore next */ cellInvalid(tableId)) && objValidate( table, (row, rowId) => validateRow(tableId, rowId, row), () => cellInvalid(tableId), ); const validateRow = (tableId, rowId, row, skipDefaults) => objValidate( skipDefaults ? row : addDefaultsToRow(row, tableId, rowId), (cell, cellId) => ifNotUndefined( getValidatedCell(tableId, rowId, cellId, cell), (validCell) => { row[cellId] = validCell; return true; }, () => false, ), () => cellInvalid(tableId, rowId), ); const getValidatedCell = (tableId, rowId, cellId, cell) => hasTablesSchema ? ifNotUndefined( mapGet(mapGet(tablesSchemaMap, tableId), cellId), (cellSchema) => getCellOrValueType(cell) != cellSchema[TYPE] ? cellInvalid(tableId, rowId, cellId, cell, cellSchema[DEFAULT]) : cell, () => cellInvalid(tableId, rowId, cellId, cell), ) : isUndefined(getCellOrValueType(cell)) ? cellInvalid(tableId, rowId, cellId, cell) : cell; const validateValues = (values, skipDefaults) => objValidate( skipDefaults ? values : addDefaultsToValues(values), (value, valueId) => ifNotUndefined( getValidatedValue(valueId, value), (validValue) => { values[valueId] = validValue; return true; }, () => false, ), () => valueInvalid(), ); const getValidatedValue = (valueId, value) => hasValuesSchema ? ifNotUndefined( mapGet(valuesSchemaMap, valueId), (valueSchema) => getCellOrValueType(value) != valueSchema[TYPE] ? valueInvalid(valueId, value, valueSchema[DEFAULT]) : value, () => valueInvalid(valueId, value), ) : isUndefined(getCellOrValueType(value)) ? valueInvalid(valueId, value) : value; const addDefaultsToRow = (row, tableId, rowId) => { ifNotUndefined( mapGet(tablesSchemaRowCache, tableId), ([rowDefaulted, rowNonDefaulted]) => { collForEach(rowDefaulted, (cell, cellId) => { if (!objHas(row, cellId)) { row[cellId] = cell; } }); collForEach(rowNonDefaulted, (cellId) => { if (!objHas(row, cellId)) { cellInvalid(tableId, rowId, cellId); } }); }, ); return row; }; const addDefaultsToValues = (values) => { if (hasValuesSchema) { collForEach(valuesDefaulted, (value, valueId) => { if (!objHas(values, valueId)) { values[valueId] = value; } }); collForEach(valuesNonDefaulted, (valueId) => { if (!objHas(values, valueId)) { valueInvalid(valueId); } }); } return values; }; const setValidTablesSchema = (tablesSchema) => mapMatch( tablesSchemaMap, tablesSchema, (_tablesSchema, tableId, tableSchema) => { const rowDefaulted = mapNew(); const rowNonDefaulted = setNew(); mapMatch( mapEnsure(tablesSchemaMap, tableId, mapNew), tableSchema, (tableSchemaMap, cellId, cellSchema) => { mapSet(tableSchemaMap, cellId, cellSchema); ifNotUndefined( cellSchema[DEFAULT], (def) => mapSet(rowDefaulted, cellId, def), () => setAdd(rowNonDefaulted, cellId), ); }, ); mapSet(tablesSchemaRowCache, tableId, [rowDefaulted, rowNonDefaulted]); }, (_tablesSchema, tableId) => { mapSet(tablesSchemaMap, tableId); mapSet(tablesSchemaRowCache, tableId); }, ); const setValidValuesSchema = (valuesSchema) => mapMatch( valuesSchemaMap, valuesSchema, (_valuesSchema, valueId, valueSchema) => { mapSet(valuesSchemaMap, valueId, valueSchema); ifNotUndefined( valueSchema[DEFAULT], (def) => mapSet(valuesDefaulted, valueId, def), () => setAdd(valuesNonDefaulted, valueId), ); }, (_valuesSchema, valueId) => { mapSet(valuesSchemaMap, valueId); mapSet(valuesDefaulted, valueId); collDel(valuesNonDefaulted, valueId); }, ); const setOrDelTables = (tables) => objIsEmpty(tables) ? delTables() : setTables(tables); const setValidContent = ([tables, values]) => { (objIsEmpty(tables) ? delTables : setTables)(tables); (objIsEmpty(values) ? delValues : setValues)(values); }; const setValidTables = (tables) => mapMatch( tablesMap, tables, (_tables, tableId, table) => setValidTable(tableId, table), (_tables, tableId) => delValidTable(tableId), ); const setValidTable = (tableId, table) => mapMatch( mapEnsure(tablesMap, tableId, () => { tableIdsChanged(tableId, 1); mapSet(tablePoolFunctions, tableId, getPoolFunctions()); mapSet(tableCellIds, tableId, mapNew()); return mapNew(); }), table, (tableMap, rowId, row) => setValidRow(tableId, tableMap, rowId, row), (tableMap, rowId) => delValidRow(tableId, tableMap, rowId), ); const setValidRow = (tableId, tableMap, rowId, row, forceDel) => mapMatch( mapEnsure(tableMap, rowId, () => { rowIdsChanged(tableId, rowId, 1); return mapNew(); }), row, (rowMap, cellId, cell) => setValidCell(tableId, rowId, rowMap, cellId, cell), (rowMap, cellId) => delValidCell(tableId, tableMap, rowId, rowMap, cellId, forceDel), ); const setValidCell = (tableId, rowId, rowMap, cellId, cell) => { if (!collHas(rowMap, cellId)) { cellIdsChanged(tableId, rowId, cellId, 1); } const oldCell = mapGet(rowMap, cellId); if (cell !== oldCell) { cellChanged(tableId, rowId, cellId, oldCell, cell); mapSet(rowMap, cellId, cell); } }; const setCellIntoDefaultRow = (tableId, tableMap, rowId, cellId, validCell) => ifNotUndefined( mapGet(tableMap, rowId), (rowMap) => setValidCell(tableId, rowId, rowMap, cellId, validCell), () => setValidRow( tableId, tableMap, rowId, addDefaultsToRow({[cellId]: validCell}, tableId, rowId), ), ); const setOrDelValues = (values) => objIsEmpty(values) ? delValues() : setValues(values); const setValidValues = (values) => mapMatch( valuesMap, values, (_valuesMap, valueId, value) => setValidValue(valueId, value), (_valuesMap, valueId) => delValidValue(valueId), ); const setValidValue = (valueId, value) => { if (!collHas(valuesMap, valueId)) { valueIdsChanged(valueId, 1); } const oldValue = mapGet(valuesMap, valueId); if (value !== oldValue) { valueChanged(valueId, oldValue, value); mapSet(valuesMap, valueId, value); } }; const getNewRowId = (tableId, reuse) => { const [getId] = mapGet(tablePoolFunctions, tableId); let rowId; do { rowId = getId(reuse); } while (collHas(mapGet(tablesMap, tableId), rowId)); return rowId; }; const getOrCreateTable = (tableId) => mapGet(tablesMap, tableId) ?? setValidTable(tableId, {}); const delValidTable = (tableId) => setValidTable(tableId, {}); const delValidRow = (tableId, tableMap, rowId) => { const [, releaseId] = mapGet(tablePoolFunctions, tableId); releaseId(rowId); setValidRow(tableId, tableMap, rowId, {}, true); }; const delValidCell = (tableId, table, rowId, row, cellId, forceDel) => { const defaultCell = mapGet( mapGet(tablesSchemaRowCache, tableId)?.[0], cellId, ); if (!isUndefined(defaultCell) && !forceDel) { return setValidCell(tableId, rowId, row, cellId, defaultCell); } const delCell2 = (cellId2) => { cellChanged(tableId, rowId, cellId2, mapGet(row, cellId2)); cellIdsChanged(tableId, rowId, cellId2, -1); mapSet(row, cellId2); }; if (isUndefined(defaultCell)) { delCell2(cellId); } else { mapForEach(row, delCell2); } if (collIsEmpty(row)) { rowIdsChanged(tableId, rowId, -1); if (collIsEmpty(mapSet(table, rowId))) { tableIdsChanged(tableId, -1); mapSet(tablesMap, tableId); mapSet(tablePoolFunctions, tableId); mapSet(tableCellIds, tableId); } } }; const delValidValue = (valueId) => { const defaultValue = mapGet(valuesDefaulted, valueId); if (!isUndefined(defaultValue)) { return setValidValue(valueId, defaultValue); } valueChanged(valueId, mapGet(valuesMap, valueId)); valueIdsChanged(valueId, -1); mapSet(valuesMap, valueId); }; const tableIdsChanged = (tableId, addedOrRemoved) => idsChanged(changedTableIds, tableId, addedOrRemoved); const rowIdsChanged = (tableId, rowId, addedOrRemoved) => idsChanged( mapEnsure(changedRowIds, tableId, mapNew), rowId, addedOrRemoved, ) && mapSet( changedRowCount, tableId, mapEnsure(changedRowCount, tableId, () => 0) + addedOrRemoved, ); const cellIdsChanged = (tableId, rowId, cellId, addedOrRemoved) => { const cellIds = mapGet(tableCellIds, tableId); const count = mapGet(cellIds, cellId) ?? 0; if ( (count == 0 && addedOrRemoved == 1) || (count == 1 && addedOrRemoved == -1) ) { idsChanged( mapEnsure(changedTableCellIds, tableId, mapNew), cellId, addedOrRemoved, ); } mapSet( cellIds, cellId, count != -addedOrRemoved ? count + addedOrRemoved : null, ); idsChanged( mapEnsure(mapEnsure(changedCellIds, tableId, mapNew), rowId, mapNew), cellId, addedOrRemoved, ); }; const cellChanged = (tableId, rowId, cellId, oldCell, newCell) => { mapEnsure( mapEnsure(mapEnsure(changedCells, tableId, mapNew), rowId, mapNew), cellId, () => [oldCell, 0], )[1] = newCell; internalListeners[3]?.(tableId, rowId, cellId, newCell); }; const valueIdsChanged = (valueId, addedOrRemoved) => idsChanged(changedValueIds, valueId, addedOrRemoved); const valueChanged = (valueId, oldValue, newValue) => { mapEnsure(changedValues, valueId, () => [oldValue, 0])[1] = newValue; internalListeners[4]?.(valueId, newValue); }; const cellInvalid = (tableId, rowId, cellId, invalidCell, defaultedCell) => { arrayPush( mapEnsure( mapEnsure(mapEnsure(invalidCells, tableId, mapNew), rowId, mapNew), cellId, () => [], ), invalidCell, ); return defaultedCell; }; const valueInvalid = (valueId, invalidValue, defaultedValue) => { arrayPush( mapEnsure(invalidValues, valueId, () => []), invalidValue, ); return defaultedValue; }; const getCellChange = (tableId, rowId, cellId) => ifNotUndefined( mapGet(mapGet(mapGet(changedCells, tableId), rowId), cellId), ([oldCell, newCell]) => [true, oldCell, newCell], () => [false, ...pairNew(getCell(tableId, rowId, cellId))], ); const getValueChange = (valueId) => ifNotUndefined( mapGet(changedValues, valueId), ([oldValue, newValue]) => [true, oldValue, newValue], () => [false, ...pairNew(getValue(valueId))], ); const callInvalidCellListeners = (mutator) => !collIsEmpty(invalidCells) && !collIsEmpty(invalidCellListeners[mutator]) ? collForEach( mutator ? mapClone3(inval