tinybase 0.0.0 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/lib/checkpoints.d.ts +876 -0
- package/lib/checkpoints.js +1 -0
- package/lib/checkpoints.js.gz +0 -0
- package/lib/common.d.ts +59 -0
- package/lib/debug/checkpoints.d.ts +876 -0
- package/lib/debug/checkpoints.js +326 -0
- package/lib/debug/common.d.ts +59 -0
- package/lib/debug/indexes.d.ts +829 -0
- package/lib/debug/indexes.js +390 -0
- package/lib/debug/metrics.d.ts +753 -0
- package/lib/debug/metrics.js +391 -0
- package/lib/debug/persisters.d.ts +704 -0
- package/lib/debug/persisters.js +191 -0
- package/lib/debug/relationships.d.ts +1114 -0
- package/lib/debug/relationships.js +418 -0
- package/lib/debug/store.d.ts +2503 -0
- package/lib/debug/store.js +725 -0
- package/lib/debug/tinybase.d.ts +13 -0
- package/lib/debug/tinybase.js +1708 -0
- package/lib/debug/ui-react.d.ts +7158 -0
- package/lib/debug/ui-react.js +1040 -0
- package/lib/indexes.d.ts +829 -0
- package/lib/indexes.js +1 -0
- package/lib/indexes.js.gz +0 -0
- package/lib/metrics.d.ts +753 -0
- package/lib/metrics.js +1 -0
- package/lib/metrics.js.gz +0 -0
- package/lib/persisters.d.ts +704 -0
- package/lib/persisters.js +1 -0
- package/lib/persisters.js.gz +0 -0
- package/lib/relationships.d.ts +1114 -0
- package/lib/relationships.js +1 -0
- package/lib/relationships.js.gz +0 -0
- package/lib/store.d.ts +2503 -0
- package/lib/store.js +1 -0
- package/lib/store.js.gz +0 -0
- package/lib/tinybase.d.ts +13 -0
- package/lib/tinybase.js +1 -0
- package/lib/tinybase.js.gz +0 -0
- package/lib/ui-react.d.ts +7158 -0
- package/lib/ui-react.js +1 -0
- package/lib/ui-react.js.gz +0 -0
- package/lib/umd/checkpoints.js +1 -0
- package/lib/umd/checkpoints.js.gz +0 -0
- package/lib/umd/indexes.js +1 -0
- package/lib/umd/indexes.js.gz +0 -0
- package/lib/umd/metrics.js +1 -0
- package/lib/umd/metrics.js.gz +0 -0
- package/lib/umd/persisters.js +1 -0
- package/lib/umd/persisters.js.gz +0 -0
- package/lib/umd/relationships.js +1 -0
- package/lib/umd/relationships.js.gz +0 -0
- package/lib/umd/store.js +1 -0
- package/lib/umd/store.js.gz +0 -0
- package/lib/umd/tinybase.js +1 -0
- package/lib/umd/tinybase.js.gz +0 -0
- package/lib/umd/ui-react.js +1 -0
- package/lib/umd/ui-react.js.gz +0 -0
- package/package.json +98 -2
- package/readme.md +195 -0
|
@@ -0,0 +1,1708 @@
|
|
|
1
|
+
import {promises, watch} from 'fs';
|
|
2
|
+
|
|
3
|
+
const getTypeOf = (thing) => typeof thing;
|
|
4
|
+
const EMPTY_OBJECT = '{}';
|
|
5
|
+
const EMPTY_STRING = '';
|
|
6
|
+
const STRING = getTypeOf(EMPTY_STRING);
|
|
7
|
+
const BOOLEAN = getTypeOf(true);
|
|
8
|
+
const NUMBER = getTypeOf(0);
|
|
9
|
+
const FUNCTION = getTypeOf(getTypeOf);
|
|
10
|
+
const TYPE = 'type';
|
|
11
|
+
const DEFAULT = 'default';
|
|
12
|
+
const UTF8 = 'utf8';
|
|
13
|
+
const SUM = 'sum';
|
|
14
|
+
const AVG = 'avg';
|
|
15
|
+
const MIN = 'min';
|
|
16
|
+
const MAX = 'max';
|
|
17
|
+
|
|
18
|
+
const arrayHas = (array, value) => array.includes(value);
|
|
19
|
+
const arrayIsSorted = (array, sorter) =>
|
|
20
|
+
array.every(
|
|
21
|
+
(value, index) => index == 0 || sorter(array[index - 1], value) <= 0,
|
|
22
|
+
);
|
|
23
|
+
const arraySort = (array, sorter) => array.sort(sorter);
|
|
24
|
+
const arrayForEach = (array, cb) => array.forEach(cb);
|
|
25
|
+
const arraySum = (array) => arrayReduce(array, (i, j) => i + j, 0);
|
|
26
|
+
const arrayLength = (array) => array.length;
|
|
27
|
+
const arrayIsEmpty = (array) => arrayLength(array) == 0;
|
|
28
|
+
const arrayReduce = (array, cb, initial) => array.reduce(cb, initial);
|
|
29
|
+
const arrayFilter = (array, cb) => array.filter(cb);
|
|
30
|
+
const arrayFromSecond = (ids) => ids.slice(1);
|
|
31
|
+
const arrayClear = (array, to) => array.splice(0, to);
|
|
32
|
+
|
|
33
|
+
const jsonString = (obj) =>
|
|
34
|
+
JSON.stringify(obj, (_key, value) =>
|
|
35
|
+
isInstanceOf(value, Map)
|
|
36
|
+
? arrayReduce(
|
|
37
|
+
[...value],
|
|
38
|
+
(obj2, [key, value2]) => {
|
|
39
|
+
obj2[key] = value2;
|
|
40
|
+
return obj2;
|
|
41
|
+
},
|
|
42
|
+
{},
|
|
43
|
+
)
|
|
44
|
+
: value,
|
|
45
|
+
);
|
|
46
|
+
const jsonParse = JSON.parse;
|
|
47
|
+
const mathMax = Math.max;
|
|
48
|
+
const mathMin = Math.min;
|
|
49
|
+
const isFiniteNumber = isFinite;
|
|
50
|
+
const isInstanceOf = (thing, cls) => thing instanceof cls;
|
|
51
|
+
const isUndefined = (thing) => thing == void 0;
|
|
52
|
+
const ifNotUndefined = (value, then, otherwise) =>
|
|
53
|
+
isUndefined(value) ? otherwise?.() : then(value);
|
|
54
|
+
const isTypeStringOrBoolean = (type) => type == STRING || type == BOOLEAN;
|
|
55
|
+
const isString = (thing) => getTypeOf(thing) == STRING;
|
|
56
|
+
const isFunction = (thing) => getTypeOf(thing) == FUNCTION;
|
|
57
|
+
const getUndefined = () => void 0;
|
|
58
|
+
|
|
59
|
+
const collSizeN = (collSizer) => (coll) =>
|
|
60
|
+
arrayReduce(collValues(coll), (total, coll2) => total + collSizer(coll2), 0);
|
|
61
|
+
const collSize = (coll) => coll.size;
|
|
62
|
+
const collSize2 = collSizeN(collSize);
|
|
63
|
+
const collSize3 = collSizeN(collSize2);
|
|
64
|
+
const collSize4 = collSizeN(collSize3);
|
|
65
|
+
const collPairSize = (pair, func = collSize) => func(pair[0]) + func(pair[1]);
|
|
66
|
+
const collHas = (coll, keyOrValue) => coll?.has(keyOrValue) ?? false;
|
|
67
|
+
const collIsEmpty = (coll) => isUndefined(coll) || collSize(coll) == 0;
|
|
68
|
+
const collValues = (coll) => [...(coll?.values() ?? [])];
|
|
69
|
+
const collClear = (coll) => coll.clear();
|
|
70
|
+
const collForEach = (coll, cb) => coll?.forEach(cb);
|
|
71
|
+
const collDel = (coll, keyOrValue) => coll?.delete(keyOrValue);
|
|
72
|
+
|
|
73
|
+
const mapNew = (entries) => new Map(entries);
|
|
74
|
+
const mapNewPair = (newFunction = mapNew) => [newFunction(), newFunction()];
|
|
75
|
+
const mapKeys = (map) => [...(map?.keys() ?? [])];
|
|
76
|
+
const mapGet = (map, key) => map?.get(key);
|
|
77
|
+
const mapForEach = (map, cb) =>
|
|
78
|
+
collForEach(map, (value, key) => cb(key, value));
|
|
79
|
+
const mapSet = (map, key, value) =>
|
|
80
|
+
isUndefined(value) ? (collDel(map, key), map) : map?.set(key, value);
|
|
81
|
+
const mapEnsure = (map, key, defaultValue, onWillAdd) => {
|
|
82
|
+
if (!collHas(map, key)) {
|
|
83
|
+
onWillAdd?.(defaultValue);
|
|
84
|
+
map.set(key, defaultValue);
|
|
85
|
+
}
|
|
86
|
+
return mapGet(map, key);
|
|
87
|
+
};
|
|
88
|
+
const mapToObj = (map, childMapper) => {
|
|
89
|
+
const obj = {};
|
|
90
|
+
const mapper = childMapper ?? ((mapValue) => mapValue);
|
|
91
|
+
collForEach(map, (value, key) => (obj[key] = mapper(value)));
|
|
92
|
+
return obj;
|
|
93
|
+
};
|
|
94
|
+
const mapClone = (map, childMapper) => {
|
|
95
|
+
const map2 = mapNew();
|
|
96
|
+
const mapper = childMapper ?? ((mapValue) => mapValue);
|
|
97
|
+
collForEach(map, (value, key) => map2.set(key, mapper(value)));
|
|
98
|
+
return map2;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const setNew = (entries) => new Set(entries);
|
|
102
|
+
const setAdd = (set, value) => set?.add(value);
|
|
103
|
+
|
|
104
|
+
const getDefinableFunctions = (store, getDefaultThing, validateRowValue) => {
|
|
105
|
+
const hasRow = store.hasRow;
|
|
106
|
+
const tableIds = mapNew();
|
|
107
|
+
const things = mapNew();
|
|
108
|
+
const allRowValues = mapNew();
|
|
109
|
+
const allSortKeys = mapNew();
|
|
110
|
+
const storeListenerIds = mapNew();
|
|
111
|
+
const getStore = () => store;
|
|
112
|
+
const getThingIds = () => mapKeys(tableIds);
|
|
113
|
+
const getTableId = (id) => mapGet(tableIds, id);
|
|
114
|
+
const getThing = (id) => mapGet(things, id);
|
|
115
|
+
const setThing = (id, thing) => mapSet(things, id, thing);
|
|
116
|
+
const removeStoreListeners = (id) =>
|
|
117
|
+
ifNotUndefined(mapGet(storeListenerIds, id), (listenerIds) => {
|
|
118
|
+
collForEach(listenerIds, store.delListener);
|
|
119
|
+
mapSet(storeListenerIds, id);
|
|
120
|
+
});
|
|
121
|
+
const setDefinition = (id, tableId, onChanged, getRowValue, getSortKey) => {
|
|
122
|
+
const changedRowValues = mapNew();
|
|
123
|
+
const changedSortKeys = mapNew();
|
|
124
|
+
mapSet(tableIds, id, tableId);
|
|
125
|
+
if (!collHas(things, id)) {
|
|
126
|
+
mapSet(things, id, getDefaultThing());
|
|
127
|
+
mapSet(allRowValues, id, mapNew());
|
|
128
|
+
mapSet(allSortKeys, id, mapNew());
|
|
129
|
+
}
|
|
130
|
+
const rowValues = mapGet(allRowValues, id);
|
|
131
|
+
const sortKeys = mapGet(allSortKeys, id);
|
|
132
|
+
const processRow = (rowId) => {
|
|
133
|
+
const getCell = (cellId) => store.getCell(tableId, rowId, cellId);
|
|
134
|
+
const oldRowValue = mapGet(rowValues, rowId);
|
|
135
|
+
const newRowValue = hasRow(tableId, rowId)
|
|
136
|
+
? validateRowValue(getRowValue(getCell, rowId))
|
|
137
|
+
: void 0;
|
|
138
|
+
if (oldRowValue != newRowValue) {
|
|
139
|
+
mapSet(changedRowValues, rowId, [oldRowValue, newRowValue]);
|
|
140
|
+
}
|
|
141
|
+
if (!isUndefined(getSortKey)) {
|
|
142
|
+
const oldSortKey = mapGet(sortKeys, rowId);
|
|
143
|
+
const newSortKey = hasRow(tableId, rowId)
|
|
144
|
+
? getSortKey(getCell, rowId)
|
|
145
|
+
: void 0;
|
|
146
|
+
if (oldSortKey != newSortKey) {
|
|
147
|
+
mapSet(changedSortKeys, rowId, newSortKey);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const processTable = (force) => {
|
|
152
|
+
onChanged(
|
|
153
|
+
() => {
|
|
154
|
+
collForEach(changedRowValues, ([, newRowValue], rowId) =>
|
|
155
|
+
mapSet(rowValues, rowId, newRowValue),
|
|
156
|
+
);
|
|
157
|
+
collForEach(changedSortKeys, (newSortKey, rowId) =>
|
|
158
|
+
mapSet(sortKeys, rowId, newSortKey),
|
|
159
|
+
);
|
|
160
|
+
},
|
|
161
|
+
changedRowValues,
|
|
162
|
+
changedSortKeys,
|
|
163
|
+
rowValues,
|
|
164
|
+
sortKeys,
|
|
165
|
+
force,
|
|
166
|
+
);
|
|
167
|
+
collClear(changedRowValues);
|
|
168
|
+
collClear(changedSortKeys);
|
|
169
|
+
};
|
|
170
|
+
mapForEach(rowValues, processRow);
|
|
171
|
+
if (store.hasTable(tableId)) {
|
|
172
|
+
arrayForEach(store.getRowIds(tableId), (rowId) => {
|
|
173
|
+
if (!collHas(rowValues, rowId)) {
|
|
174
|
+
processRow(rowId);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
processTable(true);
|
|
179
|
+
removeStoreListeners(id);
|
|
180
|
+
mapSet(
|
|
181
|
+
storeListenerIds,
|
|
182
|
+
id,
|
|
183
|
+
setNew([
|
|
184
|
+
store.addRowListener(tableId, null, (_store, _tableId, rowId) =>
|
|
185
|
+
processRow(rowId),
|
|
186
|
+
),
|
|
187
|
+
store.addTableListener(tableId, () => processTable()),
|
|
188
|
+
]),
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
const delDefinition = (id) => {
|
|
192
|
+
mapSet(tableIds, id);
|
|
193
|
+
mapSet(things, id);
|
|
194
|
+
mapSet(allRowValues, id);
|
|
195
|
+
mapSet(allSortKeys, id);
|
|
196
|
+
removeStoreListeners(id);
|
|
197
|
+
};
|
|
198
|
+
const destroy = () => mapForEach(storeListenerIds, delDefinition);
|
|
199
|
+
return [
|
|
200
|
+
getStore,
|
|
201
|
+
getThingIds,
|
|
202
|
+
getTableId,
|
|
203
|
+
getThing,
|
|
204
|
+
setThing,
|
|
205
|
+
setDefinition,
|
|
206
|
+
delDefinition,
|
|
207
|
+
destroy,
|
|
208
|
+
];
|
|
209
|
+
};
|
|
210
|
+
const getRowCellFunction = (getRowCell, defaultCellValue) =>
|
|
211
|
+
isString(getRowCell)
|
|
212
|
+
? (getCell) => getCell(getRowCell)
|
|
213
|
+
: getRowCell ?? (() => defaultCellValue ?? EMPTY_STRING);
|
|
214
|
+
const getCreateFunction = (getFunction) => {
|
|
215
|
+
const getFunctionsByStore = /* @__PURE__ */ new WeakMap();
|
|
216
|
+
return (store) => {
|
|
217
|
+
if (!getFunctionsByStore.has(store)) {
|
|
218
|
+
getFunctionsByStore.set(store, getFunction(store));
|
|
219
|
+
}
|
|
220
|
+
return getFunctionsByStore.get(store);
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const addDeepSet = (deepSet, value, ids) =>
|
|
225
|
+
arrayLength(ids) < 2
|
|
226
|
+
? setAdd(
|
|
227
|
+
arrayIsEmpty(ids) ? deepSet : mapEnsure(deepSet, ids[0], setNew()),
|
|
228
|
+
value,
|
|
229
|
+
)
|
|
230
|
+
: addDeepSet(
|
|
231
|
+
mapEnsure(deepSet, ids[0], mapNew()),
|
|
232
|
+
value,
|
|
233
|
+
arrayFromSecond(ids),
|
|
234
|
+
);
|
|
235
|
+
const forDeepSet = (valueDo) => {
|
|
236
|
+
const deep = (deepIdSet, arg, ...ids) =>
|
|
237
|
+
ifNotUndefined(deepIdSet, (deepIdSet2) =>
|
|
238
|
+
arrayIsEmpty(ids)
|
|
239
|
+
? valueDo(deepIdSet2, arg)
|
|
240
|
+
: arrayForEach([ids[0], null], (id) =>
|
|
241
|
+
deep(mapGet(deepIdSet2, id), arg, ...arrayFromSecond(ids)),
|
|
242
|
+
),
|
|
243
|
+
);
|
|
244
|
+
return deep;
|
|
245
|
+
};
|
|
246
|
+
const getListenerFunctions = (getThing) => {
|
|
247
|
+
let thing;
|
|
248
|
+
let nextId = 0;
|
|
249
|
+
const listenerPool = [];
|
|
250
|
+
const allListeners = mapNew();
|
|
251
|
+
const addListener = (listener, deepSet, idOrNulls = []) => {
|
|
252
|
+
thing ??= getThing();
|
|
253
|
+
const id = listenerPool.pop() ?? '' + nextId++;
|
|
254
|
+
mapSet(allListeners, id, [listener, deepSet, idOrNulls]);
|
|
255
|
+
addDeepSet(deepSet, id, idOrNulls);
|
|
256
|
+
return id;
|
|
257
|
+
};
|
|
258
|
+
const callListeners = (deepSet, ids = [], ...extraArgs) =>
|
|
259
|
+
forDeepSet(collForEach)(
|
|
260
|
+
deepSet,
|
|
261
|
+
(id) =>
|
|
262
|
+
ifNotUndefined(mapGet(allListeners, id), ([listener]) =>
|
|
263
|
+
listener(thing, ...ids, ...extraArgs),
|
|
264
|
+
),
|
|
265
|
+
...ids,
|
|
266
|
+
);
|
|
267
|
+
const delListener = (id) =>
|
|
268
|
+
ifNotUndefined(
|
|
269
|
+
mapGet(allListeners, id),
|
|
270
|
+
([, deepSet, idOrNulls]) => {
|
|
271
|
+
forDeepSet(collDel)(deepSet, id, ...idOrNulls);
|
|
272
|
+
mapSet(allListeners, id);
|
|
273
|
+
if (arrayLength(listenerPool) < 1e3) {
|
|
274
|
+
listenerPool.push(id);
|
|
275
|
+
}
|
|
276
|
+
return idOrNulls;
|
|
277
|
+
},
|
|
278
|
+
() => [],
|
|
279
|
+
);
|
|
280
|
+
const callListener = (id, idNullGetters, extraArgsGetter) =>
|
|
281
|
+
ifNotUndefined(mapGet(allListeners, id), ([listener, , idOrNulls]) => {
|
|
282
|
+
const callWithIds = (...ids) => {
|
|
283
|
+
const index = arrayLength(ids);
|
|
284
|
+
index == arrayLength(idOrNulls)
|
|
285
|
+
? listener(thing, ...ids, ...extraArgsGetter(ids))
|
|
286
|
+
: isUndefined(idOrNulls[index])
|
|
287
|
+
? arrayForEach(idNullGetters[index](...ids), (id2) =>
|
|
288
|
+
callWithIds(...ids, id2),
|
|
289
|
+
)
|
|
290
|
+
: callWithIds(...ids, idOrNulls[index]);
|
|
291
|
+
};
|
|
292
|
+
callWithIds();
|
|
293
|
+
});
|
|
294
|
+
return [addListener, callListeners, delListener, callListener];
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const object = Object;
|
|
298
|
+
const objIds = object.keys;
|
|
299
|
+
const objFrozen = object.isFrozen;
|
|
300
|
+
const objFreeze = object.freeze;
|
|
301
|
+
const isObject = (obj) =>
|
|
302
|
+
isInstanceOf(obj, object) && obj.constructor == object;
|
|
303
|
+
const objGet = (obj, id) => ifNotUndefined(obj, (obj2) => obj2[id]);
|
|
304
|
+
const objHas = (obj, id) => !isUndefined(objGet(obj, id));
|
|
305
|
+
const objDel = (obj, id) => delete obj[id];
|
|
306
|
+
const objForEach = (obj, cb) =>
|
|
307
|
+
arrayForEach(object.entries(obj), ([id, value]) => cb(value, id));
|
|
308
|
+
const objIsEmpty = (obj) => arrayIsEmpty(objIds(obj));
|
|
309
|
+
|
|
310
|
+
const createCheckpoints = getCreateFunction((store) => {
|
|
311
|
+
let backwardIdsSize = 100;
|
|
312
|
+
let currentId;
|
|
313
|
+
let delta = mapNew();
|
|
314
|
+
let listening = 1;
|
|
315
|
+
let nextCheckpointId;
|
|
316
|
+
let checkpointsChanged;
|
|
317
|
+
const checkpointIdsListeners = setNew();
|
|
318
|
+
const checkpointListeners = mapNew();
|
|
319
|
+
const [addListener, callListeners, delListenerImpl] = getListenerFunctions(
|
|
320
|
+
() => checkpoints,
|
|
321
|
+
);
|
|
322
|
+
const deltas = mapNew();
|
|
323
|
+
const labels = mapNew();
|
|
324
|
+
const backwardIds = [];
|
|
325
|
+
const forwardIds = [];
|
|
326
|
+
const updateStore = (oldOrNew, checkpointId) => {
|
|
327
|
+
listening = 0;
|
|
328
|
+
store.transaction(() =>
|
|
329
|
+
collForEach(mapGet(deltas, checkpointId), (table, tableId) =>
|
|
330
|
+
collForEach(table, (row, rowId) =>
|
|
331
|
+
collForEach(row, (oldNew, cellId) =>
|
|
332
|
+
isUndefined(oldNew[oldOrNew])
|
|
333
|
+
? store.delCell(tableId, rowId, cellId, true)
|
|
334
|
+
: store.setCell(tableId, rowId, cellId, oldNew[oldOrNew]),
|
|
335
|
+
),
|
|
336
|
+
),
|
|
337
|
+
),
|
|
338
|
+
);
|
|
339
|
+
listening = 1;
|
|
340
|
+
};
|
|
341
|
+
const clearCheckpointId = (checkpointId) => {
|
|
342
|
+
mapSet(deltas, checkpointId);
|
|
343
|
+
mapSet(labels, checkpointId);
|
|
344
|
+
callListeners(checkpointListeners, [checkpointId]);
|
|
345
|
+
};
|
|
346
|
+
const clearCheckpointIds = (checkpointIds, to) =>
|
|
347
|
+
arrayForEach(
|
|
348
|
+
arrayClear(checkpointIds, to ?? arrayLength(checkpointIds)),
|
|
349
|
+
clearCheckpointId,
|
|
350
|
+
);
|
|
351
|
+
const trimBackwardsIds = () =>
|
|
352
|
+
clearCheckpointIds(backwardIds, arrayLength(backwardIds) - backwardIdsSize);
|
|
353
|
+
const listenerId = store.addCellListener(
|
|
354
|
+
null,
|
|
355
|
+
null,
|
|
356
|
+
null,
|
|
357
|
+
(_store, tableId, rowId, cellId, newCell, oldCell) => {
|
|
358
|
+
if (listening) {
|
|
359
|
+
ifNotUndefined(currentId, () => {
|
|
360
|
+
backwardIds.push(currentId);
|
|
361
|
+
trimBackwardsIds();
|
|
362
|
+
clearCheckpointIds(forwardIds);
|
|
363
|
+
currentId = void 0;
|
|
364
|
+
checkpointsChanged = 1;
|
|
365
|
+
});
|
|
366
|
+
const table = mapEnsure(delta, tableId, mapNew());
|
|
367
|
+
const row = mapEnsure(table, rowId, mapNew());
|
|
368
|
+
const oldNew = mapEnsure(
|
|
369
|
+
row,
|
|
370
|
+
cellId,
|
|
371
|
+
[void 0, void 0],
|
|
372
|
+
(addOldNew) => (addOldNew[0] = oldCell),
|
|
373
|
+
);
|
|
374
|
+
oldNew[1] = newCell;
|
|
375
|
+
if (oldNew[0] === oldNew[1]) {
|
|
376
|
+
if (collIsEmpty(mapSet(row, cellId))) {
|
|
377
|
+
if (collIsEmpty(mapSet(table, rowId))) {
|
|
378
|
+
if (collIsEmpty(mapSet(delta, tableId))) {
|
|
379
|
+
currentId = backwardIds.pop();
|
|
380
|
+
checkpointsChanged = 1;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
callListenersIfChanged();
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
);
|
|
389
|
+
const addCheckpointImpl = (label = '') => {
|
|
390
|
+
if (isUndefined(currentId)) {
|
|
391
|
+
currentId = '' + nextCheckpointId++;
|
|
392
|
+
mapSet(deltas, currentId, delta);
|
|
393
|
+
setCheckpoint(currentId, label);
|
|
394
|
+
delta = mapNew();
|
|
395
|
+
checkpointsChanged = 1;
|
|
396
|
+
}
|
|
397
|
+
return currentId;
|
|
398
|
+
};
|
|
399
|
+
const goBackwardImpl = () => {
|
|
400
|
+
if (!arrayIsEmpty(backwardIds)) {
|
|
401
|
+
forwardIds.unshift(addCheckpointImpl());
|
|
402
|
+
updateStore(0, currentId);
|
|
403
|
+
currentId = backwardIds.pop();
|
|
404
|
+
checkpointsChanged = 1;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
const goForwardImpl = () => {
|
|
408
|
+
if (!arrayIsEmpty(forwardIds)) {
|
|
409
|
+
backwardIds.push(currentId);
|
|
410
|
+
currentId = forwardIds.shift();
|
|
411
|
+
updateStore(1, currentId);
|
|
412
|
+
checkpointsChanged = 1;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
const callListenersIfChanged = () => {
|
|
416
|
+
if (checkpointsChanged) {
|
|
417
|
+
callListeners(checkpointIdsListeners);
|
|
418
|
+
checkpointsChanged = 0;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
const setSize = (size) => {
|
|
422
|
+
backwardIdsSize = size;
|
|
423
|
+
trimBackwardsIds();
|
|
424
|
+
return checkpoints;
|
|
425
|
+
};
|
|
426
|
+
const addCheckpoint = (label) => {
|
|
427
|
+
const id = addCheckpointImpl(label);
|
|
428
|
+
callListenersIfChanged();
|
|
429
|
+
return id;
|
|
430
|
+
};
|
|
431
|
+
const setCheckpoint = (checkpointId, label) => {
|
|
432
|
+
if (
|
|
433
|
+
collHas(deltas, checkpointId) &&
|
|
434
|
+
mapGet(labels, checkpointId) !== label
|
|
435
|
+
) {
|
|
436
|
+
mapSet(labels, checkpointId, label);
|
|
437
|
+
callListeners(checkpointListeners, [checkpointId]);
|
|
438
|
+
}
|
|
439
|
+
return checkpoints;
|
|
440
|
+
};
|
|
441
|
+
const getStore = () => store;
|
|
442
|
+
const getCheckpointIds = () => [[...backwardIds], currentId, [...forwardIds]];
|
|
443
|
+
const getCheckpoint = (checkpointId) => mapGet(labels, checkpointId);
|
|
444
|
+
const goBackward = () => {
|
|
445
|
+
goBackwardImpl();
|
|
446
|
+
callListenersIfChanged();
|
|
447
|
+
return checkpoints;
|
|
448
|
+
};
|
|
449
|
+
const goForward = () => {
|
|
450
|
+
goForwardImpl();
|
|
451
|
+
callListenersIfChanged();
|
|
452
|
+
return checkpoints;
|
|
453
|
+
};
|
|
454
|
+
const goTo = (checkpointId) => {
|
|
455
|
+
const action = arrayHas(backwardIds, checkpointId)
|
|
456
|
+
? goBackwardImpl
|
|
457
|
+
: arrayHas(forwardIds, checkpointId)
|
|
458
|
+
? goForwardImpl
|
|
459
|
+
: null;
|
|
460
|
+
while (!isUndefined(action) && checkpointId != currentId) {
|
|
461
|
+
action();
|
|
462
|
+
}
|
|
463
|
+
callListenersIfChanged();
|
|
464
|
+
return checkpoints;
|
|
465
|
+
};
|
|
466
|
+
const addCheckpointIdsListener = (listener) =>
|
|
467
|
+
addListener(listener, checkpointIdsListeners);
|
|
468
|
+
const addCheckpointListener = (checkpointId, listener) =>
|
|
469
|
+
addListener(listener, checkpointListeners, [checkpointId]);
|
|
470
|
+
const delListener = (listenerId2) => {
|
|
471
|
+
delListenerImpl(listenerId2);
|
|
472
|
+
return checkpoints;
|
|
473
|
+
};
|
|
474
|
+
const clear = () => {
|
|
475
|
+
clearCheckpointIds(backwardIds);
|
|
476
|
+
clearCheckpointIds(forwardIds);
|
|
477
|
+
if (!isUndefined(currentId)) {
|
|
478
|
+
clearCheckpointId(currentId);
|
|
479
|
+
}
|
|
480
|
+
currentId = void 0;
|
|
481
|
+
nextCheckpointId = 0;
|
|
482
|
+
addCheckpoint();
|
|
483
|
+
return checkpoints;
|
|
484
|
+
};
|
|
485
|
+
const destroy = () => {
|
|
486
|
+
store.delListener(listenerId);
|
|
487
|
+
};
|
|
488
|
+
const getListenerStats = () => ({
|
|
489
|
+
checkpointIds: collSize(checkpointIdsListeners),
|
|
490
|
+
checkpoint: collSize2(checkpointListeners),
|
|
491
|
+
});
|
|
492
|
+
const checkpoints = {
|
|
493
|
+
setSize,
|
|
494
|
+
addCheckpoint,
|
|
495
|
+
setCheckpoint,
|
|
496
|
+
getStore,
|
|
497
|
+
getCheckpointIds,
|
|
498
|
+
getCheckpoint,
|
|
499
|
+
goBackward,
|
|
500
|
+
goForward,
|
|
501
|
+
goTo,
|
|
502
|
+
addCheckpointIdsListener,
|
|
503
|
+
addCheckpointListener,
|
|
504
|
+
delListener,
|
|
505
|
+
clear,
|
|
506
|
+
destroy,
|
|
507
|
+
getListenerStats,
|
|
508
|
+
};
|
|
509
|
+
return objFreeze(checkpoints.clear());
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const createIndexes = getCreateFunction((store) => {
|
|
513
|
+
const sliceIdsListeners = mapNew();
|
|
514
|
+
const sliceRowIdsListeners = mapNew();
|
|
515
|
+
const [
|
|
516
|
+
getStore,
|
|
517
|
+
getIndexIds,
|
|
518
|
+
getTableId,
|
|
519
|
+
getIndex,
|
|
520
|
+
setIndex,
|
|
521
|
+
setDefinition,
|
|
522
|
+
delDefinition,
|
|
523
|
+
destroy,
|
|
524
|
+
] = getDefinableFunctions(store, mapNew, (value) =>
|
|
525
|
+
isUndefined(value) ? EMPTY_STRING : value + EMPTY_STRING,
|
|
526
|
+
);
|
|
527
|
+
const [addListener, callListeners, delListenerImpl] = getListenerFunctions(
|
|
528
|
+
() => indexes,
|
|
529
|
+
);
|
|
530
|
+
const setIndexDefinition = (
|
|
531
|
+
indexId,
|
|
532
|
+
tableId,
|
|
533
|
+
getSliceId,
|
|
534
|
+
getSortKey,
|
|
535
|
+
sliceIdSorter,
|
|
536
|
+
rowIdSorter = defaultSorter,
|
|
537
|
+
) => {
|
|
538
|
+
const sliceIdArraySorter = isUndefined(sliceIdSorter)
|
|
539
|
+
? void 0
|
|
540
|
+
: ([id1], [id2]) => sliceIdSorter(id1, id2);
|
|
541
|
+
setDefinition(
|
|
542
|
+
indexId,
|
|
543
|
+
tableId,
|
|
544
|
+
(change, changedSliceIds, changedSortKeys, sliceIds, sortKeys) => {
|
|
545
|
+
let sliceIdsChanged = 0;
|
|
546
|
+
const changedSlices = setNew();
|
|
547
|
+
const unsortedSlices = setNew();
|
|
548
|
+
const index = getIndex(indexId);
|
|
549
|
+
collForEach(changedSliceIds, ([oldSliceId, newSliceId], rowId) => {
|
|
550
|
+
if (!isUndefined(oldSliceId)) {
|
|
551
|
+
setAdd(changedSlices, oldSliceId);
|
|
552
|
+
ifNotUndefined(mapGet(index, oldSliceId), (oldSlice) => {
|
|
553
|
+
collDel(oldSlice, rowId);
|
|
554
|
+
if (collIsEmpty(oldSlice)) {
|
|
555
|
+
mapSet(index, oldSliceId);
|
|
556
|
+
sliceIdsChanged = 1;
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (!isUndefined(newSliceId)) {
|
|
561
|
+
setAdd(changedSlices, newSliceId);
|
|
562
|
+
if (!collHas(index, newSliceId)) {
|
|
563
|
+
mapSet(index, newSliceId, setNew());
|
|
564
|
+
sliceIdsChanged = 1;
|
|
565
|
+
}
|
|
566
|
+
setAdd(mapGet(index, newSliceId), rowId);
|
|
567
|
+
if (!isUndefined(getSortKey)) {
|
|
568
|
+
setAdd(unsortedSlices, newSliceId);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
change();
|
|
573
|
+
mapForEach(changedSortKeys, (rowId) =>
|
|
574
|
+
ifNotUndefined(mapGet(sliceIds, rowId), (sliceId) =>
|
|
575
|
+
setAdd(unsortedSlices, sliceId),
|
|
576
|
+
),
|
|
577
|
+
);
|
|
578
|
+
if (!collIsEmpty(sortKeys)) {
|
|
579
|
+
collForEach(unsortedSlices, (sliceId) => {
|
|
580
|
+
const rowIdArraySorter = (rowId1, rowId2) =>
|
|
581
|
+
rowIdSorter(
|
|
582
|
+
mapGet(sortKeys, rowId1),
|
|
583
|
+
mapGet(sortKeys, rowId2),
|
|
584
|
+
sliceId,
|
|
585
|
+
);
|
|
586
|
+
const sliceArray = [...mapGet(index, sliceId)];
|
|
587
|
+
if (!arrayIsSorted(sliceArray, rowIdArraySorter)) {
|
|
588
|
+
mapSet(
|
|
589
|
+
index,
|
|
590
|
+
sliceId,
|
|
591
|
+
setNew(arraySort(sliceArray, rowIdArraySorter)),
|
|
592
|
+
);
|
|
593
|
+
setAdd(changedSlices, sliceId);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
if (sliceIdsChanged) {
|
|
598
|
+
if (!isUndefined(sliceIdArraySorter)) {
|
|
599
|
+
const indexArray = [...index];
|
|
600
|
+
if (!arrayIsSorted(indexArray, sliceIdArraySorter)) {
|
|
601
|
+
setIndex(
|
|
602
|
+
indexId,
|
|
603
|
+
mapNew(arraySort(indexArray, sliceIdArraySorter)),
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
callListeners(sliceIdsListeners, [indexId]);
|
|
608
|
+
}
|
|
609
|
+
collForEach(changedSlices, (sliceId) =>
|
|
610
|
+
callListeners(sliceRowIdsListeners, [indexId, sliceId]),
|
|
611
|
+
);
|
|
612
|
+
},
|
|
613
|
+
getRowCellFunction(getSliceId),
|
|
614
|
+
ifNotUndefined(getSortKey, getRowCellFunction),
|
|
615
|
+
);
|
|
616
|
+
return indexes;
|
|
617
|
+
};
|
|
618
|
+
const delIndexDefinition = (indexId) => {
|
|
619
|
+
delDefinition(indexId);
|
|
620
|
+
return indexes;
|
|
621
|
+
};
|
|
622
|
+
const getSliceIds = (indexId) => mapKeys(getIndex(indexId));
|
|
623
|
+
const getSliceRowIds = (indexId, sliceId) =>
|
|
624
|
+
collValues(mapGet(getIndex(indexId), sliceId));
|
|
625
|
+
const addSliceIdsListener = (indexId, listener) =>
|
|
626
|
+
addListener(listener, sliceIdsListeners, [indexId]);
|
|
627
|
+
const addSliceRowIdsListener = (indexId, sliceId, listener) =>
|
|
628
|
+
addListener(listener, sliceRowIdsListeners, [indexId, sliceId]);
|
|
629
|
+
const delListener = (listenerId) => {
|
|
630
|
+
delListenerImpl(listenerId);
|
|
631
|
+
return indexes;
|
|
632
|
+
};
|
|
633
|
+
const getListenerStats = () => ({
|
|
634
|
+
sliceIds: collSize2(sliceIdsListeners),
|
|
635
|
+
sliceRowIds: collSize3(sliceRowIdsListeners),
|
|
636
|
+
});
|
|
637
|
+
const indexes = {
|
|
638
|
+
setIndexDefinition,
|
|
639
|
+
delIndexDefinition,
|
|
640
|
+
getStore,
|
|
641
|
+
getIndexIds,
|
|
642
|
+
getTableId,
|
|
643
|
+
getSliceIds,
|
|
644
|
+
getSliceRowIds,
|
|
645
|
+
addSliceIdsListener,
|
|
646
|
+
addSliceRowIdsListener,
|
|
647
|
+
delListener,
|
|
648
|
+
destroy,
|
|
649
|
+
getListenerStats,
|
|
650
|
+
};
|
|
651
|
+
return objFreeze(indexes);
|
|
652
|
+
});
|
|
653
|
+
const defaultSorter = (sortKey1, sortKey2) => (sortKey1 < sortKey2 ? -1 : 1);
|
|
654
|
+
|
|
655
|
+
const aggregators = mapNew([
|
|
656
|
+
[
|
|
657
|
+
AVG,
|
|
658
|
+
[
|
|
659
|
+
(numbers, length) => arraySum(numbers) / length,
|
|
660
|
+
(metric, add, length) => metric + (add - metric) / (length + 1),
|
|
661
|
+
(metric, remove, length) => metric + (metric - remove) / (length - 1),
|
|
662
|
+
(metric, add, remove, length) => metric + (add - remove) / length,
|
|
663
|
+
],
|
|
664
|
+
],
|
|
665
|
+
[
|
|
666
|
+
MAX,
|
|
667
|
+
[
|
|
668
|
+
(numbers) => mathMax(...numbers),
|
|
669
|
+
(metric, add) => mathMax(add, metric),
|
|
670
|
+
(metric, remove) => (remove == metric ? void 0 : metric),
|
|
671
|
+
(metric, add, remove) =>
|
|
672
|
+
remove == metric ? void 0 : mathMax(add, metric),
|
|
673
|
+
],
|
|
674
|
+
],
|
|
675
|
+
[
|
|
676
|
+
MIN,
|
|
677
|
+
[
|
|
678
|
+
(numbers) => mathMin(...numbers),
|
|
679
|
+
(metric, add) => mathMin(add, metric),
|
|
680
|
+
(metric, remove) => (remove == metric ? void 0 : metric),
|
|
681
|
+
(metric, add, remove) =>
|
|
682
|
+
remove == metric ? void 0 : mathMin(add, metric),
|
|
683
|
+
],
|
|
684
|
+
],
|
|
685
|
+
[
|
|
686
|
+
SUM,
|
|
687
|
+
[
|
|
688
|
+
(numbers) => arraySum(numbers),
|
|
689
|
+
(metric, add) => metric + add,
|
|
690
|
+
(metric, remove) => metric - remove,
|
|
691
|
+
(metric, add, remove) => metric - remove + add,
|
|
692
|
+
],
|
|
693
|
+
],
|
|
694
|
+
]);
|
|
695
|
+
const createMetrics = getCreateFunction((store) => {
|
|
696
|
+
const metricListeners = mapNew();
|
|
697
|
+
const [
|
|
698
|
+
getStore,
|
|
699
|
+
getMetricIds,
|
|
700
|
+
getTableId,
|
|
701
|
+
getMetric,
|
|
702
|
+
setMetric,
|
|
703
|
+
setDefinition,
|
|
704
|
+
delDefinition,
|
|
705
|
+
destroy,
|
|
706
|
+
] = getDefinableFunctions(store, getUndefined, (value) =>
|
|
707
|
+
isNaN(value) ||
|
|
708
|
+
isUndefined(value) ||
|
|
709
|
+
value === true ||
|
|
710
|
+
value === false ||
|
|
711
|
+
value === EMPTY_STRING
|
|
712
|
+
? void 0
|
|
713
|
+
: value * 1,
|
|
714
|
+
);
|
|
715
|
+
const [addListener, callListeners, delListenerImpl] = getListenerFunctions(
|
|
716
|
+
() => metrics,
|
|
717
|
+
);
|
|
718
|
+
const setMetricDefinition = (
|
|
719
|
+
metricId,
|
|
720
|
+
tableId,
|
|
721
|
+
aggregate,
|
|
722
|
+
getNumber,
|
|
723
|
+
aggregateAdd,
|
|
724
|
+
aggregateRemove,
|
|
725
|
+
aggregateReplace,
|
|
726
|
+
) => {
|
|
727
|
+
const metricAggregators = isFunction(aggregate)
|
|
728
|
+
? [aggregate, aggregateAdd, aggregateRemove, aggregateReplace]
|
|
729
|
+
: mapGet(aggregators, aggregate) ?? mapGet(aggregators, SUM);
|
|
730
|
+
setDefinition(
|
|
731
|
+
metricId,
|
|
732
|
+
tableId,
|
|
733
|
+
(change, changedNumbers, _changedSortKeys, numbers, _sortKeys, force) => {
|
|
734
|
+
let newMetric = getMetric(metricId);
|
|
735
|
+
let length = collSize(numbers);
|
|
736
|
+
const [aggregate2, aggregateAdd2, aggregateRemove2, aggregateReplace2] =
|
|
737
|
+
metricAggregators;
|
|
738
|
+
force = force || isUndefined(newMetric);
|
|
739
|
+
collForEach(changedNumbers, ([oldNumber, newNumber]) => {
|
|
740
|
+
if (!force) {
|
|
741
|
+
newMetric = isUndefined(oldNumber)
|
|
742
|
+
? aggregateAdd2?.(newMetric, newNumber, length++)
|
|
743
|
+
: isUndefined(newNumber)
|
|
744
|
+
? aggregateRemove2?.(newMetric, oldNumber, length--)
|
|
745
|
+
: aggregateReplace2?.(newMetric, newNumber, oldNumber, length);
|
|
746
|
+
}
|
|
747
|
+
force = force || isUndefined(newMetric);
|
|
748
|
+
});
|
|
749
|
+
change();
|
|
750
|
+
if (collIsEmpty(numbers)) {
|
|
751
|
+
newMetric = void 0;
|
|
752
|
+
} else if (force) {
|
|
753
|
+
newMetric = aggregate2(collValues(numbers), collSize(numbers));
|
|
754
|
+
}
|
|
755
|
+
if (!isFiniteNumber(newMetric)) {
|
|
756
|
+
newMetric = void 0;
|
|
757
|
+
}
|
|
758
|
+
const oldMetric = getMetric(metricId);
|
|
759
|
+
if (newMetric != oldMetric) {
|
|
760
|
+
setMetric(metricId, newMetric);
|
|
761
|
+
callListeners(metricListeners, [metricId], newMetric, oldMetric);
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
getRowCellFunction(getNumber, 1),
|
|
765
|
+
);
|
|
766
|
+
return metrics;
|
|
767
|
+
};
|
|
768
|
+
const delMetricDefinition = (metricId) => {
|
|
769
|
+
delDefinition(metricId);
|
|
770
|
+
return metrics;
|
|
771
|
+
};
|
|
772
|
+
const addMetricListener = (metricId, listener) =>
|
|
773
|
+
addListener(listener, metricListeners, [metricId]);
|
|
774
|
+
const delListener = (listenerId) => {
|
|
775
|
+
delListenerImpl(listenerId);
|
|
776
|
+
return metrics;
|
|
777
|
+
};
|
|
778
|
+
const getListenerStats = () => ({metric: collSize2(metricListeners)});
|
|
779
|
+
const metrics = {
|
|
780
|
+
setMetricDefinition,
|
|
781
|
+
delMetricDefinition,
|
|
782
|
+
getStore,
|
|
783
|
+
getMetricIds,
|
|
784
|
+
getTableId,
|
|
785
|
+
getMetric,
|
|
786
|
+
addMetricListener,
|
|
787
|
+
delListener,
|
|
788
|
+
destroy,
|
|
789
|
+
getListenerStats,
|
|
790
|
+
};
|
|
791
|
+
return objFreeze(metrics);
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
const createCustomPersister = (
|
|
795
|
+
store,
|
|
796
|
+
getPersisted,
|
|
797
|
+
setPersisted,
|
|
798
|
+
startListeningToPersisted,
|
|
799
|
+
stopListeningToPersisted,
|
|
800
|
+
) => {
|
|
801
|
+
let tablesListenerId;
|
|
802
|
+
let loadSave = 0;
|
|
803
|
+
let loads = 0;
|
|
804
|
+
let saves = 0;
|
|
805
|
+
const persister = {
|
|
806
|
+
load: async (initialTables) => {
|
|
807
|
+
/* istanbul ignore else */
|
|
808
|
+
if (loadSave != 2) {
|
|
809
|
+
loadSave = 1;
|
|
810
|
+
{
|
|
811
|
+
loads++;
|
|
812
|
+
}
|
|
813
|
+
const body = await getPersisted();
|
|
814
|
+
if (!isUndefined(body) && body != '') {
|
|
815
|
+
store.setJson(body);
|
|
816
|
+
} else {
|
|
817
|
+
store.setTables(initialTables);
|
|
818
|
+
}
|
|
819
|
+
loadSave = 0;
|
|
820
|
+
}
|
|
821
|
+
return persister;
|
|
822
|
+
},
|
|
823
|
+
startAutoLoad: async (initialTables) => {
|
|
824
|
+
persister.stopAutoLoad();
|
|
825
|
+
await persister.load(initialTables);
|
|
826
|
+
startListeningToPersisted(persister.load);
|
|
827
|
+
return persister;
|
|
828
|
+
},
|
|
829
|
+
stopAutoLoad: () => {
|
|
830
|
+
stopListeningToPersisted();
|
|
831
|
+
return persister;
|
|
832
|
+
},
|
|
833
|
+
save: async () => {
|
|
834
|
+
/* istanbul ignore else */
|
|
835
|
+
if (loadSave != 1) {
|
|
836
|
+
loadSave = 2;
|
|
837
|
+
{
|
|
838
|
+
saves++;
|
|
839
|
+
}
|
|
840
|
+
await setPersisted(store.getJson());
|
|
841
|
+
loadSave = 0;
|
|
842
|
+
}
|
|
843
|
+
return persister;
|
|
844
|
+
},
|
|
845
|
+
startAutoSave: async () => {
|
|
846
|
+
await persister.stopAutoSave().save();
|
|
847
|
+
tablesListenerId = store.addTablesListener(() => persister.save());
|
|
848
|
+
return persister;
|
|
849
|
+
},
|
|
850
|
+
stopAutoSave: () => {
|
|
851
|
+
ifNotUndefined(tablesListenerId, store.delListener);
|
|
852
|
+
return persister;
|
|
853
|
+
},
|
|
854
|
+
getStore: () => store,
|
|
855
|
+
destroy: () => persister.stopAutoLoad().stopAutoSave(),
|
|
856
|
+
getStats: () => ({loads, saves}),
|
|
857
|
+
};
|
|
858
|
+
return objFreeze(persister);
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
const STORAGE = 'storage';
|
|
862
|
+
const WINDOW = globalThis.window;
|
|
863
|
+
const getStoragePersister = (store, storageName, storage) => {
|
|
864
|
+
let listener;
|
|
865
|
+
const getPersisted = async () => storage.getItem(storageName);
|
|
866
|
+
const setPersisted = async (json) => storage.setItem(storageName, json);
|
|
867
|
+
const startListeningToPersisted = (didChange) => {
|
|
868
|
+
listener = (event) => {
|
|
869
|
+
if (event.storageArea === storage && event.key === storageName) {
|
|
870
|
+
didChange();
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
WINDOW.addEventListener(STORAGE, listener);
|
|
874
|
+
};
|
|
875
|
+
const stopListeningToPersisted = () => {
|
|
876
|
+
WINDOW.removeEventListener(STORAGE, listener);
|
|
877
|
+
listener = void 0;
|
|
878
|
+
};
|
|
879
|
+
return createCustomPersister(
|
|
880
|
+
store,
|
|
881
|
+
getPersisted,
|
|
882
|
+
setPersisted,
|
|
883
|
+
startListeningToPersisted,
|
|
884
|
+
stopListeningToPersisted,
|
|
885
|
+
);
|
|
886
|
+
};
|
|
887
|
+
const createLocalPersister = (store, storageName) =>
|
|
888
|
+
getStoragePersister(store, storageName, localStorage);
|
|
889
|
+
const createSessionPersister = (store, storageName) =>
|
|
890
|
+
getStoragePersister(store, storageName, sessionStorage);
|
|
891
|
+
|
|
892
|
+
const createFilePersister = (store, filePath) => {
|
|
893
|
+
let watcher;
|
|
894
|
+
const getPersisted = async () => {
|
|
895
|
+
try {
|
|
896
|
+
return await promises.readFile(filePath, UTF8);
|
|
897
|
+
} catch {}
|
|
898
|
+
};
|
|
899
|
+
const setPersisted = async (json) => {
|
|
900
|
+
try {
|
|
901
|
+
await promises.writeFile(filePath, json, UTF8);
|
|
902
|
+
} catch {}
|
|
903
|
+
};
|
|
904
|
+
const startListeningToPersisted = (didChange) => {
|
|
905
|
+
watcher = watch(filePath, didChange);
|
|
906
|
+
};
|
|
907
|
+
const stopListeningToPersisted = () => {
|
|
908
|
+
watcher?.close();
|
|
909
|
+
watcher = void 0;
|
|
910
|
+
};
|
|
911
|
+
return createCustomPersister(
|
|
912
|
+
store,
|
|
913
|
+
getPersisted,
|
|
914
|
+
setPersisted,
|
|
915
|
+
startListeningToPersisted,
|
|
916
|
+
stopListeningToPersisted,
|
|
917
|
+
);
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
const getETag = (response) => response.headers.get('ETag');
|
|
921
|
+
const createRemotePersister = (
|
|
922
|
+
store,
|
|
923
|
+
loadUrl,
|
|
924
|
+
saveUrl,
|
|
925
|
+
autoLoadIntervalSeconds,
|
|
926
|
+
) => {
|
|
927
|
+
let interval;
|
|
928
|
+
let lastEtag;
|
|
929
|
+
const getPersisted = async () => {
|
|
930
|
+
const response = await fetch(loadUrl);
|
|
931
|
+
lastEtag = getETag(response);
|
|
932
|
+
return response.text();
|
|
933
|
+
};
|
|
934
|
+
const setPersisted = async (json) =>
|
|
935
|
+
await fetch(saveUrl, {
|
|
936
|
+
method: 'POST',
|
|
937
|
+
headers: {'Content-Type': 'application/json'},
|
|
938
|
+
body: json,
|
|
939
|
+
});
|
|
940
|
+
const startListeningToPersisted = (didChange) => {
|
|
941
|
+
interval = setInterval(async () => {
|
|
942
|
+
const response = await fetch(loadUrl, {method: 'HEAD'});
|
|
943
|
+
const currentEtag = getETag(response);
|
|
944
|
+
if (
|
|
945
|
+
!isUndefined(lastEtag) &&
|
|
946
|
+
!isUndefined(currentEtag) &&
|
|
947
|
+
currentEtag != lastEtag
|
|
948
|
+
) {
|
|
949
|
+
lastEtag = currentEtag;
|
|
950
|
+
didChange();
|
|
951
|
+
}
|
|
952
|
+
}, autoLoadIntervalSeconds * 1e3);
|
|
953
|
+
};
|
|
954
|
+
const stopListeningToPersisted = () => {
|
|
955
|
+
ifNotUndefined(interval, clearInterval);
|
|
956
|
+
interval = void 0;
|
|
957
|
+
};
|
|
958
|
+
return createCustomPersister(
|
|
959
|
+
store,
|
|
960
|
+
getPersisted,
|
|
961
|
+
setPersisted,
|
|
962
|
+
startListeningToPersisted,
|
|
963
|
+
stopListeningToPersisted,
|
|
964
|
+
);
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
const createRelationships = getCreateFunction((store) => {
|
|
968
|
+
const remoteTableIds = mapNew();
|
|
969
|
+
const remoteRowIdListeners = mapNew();
|
|
970
|
+
const localRowIdsListeners = mapNew();
|
|
971
|
+
const linkedRowIdsListeners = mapNew();
|
|
972
|
+
const [
|
|
973
|
+
getStore,
|
|
974
|
+
getRelationshipIds,
|
|
975
|
+
getLocalTableId,
|
|
976
|
+
getRelationship,
|
|
977
|
+
_setRelationship,
|
|
978
|
+
setDefinition,
|
|
979
|
+
delDefinition,
|
|
980
|
+
destroy,
|
|
981
|
+
] = getDefinableFunctions(
|
|
982
|
+
store,
|
|
983
|
+
() => [mapNew(), mapNew(), mapNew(), mapNew()],
|
|
984
|
+
(value) => (isUndefined(value) ? void 0 : value + EMPTY_STRING),
|
|
985
|
+
);
|
|
986
|
+
const [addListener, callListeners, delListenerImpl] = getListenerFunctions(
|
|
987
|
+
() => relationships,
|
|
988
|
+
);
|
|
989
|
+
const getLinkedRowIdsCache = (relationshipId, firstRowId, skipCache) =>
|
|
990
|
+
ifNotUndefined(
|
|
991
|
+
getRelationship(relationshipId),
|
|
992
|
+
([remoteRows, , linkedRowsCache]) => {
|
|
993
|
+
if (!collHas(linkedRowsCache, firstRowId)) {
|
|
994
|
+
const linkedRows = setNew();
|
|
995
|
+
if (
|
|
996
|
+
getLocalTableId(relationshipId) != getRemoteTableId(relationshipId)
|
|
997
|
+
) {
|
|
998
|
+
setAdd(linkedRows, firstRowId);
|
|
999
|
+
} else {
|
|
1000
|
+
let rowId = firstRowId;
|
|
1001
|
+
while (!isUndefined(rowId) && !collHas(linkedRows, rowId)) {
|
|
1002
|
+
setAdd(linkedRows, rowId);
|
|
1003
|
+
rowId = mapGet(remoteRows, rowId);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
if (skipCache) {
|
|
1007
|
+
return linkedRows;
|
|
1008
|
+
}
|
|
1009
|
+
mapSet(linkedRowsCache, firstRowId, linkedRows);
|
|
1010
|
+
}
|
|
1011
|
+
return mapGet(linkedRowsCache, firstRowId);
|
|
1012
|
+
},
|
|
1013
|
+
);
|
|
1014
|
+
const delLinkedRowIdsCache = (relationshipId, firstRowId) =>
|
|
1015
|
+
ifNotUndefined(getRelationship(relationshipId), ([, , linkedRowsCache]) =>
|
|
1016
|
+
mapSet(linkedRowsCache, firstRowId),
|
|
1017
|
+
);
|
|
1018
|
+
const setRelationshipDefinition = (
|
|
1019
|
+
relationshipId,
|
|
1020
|
+
localTableId,
|
|
1021
|
+
remoteTableId,
|
|
1022
|
+
getRemoteRowId2,
|
|
1023
|
+
) => {
|
|
1024
|
+
mapSet(remoteTableIds, relationshipId, remoteTableId);
|
|
1025
|
+
setDefinition(
|
|
1026
|
+
relationshipId,
|
|
1027
|
+
localTableId,
|
|
1028
|
+
(change, changedRemoteRowIds) => {
|
|
1029
|
+
const changedLocalRows = setNew();
|
|
1030
|
+
const changedRemoteRows = setNew();
|
|
1031
|
+
const changedLinkedRows = setNew();
|
|
1032
|
+
const [localRows, remoteRows] = getRelationship(relationshipId);
|
|
1033
|
+
collForEach(
|
|
1034
|
+
changedRemoteRowIds,
|
|
1035
|
+
([oldRemoteRowId, newRemoteRowId], localRowId) => {
|
|
1036
|
+
if (!isUndefined(oldRemoteRowId)) {
|
|
1037
|
+
setAdd(changedRemoteRows, oldRemoteRowId);
|
|
1038
|
+
ifNotUndefined(
|
|
1039
|
+
mapGet(remoteRows, oldRemoteRowId),
|
|
1040
|
+
(oldRemoteRow) => {
|
|
1041
|
+
collDel(oldRemoteRow, localRowId);
|
|
1042
|
+
if (collIsEmpty(oldRemoteRow)) {
|
|
1043
|
+
mapSet(remoteRows, oldRemoteRowId);
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
if (!isUndefined(newRemoteRowId)) {
|
|
1049
|
+
setAdd(changedRemoteRows, newRemoteRowId);
|
|
1050
|
+
if (!collHas(remoteRows, newRemoteRowId)) {
|
|
1051
|
+
mapSet(remoteRows, newRemoteRowId, setNew());
|
|
1052
|
+
}
|
|
1053
|
+
setAdd(mapGet(remoteRows, newRemoteRowId), localRowId);
|
|
1054
|
+
}
|
|
1055
|
+
setAdd(changedLocalRows, localRowId);
|
|
1056
|
+
mapSet(localRows, localRowId, newRemoteRowId);
|
|
1057
|
+
mapForEach(
|
|
1058
|
+
mapGet(linkedRowIdsListeners, relationshipId),
|
|
1059
|
+
(firstRowId) => {
|
|
1060
|
+
if (
|
|
1061
|
+
collHas(
|
|
1062
|
+
getLinkedRowIdsCache(relationshipId, firstRowId),
|
|
1063
|
+
localRowId,
|
|
1064
|
+
)
|
|
1065
|
+
) {
|
|
1066
|
+
setAdd(changedLinkedRows, firstRowId);
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
);
|
|
1070
|
+
},
|
|
1071
|
+
);
|
|
1072
|
+
change();
|
|
1073
|
+
collForEach(changedLocalRows, (localRowId) =>
|
|
1074
|
+
callListeners(remoteRowIdListeners, [relationshipId, localRowId]),
|
|
1075
|
+
);
|
|
1076
|
+
collForEach(changedRemoteRows, (remoteRowId) =>
|
|
1077
|
+
callListeners(localRowIdsListeners, [relationshipId, remoteRowId]),
|
|
1078
|
+
);
|
|
1079
|
+
collForEach(changedLinkedRows, (firstRowId) => {
|
|
1080
|
+
delLinkedRowIdsCache(relationshipId, firstRowId);
|
|
1081
|
+
callListeners(linkedRowIdsListeners, [relationshipId, firstRowId]);
|
|
1082
|
+
});
|
|
1083
|
+
},
|
|
1084
|
+
getRowCellFunction(getRemoteRowId2),
|
|
1085
|
+
);
|
|
1086
|
+
return relationships;
|
|
1087
|
+
};
|
|
1088
|
+
const delRelationshipDefinition = (relationshipId) => {
|
|
1089
|
+
mapSet(remoteTableIds, relationshipId);
|
|
1090
|
+
delDefinition(relationshipId);
|
|
1091
|
+
return relationships;
|
|
1092
|
+
};
|
|
1093
|
+
const getRemoteTableId = (relationshipId) =>
|
|
1094
|
+
mapGet(remoteTableIds, relationshipId);
|
|
1095
|
+
const getRemoteRowId = (relationshipId, localRowId) =>
|
|
1096
|
+
mapGet(getRelationship(relationshipId)?.[0], localRowId);
|
|
1097
|
+
const getLocalRowIds = (relationshipId, remoteRowId) =>
|
|
1098
|
+
collValues(mapGet(getRelationship(relationshipId)?.[1], remoteRowId));
|
|
1099
|
+
const getLinkedRowIds = (relationshipId, firstRowId) =>
|
|
1100
|
+
isUndefined(getRelationship(relationshipId))
|
|
1101
|
+
? [firstRowId]
|
|
1102
|
+
: collValues(getLinkedRowIdsCache(relationshipId, firstRowId, true));
|
|
1103
|
+
const addRemoteRowIdListener = (relationshipId, localRowId, listener) =>
|
|
1104
|
+
addListener(listener, remoteRowIdListeners, [relationshipId, localRowId]);
|
|
1105
|
+
const addLocalRowIdsListener = (relationshipId, remoteRowId, listener) =>
|
|
1106
|
+
addListener(listener, localRowIdsListeners, [relationshipId, remoteRowId]);
|
|
1107
|
+
const addLinkedRowIdsListener = (relationshipId, firstRowId, listener) => {
|
|
1108
|
+
getLinkedRowIdsCache(relationshipId, firstRowId);
|
|
1109
|
+
return addListener(listener, linkedRowIdsListeners, [
|
|
1110
|
+
relationshipId,
|
|
1111
|
+
firstRowId,
|
|
1112
|
+
]);
|
|
1113
|
+
};
|
|
1114
|
+
const delListener = (listenerId) => {
|
|
1115
|
+
delLinkedRowIdsCache(...delListenerImpl(listenerId));
|
|
1116
|
+
return relationships;
|
|
1117
|
+
};
|
|
1118
|
+
const getListenerStats = () => ({
|
|
1119
|
+
remoteRowId: collSize3(remoteRowIdListeners),
|
|
1120
|
+
localRowIds: collSize3(localRowIdsListeners),
|
|
1121
|
+
linkedRowIds: collSize3(linkedRowIdsListeners),
|
|
1122
|
+
});
|
|
1123
|
+
const relationships = {
|
|
1124
|
+
setRelationshipDefinition,
|
|
1125
|
+
delRelationshipDefinition,
|
|
1126
|
+
getStore,
|
|
1127
|
+
getRelationshipIds,
|
|
1128
|
+
getLocalTableId,
|
|
1129
|
+
getRemoteTableId,
|
|
1130
|
+
getRemoteRowId,
|
|
1131
|
+
getLocalRowIds,
|
|
1132
|
+
getLinkedRowIds,
|
|
1133
|
+
addRemoteRowIdListener,
|
|
1134
|
+
addLocalRowIdsListener,
|
|
1135
|
+
addLinkedRowIdsListener,
|
|
1136
|
+
delListener,
|
|
1137
|
+
destroy,
|
|
1138
|
+
getListenerStats,
|
|
1139
|
+
};
|
|
1140
|
+
return objFreeze(relationships);
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
const transformMap = (map, toBeLikeObject, setId, delId = mapSet) => {
|
|
1144
|
+
const idsToDelete = arrayFilter(
|
|
1145
|
+
mapKeys(map),
|
|
1146
|
+
(id) => !objHas(toBeLikeObject, id),
|
|
1147
|
+
);
|
|
1148
|
+
arrayForEach(objIds(toBeLikeObject), (id) =>
|
|
1149
|
+
setId(map, id, toBeLikeObject[id]),
|
|
1150
|
+
);
|
|
1151
|
+
arrayForEach(idsToDelete, (id) => delId(map, id));
|
|
1152
|
+
return map;
|
|
1153
|
+
};
|
|
1154
|
+
const getCellType = (cell) => {
|
|
1155
|
+
const type = getTypeOf(cell);
|
|
1156
|
+
return isTypeStringOrBoolean(type) || (type == NUMBER && isFiniteNumber(cell))
|
|
1157
|
+
? type
|
|
1158
|
+
: void 0;
|
|
1159
|
+
};
|
|
1160
|
+
const validate = (obj, validateChild) => {
|
|
1161
|
+
if (isUndefined(obj) || !isObject(obj) || objFrozen(obj)) {
|
|
1162
|
+
return false;
|
|
1163
|
+
}
|
|
1164
|
+
objForEach(obj, (child, id) => {
|
|
1165
|
+
if (!validateChild(child, id)) {
|
|
1166
|
+
objDel(obj, id);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
return !objIsEmpty(obj);
|
|
1170
|
+
};
|
|
1171
|
+
const idsChanged = (ids, id, added) =>
|
|
1172
|
+
mapSet(ids, id, mapGet(ids, id) == -added ? void 0 : added);
|
|
1173
|
+
const createStore = () => {
|
|
1174
|
+
let hasSchema;
|
|
1175
|
+
let nextRowId = 0;
|
|
1176
|
+
let transactions = 0;
|
|
1177
|
+
const changedTableIds = mapNew();
|
|
1178
|
+
const changedRowIds = mapNew();
|
|
1179
|
+
const changedCellIds = mapNew();
|
|
1180
|
+
const changedCells = mapNew();
|
|
1181
|
+
const schemaMap = mapNew();
|
|
1182
|
+
const schemaDefaultRows = mapNew();
|
|
1183
|
+
const tablesMap = mapNew();
|
|
1184
|
+
const tablesListeners = mapNewPair(setNew);
|
|
1185
|
+
const tableIdsListeners = mapNewPair(setNew);
|
|
1186
|
+
const tableListeners = mapNewPair();
|
|
1187
|
+
const rowIdsListeners = mapNewPair();
|
|
1188
|
+
const rowListeners = mapNewPair();
|
|
1189
|
+
const cellIdsListeners = mapNewPair();
|
|
1190
|
+
const cellListeners = mapNewPair();
|
|
1191
|
+
const [addListener, callListeners, delListenerImpl, callListenerImpl] =
|
|
1192
|
+
getListenerFunctions(() => store);
|
|
1193
|
+
const validateSchema = (schema) =>
|
|
1194
|
+
validate(schema, (tableSchema) =>
|
|
1195
|
+
validate(tableSchema, (cellSchema) => {
|
|
1196
|
+
if (
|
|
1197
|
+
!validate(cellSchema, (_child, id) => arrayHas([TYPE, DEFAULT], id))
|
|
1198
|
+
) {
|
|
1199
|
+
return false;
|
|
1200
|
+
}
|
|
1201
|
+
const type = cellSchema[TYPE];
|
|
1202
|
+
if (!isTypeStringOrBoolean(type) && type != NUMBER) {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
if (getCellType(cellSchema[DEFAULT]) != type) {
|
|
1206
|
+
objDel(cellSchema, DEFAULT);
|
|
1207
|
+
}
|
|
1208
|
+
return true;
|
|
1209
|
+
}),
|
|
1210
|
+
);
|
|
1211
|
+
const validateTables = (tables) => validate(tables, validateTable);
|
|
1212
|
+
const validateTable = (table, tableId) =>
|
|
1213
|
+
(!hasSchema || collHas(schemaMap, tableId)) &&
|
|
1214
|
+
validate(table, (row) => validateRow(tableId, row));
|
|
1215
|
+
const validateRow = (tableId, row, skipDefaults) =>
|
|
1216
|
+
validate(
|
|
1217
|
+
skipDefaults ? row : addDefaultsToRow(row, tableId),
|
|
1218
|
+
(cell, cellId) =>
|
|
1219
|
+
ifNotUndefined(
|
|
1220
|
+
getValidatedCell(tableId, cellId, cell),
|
|
1221
|
+
(validCell) => {
|
|
1222
|
+
row[cellId] = validCell;
|
|
1223
|
+
return true;
|
|
1224
|
+
},
|
|
1225
|
+
() => false,
|
|
1226
|
+
),
|
|
1227
|
+
);
|
|
1228
|
+
const getValidatedCell = (tableId, cellId, cell) =>
|
|
1229
|
+
hasSchema
|
|
1230
|
+
? ifNotUndefined(
|
|
1231
|
+
mapGet(mapGet(schemaMap, tableId), cellId),
|
|
1232
|
+
(cellSchema) =>
|
|
1233
|
+
getCellType(cell) != cellSchema[TYPE] ? cellSchema[DEFAULT] : cell,
|
|
1234
|
+
)
|
|
1235
|
+
: isUndefined(getCellType(cell))
|
|
1236
|
+
? void 0
|
|
1237
|
+
: cell;
|
|
1238
|
+
const addDefaultsToRow = (row, tableId) => {
|
|
1239
|
+
ifNotUndefined(mapGet(schemaDefaultRows, tableId), (defaultRow) =>
|
|
1240
|
+
objForEach(defaultRow, (cell, cellId) => {
|
|
1241
|
+
if (!objHas(row, cellId)) {
|
|
1242
|
+
row[cellId] = cell;
|
|
1243
|
+
}
|
|
1244
|
+
}),
|
|
1245
|
+
);
|
|
1246
|
+
return row;
|
|
1247
|
+
};
|
|
1248
|
+
const setValidSchema = (schema) =>
|
|
1249
|
+
transformMap(
|
|
1250
|
+
schemaMap,
|
|
1251
|
+
schema,
|
|
1252
|
+
(_schema, tableId, tableSchema) => {
|
|
1253
|
+
const defaultRow = {};
|
|
1254
|
+
transformMap(
|
|
1255
|
+
mapEnsure(schemaMap, tableId, mapNew()),
|
|
1256
|
+
tableSchema,
|
|
1257
|
+
(tableSchemaMap, cellId, cellSchema) => {
|
|
1258
|
+
mapSet(tableSchemaMap, cellId, cellSchema);
|
|
1259
|
+
ifNotUndefined(
|
|
1260
|
+
cellSchema[DEFAULT],
|
|
1261
|
+
(def) => (defaultRow[cellId] = def),
|
|
1262
|
+
);
|
|
1263
|
+
},
|
|
1264
|
+
);
|
|
1265
|
+
mapSet(schemaDefaultRows, tableId, defaultRow);
|
|
1266
|
+
},
|
|
1267
|
+
(_schema, tableId) => {
|
|
1268
|
+
mapSet(schemaMap, tableId);
|
|
1269
|
+
mapSet(schemaDefaultRows, tableId);
|
|
1270
|
+
},
|
|
1271
|
+
);
|
|
1272
|
+
const setValidTables = (tables) =>
|
|
1273
|
+
transformMap(
|
|
1274
|
+
tablesMap,
|
|
1275
|
+
tables,
|
|
1276
|
+
(_tables, tableId, table) => setValidTable(tableId, table),
|
|
1277
|
+
(_tables, tableId) => delValidTable(tableId),
|
|
1278
|
+
);
|
|
1279
|
+
const setValidTable = (tableId, table) =>
|
|
1280
|
+
transformMap(
|
|
1281
|
+
mapEnsure(tablesMap, tableId, mapNew(), () =>
|
|
1282
|
+
tableIdsChanged(tableId, 1),
|
|
1283
|
+
),
|
|
1284
|
+
table,
|
|
1285
|
+
(tableMap, rowId, row) => setValidRow(tableId, tableMap, rowId, row),
|
|
1286
|
+
(tableMap, rowId) => delValidRow(tableId, tableMap, rowId),
|
|
1287
|
+
);
|
|
1288
|
+
const setValidRow = (tableId, tableMap, rowId, newRow, forceDel) =>
|
|
1289
|
+
transformMap(
|
|
1290
|
+
mapEnsure(tableMap, rowId, mapNew(), () =>
|
|
1291
|
+
rowIdsChanged(tableId, rowId, 1),
|
|
1292
|
+
),
|
|
1293
|
+
newRow,
|
|
1294
|
+
(rowMap, cellId, cell) =>
|
|
1295
|
+
setValidCell(tableId, rowId, rowMap, cellId, cell),
|
|
1296
|
+
(rowMap, cellId) =>
|
|
1297
|
+
delValidCell(tableId, tableMap, rowId, rowMap, cellId, forceDel),
|
|
1298
|
+
);
|
|
1299
|
+
const setValidCell = (tableId, rowId, rowMap, cellId, newCell) => {
|
|
1300
|
+
if (!collHas(rowMap, cellId)) {
|
|
1301
|
+
cellIdsChanged(tableId, rowId, cellId, 1);
|
|
1302
|
+
}
|
|
1303
|
+
const oldCell = mapGet(rowMap, cellId);
|
|
1304
|
+
if (newCell !== oldCell) {
|
|
1305
|
+
cellChanged(tableId, rowId, cellId, oldCell);
|
|
1306
|
+
mapSet(rowMap, cellId, newCell);
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
const setValidRowTransaction = (tableId, rowId, row) =>
|
|
1310
|
+
transaction(() =>
|
|
1311
|
+
setValidRow(tableId, getOrCreateTable(tableId), rowId, row),
|
|
1312
|
+
);
|
|
1313
|
+
const setCellIntoDefaultRow = (tableId, tableMap, rowId, cellId, validCell) =>
|
|
1314
|
+
ifNotUndefined(
|
|
1315
|
+
mapGet(tableMap, rowId),
|
|
1316
|
+
(rowMap) => setValidCell(tableId, rowId, rowMap, cellId, validCell),
|
|
1317
|
+
() =>
|
|
1318
|
+
setValidRow(
|
|
1319
|
+
tableId,
|
|
1320
|
+
tableMap,
|
|
1321
|
+
rowId,
|
|
1322
|
+
addDefaultsToRow({[cellId]: validCell}, tableId),
|
|
1323
|
+
),
|
|
1324
|
+
);
|
|
1325
|
+
const getNewRowId = (tableMap) => {
|
|
1326
|
+
const rowId = '' + nextRowId++;
|
|
1327
|
+
if (!collHas(tableMap, rowId)) {
|
|
1328
|
+
return rowId;
|
|
1329
|
+
}
|
|
1330
|
+
return getNewRowId(tableMap);
|
|
1331
|
+
};
|
|
1332
|
+
const getOrCreateTable = (tableId) =>
|
|
1333
|
+
mapGet(tablesMap, tableId) ?? setValidTable(tableId, {});
|
|
1334
|
+
const delValidTable = (tableId) => setValidTable(tableId, {});
|
|
1335
|
+
const delValidRow = (tableId, tableMap, rowId) =>
|
|
1336
|
+
setValidRow(tableId, tableMap, rowId, {}, true);
|
|
1337
|
+
const delValidCell = (tableId, table, rowId, row, cellId, forceDel) => {
|
|
1338
|
+
const defaultCell = mapGet(schemaDefaultRows, tableId)?.[cellId];
|
|
1339
|
+
if (!isUndefined(defaultCell) && !forceDel) {
|
|
1340
|
+
return setValidCell(tableId, rowId, row, cellId, defaultCell);
|
|
1341
|
+
}
|
|
1342
|
+
const delCell2 = (cellId2) => {
|
|
1343
|
+
cellChanged(tableId, rowId, cellId2, mapGet(row, cellId2));
|
|
1344
|
+
cellIdsChanged(tableId, rowId, cellId2, -1);
|
|
1345
|
+
mapSet(row, cellId2);
|
|
1346
|
+
};
|
|
1347
|
+
isUndefined(defaultCell) ? delCell2(cellId) : mapForEach(row, delCell2);
|
|
1348
|
+
if (collIsEmpty(row)) {
|
|
1349
|
+
rowIdsChanged(tableId, rowId, -1);
|
|
1350
|
+
if (collIsEmpty(mapSet(table, rowId))) {
|
|
1351
|
+
tableIdsChanged(tableId, -1);
|
|
1352
|
+
mapSet(tablesMap, tableId);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
const tableIdsChanged = (tableId, added) =>
|
|
1357
|
+
idsChanged(changedTableIds, tableId, added);
|
|
1358
|
+
const rowIdsChanged = (tableId, rowId, added) =>
|
|
1359
|
+
idsChanged(mapEnsure(changedRowIds, tableId, mapNew()), rowId, added);
|
|
1360
|
+
const cellIdsChanged = (tableId, rowId, cellId, added) =>
|
|
1361
|
+
idsChanged(
|
|
1362
|
+
mapEnsure(mapEnsure(changedCellIds, tableId, mapNew()), rowId, mapNew()),
|
|
1363
|
+
cellId,
|
|
1364
|
+
added,
|
|
1365
|
+
);
|
|
1366
|
+
const cellChanged = (tableId, rowId, cellId, oldCell) =>
|
|
1367
|
+
mapEnsure(
|
|
1368
|
+
mapEnsure(mapEnsure(changedCells, tableId, mapNew()), rowId, mapNew()),
|
|
1369
|
+
cellId,
|
|
1370
|
+
oldCell,
|
|
1371
|
+
);
|
|
1372
|
+
const getCellChange = (tableId, rowId, cellId) => {
|
|
1373
|
+
const changedRow = mapGet(mapGet(changedCells, tableId), rowId);
|
|
1374
|
+
const newCell = getCell(tableId, rowId, cellId);
|
|
1375
|
+
return collHas(changedRow, cellId)
|
|
1376
|
+
? [true, mapGet(changedRow, cellId), newCell]
|
|
1377
|
+
: [false, newCell, newCell];
|
|
1378
|
+
};
|
|
1379
|
+
const callListenersForChanges = (mutator) => {
|
|
1380
|
+
const emptyIdListeners =
|
|
1381
|
+
collIsEmpty(cellIdsListeners[mutator]) &&
|
|
1382
|
+
collIsEmpty(rowIdsListeners[mutator]) &&
|
|
1383
|
+
collIsEmpty(tableIdsListeners[mutator]);
|
|
1384
|
+
const emptyOtherListeners =
|
|
1385
|
+
collIsEmpty(cellListeners[mutator]) &&
|
|
1386
|
+
collIsEmpty(rowListeners[mutator]) &&
|
|
1387
|
+
collIsEmpty(tableListeners[mutator]) &&
|
|
1388
|
+
collIsEmpty(tablesListeners[mutator]);
|
|
1389
|
+
if (emptyIdListeners && emptyOtherListeners) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
const changes = mutator
|
|
1393
|
+
? [
|
|
1394
|
+
mapClone(changedTableIds),
|
|
1395
|
+
mapClone(changedRowIds, mapClone),
|
|
1396
|
+
mapClone(changedCellIds, (table) => mapClone(table, mapClone)),
|
|
1397
|
+
mapClone(changedCells, (table) => mapClone(table, mapClone)),
|
|
1398
|
+
]
|
|
1399
|
+
: [changedTableIds, changedRowIds, changedCellIds, changedCells];
|
|
1400
|
+
if (!emptyIdListeners) {
|
|
1401
|
+
collForEach(changes[2], (rowCellIds, tableId) =>
|
|
1402
|
+
collForEach(rowCellIds, (changedIds, rowId) => {
|
|
1403
|
+
if (!collIsEmpty(changedIds)) {
|
|
1404
|
+
callListeners(cellIdsListeners[mutator], [tableId, rowId]);
|
|
1405
|
+
}
|
|
1406
|
+
}),
|
|
1407
|
+
);
|
|
1408
|
+
collForEach(changes[1], (changedIds, tableId) => {
|
|
1409
|
+
if (!collIsEmpty(changedIds)) {
|
|
1410
|
+
callListeners(rowIdsListeners[mutator], [tableId]);
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
if (!collIsEmpty(changes[0])) {
|
|
1414
|
+
callListeners(tableIdsListeners[mutator]);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (!emptyOtherListeners) {
|
|
1418
|
+
let tablesChanged;
|
|
1419
|
+
collForEach(changes[3], (rows, tableId) => {
|
|
1420
|
+
let tableChanged;
|
|
1421
|
+
collForEach(rows, (cells, rowId) => {
|
|
1422
|
+
let rowChanged;
|
|
1423
|
+
collForEach(cells, (oldCell, cellId) => {
|
|
1424
|
+
const newCell = getCell(tableId, rowId, cellId);
|
|
1425
|
+
if (newCell !== oldCell) {
|
|
1426
|
+
callListeners(
|
|
1427
|
+
cellListeners[mutator],
|
|
1428
|
+
[tableId, rowId, cellId],
|
|
1429
|
+
newCell,
|
|
1430
|
+
oldCell,
|
|
1431
|
+
getCellChange,
|
|
1432
|
+
);
|
|
1433
|
+
tablesChanged = tableChanged = rowChanged = 1;
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
if (rowChanged) {
|
|
1437
|
+
callListeners(
|
|
1438
|
+
rowListeners[mutator],
|
|
1439
|
+
[tableId, rowId],
|
|
1440
|
+
getCellChange,
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
if (tableChanged) {
|
|
1445
|
+
callListeners(tableListeners[mutator], [tableId], getCellChange);
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
if (tablesChanged) {
|
|
1449
|
+
callListeners(tablesListeners[mutator], [], getCellChange);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
const getTables = () =>
|
|
1454
|
+
mapToObj(tablesMap, (tableMap) => mapToObj(tableMap, mapToObj));
|
|
1455
|
+
const getTableIds = () => mapKeys(tablesMap);
|
|
1456
|
+
const getTable = (tableId) => mapToObj(mapGet(tablesMap, tableId), mapToObj);
|
|
1457
|
+
const getRowIds = (tableId) => mapKeys(mapGet(tablesMap, tableId));
|
|
1458
|
+
const getRow = (tableId, rowId) =>
|
|
1459
|
+
mapToObj(mapGet(mapGet(tablesMap, tableId), rowId));
|
|
1460
|
+
const getCellIds = (tableId, rowId) =>
|
|
1461
|
+
mapKeys(mapGet(mapGet(tablesMap, tableId), rowId));
|
|
1462
|
+
const getCell = (tableId, rowId, cellId) =>
|
|
1463
|
+
mapGet(mapGet(mapGet(tablesMap, tableId), rowId), cellId);
|
|
1464
|
+
const hasTable = (tableId) => collHas(tablesMap, tableId);
|
|
1465
|
+
const hasRow = (tableId, rowId) => collHas(mapGet(tablesMap, tableId), rowId);
|
|
1466
|
+
const hasCell = (tableId, rowId, cellId) =>
|
|
1467
|
+
collHas(mapGet(mapGet(tablesMap, tableId), rowId), cellId);
|
|
1468
|
+
const getJson = () => jsonString(tablesMap);
|
|
1469
|
+
const getSchemaJson = () => jsonString(schemaMap);
|
|
1470
|
+
const setTables = (tables) => {
|
|
1471
|
+
if (validateTables(tables)) {
|
|
1472
|
+
transaction(() => setValidTables(tables));
|
|
1473
|
+
}
|
|
1474
|
+
return store;
|
|
1475
|
+
};
|
|
1476
|
+
const setTable = (tableId, table) => {
|
|
1477
|
+
if (validateTable(table, tableId)) {
|
|
1478
|
+
transaction(() => setValidTable(tableId, table));
|
|
1479
|
+
}
|
|
1480
|
+
return store;
|
|
1481
|
+
};
|
|
1482
|
+
const setRow = (tableId, rowId, row) => {
|
|
1483
|
+
if (validateRow(tableId, row)) {
|
|
1484
|
+
setValidRowTransaction(tableId, rowId, row);
|
|
1485
|
+
}
|
|
1486
|
+
return store;
|
|
1487
|
+
};
|
|
1488
|
+
const addRow = (tableId, row) => {
|
|
1489
|
+
let rowId = void 0;
|
|
1490
|
+
if (validateRow(tableId, row)) {
|
|
1491
|
+
rowId = getNewRowId(mapGet(tablesMap, tableId));
|
|
1492
|
+
setValidRowTransaction(tableId, rowId, row);
|
|
1493
|
+
}
|
|
1494
|
+
return rowId;
|
|
1495
|
+
};
|
|
1496
|
+
const setPartialRow = (tableId, rowId, partialRow) => {
|
|
1497
|
+
if (validateRow(tableId, partialRow, 1)) {
|
|
1498
|
+
transaction(() => {
|
|
1499
|
+
const table = getOrCreateTable(tableId);
|
|
1500
|
+
objForEach(partialRow, (cell, cellId) =>
|
|
1501
|
+
setCellIntoDefaultRow(tableId, table, rowId, cellId, cell),
|
|
1502
|
+
);
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
return store;
|
|
1506
|
+
};
|
|
1507
|
+
const setCell = (tableId, rowId, cellId, cell) => {
|
|
1508
|
+
ifNotUndefined(
|
|
1509
|
+
getValidatedCell(
|
|
1510
|
+
tableId,
|
|
1511
|
+
cellId,
|
|
1512
|
+
isFunction(cell) ? cell(getCell(tableId, rowId, cellId)) : cell,
|
|
1513
|
+
),
|
|
1514
|
+
(validCell) =>
|
|
1515
|
+
transaction(() =>
|
|
1516
|
+
setCellIntoDefaultRow(
|
|
1517
|
+
tableId,
|
|
1518
|
+
getOrCreateTable(tableId),
|
|
1519
|
+
rowId,
|
|
1520
|
+
cellId,
|
|
1521
|
+
validCell,
|
|
1522
|
+
),
|
|
1523
|
+
),
|
|
1524
|
+
);
|
|
1525
|
+
return store;
|
|
1526
|
+
};
|
|
1527
|
+
const setJson = (json) => {
|
|
1528
|
+
try {
|
|
1529
|
+
json === EMPTY_OBJECT ? delTables() : setTables(jsonParse(json));
|
|
1530
|
+
} catch {}
|
|
1531
|
+
return store;
|
|
1532
|
+
};
|
|
1533
|
+
const setSchema = (schema) => {
|
|
1534
|
+
if ((hasSchema = validateSchema(schema))) {
|
|
1535
|
+
setValidSchema(schema);
|
|
1536
|
+
if (!collIsEmpty(tablesMap)) {
|
|
1537
|
+
const tables = getTables();
|
|
1538
|
+
delTables();
|
|
1539
|
+
setTables(tables);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return store;
|
|
1543
|
+
};
|
|
1544
|
+
const delTables = () => {
|
|
1545
|
+
transaction(() => setValidTables({}));
|
|
1546
|
+
return store;
|
|
1547
|
+
};
|
|
1548
|
+
const delTable = (tableId) => {
|
|
1549
|
+
if (collHas(tablesMap, tableId)) {
|
|
1550
|
+
transaction(() => delValidTable(tableId));
|
|
1551
|
+
}
|
|
1552
|
+
return store;
|
|
1553
|
+
};
|
|
1554
|
+
const delRow = (tableId, rowId) => {
|
|
1555
|
+
ifNotUndefined(mapGet(tablesMap, tableId), (tableMap) => {
|
|
1556
|
+
if (collHas(tableMap, rowId)) {
|
|
1557
|
+
transaction(() => delValidRow(tableId, tableMap, rowId));
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
return store;
|
|
1561
|
+
};
|
|
1562
|
+
const delCell = (tableId, rowId, cellId, forceDel) => {
|
|
1563
|
+
ifNotUndefined(mapGet(tablesMap, tableId), (tableMap) =>
|
|
1564
|
+
ifNotUndefined(mapGet(tableMap, rowId), (rowMap) => {
|
|
1565
|
+
if (collHas(rowMap, cellId)) {
|
|
1566
|
+
transaction(() =>
|
|
1567
|
+
delValidCell(tableId, tableMap, rowId, rowMap, cellId, forceDel),
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
}),
|
|
1571
|
+
);
|
|
1572
|
+
return store;
|
|
1573
|
+
};
|
|
1574
|
+
const delSchema = () => {
|
|
1575
|
+
setValidSchema({});
|
|
1576
|
+
hasSchema = false;
|
|
1577
|
+
return store;
|
|
1578
|
+
};
|
|
1579
|
+
const transaction = (actions) => {
|
|
1580
|
+
if (transactions == -1) {
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
transactions++;
|
|
1584
|
+
const result = actions();
|
|
1585
|
+
transactions--;
|
|
1586
|
+
if (transactions == 0) {
|
|
1587
|
+
transactions = 1;
|
|
1588
|
+
callListenersForChanges(1);
|
|
1589
|
+
transactions = -1;
|
|
1590
|
+
callListenersForChanges(0);
|
|
1591
|
+
transactions = 0;
|
|
1592
|
+
arrayForEach(
|
|
1593
|
+
[changedCells, changedTableIds, changedRowIds, changedCellIds],
|
|
1594
|
+
collClear,
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
return result;
|
|
1598
|
+
};
|
|
1599
|
+
const forEachTable = (tableCallback) =>
|
|
1600
|
+
collForEach(tablesMap, (tableMap, tableId) =>
|
|
1601
|
+
tableCallback(tableId, (rowCallback) =>
|
|
1602
|
+
collForEach(tableMap, (rowMap, rowId) =>
|
|
1603
|
+
rowCallback(rowId, (cellCallback) =>
|
|
1604
|
+
mapForEach(rowMap, cellCallback),
|
|
1605
|
+
),
|
|
1606
|
+
),
|
|
1607
|
+
),
|
|
1608
|
+
);
|
|
1609
|
+
const forEachRow = (tableId, rowCallback) =>
|
|
1610
|
+
collForEach(mapGet(tablesMap, tableId), (rowMap, rowId) =>
|
|
1611
|
+
rowCallback(rowId, (cellCallback) => mapForEach(rowMap, cellCallback)),
|
|
1612
|
+
);
|
|
1613
|
+
const forEachCell = (tableId, rowId, cellCallback) =>
|
|
1614
|
+
mapForEach(mapGet(mapGet(tablesMap, tableId), rowId), cellCallback);
|
|
1615
|
+
const addTablesListener = (listener, mutator) =>
|
|
1616
|
+
addListener(listener, tablesListeners[mutator ? 1 : 0]);
|
|
1617
|
+
const addTableIdsListener = (listener, mutator) =>
|
|
1618
|
+
addListener(listener, tableIdsListeners[mutator ? 1 : 0]);
|
|
1619
|
+
const addTableListener = (tableId, listener, mutator) =>
|
|
1620
|
+
addListener(listener, tableListeners[mutator ? 1 : 0], [tableId]);
|
|
1621
|
+
const addRowIdsListener = (tableId, listener, mutator) =>
|
|
1622
|
+
addListener(listener, rowIdsListeners[mutator ? 1 : 0], [tableId]);
|
|
1623
|
+
const addRowListener = (tableId, rowId, listener, mutator) =>
|
|
1624
|
+
addListener(listener, rowListeners[mutator ? 1 : 0], [tableId, rowId]);
|
|
1625
|
+
const addCellIdsListener = (tableId, rowId, listener, mutator) =>
|
|
1626
|
+
addListener(listener, cellIdsListeners[mutator ? 1 : 0], [tableId, rowId]);
|
|
1627
|
+
const addCellListener = (tableId, rowId, cellId, listener, mutator) =>
|
|
1628
|
+
addListener(listener, cellListeners[mutator ? 1 : 0], [
|
|
1629
|
+
tableId,
|
|
1630
|
+
rowId,
|
|
1631
|
+
cellId,
|
|
1632
|
+
]);
|
|
1633
|
+
const callListener = (listenerId) => {
|
|
1634
|
+
callListenerImpl(listenerId, [getTableIds, getRowIds, getCellIds], (ids) =>
|
|
1635
|
+
isUndefined(ids[2]) ? [] : Array(2).fill(getCell(...ids)),
|
|
1636
|
+
);
|
|
1637
|
+
return store;
|
|
1638
|
+
};
|
|
1639
|
+
const delListener = (listenerId) => {
|
|
1640
|
+
delListenerImpl(listenerId);
|
|
1641
|
+
return store;
|
|
1642
|
+
};
|
|
1643
|
+
const getListenerStats = () => ({
|
|
1644
|
+
tables: collPairSize(tablesListeners),
|
|
1645
|
+
tableIds: collPairSize(tableIdsListeners),
|
|
1646
|
+
table: collPairSize(tableListeners, collSize2),
|
|
1647
|
+
rowIds: collPairSize(rowIdsListeners, collSize2),
|
|
1648
|
+
row: collPairSize(rowListeners, collSize3),
|
|
1649
|
+
cellIds: collPairSize(cellIdsListeners, collSize3),
|
|
1650
|
+
cell: collPairSize(cellListeners, collSize4),
|
|
1651
|
+
});
|
|
1652
|
+
const store = {
|
|
1653
|
+
getTables,
|
|
1654
|
+
getTableIds,
|
|
1655
|
+
getTable,
|
|
1656
|
+
getRowIds,
|
|
1657
|
+
getRow,
|
|
1658
|
+
getCellIds,
|
|
1659
|
+
getCell,
|
|
1660
|
+
hasTable,
|
|
1661
|
+
hasRow,
|
|
1662
|
+
hasCell,
|
|
1663
|
+
getJson,
|
|
1664
|
+
getSchemaJson,
|
|
1665
|
+
setTables,
|
|
1666
|
+
setTable,
|
|
1667
|
+
setRow,
|
|
1668
|
+
addRow,
|
|
1669
|
+
setPartialRow,
|
|
1670
|
+
setCell,
|
|
1671
|
+
setJson,
|
|
1672
|
+
setSchema,
|
|
1673
|
+
delTables,
|
|
1674
|
+
delTable,
|
|
1675
|
+
delRow,
|
|
1676
|
+
delCell,
|
|
1677
|
+
delSchema,
|
|
1678
|
+
transaction,
|
|
1679
|
+
forEachTable,
|
|
1680
|
+
forEachRow,
|
|
1681
|
+
forEachCell,
|
|
1682
|
+
addTablesListener,
|
|
1683
|
+
addTableIdsListener,
|
|
1684
|
+
addTableListener,
|
|
1685
|
+
addRowIdsListener,
|
|
1686
|
+
addRowListener,
|
|
1687
|
+
addCellIdsListener,
|
|
1688
|
+
addCellListener,
|
|
1689
|
+
callListener,
|
|
1690
|
+
delListener,
|
|
1691
|
+
getListenerStats,
|
|
1692
|
+
};
|
|
1693
|
+
return objFreeze(store);
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1696
|
+
export {
|
|
1697
|
+
createCheckpoints,
|
|
1698
|
+
createCustomPersister,
|
|
1699
|
+
createFilePersister,
|
|
1700
|
+
createIndexes,
|
|
1701
|
+
createLocalPersister,
|
|
1702
|
+
createMetrics,
|
|
1703
|
+
createRelationships,
|
|
1704
|
+
createRemotePersister,
|
|
1705
|
+
createSessionPersister,
|
|
1706
|
+
createStore,
|
|
1707
|
+
defaultSorter,
|
|
1708
|
+
};
|