canvu-react 0.4.80 → 0.4.81
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/realtime.cjs +86 -11
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +5 -0
- package/dist/realtime.d.ts +5 -0
- package/dist/realtime.js +86 -11
- package/dist/realtime.js.map +1 -1
- package/dist/realtimeNative.cjs +86 -11
- package/dist/realtimeNative.cjs.map +1 -1
- package/dist/realtimeNative.d.cts +5 -0
- package/dist/realtimeNative.d.ts +5 -0
- package/dist/realtimeNative.js +86 -11
- package/dist/realtimeNative.js.map +1 -1
- package/package.json +1 -1
|
@@ -19,6 +19,11 @@ type RealtimeDocumentSnapshot = {
|
|
|
19
19
|
readonly updatedAt: number;
|
|
20
20
|
readonly updatedByClientId?: string;
|
|
21
21
|
readonly persistedRevision?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Tombstones for items deleted in the room. Sent by servers that support
|
|
24
|
+
* explicit delete propagation; absent on older servers.
|
|
25
|
+
*/
|
|
26
|
+
readonly deletedItemIds?: readonly string[];
|
|
22
27
|
};
|
|
23
28
|
type RealtimeConnectionInfo = {
|
|
24
29
|
readonly state: RealtimeConnectionState;
|
package/dist/realtimeNative.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ type RealtimeDocumentSnapshot = {
|
|
|
19
19
|
readonly updatedAt: number;
|
|
20
20
|
readonly updatedByClientId?: string;
|
|
21
21
|
readonly persistedRevision?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Tombstones for items deleted in the room. Sent by servers that support
|
|
24
|
+
* explicit delete propagation; absent on older servers.
|
|
25
|
+
*/
|
|
26
|
+
readonly deletedItemIds?: readonly string[];
|
|
22
27
|
};
|
|
23
28
|
type RealtimeConnectionInfo = {
|
|
24
29
|
readonly state: RealtimeConnectionState;
|
package/dist/realtimeNative.js
CHANGED
|
@@ -82,12 +82,16 @@ function parseDocumentSnapshot(value) {
|
|
|
82
82
|
const updatedAt = getNumber(value.updatedAt);
|
|
83
83
|
const items = parseItems(value.items);
|
|
84
84
|
if (revision == null || updatedAt == null || items == null) return void 0;
|
|
85
|
+
const deletedItemIds = Array.isArray(value.deletedItemIds) ? value.deletedItemIds.filter(
|
|
86
|
+
(entry) => typeof entry === "string"
|
|
87
|
+
) : void 0;
|
|
85
88
|
return {
|
|
86
89
|
revision,
|
|
87
90
|
updatedAt,
|
|
88
91
|
items,
|
|
89
92
|
...getString(value.updatedByClientId) ? { updatedByClientId: getString(value.updatedByClientId) } : {},
|
|
90
|
-
...getNumber(value.persistedRevision) != null ? { persistedRevision: getNumber(value.persistedRevision) } : {}
|
|
93
|
+
...getNumber(value.persistedRevision) != null ? { persistedRevision: getNumber(value.persistedRevision) } : {},
|
|
94
|
+
...deletedItemIds ? { deletedItemIds } : {}
|
|
91
95
|
};
|
|
92
96
|
}
|
|
93
97
|
function parseRealtimeSessionPeer(value) {
|
|
@@ -324,6 +328,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
324
328
|
const realtimeEnabled = enabled && session != null;
|
|
325
329
|
const documentRevision = session?.document?.revision ?? null;
|
|
326
330
|
const documentItems = session?.document?.items;
|
|
331
|
+
const documentDeletedItemIds = session?.document?.deletedItemIds;
|
|
327
332
|
const documentUpdatedByClientId = session?.document?.updatedByClientId ?? null;
|
|
328
333
|
const connectionClientId = session?.connection.clientId ?? null;
|
|
329
334
|
const hasLocalOfflineDraft = session?.hasLocalOfflineDraft ?? false;
|
|
@@ -373,15 +378,22 @@ function useRealtimeCanvasDocument(options) {
|
|
|
373
378
|
if (cancelled) return;
|
|
374
379
|
if (inFlightRevisionRef.current !== documentRevision) return;
|
|
375
380
|
lastAppliedRevisionRef.current = documentRevision;
|
|
376
|
-
const
|
|
381
|
+
const rawLocalItems = latestItemsRef.current;
|
|
382
|
+
const deletedIds = new Set(documentDeletedItemIds ?? []);
|
|
383
|
+
const localItems = deletedIds.size > 0 ? rawLocalItems.filter((item) => {
|
|
384
|
+
const id = getSceneItemId(item);
|
|
385
|
+
return !id || !deletedIds.has(id);
|
|
386
|
+
}) : rawLocalItems;
|
|
377
387
|
const hasLocalItems = localItems.length > 0;
|
|
378
388
|
const hasPendingLocalChanges = hasLocalOfflineDraft || hasPendingDocumentSync || hasLocalChangeInFlightRef.current;
|
|
379
389
|
if (resolvedItems.length === 0 && (hasEverPropagatedItemsRef.current || hasLocalItems || hasPendingLocalChanges)) {
|
|
380
390
|
if (hasLocalItems) {
|
|
381
391
|
const normalizedLocalItems = normalizeItems ? normalizeItems(localItems) : [...localItems];
|
|
382
392
|
session.remoteAdapter.send?.(normalizedLocalItems);
|
|
393
|
+
return;
|
|
383
394
|
}
|
|
384
|
-
|
|
395
|
+
const emptinessExplainedByDeletes = rawLocalItems.length > 0 && localItems.length === 0;
|
|
396
|
+
if (!emptinessExplainedByDeletes) return;
|
|
385
397
|
}
|
|
386
398
|
if (shouldPreserveLocalRealtimeItems({
|
|
387
399
|
localItems,
|
|
@@ -409,6 +421,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
409
421
|
}, [
|
|
410
422
|
applyIncomingItems,
|
|
411
423
|
connectionClientId,
|
|
424
|
+
documentDeletedItemIds,
|
|
412
425
|
documentItems,
|
|
413
426
|
documentRevision,
|
|
414
427
|
documentUpdatedByClientId,
|
|
@@ -585,6 +598,16 @@ var CLIENT_ONLY_IMAGE_KEYS2 = /* @__PURE__ */ new Set([
|
|
|
585
598
|
"imageThumbnailHref"
|
|
586
599
|
]);
|
|
587
600
|
var SERVER_ADD_RACE_WINDOW_MS = 2e3;
|
|
601
|
+
var LOCAL_DELETE_TOMBSTONE_TTL_MS = 1e4;
|
|
602
|
+
function isLocalDeleteTombstoneActive(board, id, now) {
|
|
603
|
+
const deletedAt = board.locallyDeletedItemIds.get(id);
|
|
604
|
+
if (deletedAt == null) return false;
|
|
605
|
+
if (now - deletedAt >= LOCAL_DELETE_TOMBSTONE_TTL_MS) {
|
|
606
|
+
board.locallyDeletedItemIds.delete(id);
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
588
611
|
function createYjsBoardDoc() {
|
|
589
612
|
const doc = new Y.Doc();
|
|
590
613
|
const yItems = doc.getArray(ITEMS_KEY);
|
|
@@ -593,9 +616,22 @@ function createYjsBoardDoc() {
|
|
|
593
616
|
yItems,
|
|
594
617
|
lastServerConfirmedIds: /* @__PURE__ */ new Set(),
|
|
595
618
|
lastServerConfirmedItemSerializations: /* @__PURE__ */ new Map(),
|
|
596
|
-
serverItemSeenAt: /* @__PURE__ */ new Map()
|
|
619
|
+
serverItemSeenAt: /* @__PURE__ */ new Map(),
|
|
620
|
+
serverDeletedItemIds: /* @__PURE__ */ new Set(),
|
|
621
|
+
locallyDeletedItemIds: /* @__PURE__ */ new Map(),
|
|
622
|
+
consumerSeenItemIds: /* @__PURE__ */ new Set()
|
|
597
623
|
};
|
|
598
624
|
}
|
|
625
|
+
function getRealtimeDeletedItemIds(board) {
|
|
626
|
+
const now = Date.now();
|
|
627
|
+
const result = new Set(board.serverDeletedItemIds);
|
|
628
|
+
for (const id of Array.from(board.locallyDeletedItemIds.keys())) {
|
|
629
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
630
|
+
result.add(id);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
599
635
|
function getItemId(item) {
|
|
600
636
|
if (item instanceof Y.Map) {
|
|
601
637
|
const id2 = item.get("id");
|
|
@@ -724,9 +760,11 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
724
760
|
const toDelete = [];
|
|
725
761
|
for (const [id, entry] of currentIndex) {
|
|
726
762
|
if (nextIds.has(id)) continue;
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
763
|
+
if (!board.consumerSeenItemIds.has(id)) {
|
|
764
|
+
const serverSeenAt = board.serverItemSeenAt.get(id);
|
|
765
|
+
if (serverSeenAt != null && now - serverSeenAt < SERVER_ADD_RACE_WINDOW_MS) {
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
730
768
|
}
|
|
731
769
|
toDelete.push({ id, index: entry.index });
|
|
732
770
|
}
|
|
@@ -734,6 +772,7 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
734
772
|
for (const { id, index } of toDelete) {
|
|
735
773
|
board.yItems.delete(index, 1);
|
|
736
774
|
board.serverItemSeenAt.delete(id);
|
|
775
|
+
board.locallyDeletedItemIds.set(id, now);
|
|
737
776
|
removedIds.push(id);
|
|
738
777
|
}
|
|
739
778
|
const refreshedIndex = indexYItemsById(board.yItems);
|
|
@@ -742,6 +781,9 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
742
781
|
if (!item) continue;
|
|
743
782
|
const id = getItemId(item);
|
|
744
783
|
if (!id) continue;
|
|
784
|
+
board.consumerSeenItemIds.add(id);
|
|
785
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
786
|
+
board.locallyDeletedItemIds.delete(id);
|
|
745
787
|
const existing = refreshedIndex.get(id);
|
|
746
788
|
if (existing) {
|
|
747
789
|
updateYMapInPlace(existing.yMap, item);
|
|
@@ -756,12 +798,33 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
756
798
|
return { addedIds, removedIds };
|
|
757
799
|
}
|
|
758
800
|
function applyServerSnapshotToYDoc(board, options) {
|
|
759
|
-
const { items: snapshotItems, origin } = options;
|
|
801
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
760
802
|
const now = Date.now();
|
|
803
|
+
const snapshotItemIds = /* @__PURE__ */ new Set();
|
|
804
|
+
for (const item of snapshotItems) {
|
|
805
|
+
const id = getItemId(item);
|
|
806
|
+
if (id) snapshotItemIds.add(id);
|
|
807
|
+
}
|
|
761
808
|
board.doc.transact(() => {
|
|
809
|
+
if (deletedItemIds) {
|
|
810
|
+
for (const id of deletedItemIds) {
|
|
811
|
+
if (snapshotItemIds.has(id)) continue;
|
|
812
|
+
board.serverDeletedItemIds.add(id);
|
|
813
|
+
board.locallyDeletedItemIds.delete(id);
|
|
814
|
+
board.serverItemSeenAt.delete(id);
|
|
815
|
+
const existing = indexYItemsById(board.yItems).get(id);
|
|
816
|
+
if (existing) {
|
|
817
|
+
board.yItems.delete(existing.index, 1);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
762
821
|
for (const item of snapshotItems) {
|
|
763
822
|
const id = getItemId(item);
|
|
764
823
|
if (!id) continue;
|
|
824
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
825
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
765
828
|
const existing = indexYItemsById(board.yItems).get(id);
|
|
766
829
|
if (existing) {
|
|
767
830
|
const currentSerialized = serializeItem(existing.yMap);
|
|
@@ -792,7 +855,7 @@ function applyServerSnapshotToYDoc(board, options) {
|
|
|
792
855
|
}
|
|
793
856
|
}
|
|
794
857
|
function replaceYDocWithSnapshot(board, options) {
|
|
795
|
-
const { items: snapshotItems, origin } = options;
|
|
858
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
796
859
|
const now = Date.now();
|
|
797
860
|
board.doc.transact(() => {
|
|
798
861
|
if (board.yItems.length > 0) {
|
|
@@ -805,6 +868,12 @@ function replaceYDocWithSnapshot(board, options) {
|
|
|
805
868
|
board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
|
|
806
869
|
board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
|
|
807
870
|
board.serverItemSeenAt = /* @__PURE__ */ new Map();
|
|
871
|
+
board.locallyDeletedItemIds = /* @__PURE__ */ new Map();
|
|
872
|
+
if (deletedItemIds) {
|
|
873
|
+
for (const id of deletedItemIds) {
|
|
874
|
+
board.serverDeletedItemIds.add(id);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
808
877
|
for (const item of snapshotItems) {
|
|
809
878
|
const id = getItemId(item);
|
|
810
879
|
if (id) {
|
|
@@ -1251,7 +1320,7 @@ function useRealtimeSession(options) {
|
|
|
1251
1320
|
const applyDocument = useCallback(
|
|
1252
1321
|
(snapshot, options2) => {
|
|
1253
1322
|
const currentSnapshot = latestDocumentRef.current;
|
|
1254
|
-
if (currentSnapshot && currentSnapshot.revision === snapshot.revision && currentSnapshot.updatedByClientId === snapshot.updatedByClientId && currentSnapshot.persistedRevision === snapshot.persistedRevision && sameSerializedItems(currentSnapshot.items, snapshot.items)) {
|
|
1323
|
+
if (currentSnapshot && currentSnapshot.revision === snapshot.revision && currentSnapshot.updatedByClientId === snapshot.updatedByClientId && currentSnapshot.persistedRevision === snapshot.persistedRevision && (currentSnapshot.deletedItemIds?.length ?? 0) === (snapshot.deletedItemIds?.length ?? 0) && sameSerializedItems(currentSnapshot.items, snapshot.items)) {
|
|
1255
1324
|
return;
|
|
1256
1325
|
}
|
|
1257
1326
|
const board = boardRef.current;
|
|
@@ -1270,11 +1339,13 @@ function useRealtimeSession(options) {
|
|
|
1270
1339
|
if (options2?.replace) {
|
|
1271
1340
|
replaceYDocWithSnapshot(board, {
|
|
1272
1341
|
items: snapshot.items,
|
|
1342
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
1273
1343
|
origin: ORIGIN_REMOTE
|
|
1274
1344
|
});
|
|
1275
1345
|
} else {
|
|
1276
1346
|
applyServerSnapshotToYDoc(board, {
|
|
1277
1347
|
items: snapshot.items,
|
|
1348
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
1278
1349
|
origin: ORIGIN_REMOTE
|
|
1279
1350
|
});
|
|
1280
1351
|
}
|
|
@@ -1282,7 +1353,11 @@ function useRealtimeSession(options) {
|
|
|
1282
1353
|
const mergedItems = board ? readVectorItems(board.yItems) : snapshot.items;
|
|
1283
1354
|
const mergedSnapshot = {
|
|
1284
1355
|
...snapshot,
|
|
1285
|
-
items: mergedItems
|
|
1356
|
+
items: mergedItems,
|
|
1357
|
+
// Expose the accumulated tombstones (server-confirmed + pending
|
|
1358
|
+
// local deletes) so document consumers can prune stale local
|
|
1359
|
+
// views instead of re-sending deleted items.
|
|
1360
|
+
...board ? { deletedItemIds: Array.from(getRealtimeDeletedItemIds(board)) } : {}
|
|
1286
1361
|
};
|
|
1287
1362
|
currentRevisionRef.current = snapshot.revision;
|
|
1288
1363
|
latestDocumentRef.current = mergedSnapshot;
|