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/realtime.d.cts
CHANGED
|
@@ -47,6 +47,11 @@ type RealtimeDocumentSnapshot = {
|
|
|
47
47
|
readonly updatedAt: number;
|
|
48
48
|
readonly updatedByClientId?: string;
|
|
49
49
|
readonly persistedRevision?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Tombstones for items deleted in the room. Sent by servers that support
|
|
52
|
+
* explicit delete propagation; absent on older servers.
|
|
53
|
+
*/
|
|
54
|
+
readonly deletedItemIds?: readonly string[];
|
|
50
55
|
};
|
|
51
56
|
type RealtimeConnectionInfo = {
|
|
52
57
|
readonly state: RealtimeConnectionState;
|
package/dist/realtime.d.ts
CHANGED
|
@@ -47,6 +47,11 @@ type RealtimeDocumentSnapshot = {
|
|
|
47
47
|
readonly updatedAt: number;
|
|
48
48
|
readonly updatedByClientId?: string;
|
|
49
49
|
readonly persistedRevision?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Tombstones for items deleted in the room. Sent by servers that support
|
|
52
|
+
* explicit delete propagation; absent on older servers.
|
|
53
|
+
*/
|
|
54
|
+
readonly deletedItemIds?: readonly string[];
|
|
50
55
|
};
|
|
51
56
|
type RealtimeConnectionInfo = {
|
|
52
57
|
readonly state: RealtimeConnectionState;
|
package/dist/realtime.js
CHANGED
|
@@ -501,12 +501,16 @@ function parseDocumentSnapshot(value) {
|
|
|
501
501
|
const updatedAt = getNumber(value.updatedAt);
|
|
502
502
|
const items = parseItems(value.items);
|
|
503
503
|
if (revision == null || updatedAt == null || items == null) return void 0;
|
|
504
|
+
const deletedItemIds = Array.isArray(value.deletedItemIds) ? value.deletedItemIds.filter(
|
|
505
|
+
(entry) => typeof entry === "string"
|
|
506
|
+
) : void 0;
|
|
504
507
|
return {
|
|
505
508
|
revision,
|
|
506
509
|
updatedAt,
|
|
507
510
|
items,
|
|
508
511
|
...getString(value.updatedByClientId) ? { updatedByClientId: getString(value.updatedByClientId) } : {},
|
|
509
|
-
...getNumber(value.persistedRevision) != null ? { persistedRevision: getNumber(value.persistedRevision) } : {}
|
|
512
|
+
...getNumber(value.persistedRevision) != null ? { persistedRevision: getNumber(value.persistedRevision) } : {},
|
|
513
|
+
...deletedItemIds ? { deletedItemIds } : {}
|
|
510
514
|
};
|
|
511
515
|
}
|
|
512
516
|
function parseRealtimeSessionPeer(value) {
|
|
@@ -2004,6 +2008,16 @@ var CLIENT_ONLY_IMAGE_KEYS = /* @__PURE__ */ new Set([
|
|
|
2004
2008
|
"imageThumbnailHref"
|
|
2005
2009
|
]);
|
|
2006
2010
|
var SERVER_ADD_RACE_WINDOW_MS = 2e3;
|
|
2011
|
+
var LOCAL_DELETE_TOMBSTONE_TTL_MS = 1e4;
|
|
2012
|
+
function isLocalDeleteTombstoneActive(board, id, now) {
|
|
2013
|
+
const deletedAt = board.locallyDeletedItemIds.get(id);
|
|
2014
|
+
if (deletedAt == null) return false;
|
|
2015
|
+
if (now - deletedAt >= LOCAL_DELETE_TOMBSTONE_TTL_MS) {
|
|
2016
|
+
board.locallyDeletedItemIds.delete(id);
|
|
2017
|
+
return false;
|
|
2018
|
+
}
|
|
2019
|
+
return true;
|
|
2020
|
+
}
|
|
2007
2021
|
function createYjsBoardDoc() {
|
|
2008
2022
|
const doc = new Y.Doc();
|
|
2009
2023
|
const yItems = doc.getArray(ITEMS_KEY);
|
|
@@ -2012,9 +2026,22 @@ function createYjsBoardDoc() {
|
|
|
2012
2026
|
yItems,
|
|
2013
2027
|
lastServerConfirmedIds: /* @__PURE__ */ new Set(),
|
|
2014
2028
|
lastServerConfirmedItemSerializations: /* @__PURE__ */ new Map(),
|
|
2015
|
-
serverItemSeenAt: /* @__PURE__ */ new Map()
|
|
2029
|
+
serverItemSeenAt: /* @__PURE__ */ new Map(),
|
|
2030
|
+
serverDeletedItemIds: /* @__PURE__ */ new Set(),
|
|
2031
|
+
locallyDeletedItemIds: /* @__PURE__ */ new Map(),
|
|
2032
|
+
consumerSeenItemIds: /* @__PURE__ */ new Set()
|
|
2016
2033
|
};
|
|
2017
2034
|
}
|
|
2035
|
+
function getRealtimeDeletedItemIds(board) {
|
|
2036
|
+
const now = Date.now();
|
|
2037
|
+
const result = new Set(board.serverDeletedItemIds);
|
|
2038
|
+
for (const id of Array.from(board.locallyDeletedItemIds.keys())) {
|
|
2039
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
2040
|
+
result.add(id);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
return result;
|
|
2044
|
+
}
|
|
2018
2045
|
function getItemId(item) {
|
|
2019
2046
|
if (item instanceof Y.Map) {
|
|
2020
2047
|
const id2 = item.get("id");
|
|
@@ -2143,9 +2170,11 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2143
2170
|
const toDelete = [];
|
|
2144
2171
|
for (const [id, entry] of currentIndex) {
|
|
2145
2172
|
if (nextIds.has(id)) continue;
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2173
|
+
if (!board.consumerSeenItemIds.has(id)) {
|
|
2174
|
+
const serverSeenAt = board.serverItemSeenAt.get(id);
|
|
2175
|
+
if (serverSeenAt != null && now - serverSeenAt < SERVER_ADD_RACE_WINDOW_MS) {
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2149
2178
|
}
|
|
2150
2179
|
toDelete.push({ id, index: entry.index });
|
|
2151
2180
|
}
|
|
@@ -2153,6 +2182,7 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2153
2182
|
for (const { id, index } of toDelete) {
|
|
2154
2183
|
board.yItems.delete(index, 1);
|
|
2155
2184
|
board.serverItemSeenAt.delete(id);
|
|
2185
|
+
board.locallyDeletedItemIds.set(id, now);
|
|
2156
2186
|
removedIds.push(id);
|
|
2157
2187
|
}
|
|
2158
2188
|
const refreshedIndex = indexYItemsById(board.yItems);
|
|
@@ -2161,6 +2191,9 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2161
2191
|
if (!item) continue;
|
|
2162
2192
|
const id = getItemId(item);
|
|
2163
2193
|
if (!id) continue;
|
|
2194
|
+
board.consumerSeenItemIds.add(id);
|
|
2195
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
2196
|
+
board.locallyDeletedItemIds.delete(id);
|
|
2164
2197
|
const existing = refreshedIndex.get(id);
|
|
2165
2198
|
if (existing) {
|
|
2166
2199
|
updateYMapInPlace(existing.yMap, item);
|
|
@@ -2175,12 +2208,33 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2175
2208
|
return { addedIds, removedIds };
|
|
2176
2209
|
}
|
|
2177
2210
|
function applyServerSnapshotToYDoc(board, options) {
|
|
2178
|
-
const { items: snapshotItems, origin } = options;
|
|
2211
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
2179
2212
|
const now = Date.now();
|
|
2213
|
+
const snapshotItemIds = /* @__PURE__ */ new Set();
|
|
2214
|
+
for (const item of snapshotItems) {
|
|
2215
|
+
const id = getItemId(item);
|
|
2216
|
+
if (id) snapshotItemIds.add(id);
|
|
2217
|
+
}
|
|
2180
2218
|
board.doc.transact(() => {
|
|
2219
|
+
if (deletedItemIds) {
|
|
2220
|
+
for (const id of deletedItemIds) {
|
|
2221
|
+
if (snapshotItemIds.has(id)) continue;
|
|
2222
|
+
board.serverDeletedItemIds.add(id);
|
|
2223
|
+
board.locallyDeletedItemIds.delete(id);
|
|
2224
|
+
board.serverItemSeenAt.delete(id);
|
|
2225
|
+
const existing = indexYItemsById(board.yItems).get(id);
|
|
2226
|
+
if (existing) {
|
|
2227
|
+
board.yItems.delete(existing.index, 1);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2181
2231
|
for (const item of snapshotItems) {
|
|
2182
2232
|
const id = getItemId(item);
|
|
2183
2233
|
if (!id) continue;
|
|
2234
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
2235
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
2236
|
+
continue;
|
|
2237
|
+
}
|
|
2184
2238
|
const existing = indexYItemsById(board.yItems).get(id);
|
|
2185
2239
|
if (existing) {
|
|
2186
2240
|
const currentSerialized = serializeItem(existing.yMap);
|
|
@@ -2211,7 +2265,7 @@ function applyServerSnapshotToYDoc(board, options) {
|
|
|
2211
2265
|
}
|
|
2212
2266
|
}
|
|
2213
2267
|
function replaceYDocWithSnapshot(board, options) {
|
|
2214
|
-
const { items: snapshotItems, origin } = options;
|
|
2268
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
2215
2269
|
const now = Date.now();
|
|
2216
2270
|
board.doc.transact(() => {
|
|
2217
2271
|
if (board.yItems.length > 0) {
|
|
@@ -2224,6 +2278,12 @@ function replaceYDocWithSnapshot(board, options) {
|
|
|
2224
2278
|
board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
|
|
2225
2279
|
board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
|
|
2226
2280
|
board.serverItemSeenAt = /* @__PURE__ */ new Map();
|
|
2281
|
+
board.locallyDeletedItemIds = /* @__PURE__ */ new Map();
|
|
2282
|
+
if (deletedItemIds) {
|
|
2283
|
+
for (const id of deletedItemIds) {
|
|
2284
|
+
board.serverDeletedItemIds.add(id);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2227
2287
|
for (const item of snapshotItems) {
|
|
2228
2288
|
const id = getItemId(item);
|
|
2229
2289
|
if (id) {
|
|
@@ -2586,7 +2646,7 @@ function useRealtimeSession(options) {
|
|
|
2586
2646
|
const applyDocument = useCallback(
|
|
2587
2647
|
(snapshot, options2) => {
|
|
2588
2648
|
const currentSnapshot = latestDocumentRef.current;
|
|
2589
|
-
if (currentSnapshot && currentSnapshot.revision === snapshot.revision && currentSnapshot.updatedByClientId === snapshot.updatedByClientId && currentSnapshot.persistedRevision === snapshot.persistedRevision && sameSerializedItems(currentSnapshot.items, snapshot.items)) {
|
|
2649
|
+
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)) {
|
|
2590
2650
|
return;
|
|
2591
2651
|
}
|
|
2592
2652
|
const board = boardRef.current;
|
|
@@ -2605,11 +2665,13 @@ function useRealtimeSession(options) {
|
|
|
2605
2665
|
if (options2?.replace) {
|
|
2606
2666
|
replaceYDocWithSnapshot(board, {
|
|
2607
2667
|
items: snapshot.items,
|
|
2668
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
2608
2669
|
origin: ORIGIN_REMOTE
|
|
2609
2670
|
});
|
|
2610
2671
|
} else {
|
|
2611
2672
|
applyServerSnapshotToYDoc(board, {
|
|
2612
2673
|
items: snapshot.items,
|
|
2674
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
2613
2675
|
origin: ORIGIN_REMOTE
|
|
2614
2676
|
});
|
|
2615
2677
|
}
|
|
@@ -2617,7 +2679,11 @@ function useRealtimeSession(options) {
|
|
|
2617
2679
|
const mergedItems = board ? readVectorItems(board.yItems) : snapshot.items;
|
|
2618
2680
|
const mergedSnapshot = {
|
|
2619
2681
|
...snapshot,
|
|
2620
|
-
items: mergedItems
|
|
2682
|
+
items: mergedItems,
|
|
2683
|
+
// Expose the accumulated tombstones (server-confirmed + pending
|
|
2684
|
+
// local deletes) so document consumers can prune stale local
|
|
2685
|
+
// views instead of re-sending deleted items.
|
|
2686
|
+
...board ? { deletedItemIds: Array.from(getRealtimeDeletedItemIds(board)) } : {}
|
|
2621
2687
|
};
|
|
2622
2688
|
currentRevisionRef.current = snapshot.revision;
|
|
2623
2689
|
latestDocumentRef.current = mergedSnapshot;
|
|
@@ -3742,6 +3808,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
3742
3808
|
const realtimeEnabled = enabled && session != null;
|
|
3743
3809
|
const documentRevision = session?.document?.revision ?? null;
|
|
3744
3810
|
const documentItems = session?.document?.items;
|
|
3811
|
+
const documentDeletedItemIds = session?.document?.deletedItemIds;
|
|
3745
3812
|
const documentUpdatedByClientId = session?.document?.updatedByClientId ?? null;
|
|
3746
3813
|
const connectionClientId = session?.connection.clientId ?? null;
|
|
3747
3814
|
const hasLocalOfflineDraft = session?.hasLocalOfflineDraft ?? false;
|
|
@@ -3794,15 +3861,22 @@ function useRealtimeCanvasDocument(options) {
|
|
|
3794
3861
|
if (cancelled) return;
|
|
3795
3862
|
if (inFlightRevisionRef.current !== documentRevision) return;
|
|
3796
3863
|
lastAppliedRevisionRef.current = documentRevision;
|
|
3797
|
-
const
|
|
3864
|
+
const rawLocalItems = latestItemsRef.current;
|
|
3865
|
+
const deletedIds = new Set(documentDeletedItemIds ?? []);
|
|
3866
|
+
const localItems = deletedIds.size > 0 ? rawLocalItems.filter((item) => {
|
|
3867
|
+
const id = getSceneItemId(item);
|
|
3868
|
+
return !id || !deletedIds.has(id);
|
|
3869
|
+
}) : rawLocalItems;
|
|
3798
3870
|
const hasLocalItems = localItems.length > 0;
|
|
3799
3871
|
const hasPendingLocalChanges = hasLocalOfflineDraft || hasPendingDocumentSync || hasLocalChangeInFlightRef.current;
|
|
3800
3872
|
if (resolvedItems.length === 0 && (hasEverPropagatedItemsRef.current || hasLocalItems || hasPendingLocalChanges)) {
|
|
3801
3873
|
if (hasLocalItems) {
|
|
3802
3874
|
const normalizedLocalItems = normalizeItems ? normalizeItems(localItems) : [...localItems];
|
|
3803
3875
|
session.remoteAdapter.send?.(normalizedLocalItems);
|
|
3876
|
+
return;
|
|
3804
3877
|
}
|
|
3805
|
-
|
|
3878
|
+
const emptinessExplainedByDeletes = rawLocalItems.length > 0 && localItems.length === 0;
|
|
3879
|
+
if (!emptinessExplainedByDeletes) return;
|
|
3806
3880
|
}
|
|
3807
3881
|
if (shouldPreserveLocalRealtimeItems({
|
|
3808
3882
|
localItems,
|
|
@@ -3830,6 +3904,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
3830
3904
|
}, [
|
|
3831
3905
|
applyIncomingItems,
|
|
3832
3906
|
connectionClientId,
|
|
3907
|
+
documentDeletedItemIds,
|
|
3833
3908
|
documentItems,
|
|
3834
3909
|
documentRevision,
|
|
3835
3910
|
documentUpdatedByClientId,
|