tinybase 5.0.0-beta.6 → 5.0.0-beta.7
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/lib/cjs/synchronizers/synchronizer-local.cjs +1 -0
- package/lib/cjs/synchronizers/synchronizer-local.cjs.gz +0 -0
- package/lib/cjs/synchronizers/synchronizer-ws-client.cjs +1 -0
- package/lib/cjs/synchronizers/synchronizer-ws-client.cjs.gz +0 -0
- package/lib/cjs/synchronizers/synchronizer-ws-server.cjs +1 -0
- package/lib/cjs/synchronizers/synchronizer-ws-server.cjs.gz +0 -0
- package/lib/cjs/synchronizers.cjs +1 -0
- package/lib/cjs/synchronizers.cjs.gz +0 -0
- package/lib/cjs/tinybase.cjs +1 -1
- package/lib/cjs/tinybase.cjs.gz +0 -0
- package/lib/cjs-es6/synchronizers/synchronizer-local.cjs +1 -0
- package/lib/cjs-es6/synchronizers/synchronizer-local.cjs.gz +0 -0
- package/lib/cjs-es6/synchronizers/synchronizer-ws-client.cjs +1 -0
- package/lib/cjs-es6/synchronizers/synchronizer-ws-client.cjs.gz +0 -0
- package/lib/cjs-es6/synchronizers/synchronizer-ws-server.cjs +1 -0
- package/lib/cjs-es6/synchronizers/synchronizer-ws-server.cjs.gz +0 -0
- package/lib/cjs-es6/synchronizers.cjs +1 -0
- package/lib/cjs-es6/synchronizers.cjs.gz +0 -0
- package/lib/cjs-es6/tinybase.cjs +1 -1
- package/lib/cjs-es6/tinybase.cjs.gz +0 -0
- package/lib/debug/synchronizers/synchronizer-local.js +502 -0
- package/lib/debug/{persisters/persister-sync.js → synchronizers/synchronizer-ws-client.js} +83 -152
- package/lib/debug/synchronizers/synchronizer-ws-server.js +57 -0
- package/lib/debug/synchronizers.js +466 -0
- package/lib/debug/tinybase.js +254 -84
- package/lib/es6/synchronizers/synchronizer-local.js +1 -0
- package/lib/es6/synchronizers/synchronizer-local.js.gz +0 -0
- package/lib/es6/synchronizers/synchronizer-ws-client.js +1 -0
- package/lib/es6/synchronizers/synchronizer-ws-client.js.gz +0 -0
- package/lib/es6/synchronizers/synchronizer-ws-server.js +1 -0
- package/lib/es6/synchronizers/synchronizer-ws-server.js.gz +0 -0
- package/lib/es6/synchronizers.js +1 -0
- package/lib/es6/synchronizers.js.gz +0 -0
- package/lib/es6/tinybase.js +1 -1
- package/lib/es6/tinybase.js.gz +0 -0
- package/lib/synchronizers/synchronizer-local.js +1 -0
- package/lib/synchronizers/synchronizer-local.js.gz +0 -0
- package/lib/synchronizers/synchronizer-ws-client.js +1 -0
- package/lib/synchronizers/synchronizer-ws-client.js.gz +0 -0
- package/lib/synchronizers/synchronizer-ws-server.js +1 -0
- package/lib/synchronizers/synchronizer-ws-server.js.gz +0 -0
- package/lib/synchronizers.js +1 -0
- package/lib/synchronizers.js.gz +0 -0
- package/lib/tinybase.js +1 -1
- package/lib/tinybase.js.gz +0 -0
- package/lib/types/persisters.d.ts +8 -16
- package/lib/types/synchronizers/synchronizer-local.d.ts +27 -0
- package/lib/types/synchronizers/synchronizer-ws-client.d.ts +30 -0
- package/lib/types/synchronizers/synchronizer-ws-server.d.ts +24 -0
- package/lib/types/synchronizers.d.ts +131 -0
- package/lib/types/tinybase.d.ts +1 -0
- package/lib/types/with-schemas/persisters.d.ts +13 -57
- package/lib/types/with-schemas/synchronizers/synchronizer-local.d.ts +29 -0
- package/lib/types/with-schemas/synchronizers/synchronizer-ws-client.d.ts +32 -0
- package/lib/types/with-schemas/synchronizers/synchronizer-ws-server.d.ts +24 -0
- package/lib/types/with-schemas/synchronizers.d.ts +146 -0
- package/lib/umd/synchronizers/synchronizer-local.js +1 -0
- package/lib/umd/synchronizers/synchronizer-local.js.gz +0 -0
- package/lib/umd/synchronizers/synchronizer-ws-client.js +1 -0
- package/lib/umd/synchronizers/synchronizer-ws-client.js.gz +0 -0
- package/lib/umd/synchronizers/synchronizer-ws-server.js +1 -0
- package/lib/umd/synchronizers/synchronizer-ws-server.js.gz +0 -0
- package/lib/umd/synchronizers.js +1 -0
- package/lib/umd/synchronizers.js.gz +0 -0
- package/lib/umd/tinybase.js +1 -1
- package/lib/umd/tinybase.js.gz +0 -0
- package/lib/umd-es6/synchronizers/synchronizer-local.js +1 -0
- package/lib/umd-es6/synchronizers/synchronizer-local.js.gz +0 -0
- package/lib/umd-es6/synchronizers/synchronizer-ws-client.js +1 -0
- package/lib/umd-es6/synchronizers/synchronizer-ws-client.js.gz +0 -0
- package/lib/umd-es6/synchronizers/synchronizer-ws-server.js +1 -0
- package/lib/umd-es6/synchronizers/synchronizer-ws-server.js.gz +0 -0
- package/lib/umd-es6/synchronizers.js +1 -0
- package/lib/umd-es6/synchronizers.js.gz +0 -0
- package/lib/umd-es6/tinybase.js +1 -1
- package/lib/umd-es6/tinybase.js.gz +0 -0
- package/package.json +1 -1
- package/readme.md +2 -2
- package/lib/cjs/persisters/persister-sync.cjs +0 -1
- package/lib/cjs/persisters/persister-sync.cjs.gz +0 -0
- package/lib/cjs-es6/persisters/persister-sync.cjs +0 -1
- package/lib/cjs-es6/persisters/persister-sync.cjs.gz +0 -0
- package/lib/es6/persisters/persister-sync.js +0 -1
- package/lib/es6/persisters/persister-sync.js.gz +0 -0
- package/lib/persisters/persister-sync.js +0 -1
- package/lib/persisters/persister-sync.js.gz +0 -0
- package/lib/types/persisters/persister-sync.d.ts +0 -182
- package/lib/types/with-schemas/persisters/persister-sync.d.ts +0 -195
- package/lib/umd/persisters/persister-sync.js +0 -1
- package/lib/umd/persisters/persister-sync.js.gz +0 -0
- package/lib/umd-es6/persisters/persister-sync.js +0 -1
- package/lib/umd-es6/persisters/persister-sync.js.gz +0 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
const getTypeOf = (thing) => typeof thing;
|
|
2
|
+
const EMPTY_STRING = '';
|
|
3
|
+
const STRING = getTypeOf(EMPTY_STRING);
|
|
4
|
+
const strCharCodeAt = (str, position) => str.charCodeAt(position);
|
|
5
|
+
|
|
6
|
+
const promise = Promise;
|
|
7
|
+
const mathMax = Math.max;
|
|
8
|
+
const isUndefined = (thing) => thing == void 0;
|
|
9
|
+
const ifNotUndefined = (value, then, otherwise) =>
|
|
10
|
+
isUndefined(value) ? otherwise?.() : then(value);
|
|
11
|
+
const isString = (thing) => getTypeOf(thing) == STRING;
|
|
12
|
+
const size = (arrayOrString) => arrayOrString.length;
|
|
13
|
+
const promiseNew = (resolver) => new promise(resolver);
|
|
14
|
+
|
|
15
|
+
const arrayForEach = (array, cb) => array.forEach(cb);
|
|
16
|
+
const arrayPush = (array, ...values) => array.push(...values);
|
|
17
|
+
const arrayShift = (array) => array.shift();
|
|
18
|
+
|
|
19
|
+
const object = Object;
|
|
20
|
+
const getPrototypeOf = (obj) => object.getPrototypeOf(obj);
|
|
21
|
+
const isObject = (obj) =>
|
|
22
|
+
!isUndefined(obj) &&
|
|
23
|
+
ifNotUndefined(
|
|
24
|
+
getPrototypeOf(obj),
|
|
25
|
+
(objPrototype) =>
|
|
26
|
+
objPrototype == object.prototype ||
|
|
27
|
+
isUndefined(getPrototypeOf(objPrototype)),
|
|
28
|
+
/* istanbul ignore next */
|
|
29
|
+
() => true,
|
|
30
|
+
);
|
|
31
|
+
const objIds = object.keys;
|
|
32
|
+
const objFreeze = object.freeze;
|
|
33
|
+
const objSize = (obj) => size(objIds(obj));
|
|
34
|
+
const objIsEmpty = (obj) => isObject(obj) && objSize(obj) == 0;
|
|
35
|
+
|
|
36
|
+
const collHas = (coll, keyOrValue) => coll?.has(keyOrValue) ?? false;
|
|
37
|
+
const collForEach = (coll, cb) => coll?.forEach(cb);
|
|
38
|
+
const collDel = (coll, keyOrValue) => coll?.delete(keyOrValue);
|
|
39
|
+
|
|
40
|
+
const mapNew = (entries) => new Map(entries);
|
|
41
|
+
const mapGet = (map, key) => map?.get(key);
|
|
42
|
+
const mapForEach = (map, cb) =>
|
|
43
|
+
collForEach(map, (value, key) => cb(key, value));
|
|
44
|
+
const mapSet = (map, key, value) =>
|
|
45
|
+
isUndefined(value) ? (collDel(map, key), map) : map?.set(key, value);
|
|
46
|
+
const mapEnsure = (map, key, getDefaultValue, hadExistingValue) => {
|
|
47
|
+
if (!collHas(map, key)) {
|
|
48
|
+
mapSet(map, key, getDefaultValue());
|
|
49
|
+
} else {
|
|
50
|
+
hadExistingValue?.(mapGet(map, key));
|
|
51
|
+
}
|
|
52
|
+
return mapGet(map, key);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const scheduleRunning = mapNew();
|
|
56
|
+
const scheduleActions = mapNew();
|
|
57
|
+
const getStoreFunctions = (supportsMergeableStore, store) =>
|
|
58
|
+
!supportsMergeableStore || isUndefined(store.getMergeableContent)
|
|
59
|
+
? [
|
|
60
|
+
0,
|
|
61
|
+
store.getContent,
|
|
62
|
+
store.getTransactionChanges,
|
|
63
|
+
([changedTables, changedValues]) =>
|
|
64
|
+
!objIsEmpty(changedTables) || !objIsEmpty(changedValues),
|
|
65
|
+
]
|
|
66
|
+
: [
|
|
67
|
+
1,
|
|
68
|
+
store.getMergeableContent,
|
|
69
|
+
store.getTransactionMergeableChanges,
|
|
70
|
+
([, [[, changedTables], [, changedValues]]]) =>
|
|
71
|
+
!objIsEmpty(changedTables) || !objIsEmpty(changedValues),
|
|
72
|
+
];
|
|
73
|
+
const createCustomPersister = (
|
|
74
|
+
store,
|
|
75
|
+
getPersisted,
|
|
76
|
+
setPersisted,
|
|
77
|
+
addPersisterListener,
|
|
78
|
+
delPersisterListener,
|
|
79
|
+
onIgnoredError,
|
|
80
|
+
supportsMergeableStore,
|
|
81
|
+
extra = {},
|
|
82
|
+
scheduleId = [],
|
|
83
|
+
) => {
|
|
84
|
+
let loadSave = 0;
|
|
85
|
+
let loads = 0;
|
|
86
|
+
let saves = 0;
|
|
87
|
+
let action;
|
|
88
|
+
let autoLoadHandle;
|
|
89
|
+
let autoSaveListenerId;
|
|
90
|
+
mapEnsure(scheduleRunning, scheduleId, () => 0);
|
|
91
|
+
mapEnsure(scheduleActions, scheduleId, () => []);
|
|
92
|
+
const [isMergeableStore, getContent, getChanges, hasChanges] =
|
|
93
|
+
getStoreFunctions(supportsMergeableStore, store);
|
|
94
|
+
const run = async () => {
|
|
95
|
+
/* istanbul ignore else */
|
|
96
|
+
if (!mapGet(scheduleRunning, scheduleId)) {
|
|
97
|
+
mapSet(scheduleRunning, scheduleId, 1);
|
|
98
|
+
while (
|
|
99
|
+
!isUndefined((action = arrayShift(mapGet(scheduleActions, scheduleId))))
|
|
100
|
+
) {
|
|
101
|
+
try {
|
|
102
|
+
await action();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
/* istanbul ignore next */
|
|
105
|
+
onIgnoredError?.(error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
mapSet(scheduleRunning, scheduleId, 0);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const loadLock = async (actions) => {
|
|
112
|
+
/* istanbul ignore else */
|
|
113
|
+
if (loadSave != 2) {
|
|
114
|
+
loadSave = 1;
|
|
115
|
+
{
|
|
116
|
+
loads++;
|
|
117
|
+
}
|
|
118
|
+
await persister.schedule(async () => {
|
|
119
|
+
await actions();
|
|
120
|
+
loadSave = 0;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return persister;
|
|
124
|
+
};
|
|
125
|
+
const setContentOrChanges = (contentOrChanges) => {
|
|
126
|
+
(isMergeableStore && isString(contentOrChanges?.[0])
|
|
127
|
+
? contentOrChanges?.[1][2] === 1
|
|
128
|
+
? store.applyMergeableChanges
|
|
129
|
+
: store.setMergeableContent
|
|
130
|
+
: contentOrChanges?.[2] === 1
|
|
131
|
+
? store.applyChanges
|
|
132
|
+
: store.setContent)(contentOrChanges);
|
|
133
|
+
};
|
|
134
|
+
const persister = {
|
|
135
|
+
load: async (initialTables, initialValues) =>
|
|
136
|
+
await loadLock(async () => {
|
|
137
|
+
try {
|
|
138
|
+
setContentOrChanges(await getPersisted());
|
|
139
|
+
} catch (error) {
|
|
140
|
+
onIgnoredError?.(error);
|
|
141
|
+
store.setContent([initialTables, initialValues]);
|
|
142
|
+
}
|
|
143
|
+
}),
|
|
144
|
+
startAutoLoad: async (initialTables = {}, initialValues = {}) => {
|
|
145
|
+
await persister.stopAutoLoad().load(initialTables, initialValues);
|
|
146
|
+
autoLoadHandle = addPersisterListener(
|
|
147
|
+
async (getContent2, getChanges2) => {
|
|
148
|
+
const changes = getChanges2?.();
|
|
149
|
+
await loadLock(async () => {
|
|
150
|
+
try {
|
|
151
|
+
setContentOrChanges(
|
|
152
|
+
changes ?? getContent2?.() ?? (await getPersisted()),
|
|
153
|
+
);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
onIgnoredError?.(error);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
return persister;
|
|
161
|
+
},
|
|
162
|
+
stopAutoLoad: () => {
|
|
163
|
+
if (autoLoadHandle) {
|
|
164
|
+
delPersisterListener(autoLoadHandle);
|
|
165
|
+
autoLoadHandle = void 0;
|
|
166
|
+
}
|
|
167
|
+
return persister;
|
|
168
|
+
},
|
|
169
|
+
isAutoLoading: () => !isUndefined(autoLoadHandle),
|
|
170
|
+
save: async (getChanges2) => {
|
|
171
|
+
/* istanbul ignore else */
|
|
172
|
+
if (loadSave != 1) {
|
|
173
|
+
loadSave = 2;
|
|
174
|
+
{
|
|
175
|
+
saves++;
|
|
176
|
+
}
|
|
177
|
+
await persister.schedule(async () => {
|
|
178
|
+
try {
|
|
179
|
+
await setPersisted(getContent, getChanges2);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
/* istanbul ignore next */
|
|
182
|
+
onIgnoredError?.(error);
|
|
183
|
+
}
|
|
184
|
+
loadSave = 0;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return persister;
|
|
188
|
+
},
|
|
189
|
+
startAutoSave: async () => {
|
|
190
|
+
await persister.stopAutoSave().save();
|
|
191
|
+
autoSaveListenerId = store.addDidFinishTransactionListener(() => {
|
|
192
|
+
const changes = getChanges();
|
|
193
|
+
if (hasChanges(changes)) {
|
|
194
|
+
persister.save(() => changes);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
return persister;
|
|
198
|
+
},
|
|
199
|
+
stopAutoSave: () => {
|
|
200
|
+
ifNotUndefined(autoSaveListenerId, store.delListener);
|
|
201
|
+
autoSaveListenerId = void 0;
|
|
202
|
+
return persister;
|
|
203
|
+
},
|
|
204
|
+
isAutoSaving: () => !isUndefined(autoSaveListenerId),
|
|
205
|
+
schedule: async (...actions) => {
|
|
206
|
+
arrayPush(mapGet(scheduleActions, scheduleId), ...actions);
|
|
207
|
+
await run();
|
|
208
|
+
return persister;
|
|
209
|
+
},
|
|
210
|
+
getStore: () => store,
|
|
211
|
+
destroy: () => persister.stopAutoLoad().stopAutoSave(),
|
|
212
|
+
getStats: () => ({loads, saves}),
|
|
213
|
+
...extra,
|
|
214
|
+
};
|
|
215
|
+
return objFreeze(persister);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const textEncoder = new globalThis.TextEncoder();
|
|
219
|
+
const getHash = (value) => {
|
|
220
|
+
let hash = 2166136261;
|
|
221
|
+
arrayForEach(textEncoder.encode(value), (char) => {
|
|
222
|
+
hash ^= char;
|
|
223
|
+
hash +=
|
|
224
|
+
(hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
225
|
+
});
|
|
226
|
+
return hash >>> 0;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const MASK6 = 63;
|
|
230
|
+
const SHIFT36 = 2 ** 36;
|
|
231
|
+
const SHIFT30 = 2 ** 30;
|
|
232
|
+
const SHIFT24 = 2 ** 24;
|
|
233
|
+
const SHIFT18 = 2 ** 18;
|
|
234
|
+
const SHIFT12 = 2 ** 12;
|
|
235
|
+
const SHIFT6 = 2 ** 6;
|
|
236
|
+
const toB64 = (num) => String.fromCharCode(48 + (num & MASK6));
|
|
237
|
+
const fromB64 = (str, pos) => strCharCodeAt(str, pos) - 48;
|
|
238
|
+
const encodeHlc = (logicalTime42, counter24, clientHash30) =>
|
|
239
|
+
toB64(logicalTime42 / SHIFT36) +
|
|
240
|
+
toB64(logicalTime42 / SHIFT30) +
|
|
241
|
+
toB64(logicalTime42 / SHIFT24) +
|
|
242
|
+
toB64(logicalTime42 / SHIFT18) +
|
|
243
|
+
toB64(logicalTime42 / SHIFT12) +
|
|
244
|
+
toB64(logicalTime42 / SHIFT6) +
|
|
245
|
+
toB64(logicalTime42) +
|
|
246
|
+
toB64(counter24 / SHIFT18) +
|
|
247
|
+
toB64(counter24 / SHIFT12) +
|
|
248
|
+
toB64(counter24 / SHIFT6) +
|
|
249
|
+
toB64(counter24) +
|
|
250
|
+
toB64(clientHash30 / SHIFT24) +
|
|
251
|
+
toB64(clientHash30 / SHIFT18) +
|
|
252
|
+
toB64(clientHash30 / SHIFT12) +
|
|
253
|
+
toB64(clientHash30 / SHIFT6) +
|
|
254
|
+
toB64(clientHash30);
|
|
255
|
+
const decodeHlc = (hlc16) => [
|
|
256
|
+
fromB64(hlc16, 0) * SHIFT36 +
|
|
257
|
+
fromB64(hlc16, 1) * SHIFT30 +
|
|
258
|
+
fromB64(hlc16, 2) * SHIFT24 +
|
|
259
|
+
fromB64(hlc16, 3) * SHIFT18 +
|
|
260
|
+
fromB64(hlc16, 4) * SHIFT12 +
|
|
261
|
+
fromB64(hlc16, 5) * SHIFT6 +
|
|
262
|
+
fromB64(hlc16, 6),
|
|
263
|
+
fromB64(hlc16, 7) * SHIFT18 +
|
|
264
|
+
fromB64(hlc16, 8) * SHIFT12 +
|
|
265
|
+
fromB64(hlc16, 9) * SHIFT6 +
|
|
266
|
+
fromB64(hlc16, 10),
|
|
267
|
+
fromB64(hlc16, 11) * SHIFT24 +
|
|
268
|
+
fromB64(hlc16, 12) * SHIFT18 +
|
|
269
|
+
fromB64(hlc16, 13) * SHIFT12 +
|
|
270
|
+
fromB64(hlc16, 14) * SHIFT6 +
|
|
271
|
+
fromB64(hlc16, 15),
|
|
272
|
+
];
|
|
273
|
+
const getHlcFunctions = (uniqueId) => {
|
|
274
|
+
let logicalTime = 0;
|
|
275
|
+
let lastCounter = -1;
|
|
276
|
+
const uniqueIdHash = getHash(uniqueId);
|
|
277
|
+
const getHlc = () => {
|
|
278
|
+
seenHlc();
|
|
279
|
+
return encodeHlc(logicalTime, ++lastCounter, uniqueIdHash);
|
|
280
|
+
};
|
|
281
|
+
const seenHlc = (hlc) => {
|
|
282
|
+
const previousLogicalTime = logicalTime;
|
|
283
|
+
const [remoteLogicalTime, remoteCounter] =
|
|
284
|
+
isUndefined(hlc) || hlc == '' ? [0, 0] : decodeHlc(hlc);
|
|
285
|
+
logicalTime = mathMax(
|
|
286
|
+
previousLogicalTime,
|
|
287
|
+
remoteLogicalTime,
|
|
288
|
+
globalThis.HLC_TIME ?? Date.now(),
|
|
289
|
+
);
|
|
290
|
+
lastCounter =
|
|
291
|
+
logicalTime == previousLogicalTime
|
|
292
|
+
? logicalTime == remoteLogicalTime
|
|
293
|
+
? mathMax(lastCounter, remoteCounter)
|
|
294
|
+
: lastCounter
|
|
295
|
+
: logicalTime == remoteLogicalTime
|
|
296
|
+
? remoteCounter
|
|
297
|
+
: -1;
|
|
298
|
+
};
|
|
299
|
+
return [getHlc, seenHlc];
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const RESPONSE = 0;
|
|
303
|
+
const CONTENT_HASHES = 1;
|
|
304
|
+
const GET_CONTENT_HASHES = 2;
|
|
305
|
+
const GET_TABLE_IDS_DIFF = 3;
|
|
306
|
+
const GET_ROW_IDS_DIFF = 4;
|
|
307
|
+
const GET_TABLES_CHANGES = 5;
|
|
308
|
+
const GET_VALUES_CHANGES = 6;
|
|
309
|
+
const createCustomSynchronizer = (
|
|
310
|
+
store,
|
|
311
|
+
send,
|
|
312
|
+
onReceive,
|
|
313
|
+
destroy,
|
|
314
|
+
requestTimeoutSeconds = 1,
|
|
315
|
+
onIgnoredError,
|
|
316
|
+
) => {
|
|
317
|
+
let persisterListener;
|
|
318
|
+
let sends = 0;
|
|
319
|
+
let receives = 0;
|
|
320
|
+
const [getHlc] = getHlcFunctions(store.getId());
|
|
321
|
+
const pendingRequests = mapNew();
|
|
322
|
+
onReceive((fromClientId, requestId, messageType, messageBody) => {
|
|
323
|
+
{
|
|
324
|
+
receives++;
|
|
325
|
+
}
|
|
326
|
+
if (messageType == RESPONSE) {
|
|
327
|
+
ifNotUndefined(
|
|
328
|
+
mapGet(pendingRequests, requestId),
|
|
329
|
+
([toClientId, handleResponse]) =>
|
|
330
|
+
isUndefined(toClientId) || toClientId == fromClientId
|
|
331
|
+
? handleResponse(messageBody, fromClientId)
|
|
332
|
+
: /* istanbul ignore next */
|
|
333
|
+
0,
|
|
334
|
+
);
|
|
335
|
+
} else if (messageType == CONTENT_HASHES && persister.isAutoLoading()) {
|
|
336
|
+
getChangesFromOtherStore(fromClientId, messageBody).then((changes) =>
|
|
337
|
+
persisterListener?.(void 0, () => changes),
|
|
338
|
+
);
|
|
339
|
+
} else {
|
|
340
|
+
ifNotUndefined(
|
|
341
|
+
messageType == GET_CONTENT_HASHES && persister.isAutoSaving()
|
|
342
|
+
? store.getMergeableContentHashes()
|
|
343
|
+
: messageType == GET_TABLE_IDS_DIFF
|
|
344
|
+
? store.getMergeableTableIdsDiff(messageBody)
|
|
345
|
+
: messageType == GET_ROW_IDS_DIFF
|
|
346
|
+
? store.getMergeableRowIdsDiff(messageBody)
|
|
347
|
+
: messageType == GET_TABLES_CHANGES
|
|
348
|
+
? store.getMergeableTablesChanges(messageBody)
|
|
349
|
+
: messageType == GET_VALUES_CHANGES
|
|
350
|
+
? store.getMergeableValuesChanges(messageBody)
|
|
351
|
+
: void 0,
|
|
352
|
+
(response) => {
|
|
353
|
+
{
|
|
354
|
+
sends++;
|
|
355
|
+
}
|
|
356
|
+
send(fromClientId, requestId, RESPONSE, response);
|
|
357
|
+
},
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
const request = async (toClientId, messageType, messageBody = EMPTY_STRING) =>
|
|
362
|
+
promiseNew((resolve, reject) => {
|
|
363
|
+
const requestId = getHlc();
|
|
364
|
+
const timeout = setTimeout(() => {
|
|
365
|
+
collDel(pendingRequests, requestId);
|
|
366
|
+
reject(
|
|
367
|
+
`No response from ${toClientId ?? 'anyone'} to '${messageType}'`,
|
|
368
|
+
);
|
|
369
|
+
}, requestTimeoutSeconds * 1e3);
|
|
370
|
+
mapSet(pendingRequests, requestId, [
|
|
371
|
+
toClientId,
|
|
372
|
+
(response, fromClientId) => {
|
|
373
|
+
clearTimeout(timeout);
|
|
374
|
+
collDel(pendingRequests, requestId);
|
|
375
|
+
resolve([response, fromClientId]);
|
|
376
|
+
},
|
|
377
|
+
]);
|
|
378
|
+
{
|
|
379
|
+
sends++;
|
|
380
|
+
}
|
|
381
|
+
send(toClientId, requestId, messageType, messageBody);
|
|
382
|
+
});
|
|
383
|
+
const getChangesFromOtherStore = async (
|
|
384
|
+
otherClientId = null,
|
|
385
|
+
otherContentHashes,
|
|
386
|
+
) => {
|
|
387
|
+
if (isUndefined(otherContentHashes)) {
|
|
388
|
+
[otherContentHashes, otherClientId] = await request(
|
|
389
|
+
otherClientId,
|
|
390
|
+
GET_CONTENT_HASHES,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
const [otherContentTime, [otherTablesHash, otherValuesHash]] =
|
|
394
|
+
otherContentHashes;
|
|
395
|
+
const [, [tablesHash, valuesHash]] = store.getMergeableContentHashes();
|
|
396
|
+
const changes = [EMPTY_STRING, [[EMPTY_STRING, {}], [EMPTY_STRING, {}], 1]];
|
|
397
|
+
if (tablesHash != otherTablesHash) {
|
|
398
|
+
changes[0] = otherContentTime;
|
|
399
|
+
changes[1][0] = (
|
|
400
|
+
await request(
|
|
401
|
+
otherClientId,
|
|
402
|
+
GET_TABLES_CHANGES,
|
|
403
|
+
store.getMergeableCellHashes(
|
|
404
|
+
(
|
|
405
|
+
await request(
|
|
406
|
+
otherClientId,
|
|
407
|
+
GET_ROW_IDS_DIFF,
|
|
408
|
+
store.getMergeableRowHashes(
|
|
409
|
+
(
|
|
410
|
+
await request(
|
|
411
|
+
otherClientId,
|
|
412
|
+
GET_TABLE_IDS_DIFF,
|
|
413
|
+
store.getMergeableTableHashes(),
|
|
414
|
+
)
|
|
415
|
+
)[0],
|
|
416
|
+
),
|
|
417
|
+
)
|
|
418
|
+
)[0],
|
|
419
|
+
),
|
|
420
|
+
)
|
|
421
|
+
)[0];
|
|
422
|
+
}
|
|
423
|
+
if (valuesHash != otherValuesHash) {
|
|
424
|
+
changes[0] = otherContentTime;
|
|
425
|
+
changes[1][1] = (
|
|
426
|
+
await request(
|
|
427
|
+
otherClientId,
|
|
428
|
+
GET_VALUES_CHANGES,
|
|
429
|
+
store.getMergeableValuesHashes(),
|
|
430
|
+
)
|
|
431
|
+
)[0];
|
|
432
|
+
}
|
|
433
|
+
return changes;
|
|
434
|
+
};
|
|
435
|
+
const getPersisted = async () => {
|
|
436
|
+
const changes = await getChangesFromOtherStore();
|
|
437
|
+
return changes[0] != EMPTY_STRING ? changes : void 0;
|
|
438
|
+
};
|
|
439
|
+
const setPersisted = async () => {
|
|
440
|
+
{
|
|
441
|
+
sends++;
|
|
442
|
+
}
|
|
443
|
+
send(null, null, CONTENT_HASHES, store.getMergeableContentHashes());
|
|
444
|
+
};
|
|
445
|
+
const addPersisterListener = (listener) => (persisterListener = listener);
|
|
446
|
+
const delPersisterListener = () => (persisterListener = void 0);
|
|
447
|
+
const persister = createCustomPersister(
|
|
448
|
+
store,
|
|
449
|
+
getPersisted,
|
|
450
|
+
setPersisted,
|
|
451
|
+
addPersisterListener,
|
|
452
|
+
delPersisterListener,
|
|
453
|
+
onIgnoredError,
|
|
454
|
+
true,
|
|
455
|
+
{
|
|
456
|
+
startSync: async () =>
|
|
457
|
+
await (await persister.startAutoLoad()).startAutoSave(),
|
|
458
|
+
stopSync: () => persister.stopAutoLoad().stopAutoSave(),
|
|
459
|
+
destroy: () => {
|
|
460
|
+
destroy();
|
|
461
|
+
return persister.stopSync();
|
|
462
|
+
},
|
|
463
|
+
getSynchronizerStats: () => ({sends, receives}),
|
|
464
|
+
},
|
|
465
|
+
);
|
|
466
|
+
return persister;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const clients = mapNew();
|
|
470
|
+
const createLocalSynchronizer = (store, onIgnoredError) => {
|
|
471
|
+
const clientId = '' + Math.random();
|
|
472
|
+
const onReceive = (receive) => {
|
|
473
|
+
mapSet(clients, clientId, receive);
|
|
474
|
+
};
|
|
475
|
+
const send = (toClientId, requestId, messageType, messageBody) => {
|
|
476
|
+
isUndefined(toClientId)
|
|
477
|
+
? mapForEach(clients, (otherClientId, receive) =>
|
|
478
|
+
otherClientId != clientId
|
|
479
|
+
? receive(clientId, requestId, messageType, messageBody)
|
|
480
|
+
: 0,
|
|
481
|
+
)
|
|
482
|
+
: mapGet(clients, toClientId)?.(
|
|
483
|
+
clientId,
|
|
484
|
+
requestId,
|
|
485
|
+
messageType,
|
|
486
|
+
messageBody,
|
|
487
|
+
);
|
|
488
|
+
};
|
|
489
|
+
const destroy = () => {
|
|
490
|
+
collDel(clients, clientId);
|
|
491
|
+
};
|
|
492
|
+
return createCustomSynchronizer(
|
|
493
|
+
store,
|
|
494
|
+
send,
|
|
495
|
+
onReceive,
|
|
496
|
+
destroy,
|
|
497
|
+
1e-3,
|
|
498
|
+
onIgnoredError,
|
|
499
|
+
);
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
export {createLocalSynchronizer};
|