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
|
*
|
|
@@ -1014,6 +1014,7 @@ function encodeYMessage(msg) {
|
|
|
1014
1014
|
break;
|
|
1015
1015
|
case 'u-s':
|
|
1016
1016
|
writeVarUint8Array(encoder, msg.u);
|
|
1017
|
+
writeVarString(encoder, msg.r || '');
|
|
1017
1018
|
break;
|
|
1018
1019
|
}
|
|
1019
1020
|
}
|
|
@@ -1400,7 +1401,8 @@ function decodeYMessage(a) {
|
|
|
1400
1401
|
table,
|
|
1401
1402
|
prop,
|
|
1402
1403
|
k,
|
|
1403
|
-
u: readVarUint8Array(decoder)
|
|
1404
|
+
u: readVarUint8Array(decoder),
|
|
1405
|
+
r: (decoder.pos < decoder.arr.length && readVarString(decoder)) || undefined,
|
|
1404
1406
|
};
|
|
1405
1407
|
default:
|
|
1406
1408
|
throw new TypeError(`Unknown message type: ${type}`);
|
|
@@ -3677,8 +3679,6 @@ function prodLog(level, ...args) {
|
|
|
3677
3679
|
*/
|
|
3678
3680
|
function setCurrentUser(db, user) {
|
|
3679
3681
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3680
|
-
if (user.userId === db.cloud.currentUserId)
|
|
3681
|
-
return; // Already this user.
|
|
3682
3682
|
const $logins = db.table('$logins');
|
|
3683
3683
|
yield db.transaction('rw', $logins, (tx) => __awaiter(this, void 0, void 0, function* () {
|
|
3684
3684
|
const existingLogins = yield $logins.toArray();
|
|
@@ -4768,6 +4768,7 @@ function syncWithServer(changes, y, syncState, baseRevs, db, databaseUrl, schema
|
|
|
4768
4768
|
baseRevs,
|
|
4769
4769
|
changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes),
|
|
4770
4770
|
y,
|
|
4771
|
+
dxcv: db.cloud.version
|
|
4771
4772
|
};
|
|
4772
4773
|
console.debug('Sync request', syncRequest);
|
|
4773
4774
|
db.syncStateChangedEvent.next({
|
|
@@ -4910,9 +4911,11 @@ function applyServerChanges(changes, db) {
|
|
|
4910
4911
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4911
4912
|
console.debug('Applying server changes', changes, Dexie.currentTransaction);
|
|
4912
4913
|
for (const { table: tableName, muts } of changes) {
|
|
4914
|
+
if (!db.dx._allTables[tableName]) {
|
|
4915
|
+
console.debug(`Server sent changes for table ${tableName} that we don't have. Ignoring.`);
|
|
4916
|
+
continue;
|
|
4917
|
+
}
|
|
4913
4918
|
const table = db.table(tableName);
|
|
4914
|
-
if (!table)
|
|
4915
|
-
continue; // If server sends changes on a table we don't have, ignore it.
|
|
4916
4919
|
const { primaryKey } = table.core.schema;
|
|
4917
4920
|
const keyDecoder = (key) => {
|
|
4918
4921
|
switch (key[0]) {
|
|
@@ -5098,9 +5101,15 @@ function listYClientMessagesAndStateVector(db, tablesToSync) {
|
|
|
5098
5101
|
|
|
5099
5102
|
function getUpdatesTable(db, table, ydocProp) {
|
|
5100
5103
|
var _a, _b, _c;
|
|
5104
|
+
if (!db.dx._allTables[table])
|
|
5105
|
+
return undefined;
|
|
5101
5106
|
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;
|
|
5102
|
-
if (!utbl)
|
|
5103
|
-
|
|
5107
|
+
if (!utbl) {
|
|
5108
|
+
console.debug(`No updatesTable found for ${table}.${ydocProp}`);
|
|
5109
|
+
return undefined;
|
|
5110
|
+
}
|
|
5111
|
+
if (!db.dx._allTables[utbl])
|
|
5112
|
+
return undefined;
|
|
5104
5113
|
return db.table(utbl);
|
|
5105
5114
|
}
|
|
5106
5115
|
|
|
@@ -5111,74 +5120,91 @@ function applyYServerMessages(yMessages, db) {
|
|
|
5111
5120
|
let resyncNeeded = false;
|
|
5112
5121
|
let yServerRevision;
|
|
5113
5122
|
for (const m of yMessages) {
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
.
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
break;
|
|
5132
|
-
}
|
|
5133
|
-
case 'u-reject': {
|
|
5134
|
-
// Acces control or constraint rejected the update.
|
|
5135
|
-
// We delete it. It's not going to be sent again.
|
|
5136
|
-
// What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
|
|
5137
|
-
// This is only an issue when the document is open. We could find the open document and
|
|
5138
|
-
// in a perfect world, we should send a reverse update to the open document to undo the change.
|
|
5139
|
-
// See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
|
|
5140
|
-
console.debug(`Y update rejected. Deleting it.`);
|
|
5141
|
-
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5142
|
-
// Delete the rejected update and all local updates since (avoid holes in the CRDT)
|
|
5143
|
-
// and destroy it's open document if there is one.
|
|
5144
|
-
const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
|
|
5145
|
-
if (primaryKey != null) {
|
|
5146
|
-
yield db.transaction('rw', utbl, (tx) => {
|
|
5147
|
-
// @ts-ignore
|
|
5148
|
-
tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
|
|
5149
|
-
return utbl
|
|
5150
|
-
.where('i')
|
|
5151
|
-
.aboveOrEqual(m.i)
|
|
5152
|
-
.filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
|
|
5153
|
-
.delete();
|
|
5154
|
-
});
|
|
5155
|
-
// Destroy active doc
|
|
5156
|
-
const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
|
|
5157
|
-
if (activeDoc)
|
|
5158
|
-
activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
|
|
5123
|
+
try {
|
|
5124
|
+
switch (m.type) {
|
|
5125
|
+
case 'u-s': {
|
|
5126
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5127
|
+
if (utbl) {
|
|
5128
|
+
const updateRow = {
|
|
5129
|
+
k: m.k,
|
|
5130
|
+
u: m.u,
|
|
5131
|
+
};
|
|
5132
|
+
if (m.r) {
|
|
5133
|
+
// @ts-ignore
|
|
5134
|
+
updateRow.r = m.r;
|
|
5135
|
+
yServerRevision = m.r;
|
|
5136
|
+
}
|
|
5137
|
+
receivedUntils[utbl.name] = yield utbl.add(updateRow);
|
|
5138
|
+
}
|
|
5139
|
+
break;
|
|
5159
5140
|
}
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5141
|
+
case 'u-ack': {
|
|
5142
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5143
|
+
if (utbl) {
|
|
5144
|
+
yield db.transaction('rw', utbl, (tx) => __awaiter(this, void 0, void 0, function* () {
|
|
5145
|
+
let syncer = (yield tx
|
|
5146
|
+
.table(utbl.name)
|
|
5147
|
+
.get(DEXIE_CLOUD_SYNCER_ID));
|
|
5148
|
+
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) }));
|
|
5149
|
+
}));
|
|
5150
|
+
}
|
|
5151
|
+
break;
|
|
5166
5152
|
}
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5153
|
+
case 'u-reject': {
|
|
5154
|
+
// Acces control or constraint rejected the update.
|
|
5155
|
+
// We delete it. It's not going to be sent again.
|
|
5156
|
+
// What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
|
|
5157
|
+
// This is only an issue when the document is open. We could find the open document and
|
|
5158
|
+
// in a perfect world, we should send a reverse update to the open document to undo the change.
|
|
5159
|
+
// See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
|
|
5160
|
+
console.debug(`Y update rejected. Deleting it.`);
|
|
5161
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5162
|
+
if (!utbl)
|
|
5163
|
+
break;
|
|
5164
|
+
// Delete the rejected update and all local updates since (avoid holes in the CRDT)
|
|
5165
|
+
// and destroy it's open document if there is one.
|
|
5166
|
+
const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
|
|
5167
|
+
if (primaryKey != null) {
|
|
5168
|
+
yield db.transaction('rw', utbl, (tx) => {
|
|
5169
|
+
// @ts-ignore
|
|
5170
|
+
tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
|
|
5171
|
+
return utbl
|
|
5172
|
+
.where('i')
|
|
5173
|
+
.aboveOrEqual(m.i)
|
|
5174
|
+
.filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
|
|
5175
|
+
.delete();
|
|
5176
|
+
});
|
|
5177
|
+
// Destroy active doc
|
|
5178
|
+
const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
|
|
5179
|
+
if (activeDoc)
|
|
5180
|
+
activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
|
|
5181
|
+
}
|
|
5182
|
+
break;
|
|
5183
|
+
}
|
|
5184
|
+
case 'in-sync': {
|
|
5185
|
+
const doc = DexieYProvider.getDocCache(db.dx).find(m.table, m.k, m.prop);
|
|
5186
|
+
if (doc && !doc.isSynced) {
|
|
5187
|
+
doc.emit('sync', [true]);
|
|
5188
|
+
}
|
|
5189
|
+
break;
|
|
5190
|
+
}
|
|
5191
|
+
case 'y-complete-sync-done': {
|
|
5192
|
+
yServerRevision = m.yServerRev;
|
|
5193
|
+
break;
|
|
5194
|
+
}
|
|
5195
|
+
case 'outdated-server-rev':
|
|
5196
|
+
resyncNeeded = true;
|
|
5197
|
+
break;
|
|
5172
5198
|
}
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5199
|
+
}
|
|
5200
|
+
catch (e) {
|
|
5201
|
+
console.error(`Failed to apply YMessage`, m, e);
|
|
5176
5202
|
}
|
|
5177
5203
|
}
|
|
5178
5204
|
return {
|
|
5179
5205
|
receivedUntils,
|
|
5180
5206
|
resyncNeeded,
|
|
5181
|
-
yServerRevision
|
|
5207
|
+
yServerRevision,
|
|
5182
5208
|
};
|
|
5183
5209
|
});
|
|
5184
5210
|
}
|
|
@@ -5289,7 +5315,9 @@ function downloadYDocsFromServer(db_1, databaseUrl_1, _a) {
|
|
|
5289
5315
|
throw new Error(`Protocol error from ${databaseUrl}/y/download`);
|
|
5290
5316
|
}
|
|
5291
5317
|
const yTable = getUpdatesTable(db, currentTable, currentProp);
|
|
5292
|
-
|
|
5318
|
+
if (yTable) {
|
|
5319
|
+
yield yTable.bulkAdd(docsToInsert);
|
|
5320
|
+
}
|
|
5293
5321
|
docsToInsert = [];
|
|
5294
5322
|
}
|
|
5295
5323
|
if (currentRealmId &&
|
|
@@ -5625,12 +5653,12 @@ function _sync(db_1, options_1, schema_1) {
|
|
|
5625
5653
|
return false; // Not needed anymore
|
|
5626
5654
|
});
|
|
5627
5655
|
}
|
|
5628
|
-
function deleteObjectsFromRemovedRealms(db, res,
|
|
5656
|
+
function deleteObjectsFromRemovedRealms(db, res, syncState) {
|
|
5629
5657
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5630
5658
|
const deletedRealms = new Set();
|
|
5631
5659
|
const rejectedRealms = new Set();
|
|
5632
|
-
const previousRealmSet =
|
|
5633
|
-
const previousInviteRealmSet =
|
|
5660
|
+
const previousRealmSet = syncState ? syncState.realms : [];
|
|
5661
|
+
const previousInviteRealmSet = syncState ? syncState.inviteRealms : [];
|
|
5634
5662
|
const updatedRealmSet = new Set(res.realms);
|
|
5635
5663
|
const updatedTotalRealmSet = new Set(res.realms.concat(res.inviteRealms));
|
|
5636
5664
|
for (const realmId of previousRealmSet) {
|
|
@@ -5672,17 +5700,10 @@ function deleteObjectsFromRemovedRealms(db, res, prevState) {
|
|
|
5672
5700
|
}
|
|
5673
5701
|
}
|
|
5674
5702
|
}
|
|
5675
|
-
if (rejectedRealms.size > 0) {
|
|
5676
|
-
// Remove rejected/deleted realms from yDownloadedRealms because of the following use case:
|
|
5677
|
-
// 1. User becomes added to the realm
|
|
5678
|
-
// 2. User syncs and all documents of the realm is downloaded (downloadYDocsFromServer.ts)
|
|
5679
|
-
// 3. User leaves the realm and all docs are deleted locally (built-in-trigger of deleting their rows in this file)
|
|
5680
|
-
// 4. User is yet again added to the realm. At this point, we must make sure the docs are not considered already downloaded.
|
|
5681
|
-
const updateSpec = {};
|
|
5703
|
+
if (rejectedRealms.size > 0 && (syncState === null || syncState === void 0 ? void 0 : syncState.yDownloadedRealms)) {
|
|
5682
5704
|
for (const realmId of rejectedRealms) {
|
|
5683
|
-
|
|
5705
|
+
delete syncState.yDownloadedRealms[realmId];
|
|
5684
5706
|
}
|
|
5685
|
-
yield db.$syncState.update('syncState', updateSpec);
|
|
5686
5707
|
}
|
|
5687
5708
|
});
|
|
5688
5709
|
}
|
|
@@ -6158,8 +6179,14 @@ function createIdGenerationMiddleware(db) {
|
|
|
6158
6179
|
}
|
|
6159
6180
|
return Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
6160
6181
|
var _a, _b;
|
|
6161
|
-
|
|
6162
|
-
if (
|
|
6182
|
+
const idbtrans = req.trans;
|
|
6183
|
+
if (idbtrans.mode === 'versionchange') {
|
|
6184
|
+
// Tell all the other middlewares to skip bothering. We're in versionchange mode.
|
|
6185
|
+
// dexie-cloud is not initialized yet.
|
|
6186
|
+
idbtrans.disableChangeTracking = true;
|
|
6187
|
+
idbtrans.disableAccessControl = true;
|
|
6188
|
+
}
|
|
6189
|
+
if (idbtrans.disableChangeTracking) {
|
|
6163
6190
|
// Disable ID policy checks and ID generation
|
|
6164
6191
|
return table.mutate(req);
|
|
6165
6192
|
}
|
|
@@ -6216,17 +6243,13 @@ function createImplicitPropSetterMiddleware(db) {
|
|
|
6216
6243
|
return Object.assign(Object.assign({}, core), { table: (tableName) => {
|
|
6217
6244
|
const table = core.table(tableName);
|
|
6218
6245
|
return Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
6219
|
-
var _a, _b, _c, _d;
|
|
6220
|
-
|
|
6221
|
-
if (
|
|
6246
|
+
var _a, _b, _c, _d, _e, _f;
|
|
6247
|
+
const trans = req.trans;
|
|
6248
|
+
if (trans.disableChangeTracking) {
|
|
6222
6249
|
return table.mutate(req);
|
|
6223
6250
|
}
|
|
6224
|
-
const
|
|
6225
|
-
if ((
|
|
6226
|
-
if (trans.mode === 'versionchange') {
|
|
6227
|
-
// Don't mutate tables marked for sync in versionchange transactions.
|
|
6228
|
-
return Promise.reject(new Dexie.UpgradeError(`Dexie Cloud Addon: Cannot upgrade or populate synced table "${tableName}". See https://dexie.org/cloud/docs/best-practices`));
|
|
6229
|
-
}
|
|
6251
|
+
const currentUserId = (_b = (_a = trans.currentUser) === null || _a === void 0 ? void 0 : _a.userId) !== null && _b !== void 0 ? _b : UNAUTHORIZED_USER.userId;
|
|
6252
|
+
if ((_d = (_c = db.cloud.schema) === null || _c === void 0 ? void 0 : _c[tableName]) === null || _d === void 0 ? void 0 : _d.markedForSync) {
|
|
6230
6253
|
if (req.type === 'add' || req.type === 'put') {
|
|
6231
6254
|
if (tableName === 'members') {
|
|
6232
6255
|
for (const member of req.values) {
|
|
@@ -6250,12 +6273,12 @@ function createImplicitPropSetterMiddleware(db) {
|
|
|
6250
6273
|
// and expect them to be returned. That scenario must work also when db.cloud.currentUserId === 'unauthorized'.
|
|
6251
6274
|
for (const obj of req.values) {
|
|
6252
6275
|
if (!obj.owner) {
|
|
6253
|
-
obj.owner =
|
|
6276
|
+
obj.owner = currentUserId;
|
|
6254
6277
|
}
|
|
6255
6278
|
if (!obj.realmId) {
|
|
6256
|
-
obj.realmId =
|
|
6279
|
+
obj.realmId = currentUserId;
|
|
6257
6280
|
}
|
|
6258
|
-
const key = (
|
|
6281
|
+
const key = (_f = (_e = table.schema.primaryKey).extractKey) === null || _f === void 0 ? void 0 : _f.call(_e, obj);
|
|
6259
6282
|
if (typeof key === 'string' && key[0] === '#') {
|
|
6260
6283
|
// Add $ts prop for put operations and
|
|
6261
6284
|
// disable update operations as well as consistent
|
|
@@ -6351,16 +6374,14 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
|
|
|
6351
6374
|
name: 'MutationTrackingMiddleware',
|
|
6352
6375
|
level: 1,
|
|
6353
6376
|
create: (core) => {
|
|
6377
|
+
const allTableNames = new Set(core.schema.tables.map((t) => t.name));
|
|
6354
6378
|
const ordinaryTables = core.schema.tables.filter((t) => !/^\$/.test(t.name));
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
}
|
|
6362
|
-
catch (_a) {
|
|
6363
|
-
throwVersionIncrementNeeded();
|
|
6379
|
+
const mutTableMap = new Map();
|
|
6380
|
+
for (const tbl of ordinaryTables) {
|
|
6381
|
+
const mutationTableName = `$${tbl.name}_mutations`;
|
|
6382
|
+
if (allTableNames.has(mutationTableName)) {
|
|
6383
|
+
mutTableMap.set(tbl.name, core.table(mutationTableName));
|
|
6384
|
+
}
|
|
6364
6385
|
}
|
|
6365
6386
|
return Object.assign(Object.assign({}, core), { transaction: (tables, mode) => {
|
|
6366
6387
|
let tx;
|
|
@@ -6438,6 +6459,11 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
|
|
|
6438
6459
|
}
|
|
6439
6460
|
const { schema } = table;
|
|
6440
6461
|
const mutsTable = mutTableMap.get(tableName);
|
|
6462
|
+
if (!mutsTable) {
|
|
6463
|
+
// We cannot track mutations on this table because there is no mutations table for it.
|
|
6464
|
+
// This might happen in upgraders that executes before cloud schema is applied.
|
|
6465
|
+
return table;
|
|
6466
|
+
}
|
|
6441
6467
|
return guardedTable(Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
6442
6468
|
var _a, _b, _c;
|
|
6443
6469
|
const trans = req.trans;
|
|
@@ -6896,13 +6922,13 @@ const SERVER_PING_TIMEOUT = 20000;
|
|
|
6896
6922
|
const CLIENT_PING_INTERVAL = 30000;
|
|
6897
6923
|
const FAIL_RETRY_WAIT_TIME = 60000;
|
|
6898
6924
|
class WSObservable extends Observable$1 {
|
|
6899
|
-
constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
|
|
6900
|
-
super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
|
|
6925
|
+
constructor(db, rev, yrev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
|
|
6926
|
+
super((subscriber) => new WSConnection(db, rev, yrev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
|
|
6901
6927
|
}
|
|
6902
6928
|
}
|
|
6903
6929
|
let counter = 0;
|
|
6904
6930
|
class WSConnection extends Subscription$1 {
|
|
6905
|
-
constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
|
|
6931
|
+
constructor(db, rev, yrev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
|
|
6906
6932
|
super(() => this.teardown());
|
|
6907
6933
|
this.id = ++counter;
|
|
6908
6934
|
this.subscriptions = new Set();
|
|
@@ -6911,6 +6937,7 @@ class WSConnection extends Subscription$1 {
|
|
|
6911
6937
|
this.db = db;
|
|
6912
6938
|
this.databaseUrl = db.cloud.options.databaseUrl;
|
|
6913
6939
|
this.rev = rev;
|
|
6940
|
+
this.yrev = yrev;
|
|
6914
6941
|
this.realmSetHash = realmSetHash;
|
|
6915
6942
|
this.clientIdentity = clientIdentity;
|
|
6916
6943
|
this.user = user;
|
|
@@ -6980,6 +7007,11 @@ class WSConnection extends Subscription$1 {
|
|
|
6980
7007
|
}
|
|
6981
7008
|
this.webSocketStatus.next('connecting');
|
|
6982
7009
|
this.pinger = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
7010
|
+
// setInterval here causes unnecessary pings when server is proved active anyway.
|
|
7011
|
+
// TODO: Use setTimout() here instead. When triggered, check if we really need to ping.
|
|
7012
|
+
// In case we've had server activity, we don't need to ping. Then schedule then next ping
|
|
7013
|
+
// to the time when we should ping next time (based on lastServerActivity + CLIENT_PING_INTERVAL).
|
|
7014
|
+
// Else, ping now and schedule next ping to CLIENT_PING_INTERVAL from now.
|
|
6983
7015
|
if (this.closed) {
|
|
6984
7016
|
console.debug('pinger check', this.id, 'CLOSED.');
|
|
6985
7017
|
this.teardown();
|
|
@@ -7026,9 +7058,13 @@ class WSConnection extends Subscription$1 {
|
|
|
7026
7058
|
if (this.subscriber.closed)
|
|
7027
7059
|
return;
|
|
7028
7060
|
searchParams.set('v', '2');
|
|
7029
|
-
|
|
7061
|
+
if (this.rev)
|
|
7062
|
+
searchParams.set('rev', this.rev);
|
|
7063
|
+
if (this.yrev)
|
|
7064
|
+
searchParams.set('yrev', this.yrev);
|
|
7030
7065
|
searchParams.set('realmsHash', this.realmSetHash);
|
|
7031
7066
|
searchParams.set('clientId', this.clientIdentity);
|
|
7067
|
+
searchParams.set('dxcv', this.db.cloud.version);
|
|
7032
7068
|
if (this.user.accessToken) {
|
|
7033
7069
|
searchParams.set('token', this.user.accessToken);
|
|
7034
7070
|
}
|
|
@@ -7065,8 +7101,8 @@ class WSConnection extends Subscription$1 {
|
|
|
7065
7101
|
}
|
|
7066
7102
|
}
|
|
7067
7103
|
}
|
|
7068
|
-
else if (msg.type === '
|
|
7069
|
-
|
|
7104
|
+
else if (msg.type === 'pong') {
|
|
7105
|
+
// Do nothing
|
|
7070
7106
|
}
|
|
7071
7107
|
else if (msg.type === 'doc-open') {
|
|
7072
7108
|
const docCache = DexieYProvider.getDocCache(this.db.dx);
|
|
@@ -7075,11 +7111,26 @@ class WSConnection extends Subscription$1 {
|
|
|
7075
7111
|
getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.
|
|
7076
7112
|
}
|
|
7077
7113
|
}
|
|
7078
|
-
else if (msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
|
|
7079
|
-
|
|
7080
|
-
|
|
7114
|
+
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') {
|
|
7115
|
+
applyYServerMessages([msg], this.db).then((_a) => __awaiter(this, [_a], void 0, function* ({ resyncNeeded, yServerRevision, receivedUntils }) {
|
|
7116
|
+
if (yServerRevision) {
|
|
7117
|
+
yield this.db.$syncState.update('syncState', { yServerRevision: yServerRevision });
|
|
7118
|
+
}
|
|
7119
|
+
if (msg.type === 'u-s' && receivedUntils) {
|
|
7120
|
+
const utbl = getUpdatesTable(this.db, msg.table, msg.prop);
|
|
7121
|
+
if (utbl) {
|
|
7122
|
+
const receivedUntil = receivedUntils[utbl.name];
|
|
7123
|
+
if (receivedUntil) {
|
|
7124
|
+
yield utbl.update(DEXIE_CLOUD_SYNCER_ID, { receivedUntil });
|
|
7125
|
+
}
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7128
|
+
if (resyncNeeded) {
|
|
7129
|
+
yield this.db.cloud.sync({ purpose: 'pull', wait: true });
|
|
7130
|
+
}
|
|
7131
|
+
}));
|
|
7081
7132
|
}
|
|
7082
|
-
else
|
|
7133
|
+
else {
|
|
7083
7134
|
// Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)
|
|
7084
7135
|
this.subscriber.next(msg);
|
|
7085
7136
|
}
|
|
@@ -7212,7 +7263,7 @@ function connectWebSocket(db) {
|
|
|
7212
7263
|
// If no new entries, server won't bother the client. If new entries, server sends only those
|
|
7213
7264
|
// and the baseRev of the last from same client-ID.
|
|
7214
7265
|
if (userLogin) {
|
|
7215
|
-
return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
|
|
7266
|
+
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);
|
|
7216
7267
|
}
|
|
7217
7268
|
else {
|
|
7218
7269
|
return from$1([]);
|
|
@@ -7342,61 +7393,95 @@ function syncIfPossible(db, cloudOptions, cloudSchema, options) {
|
|
|
7342
7393
|
}
|
|
7343
7394
|
|
|
7344
7395
|
const SECONDS = 1000;
|
|
7345
|
-
const MINUTES = 60 * SECONDS;
|
|
7346
7396
|
|
|
7347
7397
|
function LocalSyncWorker(db, cloudOptions, cloudSchema) {
|
|
7348
7398
|
let localSyncEventSubscription = null;
|
|
7349
|
-
//let syncHandler: ((event: Event) => void) | null = null;
|
|
7350
|
-
//let periodicSyncHandler: ((event: Event) => void) | null = null;
|
|
7351
7399
|
let cancelToken = { cancelled: false };
|
|
7352
|
-
let
|
|
7353
|
-
let
|
|
7354
|
-
function syncAndRetry(
|
|
7400
|
+
let nextRetryTime = 0;
|
|
7401
|
+
let syncStartTime = 0;
|
|
7402
|
+
function syncAndRetry(retryNum = 1) {
|
|
7355
7403
|
// Use setTimeout() to get onto a clean stack and
|
|
7356
7404
|
// break free from possible active transaction:
|
|
7357
7405
|
setTimeout(() => {
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
const combPurpose = retryPurpose === 'pull' ? 'pull' : purpose;
|
|
7361
|
-
retryHandle = null;
|
|
7362
|
-
retryPurpose = null;
|
|
7406
|
+
const purpose = pullSignalled ? 'pull' : 'push';
|
|
7407
|
+
syncStartTime = Date.now();
|
|
7363
7408
|
syncIfPossible(db, cloudOptions, cloudSchema, {
|
|
7364
7409
|
cancelToken,
|
|
7365
7410
|
retryImmediatelyOnFetchError: true, // workaround for "net::ERR_NETWORK_CHANGED" in chrome.
|
|
7366
|
-
purpose
|
|
7367
|
-
}).
|
|
7368
|
-
console.error('error in syncIfPossible()', e);
|
|
7411
|
+
purpose,
|
|
7412
|
+
}).then(() => {
|
|
7369
7413
|
if (cancelToken.cancelled) {
|
|
7370
7414
|
stop();
|
|
7371
7415
|
}
|
|
7372
|
-
else
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7416
|
+
else {
|
|
7417
|
+
if (pullSignalled || pushSignalled) {
|
|
7418
|
+
// If we have signalled for more sync, do it now.
|
|
7419
|
+
pullSignalled = false;
|
|
7420
|
+
pushSignalled = false;
|
|
7421
|
+
return syncAndRetry();
|
|
7422
|
+
}
|
|
7423
|
+
}
|
|
7424
|
+
ongoingSync = false;
|
|
7425
|
+
nextRetryTime = 0;
|
|
7426
|
+
syncStartTime = 0;
|
|
7427
|
+
}).catch((error) => {
|
|
7428
|
+
console.error('error in syncIfPossible()', error);
|
|
7429
|
+
if (cancelToken.cancelled) {
|
|
7430
|
+
stop();
|
|
7431
|
+
ongoingSync = false;
|
|
7432
|
+
nextRetryTime = 0;
|
|
7433
|
+
syncStartTime = 0;
|
|
7434
|
+
}
|
|
7435
|
+
else if (retryNum < 5) {
|
|
7436
|
+
// Mimic service worker sync event but a bit more eager: retry 4 times
|
|
7437
|
+
// * first retry after 20 seconds
|
|
7438
|
+
// * second retry 40 seconds later
|
|
7439
|
+
// * third retry 5 minutes later
|
|
7440
|
+
// * last retry 15 minutes later
|
|
7441
|
+
const retryIn = [0, 20, 40, 300, 900][retryNum] * SECONDS;
|
|
7442
|
+
nextRetryTime = Date.now() + retryIn;
|
|
7443
|
+
syncStartTime = 0;
|
|
7444
|
+
setTimeout(() => syncAndRetry(retryNum + 1), retryIn);
|
|
7445
|
+
}
|
|
7446
|
+
else {
|
|
7447
|
+
ongoingSync = false;
|
|
7448
|
+
nextRetryTime = 0;
|
|
7449
|
+
syncStartTime = 0;
|
|
7383
7450
|
}
|
|
7384
7451
|
});
|
|
7385
7452
|
}, 0);
|
|
7386
7453
|
}
|
|
7454
|
+
let pullSignalled = false;
|
|
7455
|
+
let pushSignalled = false;
|
|
7456
|
+
let ongoingSync = false;
|
|
7457
|
+
const consumer = (purpose) => {
|
|
7458
|
+
if (cancelToken.cancelled)
|
|
7459
|
+
return;
|
|
7460
|
+
if (purpose === 'pull') {
|
|
7461
|
+
pullSignalled = true;
|
|
7462
|
+
}
|
|
7463
|
+
if (purpose === 'push') {
|
|
7464
|
+
pushSignalled = true;
|
|
7465
|
+
}
|
|
7466
|
+
if (ongoingSync) {
|
|
7467
|
+
if (nextRetryTime) {
|
|
7468
|
+
console.debug(`Sync is paused until ${new Date(nextRetryTime).toISOString()} due to error in last sync attempt`);
|
|
7469
|
+
}
|
|
7470
|
+
else if (syncStartTime > 0 && Date.now() - syncStartTime > 20 * SECONDS) {
|
|
7471
|
+
console.debug(`An existing sync operation is taking more than 20 seconds. Will resync when done.`);
|
|
7472
|
+
}
|
|
7473
|
+
return;
|
|
7474
|
+
}
|
|
7475
|
+
ongoingSync = true;
|
|
7476
|
+
syncAndRetry();
|
|
7477
|
+
};
|
|
7387
7478
|
const start = () => {
|
|
7388
7479
|
// Sync eagerly whenever a change has happened (+ initially when there's no syncState yet)
|
|
7389
7480
|
// This initial subscribe will also trigger an sync also now.
|
|
7390
7481
|
console.debug('Starting LocalSyncWorker', db.localSyncEvent['id']);
|
|
7391
7482
|
localSyncEventSubscription = db.localSyncEvent.subscribe(({ purpose }) => {
|
|
7392
|
-
|
|
7393
|
-
syncAndRetry(purpose || 'pull');
|
|
7394
|
-
}
|
|
7395
|
-
catch (err) {
|
|
7396
|
-
console.error('What-the....', err);
|
|
7397
|
-
}
|
|
7483
|
+
consumer(purpose || 'pull');
|
|
7398
7484
|
});
|
|
7399
|
-
//setTimeout(()=>db.localSyncEvent.next({}), 5000);
|
|
7400
7485
|
};
|
|
7401
7486
|
const stop = () => {
|
|
7402
7487
|
console.debug('Stopping LocalSyncWorker');
|
|
@@ -8157,8 +8242,8 @@ function getTiedObjectId(realmId) {
|
|
|
8157
8242
|
|
|
8158
8243
|
const ydocTriggers = {};
|
|
8159
8244
|
const middlewares = new WeakMap();
|
|
8160
|
-
const txRunner = TriggerRunner("tx");
|
|
8161
|
-
const unloadRunner = TriggerRunner("unload");
|
|
8245
|
+
const txRunner = TriggerRunner("tx"); // Trigger registry for transaction completion. Avoids open docs.
|
|
8246
|
+
const unloadRunner = TriggerRunner("unload"); // Trigger registry for unload. Runs when a document is closed.
|
|
8162
8247
|
function TriggerRunner(name) {
|
|
8163
8248
|
let triggerExecPromise = null;
|
|
8164
8249
|
let triggerScheduled = false;
|
|
@@ -8168,14 +8253,17 @@ function TriggerRunner(name) {
|
|
|
8168
8253
|
for (const { db, parentId, triggers, parentTable, prop, } of registryCopy.values()) {
|
|
8169
8254
|
const yDoc = DexieYProvider.getOrCreateDocument(db, parentTable, prop, parentId);
|
|
8170
8255
|
try {
|
|
8171
|
-
DexieYProvider.load(yDoc); // If doc is open, this would just be a ++refount
|
|
8172
|
-
yield
|
|
8256
|
+
const provider = DexieYProvider.load(yDoc); // If doc is open, this would just be a ++refount
|
|
8257
|
+
yield provider.whenLoaded; // If doc is loaded, this would resolve immediately
|
|
8173
8258
|
for (const trigger of triggers) {
|
|
8174
8259
|
yield trigger(yDoc, parentId);
|
|
8175
8260
|
}
|
|
8176
8261
|
}
|
|
8177
8262
|
catch (error) {
|
|
8178
|
-
|
|
8263
|
+
if ((error === null || error === void 0 ? void 0 : error.name) === 'AbortError') ;
|
|
8264
|
+
else {
|
|
8265
|
+
console.error(`Error in YDocTrigger ${error}`);
|
|
8266
|
+
}
|
|
8179
8267
|
}
|
|
8180
8268
|
finally {
|
|
8181
8269
|
DexieYProvider.release(yDoc);
|
|
@@ -8187,16 +8275,20 @@ function TriggerRunner(name) {
|
|
|
8187
8275
|
name,
|
|
8188
8276
|
run() {
|
|
8189
8277
|
return __awaiter(this, void 0, void 0, function* () {
|
|
8278
|
+
console.log(`Running trigger (${name})?`, triggerScheduled, registry.size, !!triggerExecPromise);
|
|
8190
8279
|
if (!triggerScheduled && registry.size > 0) {
|
|
8191
8280
|
triggerScheduled = true;
|
|
8192
8281
|
if (triggerExecPromise)
|
|
8193
8282
|
yield triggerExecPromise.catch(() => { });
|
|
8194
8283
|
setTimeout(() => {
|
|
8195
8284
|
// setTimeout() is to escape from Promise.PSD zones and never run within liveQueries or transaction scopes
|
|
8285
|
+
console.log("Running trigger really!", name);
|
|
8196
8286
|
triggerScheduled = false;
|
|
8197
8287
|
const registryCopy = registry;
|
|
8198
8288
|
registry = new Map();
|
|
8199
|
-
triggerExecPromise = execute(registryCopy).finally(() =>
|
|
8289
|
+
triggerExecPromise = execute(registryCopy).finally(() => {
|
|
8290
|
+
triggerExecPromise = null;
|
|
8291
|
+
});
|
|
8200
8292
|
}, 0);
|
|
8201
8293
|
}
|
|
8202
8294
|
});
|
|
@@ -8212,6 +8304,7 @@ function TriggerRunner(name) {
|
|
|
8212
8304
|
prop,
|
|
8213
8305
|
triggers: new Set(),
|
|
8214
8306
|
};
|
|
8307
|
+
console.log(`Adding trigger ${key}`);
|
|
8215
8308
|
registry.set(key, entry);
|
|
8216
8309
|
}
|
|
8217
8310
|
entry.triggers.add(trigger);
|
|
@@ -8351,7 +8444,7 @@ function dexieCloud(dexie) {
|
|
|
8351
8444
|
const syncComplete = new Subject();
|
|
8352
8445
|
dexie.cloud = {
|
|
8353
8446
|
// @ts-ignore
|
|
8354
|
-
version: "4.1.0-beta.
|
|
8447
|
+
version: "4.1.0-beta.45",
|
|
8355
8448
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
8356
8449
|
schema: null,
|
|
8357
8450
|
get currentUserId() {
|
|
@@ -8669,7 +8762,7 @@ function dexieCloud(dexie) {
|
|
|
8669
8762
|
}
|
|
8670
8763
|
}
|
|
8671
8764
|
// @ts-ignore
|
|
8672
|
-
dexieCloud.version = "4.1.0-beta.
|
|
8765
|
+
dexieCloud.version = "4.1.0-beta.45";
|
|
8673
8766
|
Dexie.Cloud = dexieCloud;
|
|
8674
8767
|
|
|
8675
8768
|
export { dexieCloud as default, defineYDocTrigger, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
|