@liveblocks/react 1.2.3 → 1.3.0

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/index.mjs CHANGED
@@ -1,9 +1,11 @@
1
+ "use client";
2
+
1
3
  // src/index.ts
2
4
  import { detectDupes } from "@liveblocks/core";
3
5
 
4
6
  // src/version.ts
5
7
  var PKG_NAME = "@liveblocks/react";
6
- var PKG_VERSION = "1.2.3";
8
+ var PKG_VERSION = "1.3.0";
7
9
  var PKG_FORMAT = "esm";
8
10
 
9
11
  // src/ClientSideSuspense.tsx
@@ -19,15 +21,515 @@ function ClientSideSuspense(props) {
19
21
  // src/factory.tsx
20
22
  import { shallow } from "@liveblocks/client";
21
23
  import {
22
- asArrayWithLegacyMethods,
24
+ createAsyncCache,
23
25
  deprecateIf,
24
- errorIf
26
+ errorIf,
27
+ makeEventSource as makeEventSource2
25
28
  } from "@liveblocks/core";
26
29
  import * as React2 from "react";
27
30
  import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
28
31
 
29
- // src/hooks.ts
30
- import { useReducer, useRef } from "react";
32
+ // src/comments/CommentsRoom.ts
33
+ import { makePoller } from "@liveblocks/core";
34
+
35
+ // ../../node_modules/nanoid/index.js
36
+ import crypto from "crypto";
37
+
38
+ // ../../node_modules/nanoid/url-alphabet/index.js
39
+ var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
40
+
41
+ // ../../node_modules/nanoid/index.js
42
+ var POOL_SIZE_MULTIPLIER = 128;
43
+ var pool;
44
+ var poolOffset;
45
+ var fillPool = (bytes) => {
46
+ if (!pool || pool.length < bytes) {
47
+ pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
48
+ crypto.randomFillSync(pool);
49
+ poolOffset = 0;
50
+ } else if (poolOffset + bytes > pool.length) {
51
+ crypto.randomFillSync(pool);
52
+ poolOffset = 0;
53
+ }
54
+ poolOffset += bytes;
55
+ };
56
+ var nanoid = (size = 21) => {
57
+ fillPool(size -= 0);
58
+ let id = "";
59
+ for (let i = poolOffset - size; i < poolOffset; i++) {
60
+ id += urlAlphabet[pool[i] & 63];
61
+ }
62
+ return id;
63
+ };
64
+
65
+ // src/comments/CommentsRoom.ts
66
+ import { useEffect as useEffect2 } from "react";
67
+ import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
68
+
69
+ // src/comments/errors.ts
70
+ var CreateThreadError = class extends Error {
71
+ constructor(cause, context) {
72
+ super("Create thread failed.");
73
+ this.cause = cause;
74
+ this.context = context;
75
+ this.name = "CreateThreadError";
76
+ }
77
+ };
78
+ var EditThreadMetadataError = class extends Error {
79
+ constructor(cause, context) {
80
+ super("Edit thread metadata failed.");
81
+ this.cause = cause;
82
+ this.context = context;
83
+ this.name = "EditThreadMetadataError";
84
+ }
85
+ };
86
+ var CreateCommentError = class extends Error {
87
+ constructor(cause, context) {
88
+ super("Create comment failed.");
89
+ this.cause = cause;
90
+ this.context = context;
91
+ this.name = "CreateCommentError";
92
+ }
93
+ };
94
+ var EditCommentError = class extends Error {
95
+ constructor(cause, context) {
96
+ super("Edit comment failed.");
97
+ this.cause = cause;
98
+ this.context = context;
99
+ this.name = "EditCommentError";
100
+ }
101
+ };
102
+ var DeleteCommentError = class extends Error {
103
+ constructor(cause, context) {
104
+ super("Delete comment failed.");
105
+ this.cause = cause;
106
+ this.context = context;
107
+ this.name = "DeleteCommentError";
108
+ }
109
+ };
110
+
111
+ // src/comments/lib/store.ts
112
+ import { makeEventSource } from "@liveblocks/core";
113
+ function createStore(initialState) {
114
+ let state = initialState;
115
+ const eventSource = makeEventSource();
116
+ return {
117
+ get() {
118
+ return state;
119
+ },
120
+ set(newState) {
121
+ state = newState;
122
+ eventSource.notify(state);
123
+ },
124
+ subscribe(callback) {
125
+ return eventSource.subscribe(callback);
126
+ },
127
+ subscribeOnce(callback) {
128
+ return eventSource.subscribeOnce(callback);
129
+ },
130
+ subscribersCount() {
131
+ return eventSource.count();
132
+ },
133
+ destroy() {
134
+ return eventSource.clear();
135
+ }
136
+ };
137
+ }
138
+
139
+ // src/comments/CommentsRoom.ts
140
+ var POLLING_INTERVAL_REALTIME = 3e4;
141
+ var POLLING_INTERVAL = 5e3;
142
+ var THREAD_ID_PREFIX = "th";
143
+ var COMMENT_ID_PREFIX = "cm";
144
+ function createOptimisticId(prefix) {
145
+ return `${prefix}_${nanoid()}`;
146
+ }
147
+ function createCommentsRoom(room, errorEventSource) {
148
+ const store = createStore({
149
+ isLoading: true
150
+ });
151
+ let fetchThreadsPromise = null;
152
+ let numberOfMutations = 0;
153
+ function endMutation() {
154
+ numberOfMutations--;
155
+ if (numberOfMutations === 0) {
156
+ void revalidateThreads();
157
+ }
158
+ }
159
+ function startMutation() {
160
+ pollingHub.threads.stop();
161
+ numberOfMutations++;
162
+ }
163
+ const pollingHub = {
164
+ // TODO: If there's an error, it will currently infinitely retry at the current polling rate → add retry logic
165
+ threads: makePoller(revalidateThreads)
166
+ };
167
+ let unsubscribeRealtimeEvents;
168
+ let unsubscribeRealtimeConnection;
169
+ let realtimeClientConnected = false;
170
+ function getPollingInterval() {
171
+ return realtimeClientConnected ? POLLING_INTERVAL_REALTIME : POLLING_INTERVAL;
172
+ }
173
+ function ensureThreadsAreLoadedForMutations() {
174
+ const state = store.get();
175
+ if (state.isLoading || state.error) {
176
+ throw new Error(
177
+ "Cannot update threads or comments before they are loaded"
178
+ );
179
+ }
180
+ return state.threads;
181
+ }
182
+ async function revalidateThreads() {
183
+ pollingHub.threads.pause();
184
+ if (numberOfMutations === 0) {
185
+ if (fetchThreadsPromise === null) {
186
+ fetchThreadsPromise = room.getThreads();
187
+ }
188
+ setThreads(await fetchThreadsPromise);
189
+ fetchThreadsPromise = null;
190
+ }
191
+ pollingHub.threads.resume();
192
+ }
193
+ function subscribe() {
194
+ if (!unsubscribeRealtimeEvents) {
195
+ unsubscribeRealtimeEvents = room.events.comments.subscribe(() => {
196
+ pollingHub.threads.restart(getPollingInterval());
197
+ void revalidateThreads();
198
+ });
199
+ }
200
+ if (!unsubscribeRealtimeConnection) {
201
+ unsubscribeRealtimeConnection = room.events.status.subscribe((status) => {
202
+ const nextRealtimeClientConnected = status === "connected";
203
+ if (nextRealtimeClientConnected !== realtimeClientConnected) {
204
+ realtimeClientConnected = nextRealtimeClientConnected;
205
+ pollingHub.threads.restart(getPollingInterval());
206
+ }
207
+ });
208
+ }
209
+ pollingHub.threads.start(getPollingInterval());
210
+ return () => {
211
+ if (store.subscribersCount() > 1) {
212
+ return;
213
+ }
214
+ pollingHub.threads.stop();
215
+ unsubscribeRealtimeEvents?.();
216
+ unsubscribeRealtimeEvents = void 0;
217
+ unsubscribeRealtimeConnection?.();
218
+ unsubscribeRealtimeConnection = void 0;
219
+ };
220
+ }
221
+ function setThreads(newThreads) {
222
+ store.set({
223
+ threads: newThreads,
224
+ isLoading: false
225
+ });
226
+ }
227
+ function useThreadsInternal() {
228
+ useEffect2(subscribe, []);
229
+ return useSyncExternalStore(
230
+ store.subscribe,
231
+ store.get,
232
+ store.get
233
+ );
234
+ }
235
+ function useThreads() {
236
+ useEffect2(() => {
237
+ void revalidateThreads();
238
+ }, []);
239
+ return useThreadsInternal();
240
+ }
241
+ function useThreadsSuspense() {
242
+ const result = useThreadsInternal();
243
+ if (result.isLoading) {
244
+ throw revalidateThreads();
245
+ }
246
+ if (result.error) {
247
+ throw result.error;
248
+ }
249
+ return result.threads;
250
+ }
251
+ function getCurrentUserId() {
252
+ const self = room.getSelf();
253
+ if (self === null || self.id === void 0) {
254
+ return "anonymous";
255
+ } else {
256
+ return self.id;
257
+ }
258
+ }
259
+ function createThread(options) {
260
+ const body = options.body;
261
+ const metadata = "metadata" in options ? options.metadata : {};
262
+ const threads = ensureThreadsAreLoadedForMutations();
263
+ const threadId = createOptimisticId(THREAD_ID_PREFIX);
264
+ const commentId = createOptimisticId(COMMENT_ID_PREFIX);
265
+ const now = (/* @__PURE__ */ new Date()).toISOString();
266
+ const newThread = {
267
+ id: threadId,
268
+ type: "thread",
269
+ createdAt: now,
270
+ roomId: room.id,
271
+ metadata,
272
+ comments: [
273
+ {
274
+ id: commentId,
275
+ createdAt: now,
276
+ type: "comment",
277
+ userId: getCurrentUserId(),
278
+ body
279
+ }
280
+ ]
281
+ };
282
+ setThreads([...threads, newThread]);
283
+ startMutation();
284
+ room.createThread({ threadId, commentId, body, metadata }).catch(
285
+ (er) => errorEventSource.notify(
286
+ new CreateThreadError(er, {
287
+ roomId: room.id,
288
+ threadId,
289
+ commentId,
290
+ body,
291
+ metadata
292
+ })
293
+ )
294
+ ).finally(endMutation);
295
+ return newThread;
296
+ }
297
+ function editThreadMetadata(options) {
298
+ const threadId = options.threadId;
299
+ const metadata = "metadata" in options ? options.metadata : {};
300
+ const threads = ensureThreadsAreLoadedForMutations();
301
+ setThreads(
302
+ threads.map(
303
+ (thread) => thread.id === threadId ? {
304
+ ...thread,
305
+ metadata: {
306
+ ...thread.metadata,
307
+ ...metadata
308
+ }
309
+ } : thread
310
+ )
311
+ );
312
+ startMutation();
313
+ room.editThreadMetadata({ metadata, threadId }).catch(
314
+ (er) => errorEventSource.notify(
315
+ new EditThreadMetadataError(er, {
316
+ roomId: room.id,
317
+ threadId,
318
+ metadata
319
+ })
320
+ )
321
+ ).finally(endMutation);
322
+ }
323
+ function createComment({
324
+ threadId,
325
+ body
326
+ }) {
327
+ const threads = ensureThreadsAreLoadedForMutations();
328
+ const commentId = createOptimisticId(COMMENT_ID_PREFIX);
329
+ const now = (/* @__PURE__ */ new Date()).toISOString();
330
+ const comment = {
331
+ id: commentId,
332
+ threadId,
333
+ roomId: room.id,
334
+ type: "comment",
335
+ createdAt: now,
336
+ userId: getCurrentUserId(),
337
+ body
338
+ };
339
+ setThreads(
340
+ threads.map(
341
+ (thread) => thread.id === threadId ? {
342
+ ...thread,
343
+ comments: [...thread.comments, comment]
344
+ } : thread
345
+ )
346
+ );
347
+ startMutation();
348
+ room.createComment({ threadId, commentId, body }).catch(
349
+ (er) => errorEventSource.notify(
350
+ new CreateCommentError(er, {
351
+ roomId: room.id,
352
+ threadId,
353
+ commentId,
354
+ body
355
+ })
356
+ )
357
+ ).finally(endMutation);
358
+ return comment;
359
+ }
360
+ function editComment({ threadId, commentId, body }) {
361
+ const threads = ensureThreadsAreLoadedForMutations();
362
+ const now = (/* @__PURE__ */ new Date()).toISOString();
363
+ setThreads(
364
+ threads.map(
365
+ (thread) => thread.id === threadId ? {
366
+ ...thread,
367
+ comments: thread.comments.map(
368
+ (comment) => comment.id === commentId ? {
369
+ ...comment,
370
+ editedAt: now,
371
+ body
372
+ } : comment
373
+ )
374
+ } : thread
375
+ )
376
+ );
377
+ startMutation();
378
+ room.editComment({ threadId, commentId, body }).catch(
379
+ (er) => errorEventSource.notify(
380
+ new EditCommentError(er, {
381
+ roomId: room.id,
382
+ threadId,
383
+ commentId,
384
+ body
385
+ })
386
+ )
387
+ ).finally(endMutation);
388
+ }
389
+ function deleteComment({ threadId, commentId }) {
390
+ const threads = ensureThreadsAreLoadedForMutations();
391
+ const now = (/* @__PURE__ */ new Date()).toISOString();
392
+ const newThreads = [];
393
+ for (const thread of threads) {
394
+ if (thread.id === threadId) {
395
+ const newThread = {
396
+ ...thread,
397
+ comments: thread.comments.map(
398
+ (comment) => comment.id === commentId ? {
399
+ ...comment,
400
+ deletedAt: now,
401
+ body: void 0
402
+ } : comment
403
+ )
404
+ };
405
+ if (newThread.comments.some((comment) => comment.deletedAt === void 0)) {
406
+ newThreads.push(newThread);
407
+ }
408
+ } else {
409
+ newThreads.push(thread);
410
+ }
411
+ }
412
+ setThreads(newThreads);
413
+ startMutation();
414
+ room.deleteComment({ threadId, commentId }).catch(
415
+ (er) => errorEventSource.notify(
416
+ new DeleteCommentError(er, {
417
+ roomId: room.id,
418
+ threadId,
419
+ commentId
420
+ })
421
+ )
422
+ ).finally(endMutation);
423
+ }
424
+ return {
425
+ useThreads,
426
+ useThreadsSuspense,
427
+ createThread,
428
+ editThreadMetadata,
429
+ createComment,
430
+ editComment,
431
+ deleteComment
432
+ };
433
+ }
434
+
435
+ // src/comments/lib/use-debounce.ts
436
+ import { useEffect as useEffect3, useRef, useState as useState2 } from "react";
437
+ var DEFAULT_DELAY = 500;
438
+ function useDebounce(value, delay = DEFAULT_DELAY) {
439
+ const timeout = useRef();
440
+ const [debouncedValue, setDebouncedValue] = useState2(value);
441
+ useEffect3(() => {
442
+ if (delay === false) {
443
+ return;
444
+ }
445
+ if (timeout.current === void 0) {
446
+ setDebouncedValue(value);
447
+ }
448
+ timeout.current = window.setTimeout(() => {
449
+ setDebouncedValue(value);
450
+ timeout.current = void 0;
451
+ }, delay);
452
+ return () => {
453
+ window.clearTimeout(timeout.current);
454
+ };
455
+ }, [value, delay]);
456
+ return debouncedValue;
457
+ }
458
+
459
+ // src/lib/stable-stringify.ts
460
+ function stableStringify(object, ...args) {
461
+ const sortedObject = Object.keys(object).sort().reduce(
462
+ (sortedObject2, key) => {
463
+ sortedObject2[key] = object[key];
464
+ return sortedObject2;
465
+ },
466
+ {}
467
+ );
468
+ return JSON.stringify(sortedObject, ...args);
469
+ }
470
+
471
+ // src/lib/use-async-cache.ts
472
+ import { useCallback, useEffect as useEffect4, useMemo, useRef as useRef3 } from "react";
473
+ import { useSyncExternalStore as useSyncExternalStore2 } from "use-sync-external-store/shim/index.js";
474
+
475
+ // src/lib/use-initial.ts
476
+ import { useRef as useRef2 } from "react";
477
+ function useInitial(value) {
478
+ return useRef2(value).current;
479
+ }
480
+
481
+ // src/lib/use-async-cache.ts
482
+ var INITIAL_ASYNC_STATE = {
483
+ isLoading: false,
484
+ data: void 0,
485
+ error: void 0
486
+ };
487
+ var noop = () => {
488
+ };
489
+ function useAsyncCache(cache, key, options) {
490
+ const frozenOptions = useInitial(options);
491
+ const cacheItem = useMemo(() => {
492
+ if (key === null || !cache) {
493
+ return null;
494
+ }
495
+ const cacheItem2 = cache.create(key, frozenOptions?.overrideFunction);
496
+ void cacheItem2.get();
497
+ return cacheItem2;
498
+ }, [cache, frozenOptions, key]);
499
+ const subscribe = useCallback(
500
+ (callback) => cacheItem?.subscribe(callback) ?? noop,
501
+ [cacheItem]
502
+ );
503
+ const getState = useCallback(
504
+ () => cacheItem?.getState() ?? INITIAL_ASYNC_STATE,
505
+ [cacheItem]
506
+ );
507
+ const revalidate = useCallback(() => cacheItem?.revalidate(), [cacheItem]);
508
+ const state = useSyncExternalStore2(subscribe, getState, getState);
509
+ const previousData = useRef3();
510
+ let data = state.data;
511
+ useEffect4(() => {
512
+ previousData.current = { key, data: state.data };
513
+ }, [key, state]);
514
+ if (frozenOptions?.suspense && state.isLoading && cacheItem) {
515
+ throw new Promise((resolve) => {
516
+ cacheItem.subscribeOnce(() => resolve());
517
+ });
518
+ }
519
+ if (state.isLoading && frozenOptions?.keepPreviousDataWhileLoading && typeof state.data === "undefined" && previousData.current?.key !== key && typeof previousData.current?.data !== "undefined") {
520
+ data = previousData.current.data;
521
+ }
522
+ return {
523
+ isLoading: state.isLoading,
524
+ data,
525
+ error: state.error,
526
+ getState,
527
+ revalidate
528
+ };
529
+ }
530
+
531
+ // src/lib/use-rerender.ts
532
+ import { useReducer } from "react";
31
533
  function useRerender() {
32
534
  const [, update] = useReducer(
33
535
  // This implementation works by incrementing a hidden counter value that is
@@ -38,12 +540,9 @@ function useRerender() {
38
540
  );
39
541
  return update;
40
542
  }
41
- function useInitial(value) {
42
- return useRef(value).current;
43
- }
44
543
 
45
544
  // src/factory.tsx
46
- var noop = () => {
545
+ var noop2 = () => {
47
546
  };
48
547
  var identity = (x) => x;
49
548
  var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\u2019re using React ${reactVersion}. Please pass unstable_batchedUpdates at the RoomProvider level until you\u2019re ready to upgrade to React 18:
@@ -58,15 +557,11 @@ var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\
58
557
 
59
558
  Why? Please see https://liveblocks.io/docs/guides/troubleshooting#stale-props-zombie-child for more information`;
60
559
  var superfluous_unstable_batchedUpdates = "You don\u2019t need to pass unstable_batchedUpdates to RoomProvider anymore, since you\u2019re on React 18+ already.";
61
- function useSyncExternalStore(s, gs, gss) {
560
+ function useSyncExternalStore3(s, gs, gss) {
62
561
  return useSyncExternalStoreWithSelector(s, gs, gss, identity);
63
562
  }
64
- var EMPTY_OTHERS = (
65
- // NOTE: asArrayWithLegacyMethods() wrapping should no longer be necessary in 0.19
66
- asArrayWithLegacyMethods([])
67
- );
68
563
  function getEmptyOthers() {
69
- return EMPTY_OTHERS;
564
+ return [];
70
565
  }
71
566
  function makeMutationContext(room) {
72
567
  const errmsg = "This mutation cannot be used until connected to the Liveblocks room";
@@ -95,7 +590,33 @@ function makeMutationContext(room) {
95
590
  setMyPresence: room.updatePresence
96
591
  };
97
592
  }
98
- function createRoomContext(client) {
593
+ var hasWarnedIfNoResolveUser = false;
594
+ function warnIfNoResolveUser(usersCache) {
595
+ if (!hasWarnedIfNoResolveUser && !usersCache && process.env.NODE_ENV !== "production") {
596
+ console.warn(
597
+ "Set the resolveUser option in createRoomContext to specify user info."
598
+ );
599
+ hasWarnedIfNoResolveUser = true;
600
+ }
601
+ }
602
+ var hasWarnedAboutCommentsBeta = false;
603
+ function warnIfBetaCommentsHook() {
604
+ if (!hasWarnedAboutCommentsBeta && process.env.NODE_ENV !== "production") {
605
+ console.warn(
606
+ "Comments is currently in private beta. Learn more at https://liveblocks.io/docs/products/comments."
607
+ );
608
+ hasWarnedAboutCommentsBeta = true;
609
+ }
610
+ }
611
+ var ContextBundle = React2.createContext(null);
612
+ function useRoomContextBundle() {
613
+ const bundle = React2.useContext(ContextBundle);
614
+ if (bundle === null) {
615
+ throw new Error("RoomProvider is missing from the React tree.");
616
+ }
617
+ return bundle;
618
+ }
619
+ function createRoomContext(client, options) {
99
620
  const RoomContext = React2.createContext(null);
100
621
  function RoomProvider(props) {
101
622
  const {
@@ -140,19 +661,31 @@ function createRoomContext(client) {
140
661
  })
141
662
  );
142
663
  React2.useEffect(() => {
143
- setRoom(
144
- client.enter(roomId, {
664
+ const room2 = client.enter(
665
+ roomId,
666
+ {
145
667
  initialPresence: frozen.initialPresence,
146
668
  initialStorage: frozen.initialStorage,
147
669
  shouldInitiallyConnect: frozen.shouldInitiallyConnect,
148
670
  unstable_batchedUpdates: frozen.unstable_batchedUpdates
149
- })
671
+ }
150
672
  );
673
+ setRoom(room2);
151
674
  return () => {
675
+ const commentsRoom = commentsRooms.get(roomId);
676
+ if (commentsRoom) {
677
+ commentsRooms.delete(roomId);
678
+ }
152
679
  client.leave(roomId);
153
680
  };
154
681
  }, [roomId, frozen]);
155
- return /* @__PURE__ */ React2.createElement(RoomContext.Provider, { value: room }, props.children);
682
+ return /* @__PURE__ */ React2.createElement(RoomContext.Provider, { value: room }, /* @__PURE__ */ React2.createElement(
683
+ ContextBundle.Provider,
684
+ {
685
+ value: internalBundle
686
+ },
687
+ props.children
688
+ ));
156
689
  }
157
690
  function connectionIdSelector(others) {
158
691
  return others.map((user) => user.connectionId);
@@ -160,7 +693,7 @@ function createRoomContext(client) {
160
693
  function useRoom() {
161
694
  const room = React2.useContext(RoomContext);
162
695
  if (room === null) {
163
- throw new Error("RoomProvider is missing from the react tree");
696
+ throw new Error("RoomProvider is missing from the React tree.");
164
697
  }
165
698
  return room;
166
699
  }
@@ -168,13 +701,13 @@ function createRoomContext(client) {
168
701
  const room = useRoom();
169
702
  const subscribe = room.events.status.subscribe;
170
703
  const getSnapshot = room.getStatus;
171
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
704
+ return useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
172
705
  }
173
706
  function useMyPresence() {
174
707
  const room = useRoom();
175
708
  const subscribe = room.events.myPresence.subscribe;
176
709
  const getSnapshot = room.getPresence;
177
- const presence = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
710
+ const presence = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
178
711
  const setPresence = room.updatePresence;
179
712
  return [presence, setPresence];
180
713
  }
@@ -248,8 +781,8 @@ function createRoomContext(client) {
248
781
  function useBroadcastEvent() {
249
782
  const room = useRoom();
250
783
  return React2.useCallback(
251
- (event, options = { shouldQueueEventIfNotReady: false }) => {
252
- room.broadcastEvent(event, options);
784
+ (event, options2 = { shouldQueueEventIfNotReady: false }) => {
785
+ room.broadcastEvent(event, options2);
253
786
  },
254
787
  [room]
255
788
  );
@@ -314,7 +847,7 @@ function createRoomContext(client) {
314
847
  const subscribe = room.events.storageDidLoad.subscribeOnce;
315
848
  const getSnapshot = room.getStorageSnapshot;
316
849
  const getServerSnapshot = React2.useCallback(() => null, []);
317
- return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
850
+ return useSyncExternalStore3(subscribe, getSnapshot, getServerSnapshot);
318
851
  }
319
852
  function useStorageRoot() {
320
853
  return [useMutableStorageRoot()];
@@ -332,13 +865,13 @@ function createRoomContext(client) {
332
865
  const room = useRoom();
333
866
  const subscribe = room.events.history.subscribe;
334
867
  const canUndo = room.history.canUndo;
335
- return useSyncExternalStore(subscribe, canUndo, canUndo);
868
+ return useSyncExternalStore3(subscribe, canUndo, canUndo);
336
869
  }
337
870
  function useCanRedo() {
338
871
  const room = useRoom();
339
872
  const subscribe = room.events.history.subscribe;
340
873
  const canRedo = room.history.canRedo;
341
- return useSyncExternalStore(subscribe, canRedo, canRedo);
874
+ return useSyncExternalStore3(subscribe, canRedo, canRedo);
342
875
  }
343
876
  function useBatch() {
344
877
  return useRoom().batch;
@@ -395,7 +928,7 @@ function createRoomContext(client) {
395
928
  [selector]
396
929
  );
397
930
  const subscribe = React2.useCallback(
398
- (onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop,
931
+ (onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop2,
399
932
  [room, rootOrNull]
400
933
  );
401
934
  const getSnapshot = React2.useCallback(() => {
@@ -501,7 +1034,140 @@ function createRoomContext(client) {
501
1034
  useSuspendUntilStorageLoaded();
502
1035
  return useLegacyKey(key);
503
1036
  }
504
- return {
1037
+ const commentsErrorEventSource = makeEventSource2();
1038
+ const commentsRooms = /* @__PURE__ */ new Map();
1039
+ function getCommentsRoom(room) {
1040
+ let commentsRoom = commentsRooms.get(room.id);
1041
+ if (commentsRoom === void 0) {
1042
+ commentsRoom = createCommentsRoom(room, commentsErrorEventSource);
1043
+ commentsRooms.set(room.id, commentsRoom);
1044
+ }
1045
+ return commentsRoom;
1046
+ }
1047
+ function useThreads() {
1048
+ const room = useRoom();
1049
+ React2.useEffect(() => {
1050
+ warnIfBetaCommentsHook();
1051
+ }, []);
1052
+ return getCommentsRoom(room).useThreads();
1053
+ }
1054
+ function useThreadsSuspense() {
1055
+ const room = useRoom();
1056
+ React2.useEffect(() => {
1057
+ warnIfBetaCommentsHook();
1058
+ }, []);
1059
+ return getCommentsRoom(room).useThreadsSuspense();
1060
+ }
1061
+ function useCreateThread() {
1062
+ const room = useRoom();
1063
+ React2.useEffect(() => {
1064
+ warnIfBetaCommentsHook();
1065
+ }, []);
1066
+ return React2.useCallback(
1067
+ (options2) => getCommentsRoom(room).createThread(options2),
1068
+ [room]
1069
+ );
1070
+ }
1071
+ function useEditThreadMetadata() {
1072
+ const room = useRoom();
1073
+ React2.useEffect(() => {
1074
+ warnIfBetaCommentsHook();
1075
+ }, []);
1076
+ return React2.useCallback(
1077
+ (options2) => getCommentsRoom(room).editThreadMetadata(options2),
1078
+ [room]
1079
+ );
1080
+ }
1081
+ function useCreateComment() {
1082
+ const room = useRoom();
1083
+ React2.useEffect(() => {
1084
+ warnIfBetaCommentsHook();
1085
+ }, []);
1086
+ return React2.useCallback(
1087
+ (options2) => getCommentsRoom(room).createComment(options2),
1088
+ [room]
1089
+ );
1090
+ }
1091
+ function useEditComment() {
1092
+ const room = useRoom();
1093
+ React2.useEffect(() => {
1094
+ warnIfBetaCommentsHook();
1095
+ }, []);
1096
+ return React2.useCallback(
1097
+ (options2) => getCommentsRoom(room).editComment(options2),
1098
+ [room]
1099
+ );
1100
+ }
1101
+ function useDeleteComment() {
1102
+ const room = useRoom();
1103
+ React2.useEffect(() => {
1104
+ warnIfBetaCommentsHook();
1105
+ }, []);
1106
+ return React2.useCallback(
1107
+ (options2) => getCommentsRoom(room).deleteComment(options2),
1108
+ [room]
1109
+ );
1110
+ }
1111
+ const { resolveUser, resolveMentionSuggestions } = options ?? {};
1112
+ const usersCache = resolveUser ? createAsyncCache((stringifiedOptions) => {
1113
+ return resolveUser(
1114
+ JSON.parse(stringifiedOptions)
1115
+ );
1116
+ }) : void 0;
1117
+ function useUser(userId) {
1118
+ const resolverKey = React2.useMemo(
1119
+ () => stableStringify({ userId }),
1120
+ [userId]
1121
+ );
1122
+ const state = useAsyncCache(usersCache, resolverKey);
1123
+ React2.useEffect(() => warnIfNoResolveUser(usersCache), []);
1124
+ if (state.isLoading) {
1125
+ return {
1126
+ isLoading: true
1127
+ };
1128
+ } else {
1129
+ return {
1130
+ user: state.data,
1131
+ error: state.error,
1132
+ isLoading: false
1133
+ };
1134
+ }
1135
+ }
1136
+ function useUserSuspense(userId) {
1137
+ const resolverKey = React2.useMemo(
1138
+ () => stableStringify({ userId }),
1139
+ [userId]
1140
+ );
1141
+ const state = useAsyncCache(usersCache, resolverKey, {
1142
+ suspense: true
1143
+ });
1144
+ React2.useEffect(() => warnIfNoResolveUser(usersCache), []);
1145
+ return {
1146
+ user: state.data,
1147
+ error: state.error,
1148
+ isLoading: false
1149
+ };
1150
+ }
1151
+ const mentionSuggestionsCache = createAsyncCache(
1152
+ resolveMentionSuggestions ? (stringifiedOptions) => {
1153
+ return resolveMentionSuggestions(
1154
+ JSON.parse(stringifiedOptions)
1155
+ );
1156
+ } : () => Promise.resolve([])
1157
+ );
1158
+ function useMentionSuggestions(search) {
1159
+ const room = useRoom();
1160
+ const debouncedSearch = useDebounce(search, 500);
1161
+ const resolverKey = React2.useMemo(
1162
+ () => debouncedSearch !== void 0 ? stableStringify({ text: debouncedSearch, roomId: room.id }) : null,
1163
+ [debouncedSearch, room.id]
1164
+ );
1165
+ const { data } = useAsyncCache(mentionSuggestionsCache, resolverKey, {
1166
+ keepPreviousDataWhileLoading: true
1167
+ });
1168
+ return data;
1169
+ }
1170
+ const bundle = {
505
1171
  RoomContext,
506
1172
  RoomProvider,
507
1173
  useRoom,
@@ -530,6 +1196,13 @@ function createRoomContext(client) {
530
1196
  useOthersConnectionIds,
531
1197
  useOther,
532
1198
  useMutation,
1199
+ useThreads,
1200
+ useUser,
1201
+ useCreateThread,
1202
+ useEditThreadMetadata,
1203
+ useCreateComment,
1204
+ useEditComment,
1205
+ useDeleteComment,
533
1206
  suspense: {
534
1207
  RoomContext,
535
1208
  RoomProvider,
@@ -558,9 +1231,22 @@ function createRoomContext(client) {
558
1231
  useOthersMapped: useOthersMappedSuspense,
559
1232
  useOthersConnectionIds: useOthersConnectionIdsSuspense,
560
1233
  useOther: useOtherSuspense,
561
- useMutation
1234
+ useMutation,
1235
+ useThreads: useThreadsSuspense,
1236
+ useUser: useUserSuspense,
1237
+ useCreateThread,
1238
+ useEditThreadMetadata,
1239
+ useCreateComment,
1240
+ useEditComment,
1241
+ useDeleteComment
562
1242
  }
563
1243
  };
1244
+ const internalBundle = {
1245
+ ...bundle,
1246
+ hasResolveMentionSuggestions: resolveMentionSuggestions !== void 0,
1247
+ useMentionSuggestions
1248
+ };
1249
+ return bundle;
564
1250
  }
565
1251
 
566
1252
  // src/index.ts
@@ -569,6 +1255,7 @@ detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
569
1255
  export {
570
1256
  ClientSideSuspense,
571
1257
  createRoomContext,
572
- shallow2 as shallow
1258
+ shallow2 as shallow,
1259
+ useRoomContextBundle
573
1260
  };
574
1261
  //# sourceMappingURL=index.mjs.map