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
package/dist/realtimeNative.cjs
CHANGED
|
@@ -104,12 +104,16 @@ function parseDocumentSnapshot(value) {
|
|
|
104
104
|
const updatedAt = getNumber(value.updatedAt);
|
|
105
105
|
const items = parseItems(value.items);
|
|
106
106
|
if (revision == null || updatedAt == null || items == null) return void 0;
|
|
107
|
+
const deletedItemIds = Array.isArray(value.deletedItemIds) ? value.deletedItemIds.filter(
|
|
108
|
+
(entry) => typeof entry === "string"
|
|
109
|
+
) : void 0;
|
|
107
110
|
return {
|
|
108
111
|
revision,
|
|
109
112
|
updatedAt,
|
|
110
113
|
items,
|
|
111
114
|
...getString(value.updatedByClientId) ? { updatedByClientId: getString(value.updatedByClientId) } : {},
|
|
112
|
-
...getNumber(value.persistedRevision) != null ? { persistedRevision: getNumber(value.persistedRevision) } : {}
|
|
115
|
+
...getNumber(value.persistedRevision) != null ? { persistedRevision: getNumber(value.persistedRevision) } : {},
|
|
116
|
+
...deletedItemIds ? { deletedItemIds } : {}
|
|
113
117
|
};
|
|
114
118
|
}
|
|
115
119
|
function parseRealtimeSessionPeer(value) {
|
|
@@ -346,6 +350,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
346
350
|
const realtimeEnabled = enabled && session != null;
|
|
347
351
|
const documentRevision = session?.document?.revision ?? null;
|
|
348
352
|
const documentItems = session?.document?.items;
|
|
353
|
+
const documentDeletedItemIds = session?.document?.deletedItemIds;
|
|
349
354
|
const documentUpdatedByClientId = session?.document?.updatedByClientId ?? null;
|
|
350
355
|
const connectionClientId = session?.connection.clientId ?? null;
|
|
351
356
|
const hasLocalOfflineDraft = session?.hasLocalOfflineDraft ?? false;
|
|
@@ -395,15 +400,22 @@ function useRealtimeCanvasDocument(options) {
|
|
|
395
400
|
if (cancelled) return;
|
|
396
401
|
if (inFlightRevisionRef.current !== documentRevision) return;
|
|
397
402
|
lastAppliedRevisionRef.current = documentRevision;
|
|
398
|
-
const
|
|
403
|
+
const rawLocalItems = latestItemsRef.current;
|
|
404
|
+
const deletedIds = new Set(documentDeletedItemIds ?? []);
|
|
405
|
+
const localItems = deletedIds.size > 0 ? rawLocalItems.filter((item) => {
|
|
406
|
+
const id = getSceneItemId(item);
|
|
407
|
+
return !id || !deletedIds.has(id);
|
|
408
|
+
}) : rawLocalItems;
|
|
399
409
|
const hasLocalItems = localItems.length > 0;
|
|
400
410
|
const hasPendingLocalChanges = hasLocalOfflineDraft || hasPendingDocumentSync || hasLocalChangeInFlightRef.current;
|
|
401
411
|
if (resolvedItems.length === 0 && (hasEverPropagatedItemsRef.current || hasLocalItems || hasPendingLocalChanges)) {
|
|
402
412
|
if (hasLocalItems) {
|
|
403
413
|
const normalizedLocalItems = normalizeItems ? normalizeItems(localItems) : [...localItems];
|
|
404
414
|
session.remoteAdapter.send?.(normalizedLocalItems);
|
|
415
|
+
return;
|
|
405
416
|
}
|
|
406
|
-
|
|
417
|
+
const emptinessExplainedByDeletes = rawLocalItems.length > 0 && localItems.length === 0;
|
|
418
|
+
if (!emptinessExplainedByDeletes) return;
|
|
407
419
|
}
|
|
408
420
|
if (shouldPreserveLocalRealtimeItems({
|
|
409
421
|
localItems,
|
|
@@ -431,6 +443,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
431
443
|
}, [
|
|
432
444
|
applyIncomingItems,
|
|
433
445
|
connectionClientId,
|
|
446
|
+
documentDeletedItemIds,
|
|
434
447
|
documentItems,
|
|
435
448
|
documentRevision,
|
|
436
449
|
documentUpdatedByClientId,
|
|
@@ -607,6 +620,16 @@ var CLIENT_ONLY_IMAGE_KEYS2 = /* @__PURE__ */ new Set([
|
|
|
607
620
|
"imageThumbnailHref"
|
|
608
621
|
]);
|
|
609
622
|
var SERVER_ADD_RACE_WINDOW_MS = 2e3;
|
|
623
|
+
var LOCAL_DELETE_TOMBSTONE_TTL_MS = 1e4;
|
|
624
|
+
function isLocalDeleteTombstoneActive(board, id, now) {
|
|
625
|
+
const deletedAt = board.locallyDeletedItemIds.get(id);
|
|
626
|
+
if (deletedAt == null) return false;
|
|
627
|
+
if (now - deletedAt >= LOCAL_DELETE_TOMBSTONE_TTL_MS) {
|
|
628
|
+
board.locallyDeletedItemIds.delete(id);
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
610
633
|
function createYjsBoardDoc() {
|
|
611
634
|
const doc = new Y__namespace.Doc();
|
|
612
635
|
const yItems = doc.getArray(ITEMS_KEY);
|
|
@@ -615,9 +638,22 @@ function createYjsBoardDoc() {
|
|
|
615
638
|
yItems,
|
|
616
639
|
lastServerConfirmedIds: /* @__PURE__ */ new Set(),
|
|
617
640
|
lastServerConfirmedItemSerializations: /* @__PURE__ */ new Map(),
|
|
618
|
-
serverItemSeenAt: /* @__PURE__ */ new Map()
|
|
641
|
+
serverItemSeenAt: /* @__PURE__ */ new Map(),
|
|
642
|
+
serverDeletedItemIds: /* @__PURE__ */ new Set(),
|
|
643
|
+
locallyDeletedItemIds: /* @__PURE__ */ new Map(),
|
|
644
|
+
consumerSeenItemIds: /* @__PURE__ */ new Set()
|
|
619
645
|
};
|
|
620
646
|
}
|
|
647
|
+
function getRealtimeDeletedItemIds(board) {
|
|
648
|
+
const now = Date.now();
|
|
649
|
+
const result = new Set(board.serverDeletedItemIds);
|
|
650
|
+
for (const id of Array.from(board.locallyDeletedItemIds.keys())) {
|
|
651
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
652
|
+
result.add(id);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
621
657
|
function getItemId(item) {
|
|
622
658
|
if (item instanceof Y__namespace.Map) {
|
|
623
659
|
const id2 = item.get("id");
|
|
@@ -746,9 +782,11 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
746
782
|
const toDelete = [];
|
|
747
783
|
for (const [id, entry] of currentIndex) {
|
|
748
784
|
if (nextIds.has(id)) continue;
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
785
|
+
if (!board.consumerSeenItemIds.has(id)) {
|
|
786
|
+
const serverSeenAt = board.serverItemSeenAt.get(id);
|
|
787
|
+
if (serverSeenAt != null && now - serverSeenAt < SERVER_ADD_RACE_WINDOW_MS) {
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
752
790
|
}
|
|
753
791
|
toDelete.push({ id, index: entry.index });
|
|
754
792
|
}
|
|
@@ -756,6 +794,7 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
756
794
|
for (const { id, index } of toDelete) {
|
|
757
795
|
board.yItems.delete(index, 1);
|
|
758
796
|
board.serverItemSeenAt.delete(id);
|
|
797
|
+
board.locallyDeletedItemIds.set(id, now);
|
|
759
798
|
removedIds.push(id);
|
|
760
799
|
}
|
|
761
800
|
const refreshedIndex = indexYItemsById(board.yItems);
|
|
@@ -764,6 +803,9 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
764
803
|
if (!item) continue;
|
|
765
804
|
const id = getItemId(item);
|
|
766
805
|
if (!id) continue;
|
|
806
|
+
board.consumerSeenItemIds.add(id);
|
|
807
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
808
|
+
board.locallyDeletedItemIds.delete(id);
|
|
767
809
|
const existing = refreshedIndex.get(id);
|
|
768
810
|
if (existing) {
|
|
769
811
|
updateYMapInPlace(existing.yMap, item);
|
|
@@ -778,12 +820,33 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
778
820
|
return { addedIds, removedIds };
|
|
779
821
|
}
|
|
780
822
|
function applyServerSnapshotToYDoc(board, options) {
|
|
781
|
-
const { items: snapshotItems, origin } = options;
|
|
823
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
782
824
|
const now = Date.now();
|
|
825
|
+
const snapshotItemIds = /* @__PURE__ */ new Set();
|
|
826
|
+
for (const item of snapshotItems) {
|
|
827
|
+
const id = getItemId(item);
|
|
828
|
+
if (id) snapshotItemIds.add(id);
|
|
829
|
+
}
|
|
783
830
|
board.doc.transact(() => {
|
|
831
|
+
if (deletedItemIds) {
|
|
832
|
+
for (const id of deletedItemIds) {
|
|
833
|
+
if (snapshotItemIds.has(id)) continue;
|
|
834
|
+
board.serverDeletedItemIds.add(id);
|
|
835
|
+
board.locallyDeletedItemIds.delete(id);
|
|
836
|
+
board.serverItemSeenAt.delete(id);
|
|
837
|
+
const existing = indexYItemsById(board.yItems).get(id);
|
|
838
|
+
if (existing) {
|
|
839
|
+
board.yItems.delete(existing.index, 1);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
784
843
|
for (const item of snapshotItems) {
|
|
785
844
|
const id = getItemId(item);
|
|
786
845
|
if (!id) continue;
|
|
846
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
847
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
787
850
|
const existing = indexYItemsById(board.yItems).get(id);
|
|
788
851
|
if (existing) {
|
|
789
852
|
const currentSerialized = serializeItem(existing.yMap);
|
|
@@ -814,7 +877,7 @@ function applyServerSnapshotToYDoc(board, options) {
|
|
|
814
877
|
}
|
|
815
878
|
}
|
|
816
879
|
function replaceYDocWithSnapshot(board, options) {
|
|
817
|
-
const { items: snapshotItems, origin } = options;
|
|
880
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
818
881
|
const now = Date.now();
|
|
819
882
|
board.doc.transact(() => {
|
|
820
883
|
if (board.yItems.length > 0) {
|
|
@@ -827,6 +890,12 @@ function replaceYDocWithSnapshot(board, options) {
|
|
|
827
890
|
board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
|
|
828
891
|
board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
|
|
829
892
|
board.serverItemSeenAt = /* @__PURE__ */ new Map();
|
|
893
|
+
board.locallyDeletedItemIds = /* @__PURE__ */ new Map();
|
|
894
|
+
if (deletedItemIds) {
|
|
895
|
+
for (const id of deletedItemIds) {
|
|
896
|
+
board.serverDeletedItemIds.add(id);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
830
899
|
for (const item of snapshotItems) {
|
|
831
900
|
const id = getItemId(item);
|
|
832
901
|
if (id) {
|
|
@@ -1273,7 +1342,7 @@ function useRealtimeSession(options) {
|
|
|
1273
1342
|
const applyDocument = react.useCallback(
|
|
1274
1343
|
(snapshot, options2) => {
|
|
1275
1344
|
const currentSnapshot = latestDocumentRef.current;
|
|
1276
|
-
if (currentSnapshot && currentSnapshot.revision === snapshot.revision && currentSnapshot.updatedByClientId === snapshot.updatedByClientId && currentSnapshot.persistedRevision === snapshot.persistedRevision && sameSerializedItems(currentSnapshot.items, snapshot.items)) {
|
|
1345
|
+
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)) {
|
|
1277
1346
|
return;
|
|
1278
1347
|
}
|
|
1279
1348
|
const board = boardRef.current;
|
|
@@ -1292,11 +1361,13 @@ function useRealtimeSession(options) {
|
|
|
1292
1361
|
if (options2?.replace) {
|
|
1293
1362
|
replaceYDocWithSnapshot(board, {
|
|
1294
1363
|
items: snapshot.items,
|
|
1364
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
1295
1365
|
origin: ORIGIN_REMOTE
|
|
1296
1366
|
});
|
|
1297
1367
|
} else {
|
|
1298
1368
|
applyServerSnapshotToYDoc(board, {
|
|
1299
1369
|
items: snapshot.items,
|
|
1370
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
1300
1371
|
origin: ORIGIN_REMOTE
|
|
1301
1372
|
});
|
|
1302
1373
|
}
|
|
@@ -1304,7 +1375,11 @@ function useRealtimeSession(options) {
|
|
|
1304
1375
|
const mergedItems = board ? readVectorItems(board.yItems) : snapshot.items;
|
|
1305
1376
|
const mergedSnapshot = {
|
|
1306
1377
|
...snapshot,
|
|
1307
|
-
items: mergedItems
|
|
1378
|
+
items: mergedItems,
|
|
1379
|
+
// Expose the accumulated tombstones (server-confirmed + pending
|
|
1380
|
+
// local deletes) so document consumers can prune stale local
|
|
1381
|
+
// views instead of re-sending deleted items.
|
|
1382
|
+
...board ? { deletedItemIds: Array.from(getRealtimeDeletedItemIds(board)) } : {}
|
|
1308
1383
|
};
|
|
1309
1384
|
currentRevisionRef.current = snapshot.revision;
|
|
1310
1385
|
latestDocumentRef.current = mergedSnapshot;
|