dexie-cloud-addon 4.1.0-beta.43 → 4.1.0-beta.45
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/WSObservable.d.ts +4 -3
- package/dist/modern/dexie-cloud-addon.js +248 -155
- 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/service-worker.js +234 -149
- 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/getUpdatesTable.d.ts +1 -1
- package/dist/umd/WSObservable.d.ts +4 -3
- package/dist/umd/dexie-cloud-addon.js +248 -155
- 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/service-worker.js +234 -149
- 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/getUpdatesTable.d.ts +1 -1
- package/package.json +4 -4
- package/dist/modern/helpers/dbOnClosed.d.ts +0 -2
- package/dist/umd/helpers/dbOnClosed.d.ts +0 -2
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.1.0-beta.
|
|
11
|
+
* Version 4.1.0-beta.45, Mon Mar 31 2025
|
|
12
12
|
*
|
|
13
13
|
* https://dexie.org
|
|
14
14
|
*
|
|
@@ -1017,6 +1017,7 @@
|
|
|
1017
1017
|
break;
|
|
1018
1018
|
case 'u-s':
|
|
1019
1019
|
writeVarUint8Array(encoder, msg.u);
|
|
1020
|
+
writeVarString(encoder, msg.r || '');
|
|
1020
1021
|
break;
|
|
1021
1022
|
}
|
|
1022
1023
|
}
|
|
@@ -1403,7 +1404,8 @@
|
|
|
1403
1404
|
table,
|
|
1404
1405
|
prop,
|
|
1405
1406
|
k,
|
|
1406
|
-
u: readVarUint8Array(decoder)
|
|
1407
|
+
u: readVarUint8Array(decoder),
|
|
1408
|
+
r: (decoder.pos < decoder.arr.length && readVarString(decoder)) || undefined,
|
|
1407
1409
|
};
|
|
1408
1410
|
default:
|
|
1409
1411
|
throw new TypeError(`Unknown message type: ${type}`);
|
|
@@ -3680,8 +3682,6 @@
|
|
|
3680
3682
|
*/
|
|
3681
3683
|
function setCurrentUser(db, user) {
|
|
3682
3684
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3683
|
-
if (user.userId === db.cloud.currentUserId)
|
|
3684
|
-
return; // Already this user.
|
|
3685
3685
|
const $logins = db.table('$logins');
|
|
3686
3686
|
yield db.transaction('rw', $logins, (tx) => __awaiter(this, void 0, void 0, function* () {
|
|
3687
3687
|
const existingLogins = yield $logins.toArray();
|
|
@@ -4771,6 +4771,7 @@
|
|
|
4771
4771
|
baseRevs,
|
|
4772
4772
|
changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes),
|
|
4773
4773
|
y,
|
|
4774
|
+
dxcv: db.cloud.version
|
|
4774
4775
|
};
|
|
4775
4776
|
console.debug('Sync request', syncRequest);
|
|
4776
4777
|
db.syncStateChangedEvent.next({
|
|
@@ -4913,9 +4914,11 @@
|
|
|
4913
4914
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4914
4915
|
console.debug('Applying server changes', changes, Dexie.currentTransaction);
|
|
4915
4916
|
for (const { table: tableName, muts } of changes) {
|
|
4917
|
+
if (!db.dx._allTables[tableName]) {
|
|
4918
|
+
console.debug(`Server sent changes for table ${tableName} that we don't have. Ignoring.`);
|
|
4919
|
+
continue;
|
|
4920
|
+
}
|
|
4916
4921
|
const table = db.table(tableName);
|
|
4917
|
-
if (!table)
|
|
4918
|
-
continue; // If server sends changes on a table we don't have, ignore it.
|
|
4919
4922
|
const { primaryKey } = table.core.schema;
|
|
4920
4923
|
const keyDecoder = (key) => {
|
|
4921
4924
|
switch (key[0]) {
|
|
@@ -5101,9 +5104,15 @@
|
|
|
5101
5104
|
|
|
5102
5105
|
function getUpdatesTable(db, table, ydocProp) {
|
|
5103
5106
|
var _a, _b, _c;
|
|
5107
|
+
if (!db.dx._allTables[table])
|
|
5108
|
+
return undefined;
|
|
5104
5109
|
const utbl = (_c = (_b = (_a = db.table(table)) === null || _a === void 0 ? void 0 : _a.schema.yProps) === null || _b === void 0 ? void 0 : _b.find(p => p.prop === ydocProp)) === null || _c === void 0 ? void 0 : _c.updatesTable;
|
|
5105
|
-
if (!utbl)
|
|
5106
|
-
|
|
5110
|
+
if (!utbl) {
|
|
5111
|
+
console.debug(`No updatesTable found for ${table}.${ydocProp}`);
|
|
5112
|
+
return undefined;
|
|
5113
|
+
}
|
|
5114
|
+
if (!db.dx._allTables[utbl])
|
|
5115
|
+
return undefined;
|
|
5107
5116
|
return db.table(utbl);
|
|
5108
5117
|
}
|
|
5109
5118
|
|
|
@@ -5114,74 +5123,91 @@
|
|
|
5114
5123
|
let resyncNeeded = false;
|
|
5115
5124
|
let yServerRevision;
|
|
5116
5125
|
for (const m of yMessages) {
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
.
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
break;
|
|
5135
|
-
}
|
|
5136
|
-
case 'u-reject': {
|
|
5137
|
-
// Acces control or constraint rejected the update.
|
|
5138
|
-
// We delete it. It's not going to be sent again.
|
|
5139
|
-
// What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
|
|
5140
|
-
// This is only an issue when the document is open. We could find the open document and
|
|
5141
|
-
// in a perfect world, we should send a reverse update to the open document to undo the change.
|
|
5142
|
-
// See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
|
|
5143
|
-
console.debug(`Y update rejected. Deleting it.`);
|
|
5144
|
-
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5145
|
-
// Delete the rejected update and all local updates since (avoid holes in the CRDT)
|
|
5146
|
-
// and destroy it's open document if there is one.
|
|
5147
|
-
const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
|
|
5148
|
-
if (primaryKey != null) {
|
|
5149
|
-
yield db.transaction('rw', utbl, (tx) => {
|
|
5150
|
-
// @ts-ignore
|
|
5151
|
-
tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
|
|
5152
|
-
return utbl
|
|
5153
|
-
.where('i')
|
|
5154
|
-
.aboveOrEqual(m.i)
|
|
5155
|
-
.filter((u) => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
|
|
5156
|
-
.delete();
|
|
5157
|
-
});
|
|
5158
|
-
// Destroy active doc
|
|
5159
|
-
const activeDoc = Dexie.DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
|
|
5160
|
-
if (activeDoc)
|
|
5161
|
-
activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
|
|
5126
|
+
try {
|
|
5127
|
+
switch (m.type) {
|
|
5128
|
+
case 'u-s': {
|
|
5129
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5130
|
+
if (utbl) {
|
|
5131
|
+
const updateRow = {
|
|
5132
|
+
k: m.k,
|
|
5133
|
+
u: m.u,
|
|
5134
|
+
};
|
|
5135
|
+
if (m.r) {
|
|
5136
|
+
// @ts-ignore
|
|
5137
|
+
updateRow.r = m.r;
|
|
5138
|
+
yServerRevision = m.r;
|
|
5139
|
+
}
|
|
5140
|
+
receivedUntils[utbl.name] = yield utbl.add(updateRow);
|
|
5141
|
+
}
|
|
5142
|
+
break;
|
|
5162
5143
|
}
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5144
|
+
case 'u-ack': {
|
|
5145
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5146
|
+
if (utbl) {
|
|
5147
|
+
yield db.transaction('rw', utbl, (tx) => __awaiter(this, void 0, void 0, function* () {
|
|
5148
|
+
let syncer = (yield tx
|
|
5149
|
+
.table(utbl.name)
|
|
5150
|
+
.get(DEXIE_CLOUD_SYNCER_ID));
|
|
5151
|
+
yield tx.table(utbl.name).put(Object.assign(Object.assign({}, (syncer || { i: DEXIE_CLOUD_SYNCER_ID })), { unsentFrom: Math.max((syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1, m.i + 1) }));
|
|
5152
|
+
}));
|
|
5153
|
+
}
|
|
5154
|
+
break;
|
|
5169
5155
|
}
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5156
|
+
case 'u-reject': {
|
|
5157
|
+
// Acces control or constraint rejected the update.
|
|
5158
|
+
// We delete it. It's not going to be sent again.
|
|
5159
|
+
// What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
|
|
5160
|
+
// This is only an issue when the document is open. We could find the open document and
|
|
5161
|
+
// in a perfect world, we should send a reverse update to the open document to undo the change.
|
|
5162
|
+
// See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
|
|
5163
|
+
console.debug(`Y update rejected. Deleting it.`);
|
|
5164
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5165
|
+
if (!utbl)
|
|
5166
|
+
break;
|
|
5167
|
+
// Delete the rejected update and all local updates since (avoid holes in the CRDT)
|
|
5168
|
+
// and destroy it's open document if there is one.
|
|
5169
|
+
const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
|
|
5170
|
+
if (primaryKey != null) {
|
|
5171
|
+
yield db.transaction('rw', utbl, (tx) => {
|
|
5172
|
+
// @ts-ignore
|
|
5173
|
+
tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
|
|
5174
|
+
return utbl
|
|
5175
|
+
.where('i')
|
|
5176
|
+
.aboveOrEqual(m.i)
|
|
5177
|
+
.filter((u) => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
|
|
5178
|
+
.delete();
|
|
5179
|
+
});
|
|
5180
|
+
// Destroy active doc
|
|
5181
|
+
const activeDoc = Dexie.DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
|
|
5182
|
+
if (activeDoc)
|
|
5183
|
+
activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
|
|
5184
|
+
}
|
|
5185
|
+
break;
|
|
5186
|
+
}
|
|
5187
|
+
case 'in-sync': {
|
|
5188
|
+
const doc = Dexie.DexieYProvider.getDocCache(db.dx).find(m.table, m.k, m.prop);
|
|
5189
|
+
if (doc && !doc.isSynced) {
|
|
5190
|
+
doc.emit('sync', [true]);
|
|
5191
|
+
}
|
|
5192
|
+
break;
|
|
5193
|
+
}
|
|
5194
|
+
case 'y-complete-sync-done': {
|
|
5195
|
+
yServerRevision = m.yServerRev;
|
|
5196
|
+
break;
|
|
5197
|
+
}
|
|
5198
|
+
case 'outdated-server-rev':
|
|
5199
|
+
resyncNeeded = true;
|
|
5200
|
+
break;
|
|
5175
5201
|
}
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5202
|
+
}
|
|
5203
|
+
catch (e) {
|
|
5204
|
+
console.error(`Failed to apply YMessage`, m, e);
|
|
5179
5205
|
}
|
|
5180
5206
|
}
|
|
5181
5207
|
return {
|
|
5182
5208
|
receivedUntils,
|
|
5183
5209
|
resyncNeeded,
|
|
5184
|
-
yServerRevision
|
|
5210
|
+
yServerRevision,
|
|
5185
5211
|
};
|
|
5186
5212
|
});
|
|
5187
5213
|
}
|
|
@@ -5292,7 +5318,9 @@
|
|
|
5292
5318
|
throw new Error(`Protocol error from ${databaseUrl}/y/download`);
|
|
5293
5319
|
}
|
|
5294
5320
|
const yTable = getUpdatesTable(db, currentTable, currentProp);
|
|
5295
|
-
|
|
5321
|
+
if (yTable) {
|
|
5322
|
+
yield yTable.bulkAdd(docsToInsert);
|
|
5323
|
+
}
|
|
5296
5324
|
docsToInsert = [];
|
|
5297
5325
|
}
|
|
5298
5326
|
if (currentRealmId &&
|
|
@@ -5628,12 +5656,12 @@
|
|
|
5628
5656
|
return false; // Not needed anymore
|
|
5629
5657
|
});
|
|
5630
5658
|
}
|
|
5631
|
-
function deleteObjectsFromRemovedRealms(db, res,
|
|
5659
|
+
function deleteObjectsFromRemovedRealms(db, res, syncState) {
|
|
5632
5660
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5633
5661
|
const deletedRealms = new Set();
|
|
5634
5662
|
const rejectedRealms = new Set();
|
|
5635
|
-
const previousRealmSet =
|
|
5636
|
-
const previousInviteRealmSet =
|
|
5663
|
+
const previousRealmSet = syncState ? syncState.realms : [];
|
|
5664
|
+
const previousInviteRealmSet = syncState ? syncState.inviteRealms : [];
|
|
5637
5665
|
const updatedRealmSet = new Set(res.realms);
|
|
5638
5666
|
const updatedTotalRealmSet = new Set(res.realms.concat(res.inviteRealms));
|
|
5639
5667
|
for (const realmId of previousRealmSet) {
|
|
@@ -5675,17 +5703,10 @@
|
|
|
5675
5703
|
}
|
|
5676
5704
|
}
|
|
5677
5705
|
}
|
|
5678
|
-
if (rejectedRealms.size > 0) {
|
|
5679
|
-
// Remove rejected/deleted realms from yDownloadedRealms because of the following use case:
|
|
5680
|
-
// 1. User becomes added to the realm
|
|
5681
|
-
// 2. User syncs and all documents of the realm is downloaded (downloadYDocsFromServer.ts)
|
|
5682
|
-
// 3. User leaves the realm and all docs are deleted locally (built-in-trigger of deleting their rows in this file)
|
|
5683
|
-
// 4. User is yet again added to the realm. At this point, we must make sure the docs are not considered already downloaded.
|
|
5684
|
-
const updateSpec = {};
|
|
5706
|
+
if (rejectedRealms.size > 0 && (syncState === null || syncState === void 0 ? void 0 : syncState.yDownloadedRealms)) {
|
|
5685
5707
|
for (const realmId of rejectedRealms) {
|
|
5686
|
-
|
|
5708
|
+
delete syncState.yDownloadedRealms[realmId];
|
|
5687
5709
|
}
|
|
5688
|
-
yield db.$syncState.update('syncState', updateSpec);
|
|
5689
5710
|
}
|
|
5690
5711
|
});
|
|
5691
5712
|
}
|
|
@@ -6161,8 +6182,14 @@
|
|
|
6161
6182
|
}
|
|
6162
6183
|
return Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
6163
6184
|
var _a, _b;
|
|
6164
|
-
|
|
6165
|
-
if (
|
|
6185
|
+
const idbtrans = req.trans;
|
|
6186
|
+
if (idbtrans.mode === 'versionchange') {
|
|
6187
|
+
// Tell all the other middlewares to skip bothering. We're in versionchange mode.
|
|
6188
|
+
// dexie-cloud is not initialized yet.
|
|
6189
|
+
idbtrans.disableChangeTracking = true;
|
|
6190
|
+
idbtrans.disableAccessControl = true;
|
|
6191
|
+
}
|
|
6192
|
+
if (idbtrans.disableChangeTracking) {
|
|
6166
6193
|
// Disable ID policy checks and ID generation
|
|
6167
6194
|
return table.mutate(req);
|
|
6168
6195
|
}
|
|
@@ -6219,17 +6246,13 @@
|
|
|
6219
6246
|
return Object.assign(Object.assign({}, core), { table: (tableName) => {
|
|
6220
6247
|
const table = core.table(tableName);
|
|
6221
6248
|
return Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
6222
|
-
var _a, _b, _c, _d;
|
|
6223
|
-
|
|
6224
|
-
if (
|
|
6249
|
+
var _a, _b, _c, _d, _e, _f;
|
|
6250
|
+
const trans = req.trans;
|
|
6251
|
+
if (trans.disableChangeTracking) {
|
|
6225
6252
|
return table.mutate(req);
|
|
6226
6253
|
}
|
|
6227
|
-
const
|
|
6228
|
-
if ((
|
|
6229
|
-
if (trans.mode === 'versionchange') {
|
|
6230
|
-
// Don't mutate tables marked for sync in versionchange transactions.
|
|
6231
|
-
return Promise.reject(new Dexie.UpgradeError(`Dexie Cloud Addon: Cannot upgrade or populate synced table "${tableName}". See https://dexie.org/cloud/docs/best-practices`));
|
|
6232
|
-
}
|
|
6254
|
+
const currentUserId = (_b = (_a = trans.currentUser) === null || _a === void 0 ? void 0 : _a.userId) !== null && _b !== void 0 ? _b : UNAUTHORIZED_USER.userId;
|
|
6255
|
+
if ((_d = (_c = db.cloud.schema) === null || _c === void 0 ? void 0 : _c[tableName]) === null || _d === void 0 ? void 0 : _d.markedForSync) {
|
|
6233
6256
|
if (req.type === 'add' || req.type === 'put') {
|
|
6234
6257
|
if (tableName === 'members') {
|
|
6235
6258
|
for (const member of req.values) {
|
|
@@ -6253,12 +6276,12 @@
|
|
|
6253
6276
|
// and expect them to be returned. That scenario must work also when db.cloud.currentUserId === 'unauthorized'.
|
|
6254
6277
|
for (const obj of req.values) {
|
|
6255
6278
|
if (!obj.owner) {
|
|
6256
|
-
obj.owner =
|
|
6279
|
+
obj.owner = currentUserId;
|
|
6257
6280
|
}
|
|
6258
6281
|
if (!obj.realmId) {
|
|
6259
|
-
obj.realmId =
|
|
6282
|
+
obj.realmId = currentUserId;
|
|
6260
6283
|
}
|
|
6261
|
-
const key = (
|
|
6284
|
+
const key = (_f = (_e = table.schema.primaryKey).extractKey) === null || _f === void 0 ? void 0 : _f.call(_e, obj);
|
|
6262
6285
|
if (typeof key === 'string' && key[0] === '#') {
|
|
6263
6286
|
// Add $ts prop for put operations and
|
|
6264
6287
|
// disable update operations as well as consistent
|
|
@@ -6354,16 +6377,14 @@
|
|
|
6354
6377
|
name: 'MutationTrackingMiddleware',
|
|
6355
6378
|
level: 1,
|
|
6356
6379
|
create: (core) => {
|
|
6380
|
+
const allTableNames = new Set(core.schema.tables.map((t) => t.name));
|
|
6357
6381
|
const ordinaryTables = core.schema.tables.filter((t) => !/^\$/.test(t.name));
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
}
|
|
6365
|
-
catch (_a) {
|
|
6366
|
-
throwVersionIncrementNeeded();
|
|
6382
|
+
const mutTableMap = new Map();
|
|
6383
|
+
for (const tbl of ordinaryTables) {
|
|
6384
|
+
const mutationTableName = `$${tbl.name}_mutations`;
|
|
6385
|
+
if (allTableNames.has(mutationTableName)) {
|
|
6386
|
+
mutTableMap.set(tbl.name, core.table(mutationTableName));
|
|
6387
|
+
}
|
|
6367
6388
|
}
|
|
6368
6389
|
return Object.assign(Object.assign({}, core), { transaction: (tables, mode) => {
|
|
6369
6390
|
let tx;
|
|
@@ -6441,6 +6462,11 @@
|
|
|
6441
6462
|
}
|
|
6442
6463
|
const { schema } = table;
|
|
6443
6464
|
const mutsTable = mutTableMap.get(tableName);
|
|
6465
|
+
if (!mutsTable) {
|
|
6466
|
+
// We cannot track mutations on this table because there is no mutations table for it.
|
|
6467
|
+
// This might happen in upgraders that executes before cloud schema is applied.
|
|
6468
|
+
return table;
|
|
6469
|
+
}
|
|
6444
6470
|
return guardedTable(Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
6445
6471
|
var _a, _b, _c;
|
|
6446
6472
|
const trans = req.trans;
|
|
@@ -6899,13 +6925,13 @@
|
|
|
6899
6925
|
const CLIENT_PING_INTERVAL = 30000;
|
|
6900
6926
|
const FAIL_RETRY_WAIT_TIME = 60000;
|
|
6901
6927
|
class WSObservable extends rxjs.Observable {
|
|
6902
|
-
constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
|
|
6903
|
-
super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
|
|
6928
|
+
constructor(db, rev, yrev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
|
|
6929
|
+
super((subscriber) => new WSConnection(db, rev, yrev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
|
|
6904
6930
|
}
|
|
6905
6931
|
}
|
|
6906
6932
|
let counter = 0;
|
|
6907
6933
|
class WSConnection extends rxjs.Subscription {
|
|
6908
|
-
constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
|
|
6934
|
+
constructor(db, rev, yrev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
|
|
6909
6935
|
super(() => this.teardown());
|
|
6910
6936
|
this.id = ++counter;
|
|
6911
6937
|
this.subscriptions = new Set();
|
|
@@ -6914,6 +6940,7 @@
|
|
|
6914
6940
|
this.db = db;
|
|
6915
6941
|
this.databaseUrl = db.cloud.options.databaseUrl;
|
|
6916
6942
|
this.rev = rev;
|
|
6943
|
+
this.yrev = yrev;
|
|
6917
6944
|
this.realmSetHash = realmSetHash;
|
|
6918
6945
|
this.clientIdentity = clientIdentity;
|
|
6919
6946
|
this.user = user;
|
|
@@ -6983,6 +7010,11 @@
|
|
|
6983
7010
|
}
|
|
6984
7011
|
this.webSocketStatus.next('connecting');
|
|
6985
7012
|
this.pinger = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
7013
|
+
// setInterval here causes unnecessary pings when server is proved active anyway.
|
|
7014
|
+
// TODO: Use setTimout() here instead. When triggered, check if we really need to ping.
|
|
7015
|
+
// In case we've had server activity, we don't need to ping. Then schedule then next ping
|
|
7016
|
+
// to the time when we should ping next time (based on lastServerActivity + CLIENT_PING_INTERVAL).
|
|
7017
|
+
// Else, ping now and schedule next ping to CLIENT_PING_INTERVAL from now.
|
|
6986
7018
|
if (this.closed) {
|
|
6987
7019
|
console.debug('pinger check', this.id, 'CLOSED.');
|
|
6988
7020
|
this.teardown();
|
|
@@ -7029,9 +7061,13 @@
|
|
|
7029
7061
|
if (this.subscriber.closed)
|
|
7030
7062
|
return;
|
|
7031
7063
|
searchParams.set('v', '2');
|
|
7032
|
-
|
|
7064
|
+
if (this.rev)
|
|
7065
|
+
searchParams.set('rev', this.rev);
|
|
7066
|
+
if (this.yrev)
|
|
7067
|
+
searchParams.set('yrev', this.yrev);
|
|
7033
7068
|
searchParams.set('realmsHash', this.realmSetHash);
|
|
7034
7069
|
searchParams.set('clientId', this.clientIdentity);
|
|
7070
|
+
searchParams.set('dxcv', this.db.cloud.version);
|
|
7035
7071
|
if (this.user.accessToken) {
|
|
7036
7072
|
searchParams.set('token', this.user.accessToken);
|
|
7037
7073
|
}
|
|
@@ -7068,8 +7104,8 @@
|
|
|
7068
7104
|
}
|
|
7069
7105
|
}
|
|
7070
7106
|
}
|
|
7071
|
-
else if (msg.type === '
|
|
7072
|
-
|
|
7107
|
+
else if (msg.type === 'pong') {
|
|
7108
|
+
// Do nothing
|
|
7073
7109
|
}
|
|
7074
7110
|
else if (msg.type === 'doc-open') {
|
|
7075
7111
|
const docCache = Dexie.DexieYProvider.getDocCache(this.db.dx);
|
|
@@ -7078,11 +7114,26 @@
|
|
|
7078
7114
|
getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.
|
|
7079
7115
|
}
|
|
7080
7116
|
}
|
|
7081
|
-
else if (msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
|
|
7082
|
-
|
|
7083
|
-
|
|
7117
|
+
else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync' || msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
|
|
7118
|
+
applyYServerMessages([msg], this.db).then((_a) => __awaiter(this, [_a], void 0, function* ({ resyncNeeded, yServerRevision, receivedUntils }) {
|
|
7119
|
+
if (yServerRevision) {
|
|
7120
|
+
yield this.db.$syncState.update('syncState', { yServerRevision: yServerRevision });
|
|
7121
|
+
}
|
|
7122
|
+
if (msg.type === 'u-s' && receivedUntils) {
|
|
7123
|
+
const utbl = getUpdatesTable(this.db, msg.table, msg.prop);
|
|
7124
|
+
if (utbl) {
|
|
7125
|
+
const receivedUntil = receivedUntils[utbl.name];
|
|
7126
|
+
if (receivedUntil) {
|
|
7127
|
+
yield utbl.update(DEXIE_CLOUD_SYNCER_ID, { receivedUntil });
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
if (resyncNeeded) {
|
|
7132
|
+
yield this.db.cloud.sync({ purpose: 'pull', wait: true });
|
|
7133
|
+
}
|
|
7134
|
+
}));
|
|
7084
7135
|
}
|
|
7085
|
-
else
|
|
7136
|
+
else {
|
|
7086
7137
|
// Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)
|
|
7087
7138
|
this.subscriber.next(msg);
|
|
7088
7139
|
}
|
|
@@ -7215,7 +7266,7 @@
|
|
|
7215
7266
|
// If no new entries, server won't bother the client. If new entries, server sends only those
|
|
7216
7267
|
// and the baseRev of the last from same client-ID.
|
|
7217
7268
|
if (userLogin) {
|
|
7218
|
-
return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
|
|
7269
|
+
return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, db.cloud.persistedSyncState.value.yServerRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
|
|
7219
7270
|
}
|
|
7220
7271
|
else {
|
|
7221
7272
|
return rxjs.from([]);
|
|
@@ -7345,61 +7396,95 @@
|
|
|
7345
7396
|
}
|
|
7346
7397
|
|
|
7347
7398
|
const SECONDS = 1000;
|
|
7348
|
-
const MINUTES = 60 * SECONDS;
|
|
7349
7399
|
|
|
7350
7400
|
function LocalSyncWorker(db, cloudOptions, cloudSchema) {
|
|
7351
7401
|
let localSyncEventSubscription = null;
|
|
7352
|
-
//let syncHandler: ((event: Event) => void) | null = null;
|
|
7353
|
-
//let periodicSyncHandler: ((event: Event) => void) | null = null;
|
|
7354
7402
|
let cancelToken = { cancelled: false };
|
|
7355
|
-
let
|
|
7356
|
-
let
|
|
7357
|
-
function syncAndRetry(
|
|
7403
|
+
let nextRetryTime = 0;
|
|
7404
|
+
let syncStartTime = 0;
|
|
7405
|
+
function syncAndRetry(retryNum = 1) {
|
|
7358
7406
|
// Use setTimeout() to get onto a clean stack and
|
|
7359
7407
|
// break free from possible active transaction:
|
|
7360
7408
|
setTimeout(() => {
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
const combPurpose = retryPurpose === 'pull' ? 'pull' : purpose;
|
|
7364
|
-
retryHandle = null;
|
|
7365
|
-
retryPurpose = null;
|
|
7409
|
+
const purpose = pullSignalled ? 'pull' : 'push';
|
|
7410
|
+
syncStartTime = Date.now();
|
|
7366
7411
|
syncIfPossible(db, cloudOptions, cloudSchema, {
|
|
7367
7412
|
cancelToken,
|
|
7368
7413
|
retryImmediatelyOnFetchError: true, // workaround for "net::ERR_NETWORK_CHANGED" in chrome.
|
|
7369
|
-
purpose
|
|
7370
|
-
}).
|
|
7371
|
-
console.error('error in syncIfPossible()', e);
|
|
7414
|
+
purpose,
|
|
7415
|
+
}).then(() => {
|
|
7372
7416
|
if (cancelToken.cancelled) {
|
|
7373
7417
|
stop();
|
|
7374
7418
|
}
|
|
7375
|
-
else
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7419
|
+
else {
|
|
7420
|
+
if (pullSignalled || pushSignalled) {
|
|
7421
|
+
// If we have signalled for more sync, do it now.
|
|
7422
|
+
pullSignalled = false;
|
|
7423
|
+
pushSignalled = false;
|
|
7424
|
+
return syncAndRetry();
|
|
7425
|
+
}
|
|
7426
|
+
}
|
|
7427
|
+
ongoingSync = false;
|
|
7428
|
+
nextRetryTime = 0;
|
|
7429
|
+
syncStartTime = 0;
|
|
7430
|
+
}).catch((error) => {
|
|
7431
|
+
console.error('error in syncIfPossible()', error);
|
|
7432
|
+
if (cancelToken.cancelled) {
|
|
7433
|
+
stop();
|
|
7434
|
+
ongoingSync = false;
|
|
7435
|
+
nextRetryTime = 0;
|
|
7436
|
+
syncStartTime = 0;
|
|
7437
|
+
}
|
|
7438
|
+
else if (retryNum < 5) {
|
|
7439
|
+
// Mimic service worker sync event but a bit more eager: retry 4 times
|
|
7440
|
+
// * first retry after 20 seconds
|
|
7441
|
+
// * second retry 40 seconds later
|
|
7442
|
+
// * third retry 5 minutes later
|
|
7443
|
+
// * last retry 15 minutes later
|
|
7444
|
+
const retryIn = [0, 20, 40, 300, 900][retryNum] * SECONDS;
|
|
7445
|
+
nextRetryTime = Date.now() + retryIn;
|
|
7446
|
+
syncStartTime = 0;
|
|
7447
|
+
setTimeout(() => syncAndRetry(retryNum + 1), retryIn);
|
|
7448
|
+
}
|
|
7449
|
+
else {
|
|
7450
|
+
ongoingSync = false;
|
|
7451
|
+
nextRetryTime = 0;
|
|
7452
|
+
syncStartTime = 0;
|
|
7386
7453
|
}
|
|
7387
7454
|
});
|
|
7388
7455
|
}, 0);
|
|
7389
7456
|
}
|
|
7457
|
+
let pullSignalled = false;
|
|
7458
|
+
let pushSignalled = false;
|
|
7459
|
+
let ongoingSync = false;
|
|
7460
|
+
const consumer = (purpose) => {
|
|
7461
|
+
if (cancelToken.cancelled)
|
|
7462
|
+
return;
|
|
7463
|
+
if (purpose === 'pull') {
|
|
7464
|
+
pullSignalled = true;
|
|
7465
|
+
}
|
|
7466
|
+
if (purpose === 'push') {
|
|
7467
|
+
pushSignalled = true;
|
|
7468
|
+
}
|
|
7469
|
+
if (ongoingSync) {
|
|
7470
|
+
if (nextRetryTime) {
|
|
7471
|
+
console.debug(`Sync is paused until ${new Date(nextRetryTime).toISOString()} due to error in last sync attempt`);
|
|
7472
|
+
}
|
|
7473
|
+
else if (syncStartTime > 0 && Date.now() - syncStartTime > 20 * SECONDS) {
|
|
7474
|
+
console.debug(`An existing sync operation is taking more than 20 seconds. Will resync when done.`);
|
|
7475
|
+
}
|
|
7476
|
+
return;
|
|
7477
|
+
}
|
|
7478
|
+
ongoingSync = true;
|
|
7479
|
+
syncAndRetry();
|
|
7480
|
+
};
|
|
7390
7481
|
const start = () => {
|
|
7391
7482
|
// Sync eagerly whenever a change has happened (+ initially when there's no syncState yet)
|
|
7392
7483
|
// This initial subscribe will also trigger an sync also now.
|
|
7393
7484
|
console.debug('Starting LocalSyncWorker', db.localSyncEvent['id']);
|
|
7394
7485
|
localSyncEventSubscription = db.localSyncEvent.subscribe(({ purpose }) => {
|
|
7395
|
-
|
|
7396
|
-
syncAndRetry(purpose || 'pull');
|
|
7397
|
-
}
|
|
7398
|
-
catch (err) {
|
|
7399
|
-
console.error('What-the....', err);
|
|
7400
|
-
}
|
|
7486
|
+
consumer(purpose || 'pull');
|
|
7401
7487
|
});
|
|
7402
|
-
//setTimeout(()=>db.localSyncEvent.next({}), 5000);
|
|
7403
7488
|
};
|
|
7404
7489
|
const stop = () => {
|
|
7405
7490
|
console.debug('Stopping LocalSyncWorker');
|
|
@@ -8160,8 +8245,8 @@
|
|
|
8160
8245
|
|
|
8161
8246
|
const ydocTriggers = {};
|
|
8162
8247
|
const middlewares = new WeakMap();
|
|
8163
|
-
const txRunner = TriggerRunner("tx");
|
|
8164
|
-
const unloadRunner = TriggerRunner("unload");
|
|
8248
|
+
const txRunner = TriggerRunner("tx"); // Trigger registry for transaction completion. Avoids open docs.
|
|
8249
|
+
const unloadRunner = TriggerRunner("unload"); // Trigger registry for unload. Runs when a document is closed.
|
|
8165
8250
|
function TriggerRunner(name) {
|
|
8166
8251
|
let triggerExecPromise = null;
|
|
8167
8252
|
let triggerScheduled = false;
|
|
@@ -8171,14 +8256,17 @@
|
|
|
8171
8256
|
for (const { db, parentId, triggers, parentTable, prop, } of registryCopy.values()) {
|
|
8172
8257
|
const yDoc = Dexie.DexieYProvider.getOrCreateDocument(db, parentTable, prop, parentId);
|
|
8173
8258
|
try {
|
|
8174
|
-
Dexie.DexieYProvider.load(yDoc); // If doc is open, this would just be a ++refount
|
|
8175
|
-
yield
|
|
8259
|
+
const provider = Dexie.DexieYProvider.load(yDoc); // If doc is open, this would just be a ++refount
|
|
8260
|
+
yield provider.whenLoaded; // If doc is loaded, this would resolve immediately
|
|
8176
8261
|
for (const trigger of triggers) {
|
|
8177
8262
|
yield trigger(yDoc, parentId);
|
|
8178
8263
|
}
|
|
8179
8264
|
}
|
|
8180
8265
|
catch (error) {
|
|
8181
|
-
|
|
8266
|
+
if ((error === null || error === void 0 ? void 0 : error.name) === 'AbortError') ;
|
|
8267
|
+
else {
|
|
8268
|
+
console.error(`Error in YDocTrigger ${error}`);
|
|
8269
|
+
}
|
|
8182
8270
|
}
|
|
8183
8271
|
finally {
|
|
8184
8272
|
Dexie.DexieYProvider.release(yDoc);
|
|
@@ -8190,16 +8278,20 @@
|
|
|
8190
8278
|
name,
|
|
8191
8279
|
run() {
|
|
8192
8280
|
return __awaiter(this, void 0, void 0, function* () {
|
|
8281
|
+
console.log(`Running trigger (${name})?`, triggerScheduled, registry.size, !!triggerExecPromise);
|
|
8193
8282
|
if (!triggerScheduled && registry.size > 0) {
|
|
8194
8283
|
triggerScheduled = true;
|
|
8195
8284
|
if (triggerExecPromise)
|
|
8196
8285
|
yield triggerExecPromise.catch(() => { });
|
|
8197
8286
|
setTimeout(() => {
|
|
8198
8287
|
// setTimeout() is to escape from Promise.PSD zones and never run within liveQueries or transaction scopes
|
|
8288
|
+
console.log("Running trigger really!", name);
|
|
8199
8289
|
triggerScheduled = false;
|
|
8200
8290
|
const registryCopy = registry;
|
|
8201
8291
|
registry = new Map();
|
|
8202
|
-
triggerExecPromise = execute(registryCopy).finally(() =>
|
|
8292
|
+
triggerExecPromise = execute(registryCopy).finally(() => {
|
|
8293
|
+
triggerExecPromise = null;
|
|
8294
|
+
});
|
|
8203
8295
|
}, 0);
|
|
8204
8296
|
}
|
|
8205
8297
|
});
|
|
@@ -8215,6 +8307,7 @@
|
|
|
8215
8307
|
prop,
|
|
8216
8308
|
triggers: new Set(),
|
|
8217
8309
|
};
|
|
8310
|
+
console.log(`Adding trigger ${key}`);
|
|
8218
8311
|
registry.set(key, entry);
|
|
8219
8312
|
}
|
|
8220
8313
|
entry.triggers.add(trigger);
|
|
@@ -8354,7 +8447,7 @@
|
|
|
8354
8447
|
const syncComplete = new rxjs.Subject();
|
|
8355
8448
|
dexie.cloud = {
|
|
8356
8449
|
// @ts-ignore
|
|
8357
|
-
version: "4.1.0-beta.
|
|
8450
|
+
version: "4.1.0-beta.45",
|
|
8358
8451
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
8359
8452
|
schema: null,
|
|
8360
8453
|
get currentUserId() {
|
|
@@ -8672,7 +8765,7 @@
|
|
|
8672
8765
|
}
|
|
8673
8766
|
}
|
|
8674
8767
|
// @ts-ignore
|
|
8675
|
-
dexieCloud.version = "4.1.0-beta.
|
|
8768
|
+
dexieCloud.version = "4.1.0-beta.45";
|
|
8676
8769
|
Dexie.Cloud = dexieCloud;
|
|
8677
8770
|
|
|
8678
8771
|
exports.default = dexieCloud;
|