cry-synced-db-client 0.1.37 → 0.1.38
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/index.js +341 -180
- package/dist/src/db/DexieDb.d.ts +22 -4
- package/dist/src/db/SyncedDb.d.ts +2 -2
- package/dist/src/types/DbEntity.d.ts +3 -4
- package/dist/src/types/I_DexieDb.d.ts +50 -6
- package/dist/src/types/I_InMemDb.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ function resolveConflict(local, external) {
|
|
|
16
16
|
function mergeObjects(local, external) {
|
|
17
17
|
const result = { ...local };
|
|
18
18
|
for (const key of Object.keys(external)) {
|
|
19
|
-
if (key === "_id" || key === "_dirty"
|
|
19
|
+
if (key === "_id" || key === "_dirty") {
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
22
22
|
const localValue = local[key];
|
|
@@ -669,7 +669,9 @@ class SyncedDb {
|
|
|
669
669
|
}
|
|
670
670
|
async findByIds(collection, ids) {
|
|
671
671
|
this.assertCollection(collection);
|
|
672
|
-
|
|
672
|
+
if (ids.length === 0)
|
|
673
|
+
return [];
|
|
674
|
+
const items = await this.dexieDb.getByIds(collection, ids);
|
|
673
675
|
const results = [];
|
|
674
676
|
for (const item of items) {
|
|
675
677
|
if (item && !item._deleted) {
|
|
@@ -707,10 +709,9 @@ class SyncedDb {
|
|
|
707
709
|
if (!existing) {
|
|
708
710
|
console.warn(`SyncedDb.save: Object ${String(id)} not found in ${collection}, creating new`);
|
|
709
711
|
}
|
|
712
|
+
await this.dexieDb.addDirtyChange(collection, id, { ...update, _lastUpdaterId: this.updaterId }, { _ts: existing?._ts, _rev: existing?._rev });
|
|
710
713
|
const newData = {
|
|
711
714
|
...update,
|
|
712
|
-
_dirty: true,
|
|
713
|
-
_localChangedAt: new Date,
|
|
714
715
|
_lastUpdaterId: this.updaterId
|
|
715
716
|
};
|
|
716
717
|
this.schedulePendingChange(collection, id, newData, 0, "save");
|
|
@@ -738,11 +739,15 @@ class SyncedDb {
|
|
|
738
739
|
if (existing && !existing._deleted) {
|
|
739
740
|
console.warn(`SyncedDb.insert: Object ${String(id)} already exists in ${collection}, overwriting`);
|
|
740
741
|
}
|
|
742
|
+
const insertChanges = { ...data, _lastUpdaterId: this.updaterId };
|
|
743
|
+
delete insertChanges._id;
|
|
744
|
+
await this.dexieDb.addDirtyChange(collection, id, insertChanges, {
|
|
745
|
+
_ts: undefined,
|
|
746
|
+
_rev: undefined
|
|
747
|
+
});
|
|
741
748
|
const newData = {
|
|
742
749
|
_id: id,
|
|
743
750
|
...data,
|
|
744
|
-
_dirty: true,
|
|
745
|
-
_localChangedAt: new Date,
|
|
746
751
|
_lastUpdaterId: this.updaterId
|
|
747
752
|
};
|
|
748
753
|
this.schedulePendingChange(collection, id, newData, 0, "insert");
|
|
@@ -755,10 +760,9 @@ class SyncedDb {
|
|
|
755
760
|
if (!existing || existing._deleted) {
|
|
756
761
|
return null;
|
|
757
762
|
}
|
|
763
|
+
await this.dexieDb.addDirtyChange(collection, id, { _deleted: new Date, _lastUpdaterId: this.updaterId }, { _ts: existing._ts, _rev: existing._rev });
|
|
758
764
|
const deleteUpdate = {
|
|
759
765
|
_deleted: new Date,
|
|
760
|
-
_dirty: true,
|
|
761
|
-
_localChangedAt: new Date,
|
|
762
766
|
_lastUpdaterId: this.updaterId
|
|
763
767
|
};
|
|
764
768
|
this.schedulePendingChange(collection, id, deleteUpdate, 0, "deleteOne");
|
|
@@ -770,25 +774,35 @@ class SyncedDb {
|
|
|
770
774
|
const items = await this.find(collection, query);
|
|
771
775
|
if (items.length === 0)
|
|
772
776
|
return 0;
|
|
773
|
-
const
|
|
777
|
+
const ids = items.map((item) => item._id);
|
|
778
|
+
const existingItems = await this.dexieDb.getByIds(collection, ids);
|
|
774
779
|
const now = new Date;
|
|
775
|
-
|
|
780
|
+
const dirtyChangesBatch = [];
|
|
781
|
+
const idsToDelete = [];
|
|
776
782
|
for (let i = 0;i < items.length; i++) {
|
|
777
783
|
const item = items[i];
|
|
778
784
|
const existing = existingItems[i];
|
|
779
785
|
if (!item || !existing || existing._deleted)
|
|
780
786
|
continue;
|
|
787
|
+
dirtyChangesBatch.push({
|
|
788
|
+
id: item._id,
|
|
789
|
+
changes: { _deleted: now, _lastUpdaterId: this.updaterId },
|
|
790
|
+
baseMeta: { _ts: existing._ts, _rev: existing._rev }
|
|
791
|
+
});
|
|
781
792
|
const deleteUpdate = {
|
|
782
793
|
_deleted: now,
|
|
783
|
-
_dirty: true,
|
|
784
|
-
_localChangedAt: now,
|
|
785
794
|
_lastUpdaterId: this.updaterId
|
|
786
795
|
};
|
|
787
796
|
this.schedulePendingChange(collection, item._id, deleteUpdate, 0, "deleteMany");
|
|
788
|
-
|
|
789
|
-
|
|
797
|
+
idsToDelete.push(item._id);
|
|
798
|
+
}
|
|
799
|
+
if (dirtyChangesBatch.length > 0) {
|
|
800
|
+
await this.dexieDb.addDirtyChangesBatch(collection, dirtyChangesBatch);
|
|
801
|
+
}
|
|
802
|
+
if (idsToDelete.length > 0) {
|
|
803
|
+
this.inMemDb.deleteManyByIds(collection, idsToDelete);
|
|
790
804
|
}
|
|
791
|
-
return
|
|
805
|
+
return idsToDelete.length;
|
|
792
806
|
}
|
|
793
807
|
async hardDeleteOne(collection, id) {
|
|
794
808
|
this.assertCollection(collection);
|
|
@@ -1092,8 +1106,7 @@ class SyncedDb {
|
|
|
1092
1106
|
}
|
|
1093
1107
|
}
|
|
1094
1108
|
stripLocalFields(item) {
|
|
1095
|
-
|
|
1096
|
-
return rest;
|
|
1109
|
+
return item;
|
|
1097
1110
|
}
|
|
1098
1111
|
getPendingKey(collection, id) {
|
|
1099
1112
|
return `${collection}:${String(id)}`;
|
|
@@ -1257,13 +1270,17 @@ class SyncedDb {
|
|
|
1257
1270
|
if (dirtyItems.length === 0) {
|
|
1258
1271
|
return { sentCount: 0 };
|
|
1259
1272
|
}
|
|
1273
|
+
const ids = dirtyItems.map((item) => item._id);
|
|
1274
|
+
const fullItems = await this.dexieDb.getByIds(collection, ids);
|
|
1260
1275
|
const updates = [];
|
|
1261
1276
|
const deletes = [];
|
|
1262
|
-
for (const
|
|
1263
|
-
if (
|
|
1264
|
-
|
|
1277
|
+
for (const fullItem of fullItems) {
|
|
1278
|
+
if (!fullItem)
|
|
1279
|
+
continue;
|
|
1280
|
+
if (fullItem._deleted) {
|
|
1281
|
+
deletes.push(fullItem);
|
|
1265
1282
|
} else {
|
|
1266
|
-
updates.push(
|
|
1283
|
+
updates.push(fullItem);
|
|
1267
1284
|
}
|
|
1268
1285
|
}
|
|
1269
1286
|
const collectionBatches = [[{
|
|
@@ -1280,86 +1297,129 @@ class SyncedDb {
|
|
|
1280
1297
|
let sentCount = 0;
|
|
1281
1298
|
for (const result of results) {
|
|
1282
1299
|
const { results: { inserted, updated, deleted } } = result;
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
this.
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1300
|
+
const allSuccessIds = [
|
|
1301
|
+
...inserted.map((e) => e._id),
|
|
1302
|
+
...updated.map((e) => e._id),
|
|
1303
|
+
...deleted.map((e) => e._id)
|
|
1304
|
+
];
|
|
1305
|
+
if (allSuccessIds.length > 0) {
|
|
1306
|
+
await this.dexieDb.clearDirtyChangesBatch(collection, allSuccessIds);
|
|
1307
|
+
}
|
|
1308
|
+
const insertedAndUpdated = [...inserted, ...updated];
|
|
1309
|
+
if (insertedAndUpdated.length > 0) {
|
|
1310
|
+
const idsToUpdate = insertedAndUpdated.map((e) => e._id);
|
|
1311
|
+
const existingItems = await this.dexieDb.getByIds(collection, idsToUpdate);
|
|
1312
|
+
const dexieSaveBatch = [];
|
|
1313
|
+
for (let i = 0;i < insertedAndUpdated.length; i++) {
|
|
1314
|
+
const entity = insertedAndUpdated[i];
|
|
1315
|
+
const existing = existingItems[i];
|
|
1316
|
+
if (existing) {
|
|
1317
|
+
dexieSaveBatch.push({
|
|
1318
|
+
...existing,
|
|
1319
|
+
_rev: entity._rev,
|
|
1320
|
+
_ts: entity._ts
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
this.inMemDb.save(collection, entity._id, {
|
|
1324
|
+
_rev: entity._rev,
|
|
1325
|
+
_ts: entity._ts
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
if (dexieSaveBatch.length > 0) {
|
|
1329
|
+
await this.dexieDb.saveMany(collection, dexieSaveBatch);
|
|
1330
|
+
}
|
|
1331
|
+
sentCount += insertedAndUpdated.length;
|
|
1306
1332
|
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
this.
|
|
1310
|
-
|
|
1333
|
+
if (deleted.length > 0) {
|
|
1334
|
+
const deleteIds = deleted.map((e) => e._id);
|
|
1335
|
+
await this.dexieDb.deleteMany(collection, deleteIds);
|
|
1336
|
+
this.inMemDb.deleteManyByIds(collection, deleteIds);
|
|
1337
|
+
sentCount += deleted.length;
|
|
1311
1338
|
}
|
|
1312
1339
|
}
|
|
1313
1340
|
return { sentCount };
|
|
1314
1341
|
}
|
|
1315
1342
|
async recoverPendingWrites() {
|
|
1316
1343
|
const pending = getPendingWrites(this.tenant);
|
|
1344
|
+
if (pending.length === 0)
|
|
1345
|
+
return;
|
|
1346
|
+
const byCollection = new Map;
|
|
1317
1347
|
for (const write of pending) {
|
|
1348
|
+
const list = byCollection.get(write.collection) || [];
|
|
1349
|
+
list.push(write);
|
|
1350
|
+
byCollection.set(write.collection, list);
|
|
1351
|
+
}
|
|
1352
|
+
for (const [collection, writes] of byCollection) {
|
|
1318
1353
|
try {
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1354
|
+
const ids = writes.map((w) => w.id);
|
|
1355
|
+
const existingItems = await this.dexieDb.getByIds(collection, ids);
|
|
1356
|
+
const dirtyChangesBatch = [];
|
|
1357
|
+
const saveBatch = [];
|
|
1358
|
+
const insertBatch = [];
|
|
1359
|
+
for (let i = 0;i < writes.length; i++) {
|
|
1360
|
+
const write = writes[i];
|
|
1361
|
+
const existing = existingItems[i];
|
|
1362
|
+
const changes = { ...write.data };
|
|
1363
|
+
delete changes._id;
|
|
1364
|
+
dirtyChangesBatch.push({
|
|
1365
|
+
id: write.id,
|
|
1366
|
+
changes,
|
|
1367
|
+
baseMeta: { _ts: existing?._ts, _rev: existing?._rev }
|
|
1368
|
+
});
|
|
1369
|
+
if (existing) {
|
|
1370
|
+
saveBatch.push({ ...existing, ...write.data });
|
|
1371
|
+
} else {
|
|
1372
|
+
insertBatch.push(write.data);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (dirtyChangesBatch.length > 0) {
|
|
1376
|
+
await this.dexieDb.addDirtyChangesBatch(collection, dirtyChangesBatch);
|
|
1377
|
+
}
|
|
1378
|
+
const allToSave = [...saveBatch, ...insertBatch];
|
|
1379
|
+
if (allToSave.length > 0) {
|
|
1380
|
+
await this.dexieDb.saveMany(collection, allToSave);
|
|
1381
|
+
}
|
|
1382
|
+
for (const write of writes) {
|
|
1383
|
+
clearPendingWrite(this.tenant, write.collection, write.id);
|
|
1324
1384
|
}
|
|
1325
|
-
clearPendingWrite(this.tenant, write.collection, write.id);
|
|
1326
1385
|
} catch (err) {
|
|
1327
|
-
console.error(
|
|
1386
|
+
console.error(`Failed to recover pending writes for ${collection}:`, err);
|
|
1328
1387
|
}
|
|
1329
1388
|
}
|
|
1330
1389
|
}
|
|
1331
1390
|
async processIncomingServerData(collectionName, config, serverData) {
|
|
1391
|
+
if (serverData.length === 0) {
|
|
1392
|
+
return { conflictsResolved: 0, maxTs: undefined };
|
|
1393
|
+
}
|
|
1332
1394
|
let maxTs;
|
|
1333
1395
|
let conflictsResolved = 0;
|
|
1396
|
+
const serverIds = serverData.map((item) => item._id);
|
|
1397
|
+
const localItems = await this.dexieDb.getByIds(collectionName, serverIds);
|
|
1398
|
+
const dirtyChangesMap = await this.dexieDb.getDirtyChangesBatch(collectionName, serverIds);
|
|
1334
1399
|
const dexieBatch = [];
|
|
1335
1400
|
const inMemSaveBatch = [];
|
|
1336
1401
|
const inMemDeleteIds = [];
|
|
1337
|
-
for (
|
|
1338
|
-
const
|
|
1402
|
+
for (let i = 0;i < serverData.length; i++) {
|
|
1403
|
+
const serverItem = serverData[i];
|
|
1404
|
+
const localItem = localItems[i];
|
|
1405
|
+
const dirtyChange = dirtyChangesMap.get(String(serverItem._id));
|
|
1339
1406
|
if (serverItem._ts) {
|
|
1340
1407
|
if (!maxTs || this.compareTimestamps(serverItem._ts, maxTs) > 0) {
|
|
1341
1408
|
maxTs = serverItem._ts;
|
|
1342
1409
|
}
|
|
1343
1410
|
}
|
|
1344
1411
|
if (localItem) {
|
|
1345
|
-
if (
|
|
1412
|
+
if (dirtyChange) {
|
|
1346
1413
|
conflictsResolved++;
|
|
1347
1414
|
const resolved = this.resolveCollectionConflict(collectionName, config, localItem, serverItem, "sync");
|
|
1348
|
-
dexieBatch.push(
|
|
1349
|
-
...resolved,
|
|
1350
|
-
_dirty: true,
|
|
1351
|
-
_localChangedAt: localItem._localChangedAt
|
|
1352
|
-
});
|
|
1415
|
+
dexieBatch.push(resolved);
|
|
1353
1416
|
if (!resolved._deleted) {
|
|
1354
1417
|
inMemSaveBatch.push(this.stripLocalFields(resolved));
|
|
1355
1418
|
} else {
|
|
1356
1419
|
inMemDeleteIds.push(serverItem._id);
|
|
1357
1420
|
}
|
|
1358
1421
|
} else {
|
|
1359
|
-
dexieBatch.push(
|
|
1360
|
-
...serverItem,
|
|
1361
|
-
_dirty: false
|
|
1362
|
-
});
|
|
1422
|
+
dexieBatch.push(serverItem);
|
|
1363
1423
|
if (!serverItem._deleted) {
|
|
1364
1424
|
inMemSaveBatch.push(this.stripLocalFields(serverItem));
|
|
1365
1425
|
} else {
|
|
@@ -1367,10 +1427,7 @@ class SyncedDb {
|
|
|
1367
1427
|
}
|
|
1368
1428
|
}
|
|
1369
1429
|
} else {
|
|
1370
|
-
dexieBatch.push(
|
|
1371
|
-
...serverItem,
|
|
1372
|
-
_dirty: false
|
|
1373
|
-
});
|
|
1430
|
+
dexieBatch.push(serverItem);
|
|
1374
1431
|
if (!serverItem._deleted) {
|
|
1375
1432
|
inMemSaveBatch.push(this.stripLocalFields(serverItem));
|
|
1376
1433
|
}
|
|
@@ -1382,8 +1439,8 @@ class SyncedDb {
|
|
|
1382
1439
|
if (inMemSaveBatch.length > 0) {
|
|
1383
1440
|
this.inMemDb.saveMany(collectionName, inMemSaveBatch);
|
|
1384
1441
|
}
|
|
1385
|
-
|
|
1386
|
-
this.inMemDb.
|
|
1442
|
+
if (inMemDeleteIds.length > 0) {
|
|
1443
|
+
this.inMemDb.deleteManyByIds(collectionName, inMemDeleteIds);
|
|
1387
1444
|
}
|
|
1388
1445
|
if (maxTs) {
|
|
1389
1446
|
await this.dexieDb.setSyncMeta(collectionName, maxTs);
|
|
@@ -1399,16 +1456,20 @@ class SyncedDb {
|
|
|
1399
1456
|
const collectionBatches = [];
|
|
1400
1457
|
const dirtyItemsMap = new Map;
|
|
1401
1458
|
for (const [collectionName] of this.collections) {
|
|
1402
|
-
const
|
|
1403
|
-
if (
|
|
1459
|
+
const dirtyChanges = await this.dexieDb.getDirty(collectionName);
|
|
1460
|
+
if (dirtyChanges.length === 0)
|
|
1404
1461
|
continue;
|
|
1405
1462
|
const updates = [];
|
|
1406
1463
|
const deletes = [];
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1464
|
+
const ids = dirtyChanges.map((dc) => dc._id);
|
|
1465
|
+
const fullItems = await this.dexieDb.getByIds(collectionName, ids);
|
|
1466
|
+
for (const fullItem of fullItems) {
|
|
1467
|
+
if (!fullItem)
|
|
1468
|
+
continue;
|
|
1469
|
+
if (fullItem._deleted) {
|
|
1470
|
+
deletes.push(fullItem);
|
|
1410
1471
|
} else {
|
|
1411
|
-
updates.push(
|
|
1472
|
+
updates.push(fullItem);
|
|
1412
1473
|
}
|
|
1413
1474
|
}
|
|
1414
1475
|
dirtyItemsMap.set(collectionName, { updates, deletes });
|
|
@@ -1472,34 +1533,50 @@ class SyncedDb {
|
|
|
1472
1533
|
let sentCount = 0;
|
|
1473
1534
|
for (const result of results) {
|
|
1474
1535
|
const { collection, results: { inserted, updated, deleted, errors } } = result;
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
this.
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1536
|
+
const allSuccessIds = [
|
|
1537
|
+
...inserted.map((e) => e._id),
|
|
1538
|
+
...updated.map((e) => e._id),
|
|
1539
|
+
...deleted.map((e) => e._id)
|
|
1540
|
+
];
|
|
1541
|
+
if (allSuccessIds.length > 0) {
|
|
1542
|
+
await this.dexieDb.clearDirtyChangesBatch(collection, allSuccessIds);
|
|
1543
|
+
}
|
|
1544
|
+
const insertedAndUpdated = [...inserted, ...updated];
|
|
1545
|
+
if (insertedAndUpdated.length > 0) {
|
|
1546
|
+
const idsToCheck = insertedAndUpdated.map((e) => e._id);
|
|
1547
|
+
const dexieItems = await this.dexieDb.getByIds(collection, idsToCheck);
|
|
1548
|
+
const dexieSaveBatch = [];
|
|
1549
|
+
const inMemSaveIds = [];
|
|
1550
|
+
for (let i = 0;i < insertedAndUpdated.length; i++) {
|
|
1551
|
+
const entity = insertedAndUpdated[i];
|
|
1552
|
+
const dexieItem = dexieItems[i];
|
|
1553
|
+
if (dexieItem) {
|
|
1554
|
+
dexieSaveBatch.push({
|
|
1555
|
+
...dexieItem,
|
|
1556
|
+
_rev: entity._rev,
|
|
1557
|
+
_ts: entity._ts
|
|
1558
|
+
});
|
|
1559
|
+
if (!dexieItem._deleted) {
|
|
1560
|
+
inMemSaveIds.push({ id: entity._id, rev: entity._rev, ts: entity._ts });
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
if (dexieSaveBatch.length > 0) {
|
|
1565
|
+
await this.dexieDb.saveMany(collection, dexieSaveBatch);
|
|
1566
|
+
}
|
|
1567
|
+
for (const item of inMemSaveIds) {
|
|
1568
|
+
this.inMemDb.save(collection, item.id, {
|
|
1569
|
+
_rev: item.rev,
|
|
1570
|
+
_ts: item.ts
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
sentCount += insertedAndUpdated.length;
|
|
1498
1574
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
this.
|
|
1502
|
-
|
|
1575
|
+
if (deleted.length > 0) {
|
|
1576
|
+
const deleteIds = deleted.map((e) => e._id);
|
|
1577
|
+
await this.dexieDb.deleteMany(collection, deleteIds);
|
|
1578
|
+
this.inMemDb.deleteManyByIds(collection, deleteIds);
|
|
1579
|
+
sentCount += deleted.length;
|
|
1503
1580
|
}
|
|
1504
1581
|
if (errors) {
|
|
1505
1582
|
console.error(`Sync errors for ${collection}:`, errors);
|
|
@@ -1669,32 +1746,40 @@ class SyncedDb {
|
|
|
1669
1746
|
break;
|
|
1670
1747
|
}
|
|
1671
1748
|
case "batch": {
|
|
1749
|
+
const inserts = [];
|
|
1750
|
+
const updates = [];
|
|
1751
|
+
const deletes = [];
|
|
1672
1752
|
for (const item of payload.data) {
|
|
1673
|
-
if (item.operation === "insert") {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1753
|
+
if (item.operation === "insert" && item.data) {
|
|
1754
|
+
inserts.push(item.data);
|
|
1755
|
+
} else if (item.operation === "update" && item.data && item.data._id) {
|
|
1756
|
+
updates.push(item.data);
|
|
1757
|
+
} else if (item.operation === "delete" && item.data && item.data._id) {
|
|
1758
|
+
deletes.push(item.data);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
for (const serverItem of inserts) {
|
|
1762
|
+
await this.handleServerItemInsert(collectionName, serverItem);
|
|
1763
|
+
}
|
|
1764
|
+
if (updates.length > 0) {
|
|
1765
|
+
const updateIds = updates.map((u) => u._id);
|
|
1766
|
+
const localItems = await this.dexieDb.getByIds(collectionName, updateIds);
|
|
1767
|
+
for (let i = 0;i < updates.length; i++) {
|
|
1768
|
+
const deltaData = updates[i];
|
|
1769
|
+
const localItem = localItems[i];
|
|
1770
|
+
if (localItem) {
|
|
1771
|
+
await this.handleServerItemUpdate(collectionName, localItem, deltaData);
|
|
1772
|
+
} else {
|
|
1773
|
+
const fullItem = await this.restInterface.findById(collectionName, deltaData._id);
|
|
1774
|
+
if (fullItem) {
|
|
1775
|
+
await this.handleServerItemInsert(collectionName, fullItem);
|
|
1689
1776
|
}
|
|
1690
1777
|
}
|
|
1691
|
-
} else if (item.operation === "delete") {
|
|
1692
|
-
const deleteData = item.data;
|
|
1693
|
-
if (deleteData && deleteData._id) {
|
|
1694
|
-
await this.handleServerItemDelete(collectionName, deleteData._id);
|
|
1695
|
-
}
|
|
1696
1778
|
}
|
|
1697
1779
|
}
|
|
1780
|
+
for (const deleteData of deletes) {
|
|
1781
|
+
await this.handleServerItemDelete(collectionName, deleteData._id);
|
|
1782
|
+
}
|
|
1698
1783
|
await this.broadcastMetaUpdate(collectionName, Date.now());
|
|
1699
1784
|
break;
|
|
1700
1785
|
}
|
|
@@ -1703,20 +1788,19 @@ class SyncedDb {
|
|
|
1703
1788
|
async handleServerItemInsert(collectionName, serverItem) {
|
|
1704
1789
|
const localItem = await this.dexieDb.getById(collectionName, serverItem._id);
|
|
1705
1790
|
if (localItem) {
|
|
1791
|
+
const dirtyChange = await this.dexieDb.getDirtyChange(collectionName, serverItem._id);
|
|
1706
1792
|
const metaChanged = localItem._rev !== serverItem._rev || !this.timestampsEqual(localItem._ts, serverItem._ts);
|
|
1707
|
-
|
|
1708
|
-
if (metaChanged || dirtyNeedsClearing) {
|
|
1793
|
+
if (metaChanged) {
|
|
1709
1794
|
await this.dexieDb.save(collectionName, serverItem._id, {
|
|
1710
1795
|
_rev: serverItem._rev,
|
|
1711
|
-
_ts: serverItem._ts
|
|
1712
|
-
_dirty: false
|
|
1796
|
+
_ts: serverItem._ts
|
|
1713
1797
|
});
|
|
1714
1798
|
}
|
|
1799
|
+
if (dirtyChange && !metaChanged) {
|
|
1800
|
+
await this.dexieDb.clearDirtyChange(collectionName, serverItem._id);
|
|
1801
|
+
}
|
|
1715
1802
|
} else {
|
|
1716
|
-
await this.dexieDb.insert(collectionName,
|
|
1717
|
-
...serverItem,
|
|
1718
|
-
_dirty: false
|
|
1719
|
-
});
|
|
1803
|
+
await this.dexieDb.insert(collectionName, serverItem);
|
|
1720
1804
|
if (!serverItem._deleted) {
|
|
1721
1805
|
this.inMemDb.insert(collectionName, this.stripLocalFields(serverItem));
|
|
1722
1806
|
}
|
|
@@ -1726,14 +1810,13 @@ class SyncedDb {
|
|
|
1726
1810
|
const isLoopback = serverDelta._lastUpdaterId === this.updaterId && serverDelta._rev !== undefined && localItem._rev !== undefined && serverDelta._rev === localItem._rev + 1;
|
|
1727
1811
|
if (isLoopback) {
|
|
1728
1812
|
const metaChanged2 = localItem._rev !== serverDelta._rev || !this.timestampsEqual(localItem._ts, serverDelta._ts);
|
|
1729
|
-
|
|
1730
|
-
if (metaChanged2 || dirtyNeedsClearing) {
|
|
1813
|
+
if (metaChanged2) {
|
|
1731
1814
|
await this.dexieDb.save(collectionName, serverDelta._id, {
|
|
1732
1815
|
_rev: serverDelta._rev,
|
|
1733
|
-
_ts: serverDelta._ts
|
|
1734
|
-
_dirty: false
|
|
1816
|
+
_ts: serverDelta._ts
|
|
1735
1817
|
});
|
|
1736
1818
|
}
|
|
1819
|
+
await this.dexieDb.clearDirtyChange(collectionName, serverDelta._id);
|
|
1737
1820
|
return;
|
|
1738
1821
|
}
|
|
1739
1822
|
const pendingKey = this.getPendingKey(collectionName, serverDelta._id);
|
|
@@ -1752,14 +1835,12 @@ class SyncedDb {
|
|
|
1752
1835
|
}
|
|
1753
1836
|
return;
|
|
1754
1837
|
}
|
|
1838
|
+
const dirtyChange = await this.dexieDb.getDirtyChange(collectionName, serverDelta._id);
|
|
1755
1839
|
const metaChanged = localItem._rev !== serverDelta._rev || !this.timestampsEqual(localItem._ts, serverDelta._ts);
|
|
1756
|
-
if (
|
|
1840
|
+
if (dirtyChange) {
|
|
1757
1841
|
const merged = this.mergeLocalWithDelta(localItem, serverDelta);
|
|
1758
1842
|
if (metaChanged) {
|
|
1759
|
-
await this.dexieDb.save(collectionName, serverDelta._id,
|
|
1760
|
-
...merged,
|
|
1761
|
-
_dirty: true
|
|
1762
|
-
});
|
|
1843
|
+
await this.dexieDb.save(collectionName, serverDelta._id, merged);
|
|
1763
1844
|
}
|
|
1764
1845
|
if (!merged._deleted) {
|
|
1765
1846
|
this.inMemDb.save(collectionName, serverDelta._id, this.stripLocalFields(merged));
|
|
@@ -1769,10 +1850,7 @@ class SyncedDb {
|
|
|
1769
1850
|
return;
|
|
1770
1851
|
}
|
|
1771
1852
|
const merged = this.mergeLocalWithDelta(localItem, serverDelta);
|
|
1772
|
-
await this.dexieDb.save(collectionName, serverDelta._id,
|
|
1773
|
-
...merged,
|
|
1774
|
-
_dirty: false
|
|
1775
|
-
});
|
|
1853
|
+
await this.dexieDb.save(collectionName, serverDelta._id, merged);
|
|
1776
1854
|
if (!merged._deleted) {
|
|
1777
1855
|
this.inMemDb.save(collectionName, serverDelta._id, this.stripLocalFields(merged));
|
|
1778
1856
|
} else {
|
|
@@ -1783,7 +1861,7 @@ class SyncedDb {
|
|
|
1783
1861
|
getNewFieldsFromServer(local, server) {
|
|
1784
1862
|
const newFields = {};
|
|
1785
1863
|
for (const key of Object.keys(server)) {
|
|
1786
|
-
if (key !== "_id" && key !== "_dirty" &&
|
|
1864
|
+
if (key !== "_id" && key !== "_dirty" && local[key] === undefined) {
|
|
1787
1865
|
newFields[key] = server[key];
|
|
1788
1866
|
}
|
|
1789
1867
|
}
|
|
@@ -1792,7 +1870,7 @@ class SyncedDb {
|
|
|
1792
1870
|
mergeLocalWithDelta(local, delta) {
|
|
1793
1871
|
const result = { ...local };
|
|
1794
1872
|
for (const key of Object.keys(delta)) {
|
|
1795
|
-
if (key === "_id" || key === "_dirty"
|
|
1873
|
+
if (key === "_id" || key === "_dirty") {
|
|
1796
1874
|
continue;
|
|
1797
1875
|
}
|
|
1798
1876
|
result[key] = delta[key];
|
|
@@ -1803,7 +1881,8 @@ class SyncedDb {
|
|
|
1803
1881
|
const localItem = await this.dexieDb.getById(collectionName, id);
|
|
1804
1882
|
if (!localItem)
|
|
1805
1883
|
return;
|
|
1806
|
-
|
|
1884
|
+
const dirtyChange = await this.dexieDb.getDirtyChange(collectionName, id);
|
|
1885
|
+
if (dirtyChange) {
|
|
1807
1886
|
await this.dexieDb.save(collectionName, id, {
|
|
1808
1887
|
_deleted: new Date
|
|
1809
1888
|
});
|
|
@@ -1816,6 +1895,7 @@ class SyncedDb {
|
|
|
1816
1895
|
// src/db/DexieDb.ts
|
|
1817
1896
|
import Dexie from "dexie";
|
|
1818
1897
|
var SYNC_META_TABLE = "_sync_meta";
|
|
1898
|
+
var DIRTY_CHANGES_TABLE = "_dirty_changes";
|
|
1819
1899
|
|
|
1820
1900
|
class DexieDb extends Dexie {
|
|
1821
1901
|
tenant;
|
|
@@ -1823,6 +1903,7 @@ class DexieDb extends Dexie {
|
|
|
1823
1903
|
crossTabSyncDebounceMs;
|
|
1824
1904
|
collections = new Map;
|
|
1825
1905
|
syncMeta;
|
|
1906
|
+
dirtyChanges;
|
|
1826
1907
|
broadcastChannel = null;
|
|
1827
1908
|
pendingBroadcasts = new Map;
|
|
1828
1909
|
debounceTimer = null;
|
|
@@ -1834,13 +1915,15 @@ class DexieDb extends Dexie {
|
|
|
1834
1915
|
this.crossTabSyncDebounceMs = options?.crossTabSyncDebounceMs ?? 100;
|
|
1835
1916
|
const schema = {};
|
|
1836
1917
|
schema[SYNC_META_TABLE] = "[tenant+collection]";
|
|
1918
|
+
schema[DIRTY_CHANGES_TABLE] = "[collection+id]";
|
|
1837
1919
|
for (const config of collectionConfigs) {
|
|
1838
1920
|
const additionalIndexes = config.indexes || [];
|
|
1839
|
-
const indexes = ["_id", "
|
|
1921
|
+
const indexes = ["_id", "_rev", ...additionalIndexes.map(String)];
|
|
1840
1922
|
schema[config.name] = indexes.join(", ");
|
|
1841
1923
|
}
|
|
1842
1924
|
this.version(1).stores(schema);
|
|
1843
1925
|
this.syncMeta = this.table(SYNC_META_TABLE);
|
|
1926
|
+
this.dirtyChanges = this.table(DIRTY_CHANGES_TABLE);
|
|
1844
1927
|
for (const config of collectionConfigs) {
|
|
1845
1928
|
this.collections.set(config.name, this.table(config.name));
|
|
1846
1929
|
}
|
|
@@ -1902,27 +1985,17 @@ class DexieDb extends Dexie {
|
|
|
1902
1985
|
const key = this.idToString(id);
|
|
1903
1986
|
const existing = await table.get(key);
|
|
1904
1987
|
if (existing) {
|
|
1905
|
-
await table.update(key,
|
|
1906
|
-
...data,
|
|
1907
|
-
_dirty: data._dirty ?? true,
|
|
1908
|
-
_localChangedAt: new Date
|
|
1909
|
-
});
|
|
1988
|
+
await table.update(key, data);
|
|
1910
1989
|
} else {
|
|
1911
1990
|
await table.put({
|
|
1912
1991
|
_id: id,
|
|
1913
|
-
...data
|
|
1914
|
-
_dirty: data._dirty ?? true,
|
|
1915
|
-
_localChangedAt: new Date
|
|
1992
|
+
...data
|
|
1916
1993
|
});
|
|
1917
1994
|
}
|
|
1918
1995
|
}
|
|
1919
1996
|
async insert(collection, data) {
|
|
1920
1997
|
const table = this.getTable(collection);
|
|
1921
|
-
await table.put(
|
|
1922
|
-
...data,
|
|
1923
|
-
_dirty: true,
|
|
1924
|
-
_localChangedAt: new Date
|
|
1925
|
-
});
|
|
1998
|
+
await table.put(data);
|
|
1926
1999
|
}
|
|
1927
2000
|
async saveMany(collection, items) {
|
|
1928
2001
|
if (items.length === 0)
|
|
@@ -1935,6 +2008,13 @@ class DexieDb extends Dexie {
|
|
|
1935
2008
|
const key = this.idToString(id);
|
|
1936
2009
|
await table.delete(key);
|
|
1937
2010
|
}
|
|
2011
|
+
async deleteMany(collection, ids) {
|
|
2012
|
+
if (ids.length === 0)
|
|
2013
|
+
return;
|
|
2014
|
+
const table = this.getTable(collection);
|
|
2015
|
+
const keys = ids.map((id) => this.idToString(id));
|
|
2016
|
+
await table.bulkDelete(keys);
|
|
2017
|
+
}
|
|
1938
2018
|
async saveCollection(collection, data) {
|
|
1939
2019
|
const table = this.getTable(collection);
|
|
1940
2020
|
await table.clear();
|
|
@@ -1951,6 +2031,13 @@ class DexieDb extends Dexie {
|
|
|
1951
2031
|
const key = this.idToString(id);
|
|
1952
2032
|
return await table.get(key);
|
|
1953
2033
|
}
|
|
2034
|
+
async getByIds(collection, ids) {
|
|
2035
|
+
if (ids.length === 0)
|
|
2036
|
+
return [];
|
|
2037
|
+
const table = this.getTable(collection);
|
|
2038
|
+
const keys = ids.map((id) => this.idToString(id));
|
|
2039
|
+
return await table.bulkGet(keys);
|
|
2040
|
+
}
|
|
1954
2041
|
async getAll(collection) {
|
|
1955
2042
|
const table = this.getTable(collection);
|
|
1956
2043
|
return await table.toArray();
|
|
@@ -1960,23 +2047,97 @@ class DexieDb extends Dexie {
|
|
|
1960
2047
|
return await table.count();
|
|
1961
2048
|
}
|
|
1962
2049
|
async getDirty(collection) {
|
|
1963
|
-
const
|
|
1964
|
-
return
|
|
2050
|
+
const dirtyEntries = await this.dirtyChanges.where("[collection+id]").between([collection, Dexie.minKey], [collection, Dexie.maxKey]).toArray();
|
|
2051
|
+
return dirtyEntries.map((entry) => ({
|
|
2052
|
+
_id: entry.id,
|
|
2053
|
+
...entry.changes,
|
|
2054
|
+
_ts: entry.baseTs,
|
|
2055
|
+
_rev: entry.baseRev
|
|
2056
|
+
}));
|
|
1965
2057
|
}
|
|
1966
|
-
async
|
|
1967
|
-
const
|
|
1968
|
-
const
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2058
|
+
async addDirtyChange(collection, id, changes, baseMeta) {
|
|
2059
|
+
const stringId = this.idToString(id);
|
|
2060
|
+
const existing = await this.dirtyChanges.get([collection, stringId]);
|
|
2061
|
+
const now = Date.now();
|
|
2062
|
+
if (existing) {
|
|
2063
|
+
await this.dirtyChanges.put({
|
|
2064
|
+
...existing,
|
|
2065
|
+
changes: { ...existing.changes, ...changes },
|
|
2066
|
+
updatedAt: now
|
|
2067
|
+
});
|
|
2068
|
+
} else {
|
|
2069
|
+
await this.dirtyChanges.put({
|
|
2070
|
+
collection,
|
|
2071
|
+
id: stringId,
|
|
2072
|
+
changes,
|
|
2073
|
+
baseTs: baseMeta?._ts,
|
|
2074
|
+
baseRev: baseMeta?._rev,
|
|
2075
|
+
createdAt: now,
|
|
2076
|
+
updatedAt: now
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
1973
2079
|
}
|
|
1974
|
-
async
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2080
|
+
async addDirtyChangesBatch(collection, changesList) {
|
|
2081
|
+
if (changesList.length === 0)
|
|
2082
|
+
return;
|
|
2083
|
+
const now = Date.now();
|
|
2084
|
+
const keys = changesList.map((c) => [collection, this.idToString(c.id)]);
|
|
2085
|
+
const existingEntries = await this.dirtyChanges.bulkGet(keys);
|
|
2086
|
+
const toWrite = [];
|
|
2087
|
+
for (let i = 0;i < changesList.length; i++) {
|
|
2088
|
+
const changeItem = changesList[i];
|
|
2089
|
+
const stringId = this.idToString(changeItem.id);
|
|
2090
|
+
const existing = existingEntries[i];
|
|
2091
|
+
if (existing) {
|
|
2092
|
+
toWrite.push({
|
|
2093
|
+
...existing,
|
|
2094
|
+
changes: { ...existing.changes, ...changeItem.changes },
|
|
2095
|
+
updatedAt: now
|
|
2096
|
+
});
|
|
2097
|
+
} else {
|
|
2098
|
+
toWrite.push({
|
|
2099
|
+
collection,
|
|
2100
|
+
id: stringId,
|
|
2101
|
+
changes: changeItem.changes,
|
|
2102
|
+
baseTs: changeItem.baseMeta?._ts,
|
|
2103
|
+
baseRev: changeItem.baseMeta?._rev,
|
|
2104
|
+
createdAt: now,
|
|
2105
|
+
updatedAt: now
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
await this.dirtyChanges.bulkPut(toWrite);
|
|
2110
|
+
}
|
|
2111
|
+
async getDirtyChange(collection, id) {
|
|
2112
|
+
const stringId = this.idToString(id);
|
|
2113
|
+
return this.dirtyChanges.get([collection, stringId]);
|
|
2114
|
+
}
|
|
2115
|
+
async getDirtyChangesBatch(collection, ids) {
|
|
2116
|
+
const result = new Map;
|
|
2117
|
+
if (ids.length === 0)
|
|
2118
|
+
return result;
|
|
2119
|
+
const keys = ids.map((id) => [collection, this.idToString(id)]);
|
|
2120
|
+
const entries = await this.dirtyChanges.bulkGet(keys);
|
|
2121
|
+
for (let i = 0;i < ids.length; i++) {
|
|
2122
|
+
const entry = entries[i];
|
|
2123
|
+
if (entry) {
|
|
2124
|
+
result.set(this.idToString(ids[i]), entry);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
return result;
|
|
2128
|
+
}
|
|
2129
|
+
async clearDirtyChange(collection, id) {
|
|
2130
|
+
const stringId = this.idToString(id);
|
|
2131
|
+
await this.dirtyChanges.delete([collection, stringId]);
|
|
2132
|
+
}
|
|
2133
|
+
async clearDirtyChangesBatch(collection, ids) {
|
|
2134
|
+
if (ids.length === 0)
|
|
2135
|
+
return;
|
|
2136
|
+
const keys = ids.map((id) => [collection, this.idToString(id)]);
|
|
2137
|
+
await this.dirtyChanges.bulkDelete(keys);
|
|
2138
|
+
}
|
|
2139
|
+
async clearDirtyChanges(collection) {
|
|
2140
|
+
await this.dirtyChanges.where("[collection+id]").between([collection, Dexie.minKey], [collection, Dexie.maxKey]).delete();
|
|
1980
2141
|
}
|
|
1981
2142
|
async getSyncMeta(collection) {
|
|
1982
2143
|
return await this.syncMeta.get([this.tenant, collection]);
|
package/dist/src/db/DexieDb.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Dexie from "dexie";
|
|
2
|
-
import type { DexieDbOptions, I_DexieDb, MetaUpdateBroadcast, SyncMeta } from "../types/I_DexieDb";
|
|
2
|
+
import type { DexieDbOptions, DirtyChange, I_DexieDb, MetaUpdateBroadcast, SyncMeta } from "../types/I_DexieDb";
|
|
3
3
|
import type { CollectionConfig } from "../types/CollectionConfig";
|
|
4
4
|
import type { Id, LocalDbEntity } from "../types/DbEntity";
|
|
5
5
|
/**
|
|
@@ -12,6 +12,7 @@ export declare class DexieDb extends Dexie implements I_DexieDb {
|
|
|
12
12
|
private crossTabSyncDebounceMs;
|
|
13
13
|
private collections;
|
|
14
14
|
private syncMeta;
|
|
15
|
+
private dirtyChanges;
|
|
15
16
|
private broadcastChannel;
|
|
16
17
|
private pendingBroadcasts;
|
|
17
18
|
private debounceTimer;
|
|
@@ -26,14 +27,31 @@ export declare class DexieDb extends Dexie implements I_DexieDb {
|
|
|
26
27
|
insert<T extends LocalDbEntity>(collection: string, data: T): Promise<void>;
|
|
27
28
|
saveMany<T extends LocalDbEntity>(collection: string, items: T[]): Promise<void>;
|
|
28
29
|
deleteOne(collection: string, id: Id): Promise<void>;
|
|
30
|
+
deleteMany(collection: string, ids: Id[]): Promise<void>;
|
|
29
31
|
saveCollection<T extends LocalDbEntity>(collection: string, data: T[]): Promise<void>;
|
|
30
32
|
deleteCollection(collection: string): Promise<void>;
|
|
31
33
|
getById<T extends LocalDbEntity>(collection: string, id: Id): Promise<T | undefined>;
|
|
34
|
+
getByIds<T extends LocalDbEntity>(collection: string, ids: Id[]): Promise<(T | undefined)[]>;
|
|
32
35
|
getAll<T extends LocalDbEntity>(collection: string): Promise<T[]>;
|
|
33
36
|
count(collection: string): Promise<number>;
|
|
34
|
-
getDirty<T extends LocalDbEntity>(collection: string): Promise<T[]>;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
getDirty<T extends LocalDbEntity>(collection: string): Promise<Partial<T>[]>;
|
|
38
|
+
addDirtyChange(collection: string, id: Id, changes: Record<string, any>, baseMeta?: {
|
|
39
|
+
_ts?: any;
|
|
40
|
+
_rev?: number;
|
|
41
|
+
}): Promise<void>;
|
|
42
|
+
addDirtyChangesBatch(collection: string, changesList: Array<{
|
|
43
|
+
id: Id;
|
|
44
|
+
changes: Record<string, any>;
|
|
45
|
+
baseMeta?: {
|
|
46
|
+
_ts?: any;
|
|
47
|
+
_rev?: number;
|
|
48
|
+
};
|
|
49
|
+
}>): Promise<void>;
|
|
50
|
+
getDirtyChange(collection: string, id: Id): Promise<DirtyChange | undefined>;
|
|
51
|
+
getDirtyChangesBatch(collection: string, ids: Id[]): Promise<Map<string, DirtyChange>>;
|
|
52
|
+
clearDirtyChange(collection: string, id: Id): Promise<void>;
|
|
53
|
+
clearDirtyChangesBatch(collection: string, ids: Id[]): Promise<void>;
|
|
54
|
+
clearDirtyChanges(collection: string): Promise<void>;
|
|
37
55
|
getSyncMeta(collection: string): Promise<SyncMeta | undefined>;
|
|
38
56
|
setSyncMeta(collection: string, lastSyncTs: any): Promise<void>;
|
|
39
57
|
deleteSyncMeta(collection: string): Promise<void>;
|
|
@@ -202,14 +202,14 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
202
202
|
/**
|
|
203
203
|
* Handle server insert notification.
|
|
204
204
|
* Insert notifications contain full object data.
|
|
205
|
-
* Only writes to Dexie if metadata changed
|
|
205
|
+
* Only writes to Dexie if metadata changed. Clears dirty change if loopback confirmed.
|
|
206
206
|
*/
|
|
207
207
|
private handleServerItemInsert;
|
|
208
208
|
/**
|
|
209
209
|
* Handle server update notification (delta).
|
|
210
210
|
* Update notifications contain only changed fields.
|
|
211
211
|
* Caller must provide the existing local item.
|
|
212
|
-
* Only writes to Dexie if metadata changed
|
|
212
|
+
* Only writes to Dexie if metadata changed.
|
|
213
213
|
*/
|
|
214
214
|
private handleServerItemUpdate;
|
|
215
215
|
private getNewFieldsFromServer;
|
|
@@ -21,10 +21,9 @@ export interface DbEntity {
|
|
|
21
21
|
/**
|
|
22
22
|
* Razširitev DbEntity z lokalnimi polji za sync tracking
|
|
23
23
|
* Uporablja se v dexie bazi
|
|
24
|
+
*
|
|
25
|
+
* Note: _dirty field was removed. Dirty tracking is now handled via
|
|
26
|
+
* the _dirty_changes table in DexieDb with field-level granularity.
|
|
24
27
|
*/
|
|
25
28
|
export interface LocalDbEntity extends DbEntity {
|
|
26
|
-
/** Označuje, da ima objekt lokalne spremembe, ki čakajo na sync */
|
|
27
|
-
_dirty?: boolean;
|
|
28
|
-
/** Čas zadnje lokalne spremembe */
|
|
29
|
-
_localChangedAt?: Date;
|
|
30
29
|
}
|
|
@@ -7,6 +7,26 @@ export interface SyncMeta {
|
|
|
7
7
|
collection: string;
|
|
8
8
|
lastSyncTs?: any;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Dirty change entry - tracks accumulated changes for a record
|
|
12
|
+
* Stored in _dirty_changes table with composite key [collection, id]
|
|
13
|
+
*/
|
|
14
|
+
export interface DirtyChange {
|
|
15
|
+
/** Collection name */
|
|
16
|
+
collection: string;
|
|
17
|
+
/** Stringified Id */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Accumulated changed fields */
|
|
20
|
+
changes: Record<string, any>;
|
|
21
|
+
/** _ts from base record when change was made */
|
|
22
|
+
baseTs?: any;
|
|
23
|
+
/** _rev from base record when change was made */
|
|
24
|
+
baseRev?: number;
|
|
25
|
+
/** When first change was recorded */
|
|
26
|
+
createdAt: number;
|
|
27
|
+
/** When last change was accumulated */
|
|
28
|
+
updatedAt: number;
|
|
29
|
+
}
|
|
10
30
|
/**
|
|
11
31
|
* Payload for cross-tab meta update broadcasts
|
|
12
32
|
*/
|
|
@@ -37,22 +57,46 @@ export interface I_DexieDb {
|
|
|
37
57
|
insert<T extends LocalDbEntity>(collection: string, data: T): Promise<void>;
|
|
38
58
|
/** Izbriše objekt iz kolekcije */
|
|
39
59
|
deleteOne(collection: string, id: Id): Promise<void>;
|
|
60
|
+
/** Izbriše več objektov iz kolekcije (batch) */
|
|
61
|
+
deleteMany(collection: string, ids: Id[]): Promise<void>;
|
|
40
62
|
/** Shrani celotno kolekcijo (nadomesti obstoječo) */
|
|
41
63
|
saveCollection<T extends LocalDbEntity>(collection: string, data: T[]): Promise<void>;
|
|
42
64
|
/** Izbriše celotno kolekcijo */
|
|
43
65
|
deleteCollection(collection: string): Promise<void>;
|
|
44
66
|
/** Vrne objekt po ID-ju */
|
|
45
67
|
getById<T extends LocalDbEntity>(collection: string, id: Id): Promise<T | undefined>;
|
|
68
|
+
/** Vrne več objektov po ID-jih (batch) */
|
|
69
|
+
getByIds<T extends LocalDbEntity>(collection: string, ids: Id[]): Promise<(T | undefined)[]>;
|
|
46
70
|
/** Vrne vse objekte v kolekciji */
|
|
47
71
|
getAll<T extends LocalDbEntity>(collection: string): Promise<T[]>;
|
|
48
72
|
/** Vrne število objektov v kolekciji */
|
|
49
73
|
count(collection: string): Promise<number>;
|
|
50
|
-
/** Vrne vse dirty objekte (z lokalnimi spremembami) */
|
|
51
|
-
getDirty<T extends LocalDbEntity>(collection: string): Promise<T[]>;
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
74
|
+
/** Vrne vse dirty objekte (z lokalnimi spremembami) - returns only changed fields + _id + metadata */
|
|
75
|
+
getDirty<T extends LocalDbEntity>(collection: string): Promise<Partial<T>[]>;
|
|
76
|
+
/** Add or accumulate changes for a record */
|
|
77
|
+
addDirtyChange(collection: string, id: Id, changes: Record<string, any>, baseMeta?: {
|
|
78
|
+
_ts?: any;
|
|
79
|
+
_rev?: number;
|
|
80
|
+
}): Promise<void>;
|
|
81
|
+
/** Add or accumulate changes for multiple records (batch) */
|
|
82
|
+
addDirtyChangesBatch(collection: string, changes: Array<{
|
|
83
|
+
id: Id;
|
|
84
|
+
changes: Record<string, any>;
|
|
85
|
+
baseMeta?: {
|
|
86
|
+
_ts?: any;
|
|
87
|
+
_rev?: number;
|
|
88
|
+
};
|
|
89
|
+
}>): Promise<void>;
|
|
90
|
+
/** Get dirty change entry for a specific record */
|
|
91
|
+
getDirtyChange(collection: string, id: Id): Promise<DirtyChange | undefined>;
|
|
92
|
+
/** Get dirty change entries for multiple records (batch) */
|
|
93
|
+
getDirtyChangesBatch(collection: string, ids: Id[]): Promise<Map<string, DirtyChange>>;
|
|
94
|
+
/** Clear dirty change for a record (after successful sync) */
|
|
95
|
+
clearDirtyChange(collection: string, id: Id): Promise<void>;
|
|
96
|
+
/** Clear dirty changes for multiple records (batch) */
|
|
97
|
+
clearDirtyChangesBatch(collection: string, ids: Id[]): Promise<void>;
|
|
98
|
+
/** Clear all dirty changes for a collection */
|
|
99
|
+
clearDirtyChanges(collection: string): Promise<void>;
|
|
56
100
|
/** Vrne sync metapodatke za kolekcijo */
|
|
57
101
|
getSyncMeta(collection: string): Promise<SyncMeta | undefined>;
|
|
58
102
|
/** Nastavi sync metapodatke za kolekcijo */
|
|
@@ -12,6 +12,8 @@ export interface I_InMemDb {
|
|
|
12
12
|
insert<T extends DbEntity>(collection: string, data: T): void;
|
|
13
13
|
/** Izbriše objekt iz kolekcije */
|
|
14
14
|
deleteOne(collection: string, id: Id): void;
|
|
15
|
+
/** Izbriše več objektov iz kolekcije po ID-jih */
|
|
16
|
+
deleteManyByIds(collection: string, ids: Id[]): void;
|
|
15
17
|
/** Shrani celotno kolekcijo (nadomesti obstoječo) */
|
|
16
18
|
saveCollection<T extends DbEntity>(collection: string, data: T[]): void;
|
|
17
19
|
/** Izbriše celotno kolekcijo */
|