@pylonsync/sync 0.3.196 → 0.3.197
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/package.json +1 -1
- package/src/index.ts +31 -18
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -758,7 +758,15 @@ export class SyncEngine {
|
|
|
758
758
|
typeof msg.entity === "string" &&
|
|
759
759
|
typeof msg.row_id === "string"
|
|
760
760
|
) {
|
|
761
|
-
|
|
761
|
+
// Server includes its current high-water seq when known —
|
|
762
|
+
// use it as the tombstone seq so an in-flight stale frame
|
|
763
|
+
// with `seq <= server_seq` is filtered locally. A
|
|
764
|
+
// legitimate re-grant at a higher seq still lands.
|
|
765
|
+
const revokeSeq =
|
|
766
|
+
typeof msg.seq === "number" && msg.seq > 0
|
|
767
|
+
? msg.seq
|
|
768
|
+
: this.cursor.last_seq;
|
|
769
|
+
this.handleRowRevocation(msg.entity, msg.row_id, revokeSeq);
|
|
762
770
|
return;
|
|
763
771
|
}
|
|
764
772
|
|
|
@@ -1491,28 +1499,28 @@ export class SyncEngine {
|
|
|
1491
1499
|
* Drop a row from the local replica because the server signaled
|
|
1492
1500
|
* that the current subscriber's read policy was revoked for it.
|
|
1493
1501
|
*
|
|
1494
|
-
*
|
|
1495
|
-
*
|
|
1496
|
-
*
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1499
|
-
*
|
|
1502
|
+
* `tombstoneSeq` is the server's high-water seq at the time of
|
|
1503
|
+
* revocation (from the envelope). Stale in-flight WS frames with
|
|
1504
|
+
* `seq <= tombstoneSeq` are filtered locally; legitimate
|
|
1505
|
+
* re-grant + re-insert at higher seqs still land. Also fires a
|
|
1506
|
+
* catch-up pull on revocation so any frame with `seq >
|
|
1507
|
+
* tombstoneSeq` that arrives before the next legitimate event
|
|
1508
|
+
* gets reconciled against server truth.
|
|
1509
|
+
*
|
|
1510
|
+
* Uses `LocalStore.revokeRow` (not `reconcileRemove`) so the
|
|
1511
|
+
* tombstone is recorded even for CRDT-only consumers whose row
|
|
1512
|
+
* was never materialized into `tables`.
|
|
1500
1513
|
*
|
|
1501
1514
|
* Also notifies row-eviction listeners so external row-bound
|
|
1502
1515
|
* resources (LoroDoc registries, etc.) can unmount.
|
|
1503
1516
|
*/
|
|
1504
|
-
private handleRowRevocation(
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
);
|
|
1517
|
+
private handleRowRevocation(
|
|
1518
|
+
entity: string,
|
|
1519
|
+
rowId: string,
|
|
1520
|
+
tombstoneSeq: number,
|
|
1521
|
+
): void {
|
|
1522
|
+
const removed = this.store.revokeRow(entity, rowId, tombstoneSeq);
|
|
1510
1523
|
if (removed) {
|
|
1511
|
-
// Persist the deletion through the same pipe as a real Delete
|
|
1512
|
-
// event so on-disk replica + in-memory replica stay aligned.
|
|
1513
|
-
// For CRDT-only consumers `removed` is false (the row was
|
|
1514
|
-
// never materialized into `tables`), but the tombstone is
|
|
1515
|
-
// still recorded above so future replays are filtered.
|
|
1516
1524
|
if (this.persistence) {
|
|
1517
1525
|
void this.persistence.deleteRow(entity, rowId).catch(() => {
|
|
1518
1526
|
/* best-effort */
|
|
@@ -1523,6 +1531,11 @@ export class SyncEngine {
|
|
|
1523
1531
|
for (const listener of this.rowEvictionListeners) {
|
|
1524
1532
|
listener(entity, rowId);
|
|
1525
1533
|
}
|
|
1534
|
+
// Fire a catch-up pull so any in-flight frame with seq above
|
|
1535
|
+
// the revocation tombstone is resolved against server truth.
|
|
1536
|
+
// Fire-and-forget — pull is internally serialized so concurrent
|
|
1537
|
+
// triggers coalesce.
|
|
1538
|
+
void this.pull();
|
|
1526
1539
|
}
|
|
1527
1540
|
|
|
1528
1541
|
/**
|