@pylonsync/sync 0.3.195 → 0.3.196
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 +10 -7
- package/src/local-store.ts +23 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1491,18 +1491,18 @@ export class SyncEngine {
|
|
|
1491
1491
|
* Drop a row from the local replica because the server signaled
|
|
1492
1492
|
* that the current subscriber's read policy was revoked for it.
|
|
1493
1493
|
*
|
|
1494
|
-
* Calls `LocalStore.
|
|
1495
|
-
*
|
|
1496
|
-
*
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1499
|
-
*
|
|
1494
|
+
* Calls `LocalStore.revokeRow(entity, id, cursor.last_seq)` —
|
|
1495
|
+
* NOT `reconcileRemove` — because the latter early-returns when
|
|
1496
|
+
* the row isn't in memory (common for CRDT-only consumers using
|
|
1497
|
+
* `useLoroDoc` without a JSON row). `revokeRow` always records
|
|
1498
|
+
* the tombstone so a stale insert/update arriving after the
|
|
1499
|
+
* revocation can't resurrect the row.
|
|
1500
1500
|
*
|
|
1501
1501
|
* Also notifies row-eviction listeners so external row-bound
|
|
1502
1502
|
* resources (LoroDoc registries, etc.) can unmount.
|
|
1503
1503
|
*/
|
|
1504
1504
|
private handleRowRevocation(entity: string, rowId: string): void {
|
|
1505
|
-
const removed = this.store.
|
|
1505
|
+
const removed = this.store.revokeRow(
|
|
1506
1506
|
entity,
|
|
1507
1507
|
rowId,
|
|
1508
1508
|
this.cursor.last_seq,
|
|
@@ -1510,6 +1510,9 @@ export class SyncEngine {
|
|
|
1510
1510
|
if (removed) {
|
|
1511
1511
|
// Persist the deletion through the same pipe as a real Delete
|
|
1512
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.
|
|
1513
1516
|
if (this.persistence) {
|
|
1514
1517
|
void this.persistence.deleteRow(entity, rowId).catch(() => {
|
|
1515
1518
|
/* best-effort */
|
package/src/local-store.ts
CHANGED
|
@@ -80,6 +80,29 @@ export class LocalStore {
|
|
|
80
80
|
return true;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Per-subscriber row revocation: the server signaled that the
|
|
85
|
+
* current client lost read access to a specific row. ALWAYS
|
|
86
|
+
* records the tombstone (even when the row isn't in memory), so
|
|
87
|
+
* a stale insert/update event that arrives after the revocation
|
|
88
|
+
* can't resurrect the row.
|
|
89
|
+
*
|
|
90
|
+
* `reconcileRemove` returns early when the row is absent — that's
|
|
91
|
+
* the right behavior for "did anything change?" reconcile passes,
|
|
92
|
+
* but wrong here: a CRDT-only consumer using `useLoroDoc` without
|
|
93
|
+
* a JSON row never had the row in `tables` to begin with, and
|
|
94
|
+
* without the tombstone any future server-issued insert
|
|
95
|
+
* (legitimate re-grant or a slow stale frame) would land.
|
|
96
|
+
*
|
|
97
|
+
* Returns true if the row was present + removed; the tombstone
|
|
98
|
+
* is recorded regardless.
|
|
99
|
+
*/
|
|
100
|
+
revokeRow(entity: string, id: string, tombstoneSeq: number): boolean {
|
|
101
|
+
const removed = this.tables.get(entity)?.delete(id) ?? false;
|
|
102
|
+
this.recordTombstone(entity, id, tombstoneSeq);
|
|
103
|
+
return removed;
|
|
104
|
+
}
|
|
105
|
+
|
|
83
106
|
private isTombstoned(entity: string, id: string, at_seq?: number): boolean {
|
|
84
107
|
if (this.optimisticTombstones.get(entity)?.has(id)) return true;
|
|
85
108
|
const tombSeq = this.tombstones.get(entity)?.get(id);
|