canvu-react 0.4.79 → 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 +119 -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 +119 -11
- package/dist/realtime.js.map +1 -1
- package/dist/realtimeNative.cjs +119 -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 +119 -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");
|
|
@@ -2095,6 +2122,38 @@ function normalizeRealtimeItem(item) {
|
|
|
2095
2122
|
normalized.childrenSvg = "";
|
|
2096
2123
|
return normalized;
|
|
2097
2124
|
}
|
|
2125
|
+
function reorderYItemsToMatchLocalItems(yItems, items) {
|
|
2126
|
+
const currentItems = readVectorItems(yItems);
|
|
2127
|
+
const currentItemsById = /* @__PURE__ */ new Map();
|
|
2128
|
+
for (const currentItem of currentItems) {
|
|
2129
|
+
const id = getItemId(currentItem);
|
|
2130
|
+
if (id) currentItemsById.set(id, currentItem);
|
|
2131
|
+
}
|
|
2132
|
+
const orderedItems = [];
|
|
2133
|
+
const orderedIds = /* @__PURE__ */ new Set();
|
|
2134
|
+
for (const item of items) {
|
|
2135
|
+
const id = getItemId(item);
|
|
2136
|
+
if (!id) continue;
|
|
2137
|
+
const currentItem = currentItemsById.get(id);
|
|
2138
|
+
if (!currentItem) continue;
|
|
2139
|
+
orderedItems.push(currentItem);
|
|
2140
|
+
orderedIds.add(id);
|
|
2141
|
+
}
|
|
2142
|
+
for (const currentItem of currentItems) {
|
|
2143
|
+
const id = getItemId(currentItem);
|
|
2144
|
+
if (!id || !orderedIds.has(id)) orderedItems.push(currentItem);
|
|
2145
|
+
}
|
|
2146
|
+
const alreadyOrdered = currentItems.length === orderedItems.length && currentItems.every((currentItem, index) => {
|
|
2147
|
+
const nextItem = orderedItems[index];
|
|
2148
|
+
return nextItem ? getItemId(currentItem) === getItemId(nextItem) : false;
|
|
2149
|
+
});
|
|
2150
|
+
if (alreadyOrdered) return;
|
|
2151
|
+
if (yItems.length > 0) yItems.delete(0, yItems.length);
|
|
2152
|
+
yItems.insert(
|
|
2153
|
+
0,
|
|
2154
|
+
orderedItems.map((item) => vectorItemToYMap(item))
|
|
2155
|
+
);
|
|
2156
|
+
}
|
|
2098
2157
|
function applyLocalItemsToYDoc(board, options) {
|
|
2099
2158
|
const { items, origin } = options;
|
|
2100
2159
|
const addedIds = [];
|
|
@@ -2111,9 +2170,11 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2111
2170
|
const toDelete = [];
|
|
2112
2171
|
for (const [id, entry] of currentIndex) {
|
|
2113
2172
|
if (nextIds.has(id)) continue;
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
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
|
+
}
|
|
2117
2178
|
}
|
|
2118
2179
|
toDelete.push({ id, index: entry.index });
|
|
2119
2180
|
}
|
|
@@ -2121,6 +2182,7 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2121
2182
|
for (const { id, index } of toDelete) {
|
|
2122
2183
|
board.yItems.delete(index, 1);
|
|
2123
2184
|
board.serverItemSeenAt.delete(id);
|
|
2185
|
+
board.locallyDeletedItemIds.set(id, now);
|
|
2124
2186
|
removedIds.push(id);
|
|
2125
2187
|
}
|
|
2126
2188
|
const refreshedIndex = indexYItemsById(board.yItems);
|
|
@@ -2129,6 +2191,9 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2129
2191
|
if (!item) continue;
|
|
2130
2192
|
const id = getItemId(item);
|
|
2131
2193
|
if (!id) continue;
|
|
2194
|
+
board.consumerSeenItemIds.add(id);
|
|
2195
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
2196
|
+
board.locallyDeletedItemIds.delete(id);
|
|
2132
2197
|
const existing = refreshedIndex.get(id);
|
|
2133
2198
|
if (existing) {
|
|
2134
2199
|
updateYMapInPlace(existing.yMap, item);
|
|
@@ -2138,16 +2203,38 @@ function applyLocalItemsToYDoc(board, options) {
|
|
|
2138
2203
|
board.yItems.push([yMap]);
|
|
2139
2204
|
addedIds.push(id);
|
|
2140
2205
|
}
|
|
2206
|
+
reorderYItemsToMatchLocalItems(board.yItems, items);
|
|
2141
2207
|
}, origin);
|
|
2142
2208
|
return { addedIds, removedIds };
|
|
2143
2209
|
}
|
|
2144
2210
|
function applyServerSnapshotToYDoc(board, options) {
|
|
2145
|
-
const { items: snapshotItems, origin } = options;
|
|
2211
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
2146
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
|
+
}
|
|
2147
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
|
+
}
|
|
2148
2231
|
for (const item of snapshotItems) {
|
|
2149
2232
|
const id = getItemId(item);
|
|
2150
2233
|
if (!id) continue;
|
|
2234
|
+
if (board.serverDeletedItemIds.has(id)) continue;
|
|
2235
|
+
if (isLocalDeleteTombstoneActive(board, id, now)) {
|
|
2236
|
+
continue;
|
|
2237
|
+
}
|
|
2151
2238
|
const existing = indexYItemsById(board.yItems).get(id);
|
|
2152
2239
|
if (existing) {
|
|
2153
2240
|
const currentSerialized = serializeItem(existing.yMap);
|
|
@@ -2178,7 +2265,7 @@ function applyServerSnapshotToYDoc(board, options) {
|
|
|
2178
2265
|
}
|
|
2179
2266
|
}
|
|
2180
2267
|
function replaceYDocWithSnapshot(board, options) {
|
|
2181
|
-
const { items: snapshotItems, origin } = options;
|
|
2268
|
+
const { items: snapshotItems, deletedItemIds, origin } = options;
|
|
2182
2269
|
const now = Date.now();
|
|
2183
2270
|
board.doc.transact(() => {
|
|
2184
2271
|
if (board.yItems.length > 0) {
|
|
@@ -2191,6 +2278,12 @@ function replaceYDocWithSnapshot(board, options) {
|
|
|
2191
2278
|
board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
|
|
2192
2279
|
board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
|
|
2193
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
|
+
}
|
|
2194
2287
|
for (const item of snapshotItems) {
|
|
2195
2288
|
const id = getItemId(item);
|
|
2196
2289
|
if (id) {
|
|
@@ -2553,7 +2646,7 @@ function useRealtimeSession(options) {
|
|
|
2553
2646
|
const applyDocument = useCallback(
|
|
2554
2647
|
(snapshot, options2) => {
|
|
2555
2648
|
const currentSnapshot = latestDocumentRef.current;
|
|
2556
|
-
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)) {
|
|
2557
2650
|
return;
|
|
2558
2651
|
}
|
|
2559
2652
|
const board = boardRef.current;
|
|
@@ -2572,11 +2665,13 @@ function useRealtimeSession(options) {
|
|
|
2572
2665
|
if (options2?.replace) {
|
|
2573
2666
|
replaceYDocWithSnapshot(board, {
|
|
2574
2667
|
items: snapshot.items,
|
|
2668
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
2575
2669
|
origin: ORIGIN_REMOTE
|
|
2576
2670
|
});
|
|
2577
2671
|
} else {
|
|
2578
2672
|
applyServerSnapshotToYDoc(board, {
|
|
2579
2673
|
items: snapshot.items,
|
|
2674
|
+
deletedItemIds: snapshot.deletedItemIds,
|
|
2580
2675
|
origin: ORIGIN_REMOTE
|
|
2581
2676
|
});
|
|
2582
2677
|
}
|
|
@@ -2584,7 +2679,11 @@ function useRealtimeSession(options) {
|
|
|
2584
2679
|
const mergedItems = board ? readVectorItems(board.yItems) : snapshot.items;
|
|
2585
2680
|
const mergedSnapshot = {
|
|
2586
2681
|
...snapshot,
|
|
2587
|
-
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)) } : {}
|
|
2588
2687
|
};
|
|
2589
2688
|
currentRevisionRef.current = snapshot.revision;
|
|
2590
2689
|
latestDocumentRef.current = mergedSnapshot;
|
|
@@ -3709,6 +3808,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
3709
3808
|
const realtimeEnabled = enabled && session != null;
|
|
3710
3809
|
const documentRevision = session?.document?.revision ?? null;
|
|
3711
3810
|
const documentItems = session?.document?.items;
|
|
3811
|
+
const documentDeletedItemIds = session?.document?.deletedItemIds;
|
|
3712
3812
|
const documentUpdatedByClientId = session?.document?.updatedByClientId ?? null;
|
|
3713
3813
|
const connectionClientId = session?.connection.clientId ?? null;
|
|
3714
3814
|
const hasLocalOfflineDraft = session?.hasLocalOfflineDraft ?? false;
|
|
@@ -3761,15 +3861,22 @@ function useRealtimeCanvasDocument(options) {
|
|
|
3761
3861
|
if (cancelled) return;
|
|
3762
3862
|
if (inFlightRevisionRef.current !== documentRevision) return;
|
|
3763
3863
|
lastAppliedRevisionRef.current = documentRevision;
|
|
3764
|
-
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;
|
|
3765
3870
|
const hasLocalItems = localItems.length > 0;
|
|
3766
3871
|
const hasPendingLocalChanges = hasLocalOfflineDraft || hasPendingDocumentSync || hasLocalChangeInFlightRef.current;
|
|
3767
3872
|
if (resolvedItems.length === 0 && (hasEverPropagatedItemsRef.current || hasLocalItems || hasPendingLocalChanges)) {
|
|
3768
3873
|
if (hasLocalItems) {
|
|
3769
3874
|
const normalizedLocalItems = normalizeItems ? normalizeItems(localItems) : [...localItems];
|
|
3770
3875
|
session.remoteAdapter.send?.(normalizedLocalItems);
|
|
3876
|
+
return;
|
|
3771
3877
|
}
|
|
3772
|
-
|
|
3878
|
+
const emptinessExplainedByDeletes = rawLocalItems.length > 0 && localItems.length === 0;
|
|
3879
|
+
if (!emptinessExplainedByDeletes) return;
|
|
3773
3880
|
}
|
|
3774
3881
|
if (shouldPreserveLocalRealtimeItems({
|
|
3775
3882
|
localItems,
|
|
@@ -3797,6 +3904,7 @@ function useRealtimeCanvasDocument(options) {
|
|
|
3797
3904
|
}, [
|
|
3798
3905
|
applyIncomingItems,
|
|
3799
3906
|
connectionClientId,
|
|
3907
|
+
documentDeletedItemIds,
|
|
3800
3908
|
documentItems,
|
|
3801
3909
|
documentRevision,
|
|
3802
3910
|
documentUpdatedByClientId,
|