dexie-cloud-addon 4.1.0-alpha.2 → 4.1.0-alpha.21
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/dist/modern/DexieCloudOptions.d.ts +3 -0
- package/dist/modern/TSON.d.ts +1 -1
- package/dist/modern/WSObservable.d.ts +4 -4
- package/dist/modern/define-ydoc-trigger.d.ts +2 -0
- package/dist/modern/dexie-cloud-addon.d.ts +1 -0
- package/dist/modern/dexie-cloud-addon.js +584 -237
- package/dist/modern/dexie-cloud-addon.js.map +1 -1
- package/dist/modern/dexie-cloud-addon.min.js +1 -1
- package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
- package/dist/modern/getInvitesObservable.d.ts +11 -11
- package/dist/modern/service-worker.js +426 -235
- package/dist/modern/service-worker.js.map +1 -1
- package/dist/modern/service-worker.min.js +1 -1
- package/dist/modern/service-worker.min.js.map +1 -1
- package/dist/modern/yjs/applyYMessages.d.ts +4 -1
- package/dist/modern/yjs/listYClientMessagesAndStateVector.d.ts +3 -1
- package/dist/umd/DexieCloudOptions.d.ts +3 -0
- package/dist/umd/TSON.d.ts +1 -1
- package/dist/umd/WSObservable.d.ts +4 -4
- package/dist/umd/define-ydoc-trigger.d.ts +2 -0
- package/dist/umd/dexie-cloud-addon.d.ts +1 -0
- package/dist/umd/dexie-cloud-addon.js +582 -234
- package/dist/umd/dexie-cloud-addon.js.map +1 -1
- package/dist/umd/dexie-cloud-addon.min.js +1 -1
- package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
- package/dist/umd/getInvitesObservable.d.ts +11 -11
- package/dist/umd/service-worker.js +424 -233
- package/dist/umd/service-worker.js.map +1 -1
- package/dist/umd/service-worker.min.js +1 -1
- package/dist/umd/service-worker.min.js.map +1 -1
- package/dist/umd/yjs/applyYMessages.d.ts +4 -1
- package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +3 -1
- package/package.json +4 -4
- package/dist/modern/yjs/listYClientMessages.d.ts +0 -3
- package/dist/umd/yjs/listYClientMessages.d.ts +0 -3
- /package/dist/modern/yjs/{y.d.ts → Y.d.ts} +0 -0
- /package/dist/umd/yjs/{y.d.ts → Y.d.ts} +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.1.0-alpha.
|
|
11
|
+
* Version 4.1.0-alpha.21, Mon Nov 18 2024
|
|
12
12
|
*
|
|
13
13
|
* https://dexie.org
|
|
14
14
|
*
|
|
@@ -982,13 +982,17 @@
|
|
|
982
982
|
function encodeYMessage(msg) {
|
|
983
983
|
const encoder = new Encoder();
|
|
984
984
|
writeVarString(encoder, msg.type);
|
|
985
|
-
|
|
986
|
-
|
|
985
|
+
if ('table' in msg)
|
|
986
|
+
writeVarString(encoder, msg.table);
|
|
987
|
+
if ('prop' in msg)
|
|
988
|
+
writeVarString(encoder, msg.prop);
|
|
987
989
|
switch (msg.type) {
|
|
988
990
|
case 'u-ack':
|
|
989
991
|
case 'u-reject':
|
|
990
992
|
writeBigUint64(encoder, BigInt(msg.i));
|
|
991
993
|
break;
|
|
994
|
+
case 'outdated-server-rev':
|
|
995
|
+
break;
|
|
992
996
|
default:
|
|
993
997
|
writeAny(encoder, msg.k);
|
|
994
998
|
switch (msg.type) {
|
|
@@ -1332,6 +1336,9 @@
|
|
|
1332
1336
|
function decodeYMessage(a) {
|
|
1333
1337
|
const decoder = new Decoder(a);
|
|
1334
1338
|
const type = readVarString(decoder);
|
|
1339
|
+
if (type === 'outdated-server-rev') {
|
|
1340
|
+
return { type };
|
|
1341
|
+
}
|
|
1335
1342
|
const table = readVarString(decoder);
|
|
1336
1343
|
const prop = readVarString(decoder);
|
|
1337
1344
|
switch (type) {
|
|
@@ -2990,8 +2997,8 @@
|
|
|
2990
2997
|
});
|
|
2991
2998
|
}
|
|
2992
2999
|
function registerPeriodicSyncEvent(db) {
|
|
2993
|
-
var _a;
|
|
2994
3000
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3001
|
+
var _a;
|
|
2995
3002
|
try {
|
|
2996
3003
|
// Register periodicSync event to SW:
|
|
2997
3004
|
// @ts-ignore
|
|
@@ -3202,8 +3209,8 @@
|
|
|
3202
3209
|
}
|
|
3203
3210
|
|
|
3204
3211
|
function loadAccessToken(db) {
|
|
3205
|
-
var _a, _b, _c;
|
|
3206
3212
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3213
|
+
var _a, _b, _c;
|
|
3207
3214
|
const currentUser = yield db.getCurrentUser();
|
|
3208
3215
|
const { accessToken, accessTokenExpiration, refreshToken, refreshTokenExpiration, claims, } = currentUser;
|
|
3209
3216
|
if (!accessToken)
|
|
@@ -3475,8 +3482,8 @@
|
|
|
3475
3482
|
}
|
|
3476
3483
|
});
|
|
3477
3484
|
}
|
|
3478
|
-
function _logout(
|
|
3479
|
-
return __awaiter(this,
|
|
3485
|
+
function _logout(db_1) {
|
|
3486
|
+
return __awaiter(this, arguments, void 0, function* (db, { deleteUnsyncedData = false } = {}) {
|
|
3480
3487
|
// Clear the database without emptying configuration options.
|
|
3481
3488
|
const [numUnsynced, loggedOut] = yield db.dx.transaction('rw', db.dx.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
|
|
3482
3489
|
// @ts-ignore
|
|
@@ -3524,11 +3531,11 @@
|
|
|
3524
3531
|
|
|
3525
3532
|
function otpFetchTokenCallback(db) {
|
|
3526
3533
|
const { userInteraction } = db.cloud;
|
|
3527
|
-
return function otpAuthenticate(
|
|
3528
|
-
|
|
3529
|
-
|
|
3534
|
+
return function otpAuthenticate(_a) {
|
|
3535
|
+
return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
|
|
3536
|
+
var _b;
|
|
3530
3537
|
let tokenRequest;
|
|
3531
|
-
const url = (
|
|
3538
|
+
const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
|
|
3532
3539
|
if (!url)
|
|
3533
3540
|
throw new Error(`No database URL given.`);
|
|
3534
3541
|
if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
|
|
@@ -3694,8 +3701,8 @@
|
|
|
3694
3701
|
}
|
|
3695
3702
|
|
|
3696
3703
|
function login(db, hints) {
|
|
3697
|
-
var _a;
|
|
3698
3704
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3705
|
+
var _a;
|
|
3699
3706
|
const currentUser = yield db.getCurrentUser();
|
|
3700
3707
|
const origUserId = currentUser.userId;
|
|
3701
3708
|
if (currentUser.isLoggedIn && (!hints || (!hints.email && !hints.userId))) {
|
|
@@ -3883,8 +3890,8 @@
|
|
|
3883
3890
|
}
|
|
3884
3891
|
}
|
|
3885
3892
|
|
|
3886
|
-
function computeRealmSetHash(
|
|
3887
|
-
return __awaiter(this,
|
|
3893
|
+
function computeRealmSetHash(_a) {
|
|
3894
|
+
return __awaiter(this, arguments, void 0, function* ({ realms, inviteRealms, }) {
|
|
3888
3895
|
const data = JSON.stringify([
|
|
3889
3896
|
...realms.map((realmId) => ({ realmId, accepted: true })),
|
|
3890
3897
|
...inviteRealms.map((realmId) => ({ realmId, accepted: false })),
|
|
@@ -3920,8 +3927,8 @@
|
|
|
3920
3927
|
return concat.apply([], a);
|
|
3921
3928
|
}
|
|
3922
3929
|
|
|
3923
|
-
function listClientChanges(
|
|
3924
|
-
return __awaiter(this,
|
|
3930
|
+
function listClientChanges(mutationTables_1, db_1) {
|
|
3931
|
+
return __awaiter(this, arguments, void 0, function* (mutationTables, db, { since = {}, limit = Infinity } = {}) {
|
|
3925
3932
|
const allMutsOnTables = yield Promise.all(mutationTables.map((mutationTable) => __awaiter(this, void 0, void 0, function* () {
|
|
3926
3933
|
const tableName = getTableFromMutationTable(mutationTable.name);
|
|
3927
3934
|
const lastRevision = since[tableName];
|
|
@@ -4656,8 +4663,8 @@
|
|
|
4656
4663
|
// seconds (given that there is a Ratelimit-Reset header).
|
|
4657
4664
|
let syncRatelimitDelays = new WeakMap();
|
|
4658
4665
|
function checkSyncRateLimitDelay(db) {
|
|
4659
|
-
var _a, _b;
|
|
4660
4666
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4667
|
+
var _a, _b;
|
|
4661
4668
|
const delatMilliseconds = ((_b = (_a = syncRatelimitDelays.get(db)) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0) - Date.now();
|
|
4662
4669
|
if (delatMilliseconds > 0) {
|
|
4663
4670
|
console.debug(`Stalling sync request ${delatMilliseconds} ms to spare ratelimits`);
|
|
@@ -4947,7 +4954,7 @@
|
|
|
4947
4954
|
.toArray();
|
|
4948
4955
|
}
|
|
4949
4956
|
|
|
4950
|
-
function $Y(db) {
|
|
4957
|
+
function $Y$1(db) {
|
|
4951
4958
|
const $Y = db.dx._options.Y;
|
|
4952
4959
|
if (!$Y)
|
|
4953
4960
|
throw new Error('Y library not supplied to Dexie constructor');
|
|
@@ -4970,15 +4977,14 @@
|
|
|
4970
4977
|
* @param db
|
|
4971
4978
|
* @returns
|
|
4972
4979
|
*/
|
|
4973
|
-
function listYClientMessagesAndStateVector(db) {
|
|
4974
|
-
var _a;
|
|
4980
|
+
function listYClientMessagesAndStateVector(db, tablesToSync) {
|
|
4975
4981
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4976
4982
|
const result = [];
|
|
4977
4983
|
const lastUpdateIds = {};
|
|
4978
|
-
for (const table of
|
|
4979
|
-
if (table.schema.yProps
|
|
4984
|
+
for (const table of tablesToSync) {
|
|
4985
|
+
if (table.schema.yProps) {
|
|
4980
4986
|
for (const yProp of table.schema.yProps) {
|
|
4981
|
-
const Y = $Y(db); // This is how we retrieve the user-provided Y library
|
|
4987
|
+
const Y = $Y$1(db); // This is how we retrieve the user-provided Y library
|
|
4982
4988
|
const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName
|
|
4983
4989
|
const syncState = (yield yTable.get(DEXIE_CLOUD_SYNCER_ID));
|
|
4984
4990
|
// unsentFrom = the `i` value of updates that aren't yet sent to server (or at least not acked by the server yet)
|
|
@@ -5063,12 +5069,14 @@
|
|
|
5063
5069
|
|
|
5064
5070
|
function applyYServerMessages(yMessages, db) {
|
|
5065
5071
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5066
|
-
|
|
5072
|
+
var _a;
|
|
5073
|
+
const receivedUntils = {};
|
|
5074
|
+
let resyncNeeded = false;
|
|
5067
5075
|
for (const m of yMessages) {
|
|
5068
5076
|
switch (m.type) {
|
|
5069
5077
|
case 'u-s': {
|
|
5070
5078
|
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5071
|
-
|
|
5079
|
+
receivedUntils[utbl.name] = yield utbl.add({
|
|
5072
5080
|
k: m.k,
|
|
5073
5081
|
u: m.u,
|
|
5074
5082
|
});
|
|
@@ -5093,7 +5101,24 @@
|
|
|
5093
5101
|
// See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
|
|
5094
5102
|
console.debug(`Y update rejected. Deleting it.`);
|
|
5095
5103
|
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5096
|
-
|
|
5104
|
+
// Delete the rejected update and all local updates since (avoid holes in the CRDT)
|
|
5105
|
+
// and destroy it's open document if there is one.
|
|
5106
|
+
const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
|
|
5107
|
+
if (primaryKey != null) {
|
|
5108
|
+
yield db.transaction('rw', utbl, (tx) => {
|
|
5109
|
+
// @ts-ignore
|
|
5110
|
+
tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
|
|
5111
|
+
return utbl
|
|
5112
|
+
.where('i')
|
|
5113
|
+
.aboveOrEqual(m.i)
|
|
5114
|
+
.filter((u) => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
|
|
5115
|
+
.delete();
|
|
5116
|
+
});
|
|
5117
|
+
// Destroy active doc
|
|
5118
|
+
const activeDoc = Dexie.DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
|
|
5119
|
+
if (activeDoc)
|
|
5120
|
+
activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
|
|
5121
|
+
}
|
|
5097
5122
|
break;
|
|
5098
5123
|
}
|
|
5099
5124
|
case 'in-sync': {
|
|
@@ -5103,22 +5128,28 @@
|
|
|
5103
5128
|
}
|
|
5104
5129
|
break;
|
|
5105
5130
|
}
|
|
5131
|
+
case 'outdated-server-rev':
|
|
5132
|
+
resyncNeeded = true;
|
|
5133
|
+
break;
|
|
5106
5134
|
}
|
|
5107
5135
|
}
|
|
5108
|
-
return
|
|
5136
|
+
return {
|
|
5137
|
+
receivedUntils,
|
|
5138
|
+
resyncNeeded,
|
|
5139
|
+
};
|
|
5109
5140
|
});
|
|
5110
5141
|
}
|
|
5111
5142
|
|
|
5112
5143
|
function updateYSyncStates(lastUpdateIdsBeforeSync, receivedUntilsAfterSync, db, serverRevision) {
|
|
5113
|
-
var _a, _b;
|
|
5114
5144
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5145
|
+
var _a, _b, _c, _d, _e;
|
|
5115
5146
|
// We want to update unsentFrom for each yTable to the value specified in first argument
|
|
5116
5147
|
// because we got those values before we synced with server and here we are back from server
|
|
5117
5148
|
// that has successfully received all those messages - no matter if the last update was a client or server update,
|
|
5118
5149
|
// we can safely store unsentFrom to a value of the last update + 1 here.
|
|
5119
5150
|
// We also want to update receivedUntil for each yTable to the value specified in the second argument,
|
|
5120
5151
|
// because that contains the highest resulted id of each update from server after storing it.
|
|
5121
|
-
// We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
|
|
5152
|
+
// We could do these two tasks separately, but that would require two update calls on the same YSyncState, so
|
|
5122
5153
|
// to optimize the dexie calls, we merge these two maps into a single one so we can do a single update request
|
|
5123
5154
|
// per yTable.
|
|
5124
5155
|
const mergedSpec = {};
|
|
@@ -5130,28 +5161,42 @@
|
|
|
5130
5161
|
(_b = mergedSpec[yTable]) !== null && _b !== void 0 ? _b : (mergedSpec[yTable] = {});
|
|
5131
5162
|
mergedSpec[yTable].receivedUntil = lastUpdateId;
|
|
5132
5163
|
}
|
|
5133
|
-
// Now go through
|
|
5134
|
-
|
|
5164
|
+
// Now go through all yTables and update their YSyncStates:
|
|
5165
|
+
const allYTables = Object.values(db.dx._dbSchema)
|
|
5166
|
+
.filter((tblSchema) => tblSchema.yProps)
|
|
5167
|
+
.map((tblSchema) => tblSchema.yProps.map((yProp) => yProp.updatesTable))
|
|
5168
|
+
.flat();
|
|
5169
|
+
for (const yTable of allYTables) {
|
|
5170
|
+
const mergedEntry = mergedSpec[yTable];
|
|
5171
|
+
const unsentFrom = (_c = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.unsentFrom) !== null && _c !== void 0 ? _c : 1;
|
|
5172
|
+
const receivedUntil = (_e = (_d = mergedEntry === null || mergedEntry === void 0 ? void 0 : mergedEntry.receivedUntil) !== null && _d !== void 0 ? _d :
|
|
5173
|
+
// from local because we are in the same parent transaction (in sync.ts) that
|
|
5174
|
+
// applied all updates from the server
|
|
5175
|
+
(yield db
|
|
5176
|
+
.table(yTable)
|
|
5177
|
+
.where('i')
|
|
5178
|
+
.between(1, Infinity) // Because i might be string DEXIE_CLOUD_SYNCER_ID if not a number.
|
|
5179
|
+
.reverse()
|
|
5180
|
+
.limit(1)
|
|
5181
|
+
.primaryKeys())[0]) !== null && _e !== void 0 ? _e : 0;
|
|
5135
5182
|
// We're already in a transaction, but for the sake of
|
|
5136
5183
|
// code readability and correctness, let's launch an atomic sub transaction:
|
|
5137
5184
|
yield db.transaction('rw', yTable, () => __awaiter(this, void 0, void 0, function* () {
|
|
5138
|
-
const state = yield db
|
|
5185
|
+
const state = yield db
|
|
5186
|
+
.table(yTable)
|
|
5187
|
+
.get(DEXIE_CLOUD_SYNCER_ID);
|
|
5139
5188
|
if (!state) {
|
|
5140
5189
|
yield db.table(yTable).add({
|
|
5141
5190
|
i: DEXIE_CLOUD_SYNCER_ID,
|
|
5142
|
-
unsentFrom
|
|
5143
|
-
receivedUntil
|
|
5191
|
+
unsentFrom,
|
|
5192
|
+
receivedUntil,
|
|
5144
5193
|
serverRev: serverRevision,
|
|
5145
5194
|
});
|
|
5146
5195
|
}
|
|
5147
5196
|
else {
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
if (receivedUntil) {
|
|
5152
|
-
state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
|
|
5153
|
-
state.serverRev = serverRevision;
|
|
5154
|
-
}
|
|
5197
|
+
state.unsentFrom = Math.max(unsentFrom, state.unsentFrom || 1);
|
|
5198
|
+
state.receivedUntil = Math.max(receivedUntil, state.receivedUntil || 0);
|
|
5199
|
+
state.serverRev = serverRevision;
|
|
5155
5200
|
yield db.table(yTable).put(state);
|
|
5156
5201
|
}
|
|
5157
5202
|
}));
|
|
@@ -5162,9 +5207,11 @@
|
|
|
5162
5207
|
const BINSTREAM_TYPE_REALMID = 1;
|
|
5163
5208
|
const BINSTREAM_TYPE_TABLE_AND_PROP = 2;
|
|
5164
5209
|
const BINSTREAM_TYPE_DOCUMENT = 3;
|
|
5165
|
-
function downloadYDocsFromServer(
|
|
5166
|
-
return __awaiter(this,
|
|
5167
|
-
if (yDownloadedRealms &&
|
|
5210
|
+
function downloadYDocsFromServer(db_1, databaseUrl_1, _a) {
|
|
5211
|
+
return __awaiter(this, arguments, void 0, function* (db, databaseUrl, { yDownloadedRealms, realms }) {
|
|
5212
|
+
if (yDownloadedRealms &&
|
|
5213
|
+
realms &&
|
|
5214
|
+
realms.every((realmId) => yDownloadedRealms[realmId] === '*')) {
|
|
5168
5215
|
return; // Already done!
|
|
5169
5216
|
}
|
|
5170
5217
|
console.debug('Downloading Y.Docs from added realms');
|
|
@@ -5204,16 +5251,19 @@
|
|
|
5204
5251
|
yield yTable.bulkAdd(docsToInsert);
|
|
5205
5252
|
docsToInsert = [];
|
|
5206
5253
|
}
|
|
5207
|
-
if (currentRealmId &&
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5254
|
+
if (currentRealmId &&
|
|
5255
|
+
((currentTable && currentProp && lastDoc) || completedRealm)) {
|
|
5256
|
+
yield db.$syncState.update('syncState', (syncState) => {
|
|
5257
|
+
const yDownloadedRealms = syncState.yDownloadedRealms || {};
|
|
5258
|
+
yDownloadedRealms[currentRealmId] = completedRealm
|
|
5259
|
+
? '*'
|
|
5260
|
+
: {
|
|
5212
5261
|
tbl: currentTable,
|
|
5213
5262
|
prop: currentProp,
|
|
5214
5263
|
key: lastDoc.k,
|
|
5215
|
-
}
|
|
5216
|
-
|
|
5264
|
+
};
|
|
5265
|
+
syncState.yDownloadedRealms = yDownloadedRealms;
|
|
5266
|
+
});
|
|
5217
5267
|
}
|
|
5218
5268
|
});
|
|
5219
5269
|
}
|
|
@@ -5316,11 +5366,11 @@
|
|
|
5316
5366
|
return Promise.reject(error);
|
|
5317
5367
|
}));
|
|
5318
5368
|
}
|
|
5319
|
-
function _sync(
|
|
5320
|
-
isInitialSync
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5369
|
+
function _sync(db_1, options_1, schema_1) {
|
|
5370
|
+
return __awaiter(this, arguments, void 0, function* (db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
|
|
5371
|
+
isInitialSync: false,
|
|
5372
|
+
}) {
|
|
5373
|
+
var _a;
|
|
5324
5374
|
if (!justCheckIfNeeded) {
|
|
5325
5375
|
console.debug('SYNC STARTED', { isInitialSync, purpose });
|
|
5326
5376
|
}
|
|
@@ -5362,8 +5412,8 @@
|
|
|
5362
5412
|
const [clientChangeSet, syncState, baseRevs, { yMessages, lastUpdateIds }] = yield db.transaction('r', db.tables, () => __awaiter(this, void 0, void 0, function* () {
|
|
5363
5413
|
const syncState = yield db.getPersistedSyncState();
|
|
5364
5414
|
const baseRevs = yield db.$baseRevs.toArray();
|
|
5365
|
-
let clientChanges = yield listClientChanges(mutationTables);
|
|
5366
|
-
const yResults = yield listYClientMessagesAndStateVector(db);
|
|
5415
|
+
let clientChanges = yield listClientChanges(mutationTables, db);
|
|
5416
|
+
const yResults = yield listYClientMessagesAndStateVector(db, tablesToSync);
|
|
5367
5417
|
throwIfCancelled(cancelToken);
|
|
5368
5418
|
if (doSyncify) {
|
|
5369
5419
|
const alreadySyncedRealms = [
|
|
@@ -5493,11 +5543,14 @@
|
|
|
5493
5543
|
//
|
|
5494
5544
|
// apply yMessages
|
|
5495
5545
|
//
|
|
5496
|
-
const receivedUntils = yield applyYServerMessages(res.yMessages, db);
|
|
5546
|
+
const { receivedUntils, resyncNeeded } = yield applyYServerMessages(res.yMessages, db);
|
|
5497
5547
|
//
|
|
5498
5548
|
// update Y SyncStates
|
|
5499
5549
|
//
|
|
5500
5550
|
yield updateYSyncStates(lastUpdateIds, receivedUntils, db, res.serverRevision);
|
|
5551
|
+
if (resyncNeeded) {
|
|
5552
|
+
newSyncState.yDownloadedRealms = {}; // Will trigger a full download of Y-documents below...
|
|
5553
|
+
}
|
|
5501
5554
|
}
|
|
5502
5555
|
//
|
|
5503
5556
|
// Update regular syncState
|
|
@@ -5631,8 +5684,8 @@
|
|
|
5631
5684
|
event.next(null);
|
|
5632
5685
|
}
|
|
5633
5686
|
function consumeQueue() {
|
|
5634
|
-
var _a, _b, _c, _d, _e, _f;
|
|
5635
5687
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5688
|
+
var _a, _b, _c, _d, _e, _f;
|
|
5636
5689
|
while (queue.length > 0) {
|
|
5637
5690
|
const msg = queue.shift();
|
|
5638
5691
|
try {
|
|
@@ -6288,8 +6341,7 @@
|
|
|
6288
6341
|
outstandingTransactions.next(outstandingTransactions.value);
|
|
6289
6342
|
};
|
|
6290
6343
|
const txComplete = () => {
|
|
6291
|
-
if (tx.mutationsAdded &&
|
|
6292
|
-
!isEagerSyncDisabled(db)) {
|
|
6344
|
+
if (tx.mutationsAdded && !isEagerSyncDisabled(db)) {
|
|
6293
6345
|
triggerSync(db, 'push');
|
|
6294
6346
|
}
|
|
6295
6347
|
removeTransaction();
|
|
@@ -6371,26 +6423,107 @@
|
|
|
6371
6423
|
: mutateAndLog(req);
|
|
6372
6424
|
} }));
|
|
6373
6425
|
function mutateAndLog(req) {
|
|
6426
|
+
var _a, _b;
|
|
6374
6427
|
const trans = req.trans;
|
|
6375
|
-
|
|
6428
|
+
const unsyncedProps = (_b = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.unsyncedProperties) === null || _b === void 0 ? void 0 : _b[tableName];
|
|
6376
6429
|
const { txid, currentUser: { userId }, } = trans;
|
|
6377
6430
|
const { type } = req;
|
|
6378
6431
|
const opNo = ++trans.opCount;
|
|
6432
|
+
function stripChangeSpec(changeSpec) {
|
|
6433
|
+
if (!unsyncedProps)
|
|
6434
|
+
return changeSpec;
|
|
6435
|
+
let rv = changeSpec;
|
|
6436
|
+
for (const keyPath of Object.keys(changeSpec)) {
|
|
6437
|
+
if (unsyncedProps.some((p) => keyPath === p || keyPath.startsWith(p + '.'))) {
|
|
6438
|
+
if (rv === changeSpec)
|
|
6439
|
+
rv = Object.assign({}, changeSpec); // clone on demand
|
|
6440
|
+
delete rv[keyPath];
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
return rv;
|
|
6444
|
+
}
|
|
6379
6445
|
return table.mutate(req).then((res) => {
|
|
6446
|
+
var _a;
|
|
6380
6447
|
const { numFailures: hasFailures, failures } = res;
|
|
6381
6448
|
let keys = type === 'delete' ? req.keys : res.results;
|
|
6382
6449
|
let values = 'values' in req ? req.values : [];
|
|
6383
|
-
let
|
|
6450
|
+
let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
|
|
6451
|
+
let updates = 'updates' in req ? req.updates : undefined;
|
|
6384
6452
|
if (hasFailures) {
|
|
6385
6453
|
keys = keys.filter((_, idx) => !failures[idx]);
|
|
6386
6454
|
values = values.filter((_, idx) => !failures[idx]);
|
|
6387
6455
|
}
|
|
6456
|
+
if (unsyncedProps) {
|
|
6457
|
+
// Filter out unsynced properties
|
|
6458
|
+
values = values.map((value) => {
|
|
6459
|
+
const newValue = Object.assign({}, value);
|
|
6460
|
+
for (const prop of unsyncedProps) {
|
|
6461
|
+
delete newValue[prop];
|
|
6462
|
+
}
|
|
6463
|
+
return newValue;
|
|
6464
|
+
});
|
|
6465
|
+
if (changeSpec) {
|
|
6466
|
+
// modify operation with criteria and changeSpec.
|
|
6467
|
+
// We must strip out unsynced properties from changeSpec.
|
|
6468
|
+
// We deal with criteria later.
|
|
6469
|
+
changeSpec = stripChangeSpec(changeSpec);
|
|
6470
|
+
if (Object.keys(changeSpec).length === 0) {
|
|
6471
|
+
// Nothing to change on server
|
|
6472
|
+
return res;
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
if (updates) {
|
|
6476
|
+
let strippedChangeSpecs = updates.changeSpecs.map(stripChangeSpec);
|
|
6477
|
+
let newUpdates = {
|
|
6478
|
+
keys: [],
|
|
6479
|
+
changeSpecs: [],
|
|
6480
|
+
};
|
|
6481
|
+
const validKeys = new Dexie.RangeSet();
|
|
6482
|
+
let anyChangeSpecBecameEmpty = false;
|
|
6483
|
+
for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
|
|
6484
|
+
if (Object.keys(strippedChangeSpecs[i]).length > 0) {
|
|
6485
|
+
newUpdates.keys.push(updates.keys[i]);
|
|
6486
|
+
newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
|
|
6487
|
+
validKeys.addKey(updates.keys[i]);
|
|
6488
|
+
}
|
|
6489
|
+
else {
|
|
6490
|
+
anyChangeSpecBecameEmpty = true;
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
updates = newUpdates;
|
|
6494
|
+
if (anyChangeSpecBecameEmpty) {
|
|
6495
|
+
// Some keys were stripped. We must also strip them from keys and values
|
|
6496
|
+
let newKeys = [];
|
|
6497
|
+
let newValues = [];
|
|
6498
|
+
for (let i = 0, l = keys.length; i < l; ++i) {
|
|
6499
|
+
if (validKeys.hasKey(keys[i])) {
|
|
6500
|
+
newKeys.push(keys[i]);
|
|
6501
|
+
newValues.push(values[i]);
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
keys = newKeys;
|
|
6505
|
+
values = newValues;
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
}
|
|
6388
6509
|
const ts = Date.now();
|
|
6389
6510
|
// Canonicalize req.criteria.index to null if it's on the primary key.
|
|
6390
|
-
|
|
6511
|
+
let criteria = 'criteria' in req && req.criteria
|
|
6391
6512
|
? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
|
|
6392
6513
|
? null // This will disable the server from trying to log consistent operations where it shouldnt.
|
|
6393
6514
|
: req.criteria.index }) : undefined;
|
|
6515
|
+
if (unsyncedProps && (criteria === null || criteria === void 0 ? void 0 : criteria.index)) {
|
|
6516
|
+
const keyPaths = (_a = schema.indexes.find((idx) => idx.name === criteria.index)) === null || _a === void 0 ? void 0 : _a.keyPath;
|
|
6517
|
+
const involvedProps = keyPaths
|
|
6518
|
+
? typeof keyPaths === 'string'
|
|
6519
|
+
? [keyPaths]
|
|
6520
|
+
: keyPaths
|
|
6521
|
+
: [];
|
|
6522
|
+
if (involvedProps.some((p) => unsyncedProps === null || unsyncedProps === void 0 ? void 0 : unsyncedProps.includes(p))) {
|
|
6523
|
+
// Don't log criteria on unsynced properties as the server could not test them.
|
|
6524
|
+
criteria = undefined;
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6394
6527
|
const mut = req.type === 'delete'
|
|
6395
6528
|
? {
|
|
6396
6529
|
type: 'delete',
|
|
@@ -6411,7 +6544,7 @@
|
|
|
6411
6544
|
userId,
|
|
6412
6545
|
values,
|
|
6413
6546
|
}
|
|
6414
|
-
: criteria &&
|
|
6547
|
+
: criteria && changeSpec
|
|
6415
6548
|
? {
|
|
6416
6549
|
// Common changeSpec for all keys
|
|
6417
6550
|
type: 'modify',
|
|
@@ -6419,37 +6552,51 @@
|
|
|
6419
6552
|
opNo,
|
|
6420
6553
|
keys,
|
|
6421
6554
|
criteria,
|
|
6422
|
-
changeSpec
|
|
6555
|
+
changeSpec,
|
|
6423
6556
|
txid,
|
|
6424
6557
|
userId,
|
|
6425
6558
|
}
|
|
6426
|
-
:
|
|
6559
|
+
: changeSpec
|
|
6427
6560
|
? {
|
|
6428
|
-
//
|
|
6561
|
+
// In case criteria involved an unsynced property, we go for keys instead.
|
|
6429
6562
|
type: 'update',
|
|
6430
6563
|
ts,
|
|
6431
6564
|
opNo,
|
|
6432
|
-
keys: updates.keys,
|
|
6433
|
-
changeSpecs: updates.changeSpecs,
|
|
6434
|
-
txid,
|
|
6435
|
-
userId,
|
|
6436
|
-
}
|
|
6437
|
-
: {
|
|
6438
|
-
type: 'upsert',
|
|
6439
|
-
ts,
|
|
6440
|
-
opNo,
|
|
6441
6565
|
keys,
|
|
6442
|
-
|
|
6566
|
+
changeSpecs: keys.map(() => changeSpec),
|
|
6443
6567
|
txid,
|
|
6444
6568
|
userId,
|
|
6445
|
-
}
|
|
6569
|
+
}
|
|
6570
|
+
: updates
|
|
6571
|
+
? {
|
|
6572
|
+
// One changeSpec per key
|
|
6573
|
+
type: 'update',
|
|
6574
|
+
ts,
|
|
6575
|
+
opNo,
|
|
6576
|
+
keys: updates.keys,
|
|
6577
|
+
changeSpecs: updates.changeSpecs,
|
|
6578
|
+
txid,
|
|
6579
|
+
userId,
|
|
6580
|
+
}
|
|
6581
|
+
: {
|
|
6582
|
+
type: 'upsert',
|
|
6583
|
+
ts,
|
|
6584
|
+
opNo,
|
|
6585
|
+
keys,
|
|
6586
|
+
values,
|
|
6587
|
+
txid,
|
|
6588
|
+
userId,
|
|
6589
|
+
};
|
|
6446
6590
|
if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
|
|
6447
6591
|
mut.isAdditionalChunk = true;
|
|
6448
6592
|
}
|
|
6449
6593
|
return keys.length > 0 || criteria
|
|
6450
6594
|
? mutsTable
|
|
6451
6595
|
.mutate({ type: 'add', trans, values: [mut] }) // Log entry
|
|
6452
|
-
.then(() =>
|
|
6596
|
+
.then(() => {
|
|
6597
|
+
trans.mutationsAdded = true; // Mark transaction as having added mutations to trigger eager sync
|
|
6598
|
+
return res; // Return original response
|
|
6599
|
+
})
|
|
6453
6600
|
: res;
|
|
6454
6601
|
});
|
|
6455
6602
|
}
|
|
@@ -6616,38 +6763,51 @@
|
|
|
6616
6763
|
|
|
6617
6764
|
function createYClientUpdateObservable(db) {
|
|
6618
6765
|
const yTableRecords = flatten(db.tables
|
|
6619
|
-
.filter((table) => { var _a; return ((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name].markedForSync) && table.schema.yProps; })
|
|
6766
|
+
.filter((table) => { var _a, _b; return ((_b = (_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name]) === null || _b === void 0 ? void 0 : _b.markedForSync) && table.schema.yProps; })
|
|
6620
6767
|
.map((table) => table.schema.yProps.map((p) => ({
|
|
6621
6768
|
table: table.name,
|
|
6622
6769
|
ydocProp: p.prop,
|
|
6623
6770
|
updatesTable: p.updatesTable,
|
|
6624
6771
|
}))));
|
|
6625
6772
|
return rxjs.merge(...yTableRecords.map(({ table, ydocProp, updatesTable }) => {
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6773
|
+
// Per updates table (table+prop combo), we first read syncer.unsentFrom,
|
|
6774
|
+
// and then start listening for updates since that number.
|
|
6775
|
+
const yTbl = db.table(updatesTable);
|
|
6776
|
+
return rxjs.from(yTbl.get(DEXIE_CLOUD_SYNCER_ID)).pipe(rxjs.switchMap((syncer) => {
|
|
6777
|
+
let currentUnsentFrom = (syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1;
|
|
6778
|
+
return rxjs.from(Dexie.liveQuery(() => __awaiter(this, void 0, void 0, function* () {
|
|
6779
|
+
const addedUpdates = yield listUpdatesSince(yTbl, currentUnsentFrom);
|
|
6780
|
+
return addedUpdates
|
|
6781
|
+
.filter((update) => update.f && update.f & 1) // Only include local updates
|
|
6782
|
+
.map((update) => {
|
|
6783
|
+
return {
|
|
6784
|
+
type: 'u-c',
|
|
6785
|
+
table,
|
|
6786
|
+
prop: ydocProp,
|
|
6787
|
+
k: update.k,
|
|
6788
|
+
u: update.u,
|
|
6789
|
+
i: update.i,
|
|
6790
|
+
};
|
|
6791
|
+
});
|
|
6792
|
+
}))).pipe(rxjs.tap((addedUpdates) => {
|
|
6793
|
+
// Update currentUnsentFrom to only listen for updates that will be newer than the ones we emitted.
|
|
6794
|
+
// (Before, we did this within the liveQuery, but that caused a bug because
|
|
6795
|
+
// a cancelled emittion of a liveQuery would update the currentUnsentFrom without
|
|
6796
|
+
// emitting anything, leading to that we jumped over some updates. Here we update it
|
|
6797
|
+
// after the liveQuery has emitted its updates)
|
|
6798
|
+
if (addedUpdates.length > 0) {
|
|
6799
|
+
currentUnsentFrom = addedUpdates.at(-1).i + 1;
|
|
6800
|
+
}
|
|
6801
|
+
}));
|
|
6649
6802
|
}));
|
|
6650
|
-
})).pipe(
|
|
6803
|
+
})).pipe(
|
|
6804
|
+
// Flatten the array of messages.
|
|
6805
|
+
// If messageProducer emits empty array, nothing is emitted
|
|
6806
|
+
// but if messageProducer emits array of messages, they are
|
|
6807
|
+
// emitted one by one.
|
|
6808
|
+
rxjs.mergeMap((messages) => messages), rxjs.tap((message) => {
|
|
6809
|
+
console.debug('dexie-cloud emitting y-c', message);
|
|
6810
|
+
}));
|
|
6651
6811
|
}
|
|
6652
6812
|
|
|
6653
6813
|
function getAwarenessLibrary(db) {
|
|
@@ -6664,25 +6824,24 @@
|
|
|
6664
6824
|
const CLIENT_PING_INTERVAL = 30000;
|
|
6665
6825
|
const FAIL_RETRY_WAIT_TIME = 60000;
|
|
6666
6826
|
class WSObservable extends rxjs.Observable {
|
|
6667
|
-
constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus,
|
|
6668
|
-
super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity,
|
|
6827
|
+
constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
|
|
6828
|
+
super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
|
|
6669
6829
|
}
|
|
6670
6830
|
}
|
|
6671
6831
|
let counter = 0;
|
|
6672
6832
|
class WSConnection extends rxjs.Subscription {
|
|
6673
|
-
constructor(db, rev, realmSetHash, clientIdentity,
|
|
6833
|
+
constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
|
|
6674
6834
|
super(() => this.teardown());
|
|
6675
6835
|
this.id = ++counter;
|
|
6676
6836
|
this.subscriptions = new Set();
|
|
6677
6837
|
this.reconnecting = false;
|
|
6678
|
-
console.debug('New WebSocket Connection', this.id,
|
|
6838
|
+
console.debug('New WebSocket Connection', this.id, user.accessToken ? 'authorized' : 'unauthorized');
|
|
6679
6839
|
this.db = db;
|
|
6680
6840
|
this.databaseUrl = db.cloud.options.databaseUrl;
|
|
6681
6841
|
this.rev = rev;
|
|
6682
6842
|
this.realmSetHash = realmSetHash;
|
|
6683
6843
|
this.clientIdentity = clientIdentity;
|
|
6684
|
-
this.
|
|
6685
|
-
this.tokenExpiration = tokenExpiration;
|
|
6844
|
+
this.user = user;
|
|
6686
6845
|
this.subscriber = subscriber;
|
|
6687
6846
|
this.lastUserActivity = new Date();
|
|
6688
6847
|
this.messageProducer = messageProducer;
|
|
@@ -6742,7 +6901,8 @@
|
|
|
6742
6901
|
//console.debug('SyncStatus: DUBB: Ooops it was closed!');
|
|
6743
6902
|
return;
|
|
6744
6903
|
}
|
|
6745
|
-
|
|
6904
|
+
const tokenExpiration = this.user.accessTokenExpiration;
|
|
6905
|
+
if (tokenExpiration && tokenExpiration < new Date()) {
|
|
6746
6906
|
this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
|
|
6747
6907
|
return;
|
|
6748
6908
|
}
|
|
@@ -6797,8 +6957,8 @@
|
|
|
6797
6957
|
searchParams.set('rev', this.rev);
|
|
6798
6958
|
searchParams.set('realmsHash', this.realmSetHash);
|
|
6799
6959
|
searchParams.set('clientId', this.clientIdentity);
|
|
6800
|
-
if (this.
|
|
6801
|
-
searchParams.set('token', this.
|
|
6960
|
+
if (this.user.accessToken) {
|
|
6961
|
+
searchParams.set('token', this.user.accessToken);
|
|
6802
6962
|
}
|
|
6803
6963
|
// Connect the WebSocket to given url:
|
|
6804
6964
|
console.debug('dexie-cloud WebSocket create');
|
|
@@ -6813,12 +6973,12 @@
|
|
|
6813
6973
|
ws.onmessage = (event) => {
|
|
6814
6974
|
if (!this.pinger)
|
|
6815
6975
|
return;
|
|
6816
|
-
console.debug('dexie-cloud WebSocket onmessage', event.data);
|
|
6817
6976
|
this.lastServerActivity = new Date();
|
|
6818
6977
|
try {
|
|
6819
6978
|
const msg = typeof event.data === 'string'
|
|
6820
6979
|
? TSON.parse(event.data)
|
|
6821
6980
|
: decodeYMessage(new Uint8Array(event.data));
|
|
6981
|
+
console.debug('dexie-cloud WebSocket onmessage', msg.type, msg);
|
|
6822
6982
|
if (msg.type === 'error') {
|
|
6823
6983
|
throw new Error(`Error message from dexie-cloud: ${msg.error}`);
|
|
6824
6984
|
}
|
|
@@ -6839,6 +6999,10 @@
|
|
|
6839
6999
|
else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync') {
|
|
6840
7000
|
applyYServerMessages([msg], this.db);
|
|
6841
7001
|
}
|
|
7002
|
+
else if (msg.type === 'outdated-server-rev') {
|
|
7003
|
+
// Won't happen but need this for typing.
|
|
7004
|
+
throw new Error('Outdated server revision not expected over WebSocket - only in sync using fetch()');
|
|
7005
|
+
}
|
|
6842
7006
|
else if (msg.type !== 'pong') {
|
|
6843
7007
|
this.subscriber.next(msg);
|
|
6844
7008
|
}
|
|
@@ -6874,17 +7038,27 @@
|
|
|
6874
7038
|
this.webSocketStatus.value !== 'connected') {
|
|
6875
7039
|
this.webSocketStatus.next('connected');
|
|
6876
7040
|
}
|
|
7041
|
+
console.debug('dexie-cloud WebSocket send', msg.type, msg);
|
|
6877
7042
|
if (msg.type === 'ready') {
|
|
6878
7043
|
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
|
|
6879
7044
|
}
|
|
6880
7045
|
else {
|
|
6881
7046
|
// If it's not a "ready" message, it's an YMessage.
|
|
6882
7047
|
// YMessages can be sent binary encoded.
|
|
7048
|
+
if (msg.type === 'u-c') {
|
|
7049
|
+
console.log("u-c:B", ++gotClientUpdateB);
|
|
7050
|
+
}
|
|
6883
7051
|
(_b = this.ws) === null || _b === void 0 ? void 0 : _b.send(encodeYMessage(msg));
|
|
6884
7052
|
}
|
|
6885
7053
|
}
|
|
6886
7054
|
}));
|
|
6887
|
-
|
|
7055
|
+
if (this.user.isLoggedIn && !isEagerSyncDisabled(this.db)) {
|
|
7056
|
+
this.subscriptions.add(createYClientUpdateObservable(this.db).pipe(rxjs.tap((msg) => {
|
|
7057
|
+
if (msg.type === 'u-c') {
|
|
7058
|
+
console.log("u-c:A", ++gotClientUpdateA, msg.i);
|
|
7059
|
+
}
|
|
7060
|
+
})).subscribe(this.db.messageProducer));
|
|
7061
|
+
}
|
|
6888
7062
|
}
|
|
6889
7063
|
catch (error) {
|
|
6890
7064
|
this.pauseUntil = new Date(Date.now() + FAIL_RETRY_WAIT_TIME);
|
|
@@ -6892,6 +7066,8 @@
|
|
|
6892
7066
|
});
|
|
6893
7067
|
}
|
|
6894
7068
|
}
|
|
7069
|
+
let gotClientUpdateA = 0;
|
|
7070
|
+
let gotClientUpdateB = 0;
|
|
6895
7071
|
|
|
6896
7072
|
class InvalidLicenseError extends Error {
|
|
6897
7073
|
constructor(license) {
|
|
@@ -6954,7 +7130,7 @@
|
|
|
6954
7130
|
return db.cloud.persistedSyncState.pipe(filter((syncState) => (syncState === null || syncState === void 0 ? void 0 : syncState.realms.includes(userLogin.userId)) || false), take(1), map((syncState) => [userLogin, syncState]));
|
|
6955
7131
|
}
|
|
6956
7132
|
return new rxjs.BehaviorSubject([userLogin, syncState]);
|
|
6957
|
-
}), switchMap((
|
|
7133
|
+
}), switchMap((_a) => __awaiter(this, [_a], void 0, function* ([userLogin, syncState]) { return [userLogin, yield computeRealmSetHash(syncState)]; })), distinctUntilChanged(([prevUser, prevHash], [currUser, currHash]) => prevUser === currUser && prevHash === currHash), switchMap(([userLogin, realmSetHash]) => {
|
|
6958
7134
|
var _a;
|
|
6959
7135
|
if (!((_a = db.cloud.persistedSyncState) === null || _a === void 0 ? void 0 : _a.value)) {
|
|
6960
7136
|
// Restart the flow if persistedSyncState is not yet available.
|
|
@@ -6964,7 +7140,7 @@
|
|
|
6964
7140
|
// If no new entries, server won't bother the client. If new entries, server sends only those
|
|
6965
7141
|
// and the baseRev of the last from same client-ID.
|
|
6966
7142
|
if (userLogin) {
|
|
6967
|
-
return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin
|
|
7143
|
+
return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
|
|
6968
7144
|
}
|
|
6969
7145
|
else {
|
|
6970
7146
|
return rxjs.from([]);
|
|
@@ -7015,8 +7191,8 @@
|
|
|
7015
7191
|
}
|
|
7016
7192
|
|
|
7017
7193
|
function isSyncNeeded(db) {
|
|
7018
|
-
var _a;
|
|
7019
7194
|
return __awaiter(this, void 0, void 0, function* () {
|
|
7195
|
+
var _a;
|
|
7020
7196
|
return ((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl) && db.cloud.schema
|
|
7021
7197
|
? yield sync(db, db.cloud.options, db.cloud.schema, { justCheckIfNeeded: true })
|
|
7022
7198
|
: false;
|
|
@@ -7700,6 +7876,7 @@
|
|
|
7700
7876
|
// If user can update any prop in any table in this realm, return true unless
|
|
7701
7877
|
// it regards to ownership change:
|
|
7702
7878
|
if (this.permissions.update === '*') {
|
|
7879
|
+
// @ts-ignore
|
|
7703
7880
|
return props.every((prop) => prop !== 'owner');
|
|
7704
7881
|
}
|
|
7705
7882
|
const tablePermissions = (_b = this.permissions.update) === null || _b === void 0 ? void 0 : _b[this.tableName];
|
|
@@ -7771,125 +7948,139 @@
|
|
|
7771
7948
|
});
|
|
7772
7949
|
|
|
7773
7950
|
function createYHandler(db) {
|
|
7774
|
-
const awap = getAwarenessLibrary(db);
|
|
7775
7951
|
return (provider) => {
|
|
7776
7952
|
var _a;
|
|
7777
7953
|
const doc = provider.doc;
|
|
7778
|
-
const { parentTable
|
|
7954
|
+
const { parentTable } = doc.meta || {};
|
|
7779
7955
|
if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
|
|
7780
7956
|
return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
|
|
7781
7957
|
}
|
|
7782
|
-
let awareness
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7958
|
+
let awareness;
|
|
7959
|
+
Object.defineProperty(provider, "awareness", {
|
|
7960
|
+
get() {
|
|
7961
|
+
if (awareness)
|
|
7962
|
+
return awareness;
|
|
7963
|
+
awareness = createAwareness(db, doc, provider);
|
|
7964
|
+
awarenessWeakMap.set(doc, awareness);
|
|
7965
|
+
return awareness;
|
|
7966
|
+
}
|
|
7967
|
+
});
|
|
7968
|
+
};
|
|
7969
|
+
}
|
|
7970
|
+
function createAwareness(db, doc, provider) {
|
|
7971
|
+
const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
|
|
7972
|
+
const awap = getAwarenessLibrary(db);
|
|
7973
|
+
const awareness = new awap.Awareness(doc);
|
|
7974
|
+
awareness.on('update', ({ added, updated, removed }, origin) => {
|
|
7975
|
+
// Send the update
|
|
7976
|
+
const changedClients = added.concat(updated).concat(removed);
|
|
7977
|
+
const user = db.cloud.currentUser.value;
|
|
7978
|
+
if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
|
|
7979
|
+
const update = awap.encodeAwarenessUpdate(awareness, changedClients);
|
|
7980
|
+
db.messageProducer.next({
|
|
7981
|
+
type: 'aware',
|
|
7982
|
+
table: parentTable,
|
|
7983
|
+
prop: parentProp,
|
|
7984
|
+
k: doc.meta.parentId,
|
|
7985
|
+
u: update,
|
|
7986
|
+
});
|
|
7987
|
+
if (provider.destroyed) {
|
|
7988
|
+
// We're called from awareness.on('destroy') that did
|
|
7989
|
+
// removeAwarenessStates.
|
|
7990
|
+
// It's time to also send the doc-close message that dexie-cloud understands
|
|
7991
|
+
// and uses to stop subscribing for updates and awareness updates and brings
|
|
7992
|
+
// down the cached information in memory on the WS connection for this.
|
|
7790
7993
|
db.messageProducer.next({
|
|
7791
|
-
type: '
|
|
7994
|
+
type: 'doc-close',
|
|
7792
7995
|
table: parentTable,
|
|
7793
7996
|
prop: parentProp,
|
|
7794
|
-
k: doc.meta.parentId
|
|
7795
|
-
u: update,
|
|
7997
|
+
k: doc.meta.parentId
|
|
7796
7998
|
});
|
|
7797
|
-
if (provider.destroyed) {
|
|
7798
|
-
// We're called from awareness.on('destroy') that did
|
|
7799
|
-
// removeAwarenessStates.
|
|
7800
|
-
// It's time to also send the doc-close message that dexie-cloud understands
|
|
7801
|
-
// and uses to stop subscribing for updates and awareness updates and brings
|
|
7802
|
-
// down the cached information in memory on the WS connection for this.
|
|
7803
|
-
db.messageProducer.next({
|
|
7804
|
-
type: 'doc-close',
|
|
7805
|
-
table: parentTable,
|
|
7806
|
-
prop: parentProp,
|
|
7807
|
-
k: doc.meta.parentId
|
|
7808
|
-
});
|
|
7809
|
-
}
|
|
7810
7999
|
}
|
|
7811
|
-
}
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
8000
|
+
}
|
|
8001
|
+
});
|
|
8002
|
+
awareness.on('destroy', () => {
|
|
8003
|
+
// Signal to server that this provider is destroyed (the update event will be triggered, which
|
|
8004
|
+
// in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
|
|
8005
|
+
awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
|
|
8006
|
+
});
|
|
8007
|
+
// Open the document on the server
|
|
8008
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
8009
|
+
if (provider.destroyed)
|
|
8010
|
+
return;
|
|
8011
|
+
let connected = false;
|
|
8012
|
+
let currentFlowId = 1;
|
|
8013
|
+
const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
|
|
7819
8014
|
if (provider.destroyed)
|
|
7820
8015
|
return;
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
8016
|
+
// Keep "connected" state in a variable so we can check it after async operations
|
|
8017
|
+
connected = wsStatus === 'connected';
|
|
8018
|
+
// We are or got connected. Open the document on the server.
|
|
8019
|
+
const user = db.cloud.currentUser.value;
|
|
8020
|
+
if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
|
|
8021
|
+
++currentFlowId;
|
|
8022
|
+
openDocumentOnServer().catch(error => {
|
|
8023
|
+
console.warn(`Error catched in createYHandler.ts: ${error}`);
|
|
8024
|
+
});
|
|
8025
|
+
}
|
|
8026
|
+
});
|
|
8027
|
+
// Wait until WebSocket is connected
|
|
8028
|
+
provider.addCleanupHandler(subscription);
|
|
8029
|
+
/** Sends an 'doc-open' message to server whenever websocket becomes
|
|
8030
|
+
* connected, or if it is already connected.
|
|
8031
|
+
* The flow is aborted in case websocket is disconnected while querying
|
|
8032
|
+
* information required to compute the state vector. Flow is also
|
|
8033
|
+
* aborted in case document or provider has been destroyed during
|
|
8034
|
+
* the async parts of the task.
|
|
8035
|
+
*
|
|
8036
|
+
* The state vector is only computed from the updates that have occured
|
|
8037
|
+
* after the last full sync - which could very often be zero - in which
|
|
8038
|
+
* case no state vector is sent (then the server already knows us by
|
|
8039
|
+
* revision)
|
|
8040
|
+
*
|
|
8041
|
+
* When server gets the doc-open message, it will authorized us for
|
|
8042
|
+
* whether we are allowed to read / write to this document, and then
|
|
8043
|
+
* keep the cached information in memory on the WS connection for this
|
|
8044
|
+
* particular document, as well as subscribe to updates and awareness updates
|
|
8045
|
+
* from other clients on the document.
|
|
8046
|
+
*/
|
|
8047
|
+
function openDocumentOnServer(wsStatus) {
|
|
8048
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
8049
|
+
const myFlow = currentFlowId; // So we can abort when a new flow is started
|
|
8050
|
+
const yTbl = db.table(updatesTable);
|
|
8051
|
+
const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
|
|
8052
|
+
// After every await, check if we still should be working on this task.
|
|
8053
|
+
if (provider.destroyed || currentFlowId !== myFlow || !connected)
|
|
7825
8054
|
return;
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
8055
|
+
const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
|
|
8056
|
+
const docOpenMsg = {
|
|
8057
|
+
type: 'doc-open',
|
|
8058
|
+
table: parentTable,
|
|
8059
|
+
prop: parentProp,
|
|
8060
|
+
k: parentId,
|
|
8061
|
+
serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
|
|
8062
|
+
};
|
|
8063
|
+
const serverUpdatesSinceLastSync = yield yTbl
|
|
8064
|
+
.where('i')
|
|
8065
|
+
.between(receivedUntil, Infinity, false)
|
|
8066
|
+
.filter((update) => Dexie.cmp(update.k, parentId) === 0 && // Only updates for this document
|
|
8067
|
+
((update.f || 0) & 1) === 0 // Don't include local changes
|
|
8068
|
+
)
|
|
8069
|
+
.toArray();
|
|
8070
|
+
// After every await, check if we still should be working on this task.
|
|
8071
|
+
if (provider.destroyed || currentFlowId !== myFlow || !connected)
|
|
8072
|
+
return;
|
|
8073
|
+
if (serverUpdatesSinceLastSync.length > 0) {
|
|
8074
|
+
const Y = $Y$1(db); // Get the Yjs library from Dexie constructor options
|
|
8075
|
+
const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
|
|
8076
|
+
const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
|
|
8077
|
+
docOpenMsg.sv = stateVector;
|
|
7834
8078
|
}
|
|
8079
|
+
db.messageProducer.next(docOpenMsg);
|
|
7835
8080
|
});
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
* connected, or if it is already connected.
|
|
7840
|
-
* The flow is aborted in case websocket is disconnected while querying
|
|
7841
|
-
* information required to compute the state vector. Flow is also
|
|
7842
|
-
* aborted in case document or provider has been destroyed during
|
|
7843
|
-
* the async parts of the task.
|
|
7844
|
-
*
|
|
7845
|
-
* The state vector is only computed from the updates that have occured
|
|
7846
|
-
* after the last full sync - which could very often be zero - in which
|
|
7847
|
-
* case no state vector is sent (then the server already knows us by
|
|
7848
|
-
* revision)
|
|
7849
|
-
*
|
|
7850
|
-
* When server gets the doc-open message, it will authorized us for
|
|
7851
|
-
* whether we are allowed to read / write to this document, and then
|
|
7852
|
-
* keep the cached information in memory on the WS connection for this
|
|
7853
|
-
* particular document, as well as subscribe to updates and awareness updates
|
|
7854
|
-
* from other clients on the document.
|
|
7855
|
-
*/
|
|
7856
|
-
function openDocumentOnServer(wsStatus) {
|
|
7857
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
7858
|
-
const myFlow = currentFlowId; // So we can abort when a new flow is started
|
|
7859
|
-
const yTbl = db.table(updatesTable);
|
|
7860
|
-
const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
|
|
7861
|
-
// After every await, check if we still should be working on this task.
|
|
7862
|
-
if (provider.destroyed || currentFlowId !== myFlow || !connected)
|
|
7863
|
-
return;
|
|
7864
|
-
const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
|
|
7865
|
-
const docOpenMsg = {
|
|
7866
|
-
type: 'doc-open',
|
|
7867
|
-
table: parentTable,
|
|
7868
|
-
prop: parentProp,
|
|
7869
|
-
k: parentId,
|
|
7870
|
-
serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
|
|
7871
|
-
};
|
|
7872
|
-
const serverUpdatesSinceLastSync = yield yTbl
|
|
7873
|
-
.where('i')
|
|
7874
|
-
.between(receivedUntil, Infinity, false)
|
|
7875
|
-
.filter((update) => Dexie.cmp(update.k, parentId) === 0 && // Only updates for this document
|
|
7876
|
-
((update.f || 0) & 1) === 0 // Don't include local changes
|
|
7877
|
-
)
|
|
7878
|
-
.toArray();
|
|
7879
|
-
// After every await, check if we still should be working on this task.
|
|
7880
|
-
if (provider.destroyed || currentFlowId !== myFlow || !connected)
|
|
7881
|
-
return;
|
|
7882
|
-
if (serverUpdatesSinceLastSync.length > 0) {
|
|
7883
|
-
const Y = $Y(db); // Get the Yjs library from Dexie constructor options
|
|
7884
|
-
const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
|
|
7885
|
-
const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
|
|
7886
|
-
docOpenMsg.sv = stateVector;
|
|
7887
|
-
}
|
|
7888
|
-
db.messageProducer.next(docOpenMsg);
|
|
7889
|
-
});
|
|
7890
|
-
}
|
|
7891
|
-
}));
|
|
7892
|
-
};
|
|
8081
|
+
}
|
|
8082
|
+
}))();
|
|
8083
|
+
return awareness;
|
|
7893
8084
|
}
|
|
7894
8085
|
|
|
7895
8086
|
function getTiedRealmId(objectId) {
|
|
@@ -7939,7 +8130,7 @@
|
|
|
7939
8130
|
const syncComplete = new rxjs.Subject();
|
|
7940
8131
|
dexie.cloud = {
|
|
7941
8132
|
// @ts-ignore
|
|
7942
|
-
version: "4.1.0-alpha.
|
|
8133
|
+
version: "4.1.0-alpha.21",
|
|
7943
8134
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
7944
8135
|
schema: null,
|
|
7945
8136
|
get currentUserId() {
|
|
@@ -7975,16 +8166,16 @@
|
|
|
7975
8166
|
}
|
|
7976
8167
|
updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
|
|
7977
8168
|
},
|
|
7978
|
-
logout(
|
|
7979
|
-
return __awaiter(this,
|
|
8169
|
+
logout() {
|
|
8170
|
+
return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
|
|
7980
8171
|
force
|
|
7981
8172
|
? yield _logout(DexieCloudDB(dexie), { deleteUnsyncedData: true })
|
|
7982
8173
|
: yield logout(DexieCloudDB(dexie));
|
|
7983
8174
|
});
|
|
7984
8175
|
},
|
|
7985
|
-
sync(
|
|
7986
|
-
|
|
7987
|
-
|
|
8176
|
+
sync() {
|
|
8177
|
+
return __awaiter(this, arguments, void 0, function* ({ wait, purpose } = { wait: true, purpose: 'push' }) {
|
|
8178
|
+
var _a;
|
|
7988
8179
|
if (wait === undefined)
|
|
7989
8180
|
wait = true;
|
|
7990
8181
|
const db = DexieCloudDB(dexie);
|
|
@@ -8042,8 +8233,8 @@
|
|
|
8042
8233
|
dexie.use(createImplicitPropSetterMiddleware(DexieCloudDB(dexie)));
|
|
8043
8234
|
dexie.use(createIdGenerationMiddleware(DexieCloudDB(dexie)));
|
|
8044
8235
|
function onDbReady(dexie) {
|
|
8045
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
8046
8236
|
return __awaiter(this, void 0, void 0, function* () {
|
|
8237
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
8047
8238
|
closed = false; // As Dexie calls us, we are not closed anymore. Maybe reopened? Remember db.ready event is registered with sticky flag!
|
|
8048
8239
|
const db = DexieCloudDB(dexie);
|
|
8049
8240
|
// Setup default GUI:
|
|
@@ -8066,7 +8257,7 @@
|
|
|
8066
8257
|
? yield navigator.serviceWorker.getRegistrations()
|
|
8067
8258
|
: [];
|
|
8068
8259
|
const [initiallySynced, lastSyncedRealms] = yield db.transaction('rw', db.$syncState, () => __awaiter(this, void 0, void 0, function* () {
|
|
8069
|
-
var
|
|
8260
|
+
var _a, _b;
|
|
8070
8261
|
const { options, schema } = db.cloud;
|
|
8071
8262
|
const [persistedOptions, persistedSchema, persistedSyncState] = yield Promise.all([
|
|
8072
8263
|
db.getOptions(),
|
|
@@ -8088,7 +8279,7 @@
|
|
|
8088
8279
|
delete newPersistedOptions.awarenessProtocol;
|
|
8089
8280
|
yield db.$syncState.put(newPersistedOptions, 'options');
|
|
8090
8281
|
}
|
|
8091
|
-
if (((
|
|
8282
|
+
if (((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.tryUseServiceWorker) &&
|
|
8092
8283
|
'serviceWorker' in navigator &&
|
|
8093
8284
|
swRegistrations.length > 0 &&
|
|
8094
8285
|
!DISABLE_SERVICEWORKER_STRATEGY) {
|
|
@@ -8102,7 +8293,7 @@
|
|
|
8102
8293
|
// Not configured for using service worker or no service worker
|
|
8103
8294
|
// registration exists. Don't rely on service worker to do any job.
|
|
8104
8295
|
// Use LocalSyncWorker instead.
|
|
8105
|
-
if (((
|
|
8296
|
+
if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.tryUseServiceWorker) &&
|
|
8106
8297
|
!db.cloud.isServiceWorkerDB) {
|
|
8107
8298
|
console.debug('dexie-cloud-addon: Not using service worker.', swRegistrations.length === 0
|
|
8108
8299
|
? 'No SW registrations found.'
|
|
@@ -8241,10 +8432,167 @@
|
|
|
8241
8432
|
}
|
|
8242
8433
|
}
|
|
8243
8434
|
// @ts-ignore
|
|
8244
|
-
dexieCloud.version = "4.1.0-alpha.
|
|
8435
|
+
dexieCloud.version = "4.1.0-alpha.21";
|
|
8245
8436
|
Dexie.Cloud = dexieCloud;
|
|
8246
8437
|
|
|
8438
|
+
const ydocTriggers = {};
|
|
8439
|
+
const docIsAlreadyHooked = new WeakSet();
|
|
8440
|
+
const middlewares = new WeakMap();
|
|
8441
|
+
const createMiddleware = (db) => ({
|
|
8442
|
+
stack: 'dbcore',
|
|
8443
|
+
level: 10,
|
|
8444
|
+
name: 'yTriggerMiddleware',
|
|
8445
|
+
create: (down) => {
|
|
8446
|
+
return Object.assign(Object.assign({}, down), { transaction: (stores, mode, options) => {
|
|
8447
|
+
const idbtrans = down.transaction(stores, mode, options);
|
|
8448
|
+
idbtrans.addEventListener('complete', onTransactionCommitted);
|
|
8449
|
+
return idbtrans;
|
|
8450
|
+
}, table: (tblName) => {
|
|
8451
|
+
const coreTable = down.table(tblName);
|
|
8452
|
+
const triggerSpec = ydocTriggers[tblName];
|
|
8453
|
+
if (!triggerSpec)
|
|
8454
|
+
return coreTable;
|
|
8455
|
+
const { trigger, parentTable, prop } = triggerSpec;
|
|
8456
|
+
return Object.assign(Object.assign({}, coreTable), { mutate(req) {
|
|
8457
|
+
switch (req.type) {
|
|
8458
|
+
case 'add': {
|
|
8459
|
+
for (const yUpdateRow of req.values) {
|
|
8460
|
+
if (yUpdateRow.k == undefined)
|
|
8461
|
+
continue; // A syncer or garbage collection state does not point to a key
|
|
8462
|
+
const primaryKey = yUpdateRow.k;
|
|
8463
|
+
const doc = Dexie.DexieYProvider.getDocCache(db).find(parentTable, primaryKey, prop);
|
|
8464
|
+
if (doc) {
|
|
8465
|
+
if (!docIsAlreadyHooked.has(doc)) {
|
|
8466
|
+
hookToDoc(doc, primaryKey, trigger);
|
|
8467
|
+
docIsAlreadyHooked.add(doc);
|
|
8468
|
+
}
|
|
8469
|
+
}
|
|
8470
|
+
else {
|
|
8471
|
+
enqueueTrigger(db, tblName, primaryKey, trigger);
|
|
8472
|
+
}
|
|
8473
|
+
}
|
|
8474
|
+
break;
|
|
8475
|
+
}
|
|
8476
|
+
case 'delete':
|
|
8477
|
+
// @ts-ignore
|
|
8478
|
+
if (req.trans._rejecting_y_ypdate) {
|
|
8479
|
+
// The deletion came from a rejection, not garbage collection.
|
|
8480
|
+
// When that happens, let the triggers run to compute new values
|
|
8481
|
+
// based on the deleted updates.
|
|
8482
|
+
coreTable
|
|
8483
|
+
.getMany({
|
|
8484
|
+
keys: req.keys,
|
|
8485
|
+
trans: req.trans,
|
|
8486
|
+
cache: 'immutable',
|
|
8487
|
+
})
|
|
8488
|
+
.then((updates) => {
|
|
8489
|
+
const keySet = new Dexie.RangeSet();
|
|
8490
|
+
for (const { k } of updates) {
|
|
8491
|
+
if (k != undefined)
|
|
8492
|
+
keySet.addKey(k);
|
|
8493
|
+
}
|
|
8494
|
+
for (const interval of keySet) {
|
|
8495
|
+
enqueueTrigger(db, tblName, interval.from, trigger);
|
|
8496
|
+
}
|
|
8497
|
+
});
|
|
8498
|
+
}
|
|
8499
|
+
break;
|
|
8500
|
+
}
|
|
8501
|
+
return coreTable.mutate(req);
|
|
8502
|
+
} });
|
|
8503
|
+
} });
|
|
8504
|
+
},
|
|
8505
|
+
});
|
|
8506
|
+
let triggerExecPromise = null;
|
|
8507
|
+
let triggerScheduled = false;
|
|
8508
|
+
let scheduledTriggers = [];
|
|
8509
|
+
function $Y(db) {
|
|
8510
|
+
const $Y = db._options.Y;
|
|
8511
|
+
if (!$Y)
|
|
8512
|
+
throw new Error('Y library not supplied to Dexie constructor');
|
|
8513
|
+
return $Y;
|
|
8514
|
+
}
|
|
8515
|
+
function executeTriggers(triggersToRun) {
|
|
8516
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
8517
|
+
for (const { db, parentId, trigger, updatesTable } of triggersToRun) {
|
|
8518
|
+
// Load entire document into an Y.Doc instance:
|
|
8519
|
+
const updates = yield db
|
|
8520
|
+
.table(updatesTable)
|
|
8521
|
+
.where({ k: parentId })
|
|
8522
|
+
.toArray();
|
|
8523
|
+
const Y = $Y(db);
|
|
8524
|
+
const yDoc = new Y.Doc();
|
|
8525
|
+
for (const update of updates) {
|
|
8526
|
+
Y.applyUpdateV2(yDoc, update.u);
|
|
8527
|
+
}
|
|
8528
|
+
try {
|
|
8529
|
+
yield trigger(yDoc, parentId);
|
|
8530
|
+
}
|
|
8531
|
+
catch (error) {
|
|
8532
|
+
console.error(`Error in YDocTrigger ${error}`);
|
|
8533
|
+
}
|
|
8534
|
+
}
|
|
8535
|
+
});
|
|
8536
|
+
}
|
|
8537
|
+
function enqueueTrigger(db, updatesTable, parentId, trigger) {
|
|
8538
|
+
scheduledTriggers.push({
|
|
8539
|
+
db,
|
|
8540
|
+
updatesTable,
|
|
8541
|
+
parentId,
|
|
8542
|
+
trigger,
|
|
8543
|
+
});
|
|
8544
|
+
}
|
|
8545
|
+
function onTransactionCommitted() {
|
|
8546
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
8547
|
+
if (!triggerScheduled && scheduledTriggers.length > 0) {
|
|
8548
|
+
triggerScheduled = true;
|
|
8549
|
+
if (triggerExecPromise)
|
|
8550
|
+
yield triggerExecPromise.catch(() => { });
|
|
8551
|
+
setTimeout(() => {
|
|
8552
|
+
// setTimeout() is to escape from Promise.PSD zones and never run within liveQueries or transaction scopes
|
|
8553
|
+
triggerScheduled = false;
|
|
8554
|
+
const triggersToRun = scheduledTriggers;
|
|
8555
|
+
scheduledTriggers = [];
|
|
8556
|
+
triggerExecPromise = executeTriggers(triggersToRun).finally(() => (triggerExecPromise = null));
|
|
8557
|
+
}, 0);
|
|
8558
|
+
}
|
|
8559
|
+
});
|
|
8560
|
+
}
|
|
8561
|
+
function hookToDoc(doc, parentId, trigger) {
|
|
8562
|
+
// From now on, keep listening to doc updates and execute the trigger when it happens there instead
|
|
8563
|
+
doc.on('updateV2', (update, origin) => {
|
|
8564
|
+
//Dexie.ignoreTransaction(()=>{
|
|
8565
|
+
trigger(doc, parentId);
|
|
8566
|
+
//});
|
|
8567
|
+
});
|
|
8568
|
+
/*
|
|
8569
|
+
NOT NEEDED because DexieYProvider's docCache will also listen to destroy and remove it from its cache:
|
|
8570
|
+
doc.on('destroy', ()=>{
|
|
8571
|
+
docIsAlreadyHooked.delete(doc);
|
|
8572
|
+
})
|
|
8573
|
+
*/
|
|
8574
|
+
}
|
|
8575
|
+
function defineYDocTrigger(table, prop, trigger) {
|
|
8576
|
+
var _a, _b;
|
|
8577
|
+
const updatesTable = (_b = (_a = table.schema.yProps) === null || _a === void 0 ? void 0 : _a.find((p) => p.prop === prop)) === null || _b === void 0 ? void 0 : _b.updatesTable;
|
|
8578
|
+
if (!updatesTable)
|
|
8579
|
+
throw new Error(`Table ${table.name} does not have a Yjs property named ${prop}`);
|
|
8580
|
+
ydocTriggers[updatesTable] = {
|
|
8581
|
+
trigger,
|
|
8582
|
+
parentTable: table.name,
|
|
8583
|
+
prop,
|
|
8584
|
+
};
|
|
8585
|
+
const db = table.db._novip;
|
|
8586
|
+
let mw = middlewares.get(db);
|
|
8587
|
+
if (!mw) {
|
|
8588
|
+
mw = createMiddleware(db);
|
|
8589
|
+
middlewares.set(db, mw);
|
|
8590
|
+
}
|
|
8591
|
+
db.use(mw);
|
|
8592
|
+
}
|
|
8593
|
+
|
|
8247
8594
|
exports.default = dexieCloud;
|
|
8595
|
+
exports.defineYDocTrigger = defineYDocTrigger;
|
|
8248
8596
|
exports.dexieCloud = dexieCloud;
|
|
8249
8597
|
exports.getTiedObjectId = getTiedObjectId;
|
|
8250
8598
|
exports.getTiedRealmId = getTiedRealmId;
|