dexie-cloud-addon 4.1.0-alpha.10 → 4.1.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modern/DexieCloudOptions.d.ts +3 -0
- package/dist/modern/define-ydoc-trigger.d.ts +2 -0
- package/dist/modern/dexie-cloud-addon.d.ts +1 -0
- package/dist/modern/dexie-cloud-addon.js +406 -133
- 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 +251 -130
- 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/umd/DexieCloudOptions.d.ts +3 -0
- package/dist/umd/define-ydoc-trigger.d.ts +2 -0
- package/dist/umd/dexie-cloud-addon.d.ts +1 -0
- package/dist/umd/dexie-cloud-addon.js +405 -131
- 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 +250 -129
- 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/package.json +2 -2
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.1.0-alpha.
|
|
11
|
+
* Version 4.1.0-alpha.13, Thu Oct 17 2024
|
|
12
12
|
*
|
|
13
13
|
* https://dexie.org
|
|
14
14
|
*
|
|
@@ -4947,7 +4947,7 @@
|
|
|
4947
4947
|
.toArray();
|
|
4948
4948
|
}
|
|
4949
4949
|
|
|
4950
|
-
function $Y(db) {
|
|
4950
|
+
function $Y$1(db) {
|
|
4951
4951
|
const $Y = db.dx._options.Y;
|
|
4952
4952
|
if (!$Y)
|
|
4953
4953
|
throw new Error('Y library not supplied to Dexie constructor');
|
|
@@ -4977,7 +4977,7 @@
|
|
|
4977
4977
|
for (const table of tablesToSync) {
|
|
4978
4978
|
if (table.schema.yProps) {
|
|
4979
4979
|
for (const yProp of table.schema.yProps) {
|
|
4980
|
-
const Y = $Y(db); // This is how we retrieve the user-provided Y library
|
|
4980
|
+
const Y = $Y$1(db); // This is how we retrieve the user-provided Y library
|
|
4981
4981
|
const yTable = db.table(yProp.updatesTable); // the updates-table for this combo of table+propName
|
|
4982
4982
|
const syncState = (yield yTable.get(DEXIE_CLOUD_SYNCER_ID));
|
|
4983
4983
|
// unsentFrom = the `i` value of updates that aren't yet sent to server (or at least not acked by the server yet)
|
|
@@ -5061,6 +5061,7 @@
|
|
|
5061
5061
|
}
|
|
5062
5062
|
|
|
5063
5063
|
function applyYServerMessages(yMessages, db) {
|
|
5064
|
+
var _a;
|
|
5064
5065
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5065
5066
|
const result = {};
|
|
5066
5067
|
for (const m of yMessages) {
|
|
@@ -5092,7 +5093,24 @@
|
|
|
5092
5093
|
// See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
|
|
5093
5094
|
console.debug(`Y update rejected. Deleting it.`);
|
|
5094
5095
|
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
5095
|
-
|
|
5096
|
+
// Delete the rejected update and all local updates since (avoid holes in the CRDT)
|
|
5097
|
+
// and destroy it's open document if there is one.
|
|
5098
|
+
const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
|
|
5099
|
+
if (primaryKey != null) {
|
|
5100
|
+
yield db.transaction('rw', utbl, tx => {
|
|
5101
|
+
// @ts-ignore
|
|
5102
|
+
tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
|
|
5103
|
+
return utbl
|
|
5104
|
+
.where('i')
|
|
5105
|
+
.aboveOrEqual(m.i)
|
|
5106
|
+
.filter(u => Dexie.cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
|
|
5107
|
+
.delete();
|
|
5108
|
+
});
|
|
5109
|
+
// Destroy active doc
|
|
5110
|
+
const activeDoc = Dexie.DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
|
|
5111
|
+
if (activeDoc)
|
|
5112
|
+
activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
|
|
5113
|
+
}
|
|
5096
5114
|
break;
|
|
5097
5115
|
}
|
|
5098
5116
|
case 'in-sync': {
|
|
@@ -6306,8 +6324,7 @@
|
|
|
6306
6324
|
outstandingTransactions.next(outstandingTransactions.value);
|
|
6307
6325
|
};
|
|
6308
6326
|
const txComplete = () => {
|
|
6309
|
-
if (tx.mutationsAdded &&
|
|
6310
|
-
!isEagerSyncDisabled(db)) {
|
|
6327
|
+
if (tx.mutationsAdded && !isEagerSyncDisabled(db)) {
|
|
6311
6328
|
triggerSync(db, 'push');
|
|
6312
6329
|
}
|
|
6313
6330
|
removeTransaction();
|
|
@@ -6389,26 +6406,107 @@
|
|
|
6389
6406
|
: mutateAndLog(req);
|
|
6390
6407
|
} }));
|
|
6391
6408
|
function mutateAndLog(req) {
|
|
6409
|
+
var _a, _b;
|
|
6392
6410
|
const trans = req.trans;
|
|
6393
|
-
|
|
6411
|
+
const unsyncedProps = (_b = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.unsyncedProperties) === null || _b === void 0 ? void 0 : _b[tableName];
|
|
6394
6412
|
const { txid, currentUser: { userId }, } = trans;
|
|
6395
6413
|
const { type } = req;
|
|
6396
6414
|
const opNo = ++trans.opCount;
|
|
6415
|
+
function stripChangeSpec(changeSpec) {
|
|
6416
|
+
if (!unsyncedProps)
|
|
6417
|
+
return changeSpec;
|
|
6418
|
+
let rv = changeSpec;
|
|
6419
|
+
for (const keyPath of Object.keys(changeSpec)) {
|
|
6420
|
+
if (unsyncedProps.some((p) => keyPath === p || keyPath.startsWith(p + '.'))) {
|
|
6421
|
+
if (rv === changeSpec)
|
|
6422
|
+
rv = Object.assign({}, changeSpec); // clone on demand
|
|
6423
|
+
delete rv[keyPath];
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
return rv;
|
|
6427
|
+
}
|
|
6397
6428
|
return table.mutate(req).then((res) => {
|
|
6429
|
+
var _a;
|
|
6398
6430
|
const { numFailures: hasFailures, failures } = res;
|
|
6399
6431
|
let keys = type === 'delete' ? req.keys : res.results;
|
|
6400
6432
|
let values = 'values' in req ? req.values : [];
|
|
6401
|
-
let
|
|
6433
|
+
let changeSpec = 'changeSpec' in req ? req.changeSpec : undefined;
|
|
6434
|
+
let updates = 'updates' in req ? req.updates : undefined;
|
|
6402
6435
|
if (hasFailures) {
|
|
6403
6436
|
keys = keys.filter((_, idx) => !failures[idx]);
|
|
6404
6437
|
values = values.filter((_, idx) => !failures[idx]);
|
|
6405
6438
|
}
|
|
6439
|
+
if (unsyncedProps) {
|
|
6440
|
+
// Filter out unsynced properties
|
|
6441
|
+
values = values.map((value) => {
|
|
6442
|
+
const newValue = Object.assign({}, value);
|
|
6443
|
+
for (const prop of unsyncedProps) {
|
|
6444
|
+
delete newValue[prop];
|
|
6445
|
+
}
|
|
6446
|
+
return newValue;
|
|
6447
|
+
});
|
|
6448
|
+
if (changeSpec) {
|
|
6449
|
+
// modify operation with criteria and changeSpec.
|
|
6450
|
+
// We must strip out unsynced properties from changeSpec.
|
|
6451
|
+
// We deal with criteria later.
|
|
6452
|
+
changeSpec = stripChangeSpec(changeSpec);
|
|
6453
|
+
if (Object.keys(changeSpec).length === 0) {
|
|
6454
|
+
// Nothing to change on server
|
|
6455
|
+
return res;
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
if (updates) {
|
|
6459
|
+
let strippedChangeSpecs = updates.changeSpecs.map(stripChangeSpec);
|
|
6460
|
+
let newUpdates = {
|
|
6461
|
+
keys: [],
|
|
6462
|
+
changeSpecs: [],
|
|
6463
|
+
};
|
|
6464
|
+
const validKeys = new Dexie.RangeSet();
|
|
6465
|
+
let anyChangeSpecBecameEmpty = false;
|
|
6466
|
+
for (let i = 0, l = strippedChangeSpecs.length; i < l; ++i) {
|
|
6467
|
+
if (Object.keys(strippedChangeSpecs[i]).length > 0) {
|
|
6468
|
+
newUpdates.keys.push(updates.keys[i]);
|
|
6469
|
+
newUpdates.changeSpecs.push(strippedChangeSpecs[i]);
|
|
6470
|
+
validKeys.addKey(updates.keys[i]);
|
|
6471
|
+
}
|
|
6472
|
+
else {
|
|
6473
|
+
anyChangeSpecBecameEmpty = true;
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
6476
|
+
updates = newUpdates;
|
|
6477
|
+
if (anyChangeSpecBecameEmpty) {
|
|
6478
|
+
// Some keys were stripped. We must also strip them from keys and values
|
|
6479
|
+
let newKeys = [];
|
|
6480
|
+
let newValues = [];
|
|
6481
|
+
for (let i = 0, l = keys.length; i < l; ++i) {
|
|
6482
|
+
if (validKeys.hasKey(keys[i])) {
|
|
6483
|
+
newKeys.push(keys[i]);
|
|
6484
|
+
newValues.push(values[i]);
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
keys = newKeys;
|
|
6488
|
+
values = newValues;
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
}
|
|
6406
6492
|
const ts = Date.now();
|
|
6407
6493
|
// Canonicalize req.criteria.index to null if it's on the primary key.
|
|
6408
|
-
|
|
6494
|
+
let criteria = 'criteria' in req && req.criteria
|
|
6409
6495
|
? Object.assign(Object.assign({}, req.criteria), { index: req.criteria.index === schema.primaryKey.keyPath // Use null to inform server that criteria is on primary key
|
|
6410
6496
|
? null // This will disable the server from trying to log consistent operations where it shouldnt.
|
|
6411
6497
|
: req.criteria.index }) : undefined;
|
|
6498
|
+
if (unsyncedProps && (criteria === null || criteria === void 0 ? void 0 : criteria.index)) {
|
|
6499
|
+
const keyPaths = (_a = schema.indexes.find((idx) => idx.name === criteria.index)) === null || _a === void 0 ? void 0 : _a.keyPath;
|
|
6500
|
+
const involvedProps = keyPaths
|
|
6501
|
+
? typeof keyPaths === 'string'
|
|
6502
|
+
? [keyPaths]
|
|
6503
|
+
: keyPaths
|
|
6504
|
+
: [];
|
|
6505
|
+
if (involvedProps.some((p) => unsyncedProps === null || unsyncedProps === void 0 ? void 0 : unsyncedProps.includes(p))) {
|
|
6506
|
+
// Don't log criteria on unsynced properties as the server could not test them.
|
|
6507
|
+
criteria = undefined;
|
|
6508
|
+
}
|
|
6509
|
+
}
|
|
6412
6510
|
const mut = req.type === 'delete'
|
|
6413
6511
|
? {
|
|
6414
6512
|
type: 'delete',
|
|
@@ -6429,7 +6527,7 @@
|
|
|
6429
6527
|
userId,
|
|
6430
6528
|
values,
|
|
6431
6529
|
}
|
|
6432
|
-
: criteria &&
|
|
6530
|
+
: criteria && changeSpec
|
|
6433
6531
|
? {
|
|
6434
6532
|
// Common changeSpec for all keys
|
|
6435
6533
|
type: 'modify',
|
|
@@ -6437,30 +6535,41 @@
|
|
|
6437
6535
|
opNo,
|
|
6438
6536
|
keys,
|
|
6439
6537
|
criteria,
|
|
6440
|
-
changeSpec
|
|
6538
|
+
changeSpec,
|
|
6441
6539
|
txid,
|
|
6442
6540
|
userId,
|
|
6443
6541
|
}
|
|
6444
|
-
:
|
|
6542
|
+
: changeSpec
|
|
6445
6543
|
? {
|
|
6446
|
-
//
|
|
6544
|
+
// In case criteria involved an unsynced property, we go for keys instead.
|
|
6447
6545
|
type: 'update',
|
|
6448
6546
|
ts,
|
|
6449
6547
|
opNo,
|
|
6450
|
-
keys: updates.keys,
|
|
6451
|
-
changeSpecs: updates.changeSpecs,
|
|
6452
|
-
txid,
|
|
6453
|
-
userId,
|
|
6454
|
-
}
|
|
6455
|
-
: {
|
|
6456
|
-
type: 'upsert',
|
|
6457
|
-
ts,
|
|
6458
|
-
opNo,
|
|
6459
6548
|
keys,
|
|
6460
|
-
|
|
6549
|
+
changeSpecs: keys.map(() => changeSpec),
|
|
6461
6550
|
txid,
|
|
6462
6551
|
userId,
|
|
6463
|
-
}
|
|
6552
|
+
}
|
|
6553
|
+
: updates
|
|
6554
|
+
? {
|
|
6555
|
+
// One changeSpec per key
|
|
6556
|
+
type: 'update',
|
|
6557
|
+
ts,
|
|
6558
|
+
opNo,
|
|
6559
|
+
keys: updates.keys,
|
|
6560
|
+
changeSpecs: updates.changeSpecs,
|
|
6561
|
+
txid,
|
|
6562
|
+
userId,
|
|
6563
|
+
}
|
|
6564
|
+
: {
|
|
6565
|
+
type: 'upsert',
|
|
6566
|
+
ts,
|
|
6567
|
+
opNo,
|
|
6568
|
+
keys,
|
|
6569
|
+
values,
|
|
6570
|
+
txid,
|
|
6571
|
+
userId,
|
|
6572
|
+
};
|
|
6464
6573
|
if ('isAdditionalChunk' in req && req.isAdditionalChunk) {
|
|
6465
6574
|
mut.isAdditionalChunk = true;
|
|
6466
6575
|
}
|
|
@@ -7807,125 +7916,137 @@
|
|
|
7807
7916
|
function createYHandler(db) {
|
|
7808
7917
|
return (provider) => {
|
|
7809
7918
|
var _a;
|
|
7810
|
-
const awap = getAwarenessLibrary(db);
|
|
7811
7919
|
const doc = provider.doc;
|
|
7812
|
-
const { parentTable
|
|
7920
|
+
const { parentTable } = doc.meta || {};
|
|
7813
7921
|
if (!((_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[parentTable].markedForSync)) {
|
|
7814
7922
|
return; // The table that holds the doc is not marked for sync - leave it to dexie. No syncing, no awareness.
|
|
7815
7923
|
}
|
|
7816
|
-
let awareness
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7924
|
+
let awareness;
|
|
7925
|
+
Object.defineProperty(provider, "awareness", {
|
|
7926
|
+
get() {
|
|
7927
|
+
if (awareness)
|
|
7928
|
+
return awareness;
|
|
7929
|
+
awarenessWeakMap.set(doc, awareness);
|
|
7930
|
+
awareness = createAwareness(db, doc, provider);
|
|
7931
|
+
return awareness;
|
|
7932
|
+
}
|
|
7933
|
+
});
|
|
7934
|
+
};
|
|
7935
|
+
}
|
|
7936
|
+
function createAwareness(db, doc, provider) {
|
|
7937
|
+
const { parentTable, parentId, parentProp, updatesTable } = doc.meta;
|
|
7938
|
+
const awap = getAwarenessLibrary(db);
|
|
7939
|
+
const awareness = new awap.Awareness(doc);
|
|
7940
|
+
awareness.on('update', ({ added, updated, removed }, origin) => {
|
|
7941
|
+
// Send the update
|
|
7942
|
+
const changedClients = added.concat(updated).concat(removed);
|
|
7943
|
+
const user = db.cloud.currentUser.value;
|
|
7944
|
+
if (origin !== 'server' && user.isLoggedIn && !isEagerSyncDisabled(db)) {
|
|
7945
|
+
const update = awap.encodeAwarenessUpdate(awareness, changedClients);
|
|
7946
|
+
db.messageProducer.next({
|
|
7947
|
+
type: 'aware',
|
|
7948
|
+
table: parentTable,
|
|
7949
|
+
prop: parentProp,
|
|
7950
|
+
k: doc.meta.parentId,
|
|
7951
|
+
u: update,
|
|
7952
|
+
});
|
|
7953
|
+
if (provider.destroyed) {
|
|
7954
|
+
// We're called from awareness.on('destroy') that did
|
|
7955
|
+
// removeAwarenessStates.
|
|
7956
|
+
// It's time to also send the doc-close message that dexie-cloud understands
|
|
7957
|
+
// and uses to stop subscribing for updates and awareness updates and brings
|
|
7958
|
+
// down the cached information in memory on the WS connection for this.
|
|
7825
7959
|
db.messageProducer.next({
|
|
7826
|
-
type: '
|
|
7960
|
+
type: 'doc-close',
|
|
7827
7961
|
table: parentTable,
|
|
7828
7962
|
prop: parentProp,
|
|
7829
|
-
k: doc.meta.parentId
|
|
7830
|
-
u: update,
|
|
7963
|
+
k: doc.meta.parentId
|
|
7831
7964
|
});
|
|
7832
|
-
if (provider.destroyed) {
|
|
7833
|
-
// We're called from awareness.on('destroy') that did
|
|
7834
|
-
// removeAwarenessStates.
|
|
7835
|
-
// It's time to also send the doc-close message that dexie-cloud understands
|
|
7836
|
-
// and uses to stop subscribing for updates and awareness updates and brings
|
|
7837
|
-
// down the cached information in memory on the WS connection for this.
|
|
7838
|
-
db.messageProducer.next({
|
|
7839
|
-
type: 'doc-close',
|
|
7840
|
-
table: parentTable,
|
|
7841
|
-
prop: parentProp,
|
|
7842
|
-
k: doc.meta.parentId
|
|
7843
|
-
});
|
|
7844
|
-
}
|
|
7845
7965
|
}
|
|
7846
|
-
}
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7966
|
+
}
|
|
7967
|
+
});
|
|
7968
|
+
awareness.on('destroy', () => {
|
|
7969
|
+
// Signal to server that this provider is destroyed (the update event will be triggered, which
|
|
7970
|
+
// in turn will trigger db.messageProducer that will send the message to the server if WS is connected)
|
|
7971
|
+
awap.removeAwarenessStates(awareness, [doc.clientID], 'provider destroyed');
|
|
7972
|
+
});
|
|
7973
|
+
// Open the document on the server
|
|
7974
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
7975
|
+
if (provider.destroyed)
|
|
7976
|
+
return;
|
|
7977
|
+
let connected = false;
|
|
7978
|
+
let currentFlowId = 1;
|
|
7979
|
+
const subscription = db.cloud.webSocketStatus.subscribe((wsStatus) => {
|
|
7854
7980
|
if (provider.destroyed)
|
|
7855
7981
|
return;
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7982
|
+
// Keep "connected" state in a variable so we can check it after async operations
|
|
7983
|
+
connected = wsStatus === 'connected';
|
|
7984
|
+
// We are or got connected. Open the document on the server.
|
|
7985
|
+
const user = db.cloud.currentUser.value;
|
|
7986
|
+
if (wsStatus === "connected" && user.isLoggedIn && !isEagerSyncDisabled(db)) {
|
|
7987
|
+
++currentFlowId;
|
|
7988
|
+
openDocumentOnServer().catch(error => {
|
|
7989
|
+
console.warn(`Error catched in createYHandler.ts: ${error}`);
|
|
7990
|
+
});
|
|
7991
|
+
}
|
|
7992
|
+
});
|
|
7993
|
+
// Wait until WebSocket is connected
|
|
7994
|
+
provider.addCleanupHandler(subscription);
|
|
7995
|
+
/** Sends an 'doc-open' message to server whenever websocket becomes
|
|
7996
|
+
* connected, or if it is already connected.
|
|
7997
|
+
* The flow is aborted in case websocket is disconnected while querying
|
|
7998
|
+
* information required to compute the state vector. Flow is also
|
|
7999
|
+
* aborted in case document or provider has been destroyed during
|
|
8000
|
+
* the async parts of the task.
|
|
8001
|
+
*
|
|
8002
|
+
* The state vector is only computed from the updates that have occured
|
|
8003
|
+
* after the last full sync - which could very often be zero - in which
|
|
8004
|
+
* case no state vector is sent (then the server already knows us by
|
|
8005
|
+
* revision)
|
|
8006
|
+
*
|
|
8007
|
+
* When server gets the doc-open message, it will authorized us for
|
|
8008
|
+
* whether we are allowed to read / write to this document, and then
|
|
8009
|
+
* keep the cached information in memory on the WS connection for this
|
|
8010
|
+
* particular document, as well as subscribe to updates and awareness updates
|
|
8011
|
+
* from other clients on the document.
|
|
8012
|
+
*/
|
|
8013
|
+
function openDocumentOnServer(wsStatus) {
|
|
8014
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
8015
|
+
const myFlow = currentFlowId; // So we can abort when a new flow is started
|
|
8016
|
+
const yTbl = db.table(updatesTable);
|
|
8017
|
+
const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
|
|
8018
|
+
// After every await, check if we still should be working on this task.
|
|
8019
|
+
if (provider.destroyed || currentFlowId !== myFlow || !connected)
|
|
7860
8020
|
return;
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
8021
|
+
const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
|
|
8022
|
+
const docOpenMsg = {
|
|
8023
|
+
type: 'doc-open',
|
|
8024
|
+
table: parentTable,
|
|
8025
|
+
prop: parentProp,
|
|
8026
|
+
k: parentId,
|
|
8027
|
+
serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
|
|
8028
|
+
};
|
|
8029
|
+
const serverUpdatesSinceLastSync = yield yTbl
|
|
8030
|
+
.where('i')
|
|
8031
|
+
.between(receivedUntil, Infinity, false)
|
|
8032
|
+
.filter((update) => Dexie.cmp(update.k, parentId) === 0 && // Only updates for this document
|
|
8033
|
+
((update.f || 0) & 1) === 0 // Don't include local changes
|
|
8034
|
+
)
|
|
8035
|
+
.toArray();
|
|
8036
|
+
// After every await, check if we still should be working on this task.
|
|
8037
|
+
if (provider.destroyed || currentFlowId !== myFlow || !connected)
|
|
8038
|
+
return;
|
|
8039
|
+
if (serverUpdatesSinceLastSync.length > 0) {
|
|
8040
|
+
const Y = $Y$1(db); // Get the Yjs library from Dexie constructor options
|
|
8041
|
+
const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
|
|
8042
|
+
const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
|
|
8043
|
+
docOpenMsg.sv = stateVector;
|
|
7870
8044
|
}
|
|
8045
|
+
db.messageProducer.next(docOpenMsg);
|
|
7871
8046
|
});
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
* connected, or if it is already connected.
|
|
7876
|
-
* The flow is aborted in case websocket is disconnected while querying
|
|
7877
|
-
* information required to compute the state vector. Flow is also
|
|
7878
|
-
* aborted in case document or provider has been destroyed during
|
|
7879
|
-
* the async parts of the task.
|
|
7880
|
-
*
|
|
7881
|
-
* The state vector is only computed from the updates that have occured
|
|
7882
|
-
* after the last full sync - which could very often be zero - in which
|
|
7883
|
-
* case no state vector is sent (then the server already knows us by
|
|
7884
|
-
* revision)
|
|
7885
|
-
*
|
|
7886
|
-
* When server gets the doc-open message, it will authorized us for
|
|
7887
|
-
* whether we are allowed to read / write to this document, and then
|
|
7888
|
-
* keep the cached information in memory on the WS connection for this
|
|
7889
|
-
* particular document, as well as subscribe to updates and awareness updates
|
|
7890
|
-
* from other clients on the document.
|
|
7891
|
-
*/
|
|
7892
|
-
function openDocumentOnServer(wsStatus) {
|
|
7893
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
7894
|
-
const myFlow = currentFlowId; // So we can abort when a new flow is started
|
|
7895
|
-
const yTbl = db.table(updatesTable);
|
|
7896
|
-
const syncState = yield yTbl.get(DEXIE_CLOUD_SYNCER_ID);
|
|
7897
|
-
// After every await, check if we still should be working on this task.
|
|
7898
|
-
if (provider.destroyed || currentFlowId !== myFlow || !connected)
|
|
7899
|
-
return;
|
|
7900
|
-
const receivedUntil = (syncState === null || syncState === void 0 ? void 0 : syncState.receivedUntil) || 0;
|
|
7901
|
-
const docOpenMsg = {
|
|
7902
|
-
type: 'doc-open',
|
|
7903
|
-
table: parentTable,
|
|
7904
|
-
prop: parentProp,
|
|
7905
|
-
k: parentId,
|
|
7906
|
-
serverRev: syncState === null || syncState === void 0 ? void 0 : syncState.serverRev,
|
|
7907
|
-
};
|
|
7908
|
-
const serverUpdatesSinceLastSync = yield yTbl
|
|
7909
|
-
.where('i')
|
|
7910
|
-
.between(receivedUntil, Infinity, false)
|
|
7911
|
-
.filter((update) => Dexie.cmp(update.k, parentId) === 0 && // Only updates for this document
|
|
7912
|
-
((update.f || 0) & 1) === 0 // Don't include local changes
|
|
7913
|
-
)
|
|
7914
|
-
.toArray();
|
|
7915
|
-
// After every await, check if we still should be working on this task.
|
|
7916
|
-
if (provider.destroyed || currentFlowId !== myFlow || !connected)
|
|
7917
|
-
return;
|
|
7918
|
-
if (serverUpdatesSinceLastSync.length > 0) {
|
|
7919
|
-
const Y = $Y(db); // Get the Yjs library from Dexie constructor options
|
|
7920
|
-
const mergedUpdate = Y.mergeUpdatesV2(serverUpdatesSinceLastSync.map((update) => update.u));
|
|
7921
|
-
const stateVector = Y.encodeStateVectorFromUpdateV2(mergedUpdate);
|
|
7922
|
-
docOpenMsg.sv = stateVector;
|
|
7923
|
-
}
|
|
7924
|
-
db.messageProducer.next(docOpenMsg);
|
|
7925
|
-
});
|
|
7926
|
-
}
|
|
7927
|
-
}));
|
|
7928
|
-
};
|
|
8047
|
+
}
|
|
8048
|
+
}))();
|
|
8049
|
+
return awareness;
|
|
7929
8050
|
}
|
|
7930
8051
|
|
|
7931
8052
|
function getTiedRealmId(objectId) {
|
|
@@ -7975,7 +8096,7 @@
|
|
|
7975
8096
|
const syncComplete = new rxjs.Subject();
|
|
7976
8097
|
dexie.cloud = {
|
|
7977
8098
|
// @ts-ignore
|
|
7978
|
-
version: "4.1.0-alpha.
|
|
8099
|
+
version: "4.1.0-alpha.13",
|
|
7979
8100
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
7980
8101
|
schema: null,
|
|
7981
8102
|
get currentUserId() {
|
|
@@ -8277,10 +8398,163 @@
|
|
|
8277
8398
|
}
|
|
8278
8399
|
}
|
|
8279
8400
|
// @ts-ignore
|
|
8280
|
-
dexieCloud.version = "4.1.0-alpha.
|
|
8401
|
+
dexieCloud.version = "4.1.0-alpha.13";
|
|
8281
8402
|
Dexie.Cloud = dexieCloud;
|
|
8282
8403
|
|
|
8404
|
+
const ydocTriggers = {};
|
|
8405
|
+
const docIsAlreadyHooked = new WeakSet();
|
|
8406
|
+
const middlewares = new WeakMap();
|
|
8407
|
+
const createMiddleware = (db) => ({
|
|
8408
|
+
stack: 'dbcore',
|
|
8409
|
+
level: 10,
|
|
8410
|
+
name: 'yTriggerMiddleware',
|
|
8411
|
+
create: (down) => {
|
|
8412
|
+
return Object.assign(Object.assign({}, down), { transaction: (stores, mode, options) => {
|
|
8413
|
+
const idbtrans = down.transaction(stores, mode, options);
|
|
8414
|
+
idbtrans.addEventListener('complete', onTransactionCommitted);
|
|
8415
|
+
return idbtrans;
|
|
8416
|
+
}, table: (tblName) => {
|
|
8417
|
+
const coreTable = down.table(tblName);
|
|
8418
|
+
const triggerSpec = ydocTriggers[tblName];
|
|
8419
|
+
if (!triggerSpec)
|
|
8420
|
+
return coreTable;
|
|
8421
|
+
const { trigger, parentTable, prop } = triggerSpec;
|
|
8422
|
+
return Object.assign(Object.assign({}, coreTable), { mutate(req) {
|
|
8423
|
+
switch (req.type) {
|
|
8424
|
+
case 'add': {
|
|
8425
|
+
for (const yUpdateRow of req.values) {
|
|
8426
|
+
const primaryKey = yUpdateRow.k;
|
|
8427
|
+
const doc = Dexie.DexieYProvider.getDocCache(db).find(parentTable, primaryKey, prop);
|
|
8428
|
+
if (doc) {
|
|
8429
|
+
if (!docIsAlreadyHooked.has(doc)) {
|
|
8430
|
+
hookToDoc(doc, primaryKey, trigger);
|
|
8431
|
+
docIsAlreadyHooked.add(doc);
|
|
8432
|
+
}
|
|
8433
|
+
}
|
|
8434
|
+
else {
|
|
8435
|
+
enqueueTrigger(tblName, primaryKey, trigger);
|
|
8436
|
+
}
|
|
8437
|
+
}
|
|
8438
|
+
break;
|
|
8439
|
+
}
|
|
8440
|
+
case 'delete':
|
|
8441
|
+
// @ts-ignore
|
|
8442
|
+
if (req.trans._rejecting_y_ypdate) {
|
|
8443
|
+
// The deletion came from a rejection, not garbage collection.
|
|
8444
|
+
// When that happens, let the triggers run to compute new values
|
|
8445
|
+
// based on the deleted updates.
|
|
8446
|
+
coreTable
|
|
8447
|
+
.getMany({
|
|
8448
|
+
keys: req.keys,
|
|
8449
|
+
trans: req.trans,
|
|
8450
|
+
cache: 'immutable',
|
|
8451
|
+
})
|
|
8452
|
+
.then((updates) => {
|
|
8453
|
+
const keySet = new Dexie.RangeSet();
|
|
8454
|
+
for (const { k } of updates) {
|
|
8455
|
+
keySet.addKey(k);
|
|
8456
|
+
}
|
|
8457
|
+
for (const key of keySet) {
|
|
8458
|
+
enqueueTrigger(tblName, key, trigger);
|
|
8459
|
+
}
|
|
8460
|
+
});
|
|
8461
|
+
}
|
|
8462
|
+
break;
|
|
8463
|
+
}
|
|
8464
|
+
return coreTable.mutate(req);
|
|
8465
|
+
} });
|
|
8466
|
+
} });
|
|
8467
|
+
},
|
|
8468
|
+
});
|
|
8469
|
+
let triggerExecPromise = null;
|
|
8470
|
+
let triggerScheduled = false;
|
|
8471
|
+
let scheduledTriggers = [];
|
|
8472
|
+
function $Y(db) {
|
|
8473
|
+
const $Y = db._options.Y;
|
|
8474
|
+
if (!$Y)
|
|
8475
|
+
throw new Error('Y library not supplied to Dexie constructor');
|
|
8476
|
+
return $Y;
|
|
8477
|
+
}
|
|
8478
|
+
function executeTriggers(triggersToRun) {
|
|
8479
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
8480
|
+
for (const { db, parentId, trigger, updatesTable } of triggersToRun) {
|
|
8481
|
+
// Load entire document into an Y.Doc instance:
|
|
8482
|
+
const updates = yield db
|
|
8483
|
+
.table(updatesTable)
|
|
8484
|
+
.where({ k: parentId })
|
|
8485
|
+
.toArray();
|
|
8486
|
+
const Y = $Y(db);
|
|
8487
|
+
const yDoc = new Y.Doc();
|
|
8488
|
+
for (const update of updates) {
|
|
8489
|
+
Y.applyUpdateV2(yDoc, update);
|
|
8490
|
+
}
|
|
8491
|
+
try {
|
|
8492
|
+
yield trigger(yDoc, parentId);
|
|
8493
|
+
}
|
|
8494
|
+
catch (error) {
|
|
8495
|
+
console.error(`Error in YDocTrigger ${error}`);
|
|
8496
|
+
}
|
|
8497
|
+
}
|
|
8498
|
+
});
|
|
8499
|
+
}
|
|
8500
|
+
function enqueueTrigger(updatesTable, parentId, trigger) {
|
|
8501
|
+
var _a;
|
|
8502
|
+
((_a = scheduledTriggers[updatesTable]) !== null && _a !== void 0 ? _a : (scheduledTriggers[updatesTable] = [])).push({
|
|
8503
|
+
parentId,
|
|
8504
|
+
trigger,
|
|
8505
|
+
});
|
|
8506
|
+
}
|
|
8507
|
+
function onTransactionCommitted() {
|
|
8508
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
8509
|
+
if (!triggerScheduled && scheduledTriggers.length > 0) {
|
|
8510
|
+
triggerScheduled = true;
|
|
8511
|
+
if (triggerExecPromise)
|
|
8512
|
+
yield triggerExecPromise.catch(() => { });
|
|
8513
|
+
setTimeout(() => {
|
|
8514
|
+
// setTimeout() is to escape from Promise.PSD zones and never run within liveQueries or transaction scopes
|
|
8515
|
+
triggerScheduled = false;
|
|
8516
|
+
const triggersToRun = scheduledTriggers;
|
|
8517
|
+
scheduledTriggers = [];
|
|
8518
|
+
triggerExecPromise = executeTriggers(triggersToRun).finally(() => (triggerExecPromise = null));
|
|
8519
|
+
}, 0);
|
|
8520
|
+
}
|
|
8521
|
+
});
|
|
8522
|
+
}
|
|
8523
|
+
function hookToDoc(doc, parentId, trigger) {
|
|
8524
|
+
// From now on, keep listening to doc updates and execute the trigger when it happens there instead
|
|
8525
|
+
doc.on('updateV2', (update, origin) => {
|
|
8526
|
+
//Dexie.ignoreTransaction(()=>{
|
|
8527
|
+
trigger(doc, parentId);
|
|
8528
|
+
//});
|
|
8529
|
+
});
|
|
8530
|
+
/*
|
|
8531
|
+
NOT NEEDED because DexieYProvider's docCache will also listen to destroy and remove it from its cache:
|
|
8532
|
+
doc.on('destroy', ()=>{
|
|
8533
|
+
docIsAlreadyHooked.delete(doc);
|
|
8534
|
+
})
|
|
8535
|
+
*/
|
|
8536
|
+
}
|
|
8537
|
+
function defineYDocTrigger(table, prop, trigger) {
|
|
8538
|
+
var _a, _b;
|
|
8539
|
+
const updatesTable = (_b = (_a = table.schema.yProps) === null || _a === void 0 ? void 0 : _a.find((p) => p.prop === prop)) === null || _b === void 0 ? void 0 : _b.updatesTable;
|
|
8540
|
+
if (!updatesTable)
|
|
8541
|
+
throw new Error(`Table ${table.name} does not have a Yjs property named ${prop}`);
|
|
8542
|
+
ydocTriggers[updatesTable] = {
|
|
8543
|
+
trigger,
|
|
8544
|
+
parentTable: table.name,
|
|
8545
|
+
prop,
|
|
8546
|
+
};
|
|
8547
|
+
const db = table.db._novip;
|
|
8548
|
+
let mw = middlewares.get(db);
|
|
8549
|
+
if (!mw) {
|
|
8550
|
+
mw = createMiddleware(db);
|
|
8551
|
+
middlewares.set(db, mw);
|
|
8552
|
+
}
|
|
8553
|
+
db.use(mw);
|
|
8554
|
+
}
|
|
8555
|
+
|
|
8283
8556
|
exports.default = dexieCloud;
|
|
8557
|
+
exports.defineYDocTrigger = defineYDocTrigger;
|
|
8284
8558
|
exports.dexieCloud = dexieCloud;
|
|
8285
8559
|
exports.getTiedObjectId = getTiedObjectId;
|
|
8286
8560
|
exports.getTiedRealmId = getTiedRealmId;
|