dexie-cloud-addon 4.1.0-beta.44 → 4.1.0-beta.46
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 +220 -130
- 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 +206 -124
- 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 +220 -130
- 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 +206 -124
- 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 +3 -3
- package/dist/modern/helpers/dbOnClosed.d.ts +0 -2
- package/dist/umd/helpers/dbOnClosed.d.ts +0 -2
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { DexieCloudDB } from "../db/DexieCloudDB";
|
|
2
2
|
import { YTable } from "./YTable";
|
|
3
|
-
export declare function getUpdatesTable(db: DexieCloudDB, table: string, ydocProp: string): YTable;
|
|
3
|
+
export declare function getUpdatesTable(db: DexieCloudDB, table: string, ydocProp: string): YTable | undefined;
|
|
@@ -42,7 +42,7 @@ export interface TokenExpiredMessage {
|
|
|
42
42
|
type: 'token-expired';
|
|
43
43
|
}
|
|
44
44
|
export declare class WSObservable extends Observable<WSConnectionMsg> {
|
|
45
|
-
constructor(db: DexieCloudDB, rev: string, realmSetHash: string, clientIdentity: string, messageProducer: Observable<WSClientToServerMsg>, webSocketStatus: BehaviorSubject<DXCWebSocketStatus>, user: UserLogin);
|
|
45
|
+
constructor(db: DexieCloudDB, rev: string | undefined, yrev: string | undefined, realmSetHash: string, clientIdentity: string, messageProducer: Observable<WSClientToServerMsg>, webSocketStatus: BehaviorSubject<DXCWebSocketStatus>, user: UserLogin);
|
|
46
46
|
}
|
|
47
47
|
export declare class WSConnection extends Subscription {
|
|
48
48
|
db: DexieCloudDB;
|
|
@@ -51,7 +51,8 @@ export declare class WSConnection extends Subscription {
|
|
|
51
51
|
lastUserActivity: Date;
|
|
52
52
|
lastPing: Date;
|
|
53
53
|
databaseUrl: string;
|
|
54
|
-
rev: string;
|
|
54
|
+
rev: string | undefined;
|
|
55
|
+
yrev: string | undefined;
|
|
55
56
|
realmSetHash: string;
|
|
56
57
|
clientIdentity: string;
|
|
57
58
|
user: UserLogin;
|
|
@@ -62,7 +63,7 @@ export declare class WSConnection extends Subscription {
|
|
|
62
63
|
id: number;
|
|
63
64
|
private pinger;
|
|
64
65
|
private subscriptions;
|
|
65
|
-
constructor(db: DexieCloudDB, rev: string, realmSetHash: string, clientIdentity: string, user: UserLogin, subscriber: Subscriber<WSConnectionMsg>, messageProducer: Observable<WSClientToServerMsg>, webSocketStatus: BehaviorSubject<DXCWebSocketStatus>);
|
|
66
|
+
constructor(db: DexieCloudDB, rev: string | undefined, yrev: string | undefined, realmSetHash: string, clientIdentity: string, user: UserLogin, subscriber: Subscriber<WSConnectionMsg>, messageProducer: Observable<WSClientToServerMsg>, webSocketStatus: BehaviorSubject<DXCWebSocketStatus>);
|
|
66
67
|
private teardown;
|
|
67
68
|
private disconnect;
|
|
68
69
|
reconnecting: boolean;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.1.0-beta.
|
|
11
|
+
* Version 4.1.0-beta.46, Thu Apr 03 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}`);
|
|
@@ -4769,6 +4771,7 @@
|
|
|
4769
4771
|
baseRevs,
|
|
4770
4772
|
changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes),
|
|
4771
4773
|
y,
|
|
4774
|
+
dxcv: db.cloud.version
|
|
4772
4775
|
};
|
|
4773
4776
|
console.debug('Sync request', syncRequest);
|
|
4774
4777
|
db.syncStateChangedEvent.next({
|
|
@@ -4911,9 +4914,11 @@
|
|
|
4911
4914
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4912
4915
|
console.debug('Applying server changes', changes, Dexie.currentTransaction);
|
|
4913
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
|
+
}
|
|
4914
4921
|
const table = db.table(tableName);
|
|
4915
|
-
if (!table)
|
|
4916
|
-
continue; // If server sends changes on a table we don't have, ignore it.
|
|
4917
4922
|
const { primaryKey } = table.core.schema;
|
|
4918
4923
|
const keyDecoder = (key) => {
|
|
4919
4924
|
switch (key[0]) {
|
|
@@ -5099,9 +5104,15 @@
|
|
|
5099
5104
|
|
|
5100
5105
|
function getUpdatesTable(db, table, ydocProp) {
|
|
5101
5106
|
var _a, _b, _c;
|
|
5107
|
+
if (!db.dx._allTables[table])
|
|
5108
|
+
return undefined;
|
|
5102
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;
|
|
5103
|
-
if (!utbl)
|
|
5104
|
-
|
|
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;
|
|
5105
5116
|
return db.table(utbl);
|
|
5106
5117
|
}
|
|
5107
5118
|
|
|
@@ -5112,74 +5123,91 @@
|
|
|
5112
5123
|
let resyncNeeded = false;
|
|
5113
5124
|
let yServerRevision;
|
|
5114
5125
|
for (const m of yMessages) {
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
.
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
break;
|
|
5133
|
-
}
|
|
5134
|
-
case 'u-reject': {
|
|
5135
|
-
// Acces control or constraint rejected the update.
|
|
5136
|
-
// We delete it. It's not going to be sent again.
|
|
5137
|
-
// What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
|
|
5138
|
-
// This is only an issue when the document is open. We could find the open document and
|
|
5139
|
-
// in a perfect world, we should send a reverse update to the open document to undo the change.
|
|
5140
|
-
// See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
|
|
5141
|
-
console.debug(`Y update rejected. Deleting it.`);
|
|
5142
|
-
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5143
|
-
// Delete the rejected update and all local updates since (avoid holes in the CRDT)
|
|
5144
|
-
// and destroy it's open document if there is one.
|
|
5145
|
-
const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
|
|
5146
|
-
if (primaryKey != null) {
|
|
5147
|
-
yield db.transaction('rw', utbl, (tx) => {
|
|
5148
|
-
// @ts-ignore
|
|
5149
|
-
tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
|
|
5150
|
-
return utbl
|
|
5151
|
-
.where('i')
|
|
5152
|
-
.aboveOrEqual(m.i)
|
|
5153
|
-
.filter((u) => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
|
|
5154
|
-
.delete();
|
|
5155
|
-
});
|
|
5156
|
-
// Destroy active doc
|
|
5157
|
-
const activeDoc = Dexie.DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
|
|
5158
|
-
if (activeDoc)
|
|
5159
|
-
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;
|
|
5160
5143
|
}
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
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;
|
|
5167
5155
|
}
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
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;
|
|
5173
5201
|
}
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5202
|
+
}
|
|
5203
|
+
catch (e) {
|
|
5204
|
+
console.error(`Failed to apply YMessage`, m, e);
|
|
5177
5205
|
}
|
|
5178
5206
|
}
|
|
5179
5207
|
return {
|
|
5180
5208
|
receivedUntils,
|
|
5181
5209
|
resyncNeeded,
|
|
5182
|
-
yServerRevision
|
|
5210
|
+
yServerRevision,
|
|
5183
5211
|
};
|
|
5184
5212
|
});
|
|
5185
5213
|
}
|
|
@@ -5290,7 +5318,9 @@
|
|
|
5290
5318
|
throw new Error(`Protocol error from ${databaseUrl}/y/download`);
|
|
5291
5319
|
}
|
|
5292
5320
|
const yTable = getUpdatesTable(db, currentTable, currentProp);
|
|
5293
|
-
|
|
5321
|
+
if (yTable) {
|
|
5322
|
+
yield yTable.bulkAdd(docsToInsert);
|
|
5323
|
+
}
|
|
5294
5324
|
docsToInsert = [];
|
|
5295
5325
|
}
|
|
5296
5326
|
if (currentRealmId &&
|
|
@@ -5626,12 +5656,12 @@
|
|
|
5626
5656
|
return false; // Not needed anymore
|
|
5627
5657
|
});
|
|
5628
5658
|
}
|
|
5629
|
-
function deleteObjectsFromRemovedRealms(db, res,
|
|
5659
|
+
function deleteObjectsFromRemovedRealms(db, res, syncState) {
|
|
5630
5660
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5631
5661
|
const deletedRealms = new Set();
|
|
5632
5662
|
const rejectedRealms = new Set();
|
|
5633
|
-
const previousRealmSet =
|
|
5634
|
-
const previousInviteRealmSet =
|
|
5663
|
+
const previousRealmSet = syncState ? syncState.realms : [];
|
|
5664
|
+
const previousInviteRealmSet = syncState ? syncState.inviteRealms : [];
|
|
5635
5665
|
const updatedRealmSet = new Set(res.realms);
|
|
5636
5666
|
const updatedTotalRealmSet = new Set(res.realms.concat(res.inviteRealms));
|
|
5637
5667
|
for (const realmId of previousRealmSet) {
|
|
@@ -5673,17 +5703,10 @@
|
|
|
5673
5703
|
}
|
|
5674
5704
|
}
|
|
5675
5705
|
}
|
|
5676
|
-
if (rejectedRealms.size > 0) {
|
|
5677
|
-
// Remove rejected/deleted realms from yDownloadedRealms because of the following use case:
|
|
5678
|
-
// 1. User becomes added to the realm
|
|
5679
|
-
// 2. User syncs and all documents of the realm is downloaded (downloadYDocsFromServer.ts)
|
|
5680
|
-
// 3. User leaves the realm and all docs are deleted locally (built-in-trigger of deleting their rows in this file)
|
|
5681
|
-
// 4. User is yet again added to the realm. At this point, we must make sure the docs are not considered already downloaded.
|
|
5682
|
-
const updateSpec = {};
|
|
5706
|
+
if (rejectedRealms.size > 0 && (syncState === null || syncState === void 0 ? void 0 : syncState.yDownloadedRealms)) {
|
|
5683
5707
|
for (const realmId of rejectedRealms) {
|
|
5684
|
-
|
|
5708
|
+
delete syncState.yDownloadedRealms[realmId];
|
|
5685
5709
|
}
|
|
5686
|
-
yield db.$syncState.update('syncState', updateSpec);
|
|
5687
5710
|
}
|
|
5688
5711
|
});
|
|
5689
5712
|
}
|
|
@@ -6902,13 +6925,13 @@
|
|
|
6902
6925
|
const CLIENT_PING_INTERVAL = 30000;
|
|
6903
6926
|
const FAIL_RETRY_WAIT_TIME = 60000;
|
|
6904
6927
|
class WSObservable extends rxjs.Observable {
|
|
6905
|
-
constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
|
|
6906
|
-
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));
|
|
6907
6930
|
}
|
|
6908
6931
|
}
|
|
6909
6932
|
let counter = 0;
|
|
6910
6933
|
class WSConnection extends rxjs.Subscription {
|
|
6911
|
-
constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
|
|
6934
|
+
constructor(db, rev, yrev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
|
|
6912
6935
|
super(() => this.teardown());
|
|
6913
6936
|
this.id = ++counter;
|
|
6914
6937
|
this.subscriptions = new Set();
|
|
@@ -6917,6 +6940,7 @@
|
|
|
6917
6940
|
this.db = db;
|
|
6918
6941
|
this.databaseUrl = db.cloud.options.databaseUrl;
|
|
6919
6942
|
this.rev = rev;
|
|
6943
|
+
this.yrev = yrev;
|
|
6920
6944
|
this.realmSetHash = realmSetHash;
|
|
6921
6945
|
this.clientIdentity = clientIdentity;
|
|
6922
6946
|
this.user = user;
|
|
@@ -6986,6 +7010,11 @@
|
|
|
6986
7010
|
}
|
|
6987
7011
|
this.webSocketStatus.next('connecting');
|
|
6988
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.
|
|
6989
7018
|
if (this.closed) {
|
|
6990
7019
|
console.debug('pinger check', this.id, 'CLOSED.');
|
|
6991
7020
|
this.teardown();
|
|
@@ -7032,9 +7061,13 @@
|
|
|
7032
7061
|
if (this.subscriber.closed)
|
|
7033
7062
|
return;
|
|
7034
7063
|
searchParams.set('v', '2');
|
|
7035
|
-
|
|
7064
|
+
if (this.rev)
|
|
7065
|
+
searchParams.set('rev', this.rev);
|
|
7066
|
+
if (this.yrev)
|
|
7067
|
+
searchParams.set('yrev', this.yrev);
|
|
7036
7068
|
searchParams.set('realmsHash', this.realmSetHash);
|
|
7037
7069
|
searchParams.set('clientId', this.clientIdentity);
|
|
7070
|
+
searchParams.set('dxcv', this.db.cloud.version);
|
|
7038
7071
|
if (this.user.accessToken) {
|
|
7039
7072
|
searchParams.set('token', this.user.accessToken);
|
|
7040
7073
|
}
|
|
@@ -7071,8 +7104,8 @@
|
|
|
7071
7104
|
}
|
|
7072
7105
|
}
|
|
7073
7106
|
}
|
|
7074
|
-
else if (msg.type === '
|
|
7075
|
-
|
|
7107
|
+
else if (msg.type === 'pong') {
|
|
7108
|
+
// Do nothing
|
|
7076
7109
|
}
|
|
7077
7110
|
else if (msg.type === 'doc-open') {
|
|
7078
7111
|
const docCache = Dexie.DexieYProvider.getDocCache(this.db.dx);
|
|
@@ -7081,11 +7114,26 @@
|
|
|
7081
7114
|
getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.
|
|
7082
7115
|
}
|
|
7083
7116
|
}
|
|
7084
|
-
else if (msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
|
|
7085
|
-
|
|
7086
|
-
|
|
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
|
+
}));
|
|
7087
7135
|
}
|
|
7088
|
-
else
|
|
7136
|
+
else {
|
|
7089
7137
|
// Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)
|
|
7090
7138
|
this.subscriber.next(msg);
|
|
7091
7139
|
}
|
|
@@ -7218,7 +7266,7 @@
|
|
|
7218
7266
|
// If no new entries, server won't bother the client. If new entries, server sends only those
|
|
7219
7267
|
// and the baseRev of the last from same client-ID.
|
|
7220
7268
|
if (userLogin) {
|
|
7221
|
-
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);
|
|
7222
7270
|
}
|
|
7223
7271
|
else {
|
|
7224
7272
|
return rxjs.from([]);
|
|
@@ -7348,61 +7396,95 @@
|
|
|
7348
7396
|
}
|
|
7349
7397
|
|
|
7350
7398
|
const SECONDS = 1000;
|
|
7351
|
-
const MINUTES = 60 * SECONDS;
|
|
7352
7399
|
|
|
7353
7400
|
function LocalSyncWorker(db, cloudOptions, cloudSchema) {
|
|
7354
7401
|
let localSyncEventSubscription = null;
|
|
7355
|
-
//let syncHandler: ((event: Event) => void) | null = null;
|
|
7356
|
-
//let periodicSyncHandler: ((event: Event) => void) | null = null;
|
|
7357
7402
|
let cancelToken = { cancelled: false };
|
|
7358
|
-
let
|
|
7359
|
-
let
|
|
7360
|
-
function syncAndRetry(
|
|
7403
|
+
let nextRetryTime = 0;
|
|
7404
|
+
let syncStartTime = 0;
|
|
7405
|
+
function syncAndRetry(retryNum = 1) {
|
|
7361
7406
|
// Use setTimeout() to get onto a clean stack and
|
|
7362
7407
|
// break free from possible active transaction:
|
|
7363
7408
|
setTimeout(() => {
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
const combPurpose = retryPurpose === 'pull' ? 'pull' : purpose;
|
|
7367
|
-
retryHandle = null;
|
|
7368
|
-
retryPurpose = null;
|
|
7409
|
+
const purpose = pullSignalled ? 'pull' : 'push';
|
|
7410
|
+
syncStartTime = Date.now();
|
|
7369
7411
|
syncIfPossible(db, cloudOptions, cloudSchema, {
|
|
7370
7412
|
cancelToken,
|
|
7371
7413
|
retryImmediatelyOnFetchError: true, // workaround for "net::ERR_NETWORK_CHANGED" in chrome.
|
|
7372
|
-
purpose
|
|
7373
|
-
}).
|
|
7374
|
-
|
|
7414
|
+
purpose,
|
|
7415
|
+
}).then(() => {
|
|
7416
|
+
if (cancelToken.cancelled) {
|
|
7417
|
+
stop();
|
|
7418
|
+
}
|
|
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);
|
|
7375
7432
|
if (cancelToken.cancelled) {
|
|
7376
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);
|
|
7377
7448
|
}
|
|
7378
|
-
else
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
const combinedPurpose = retryPurpose && retryPurpose === 'pull' ? 'pull' : purpose;
|
|
7383
|
-
const handle = setTimeout(() => syncAndRetry(combinedPurpose, retryNum + 1), [0, 5, 15][retryNum] * MINUTES);
|
|
7384
|
-
// Cancel the previous retryHandle if it exists to avoid scheduling loads of retries.
|
|
7385
|
-
if (retryHandle)
|
|
7386
|
-
clearTimeout(retryHandle);
|
|
7387
|
-
retryHandle = handle;
|
|
7388
|
-
retryPurpose = combinedPurpose;
|
|
7449
|
+
else {
|
|
7450
|
+
ongoingSync = false;
|
|
7451
|
+
nextRetryTime = 0;
|
|
7452
|
+
syncStartTime = 0;
|
|
7389
7453
|
}
|
|
7390
7454
|
});
|
|
7391
7455
|
}, 0);
|
|
7392
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
|
+
};
|
|
7393
7481
|
const start = () => {
|
|
7394
7482
|
// Sync eagerly whenever a change has happened (+ initially when there's no syncState yet)
|
|
7395
7483
|
// This initial subscribe will also trigger an sync also now.
|
|
7396
7484
|
console.debug('Starting LocalSyncWorker', db.localSyncEvent['id']);
|
|
7397
7485
|
localSyncEventSubscription = db.localSyncEvent.subscribe(({ purpose }) => {
|
|
7398
|
-
|
|
7399
|
-
syncAndRetry(purpose || 'pull');
|
|
7400
|
-
}
|
|
7401
|
-
catch (err) {
|
|
7402
|
-
console.error('What-the....', err);
|
|
7403
|
-
}
|
|
7486
|
+
consumer(purpose || 'pull');
|
|
7404
7487
|
});
|
|
7405
|
-
//setTimeout(()=>db.localSyncEvent.next({}), 5000);
|
|
7406
7488
|
};
|
|
7407
7489
|
const stop = () => {
|
|
7408
7490
|
console.debug('Stopping LocalSyncWorker');
|
|
@@ -8163,8 +8245,8 @@
|
|
|
8163
8245
|
|
|
8164
8246
|
const ydocTriggers = {};
|
|
8165
8247
|
const middlewares = new WeakMap();
|
|
8166
|
-
const txRunner = TriggerRunner("tx");
|
|
8167
|
-
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.
|
|
8168
8250
|
function TriggerRunner(name) {
|
|
8169
8251
|
let triggerExecPromise = null;
|
|
8170
8252
|
let triggerScheduled = false;
|
|
@@ -8174,14 +8256,17 @@
|
|
|
8174
8256
|
for (const { db, parentId, triggers, parentTable, prop, } of registryCopy.values()) {
|
|
8175
8257
|
const yDoc = Dexie.DexieYProvider.getOrCreateDocument(db, parentTable, prop, parentId);
|
|
8176
8258
|
try {
|
|
8177
|
-
Dexie.DexieYProvider.load(yDoc); // If doc is open, this would just be a ++refount
|
|
8178
|
-
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
|
|
8179
8261
|
for (const trigger of triggers) {
|
|
8180
8262
|
yield trigger(yDoc, parentId);
|
|
8181
8263
|
}
|
|
8182
8264
|
}
|
|
8183
8265
|
catch (error) {
|
|
8184
|
-
|
|
8266
|
+
if ((error === null || error === void 0 ? void 0 : error.name) === 'AbortError') ;
|
|
8267
|
+
else {
|
|
8268
|
+
console.error(`Error in YDocTrigger ${error}`);
|
|
8269
|
+
}
|
|
8185
8270
|
}
|
|
8186
8271
|
finally {
|
|
8187
8272
|
Dexie.DexieYProvider.release(yDoc);
|
|
@@ -8193,16 +8278,20 @@
|
|
|
8193
8278
|
name,
|
|
8194
8279
|
run() {
|
|
8195
8280
|
return __awaiter(this, void 0, void 0, function* () {
|
|
8281
|
+
console.log(`Running trigger (${name})?`, triggerScheduled, registry.size, !!triggerExecPromise);
|
|
8196
8282
|
if (!triggerScheduled && registry.size > 0) {
|
|
8197
8283
|
triggerScheduled = true;
|
|
8198
8284
|
if (triggerExecPromise)
|
|
8199
8285
|
yield triggerExecPromise.catch(() => { });
|
|
8200
8286
|
setTimeout(() => {
|
|
8201
8287
|
// setTimeout() is to escape from Promise.PSD zones and never run within liveQueries or transaction scopes
|
|
8288
|
+
console.log("Running trigger really!", name);
|
|
8202
8289
|
triggerScheduled = false;
|
|
8203
8290
|
const registryCopy = registry;
|
|
8204
8291
|
registry = new Map();
|
|
8205
|
-
triggerExecPromise = execute(registryCopy).finally(() =>
|
|
8292
|
+
triggerExecPromise = execute(registryCopy).finally(() => {
|
|
8293
|
+
triggerExecPromise = null;
|
|
8294
|
+
});
|
|
8206
8295
|
}, 0);
|
|
8207
8296
|
}
|
|
8208
8297
|
});
|
|
@@ -8218,6 +8307,7 @@
|
|
|
8218
8307
|
prop,
|
|
8219
8308
|
triggers: new Set(),
|
|
8220
8309
|
};
|
|
8310
|
+
console.log(`Adding trigger ${key}`);
|
|
8221
8311
|
registry.set(key, entry);
|
|
8222
8312
|
}
|
|
8223
8313
|
entry.triggers.add(trigger);
|
|
@@ -8357,7 +8447,7 @@
|
|
|
8357
8447
|
const syncComplete = new rxjs.Subject();
|
|
8358
8448
|
dexie.cloud = {
|
|
8359
8449
|
// @ts-ignore
|
|
8360
|
-
version: "4.1.0-beta.
|
|
8450
|
+
version: "4.1.0-beta.46",
|
|
8361
8451
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
8362
8452
|
schema: null,
|
|
8363
8453
|
get currentUserId() {
|
|
@@ -8675,7 +8765,7 @@
|
|
|
8675
8765
|
}
|
|
8676
8766
|
}
|
|
8677
8767
|
// @ts-ignore
|
|
8678
|
-
dexieCloud.version = "4.1.0-beta.
|
|
8768
|
+
dexieCloud.version = "4.1.0-beta.46";
|
|
8679
8769
|
Dexie.Cloud = dexieCloud;
|
|
8680
8770
|
|
|
8681
8771
|
exports.default = dexieCloud;
|