@liveblocks/react 1.9.2 → 1.9.4

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
@@ -5,7 +5,7 @@ import { detectDupes } from "@liveblocks/core";
5
5
 
6
6
  // src/version.ts
7
7
  var PKG_NAME = "@liveblocks/react";
8
- var PKG_VERSION = "1.9.2";
8
+ var PKG_VERSION = "1.9.4";
9
9
  var PKG_FORMAT = "esm";
10
10
 
11
11
  // src/ClientSideSuspense.tsx
@@ -29,7 +29,7 @@ import {
29
29
  stringify as stringify2
30
30
  } from "@liveblocks/core";
31
31
  import * as React3 from "react";
32
- import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
32
+ import { useSyncExternalStoreWithSelector as useSyncExternalStoreWithSelector2 } from "use-sync-external-store/shim/with-selector.js";
33
33
 
34
34
  // src/comments/CommentsRoom.tsx
35
35
  import { CommentsApiError, makeEventSource, stringify } from "@liveblocks/core";
@@ -39,10 +39,10 @@ import React2, {
39
39
  useCallback as useCallback3,
40
40
  useContext,
41
41
  useEffect as useEffect3,
42
- useMemo,
43
- useRef as useRef3
42
+ useMemo
44
43
  } from "react";
45
- import { useSyncExternalStore as useSyncExternalStore3 } from "use-sync-external-store/shim/index.js";
44
+ import { useSyncExternalStore as useSyncExternalStore3 } from "use-sync-external-store/shim";
45
+ import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
46
46
 
47
47
  // src/comments/errors.ts
48
48
  var CreateThreadError = class extends Error {
@@ -283,122 +283,17 @@ function useMutate(manager, revalidateCache) {
283
283
  );
284
284
  return mutate;
285
285
  }
286
- function useAutomaticRevalidation(manager, revalidateCache, options = {}) {
287
- const isOnline = useIsOnline();
288
- const isDocumentVisible = useIsDocumentVisible();
289
- const {
290
- revalidateOnFocus = true,
291
- revalidateOnReconnect = true,
292
- refreshInterval = 0
293
- } = options;
294
- useEffect2(() => {
295
- let revalidationTimerId;
296
- function scheduleRevalidation() {
297
- if (refreshInterval === 0)
298
- return;
299
- revalidationTimerId = window.setTimeout(() => {
300
- if (isOnline && isDocumentVisible && !manager.getError()) {
301
- void revalidateCache({ shouldDedupe: true }).then(
302
- scheduleRevalidation
303
- );
304
- return;
305
- }
306
- scheduleRevalidation();
307
- }, refreshInterval);
308
- }
309
- scheduleRevalidation();
310
- return () => {
311
- window.clearTimeout(revalidationTimerId);
312
- };
313
- }, [revalidateCache, refreshInterval, isOnline, isDocumentVisible, manager]);
314
- useEffect2(() => {
315
- function handleIsOnline() {
316
- if (revalidateOnReconnect && isDocumentVisible) {
317
- void revalidateCache({ shouldDedupe: true });
318
- }
319
- }
320
- window.addEventListener("online", handleIsOnline);
321
- return () => {
322
- window.removeEventListener("online", handleIsOnline);
323
- };
324
- }, [revalidateCache, revalidateOnReconnect, isDocumentVisible]);
325
- useEffect2(() => {
326
- function handleVisibilityChange() {
327
- const isVisible = document.visibilityState === "visible";
328
- if (revalidateOnFocus && isVisible && isOnline) {
329
- void revalidateCache({ shouldDedupe: true });
330
- }
331
- }
332
- document.addEventListener("visibilitychange", handleVisibilityChange);
333
- return () => {
334
- document.removeEventListener("visibilitychange", handleVisibilityChange);
335
- };
336
- }, [revalidateCache, revalidateOnFocus, isOnline]);
337
- }
338
286
 
339
287
  // src/comments/CommentsRoom.tsx
340
- var POLLING_INTERVAL_REALTIME = 3e4;
341
- var POLLING_INTERVAL = 5e3;
342
288
  var THREAD_ID_PREFIX = "th";
343
289
  var COMMENT_ID_PREFIX = "cm";
290
+ var POLLING_INTERVAL_REALTIME = 3e4;
291
+ var POLLING_INTERVAL = 5e3;
344
292
  function createCommentsRoom(errorEventSource) {
345
- const manager = createThreadsCacheManager();
346
- const filterOptions = /* @__PURE__ */ new Map();
347
- const cacheStates = /* @__PURE__ */ new Map();
348
- const revalidationManagers = /* @__PURE__ */ new Map();
349
- function createThreadsRevalidationManager(key) {
350
- let request;
351
- let error;
352
- return {
353
- getCache() {
354
- return void 0;
355
- },
356
- setCache(value) {
357
- const cache = new Map(
358
- (manager.getCache() ?? []).map((thread) => [thread.id, thread])
359
- );
360
- for (const thread of value) {
361
- cache.set(thread.id, thread);
362
- }
363
- setCache(key, {
364
- isLoading: false,
365
- data: value
366
- });
367
- manager.setCache(Array.from(cache.values()));
368
- },
369
- // Request
370
- getRequest() {
371
- return request;
372
- },
373
- setRequest(value) {
374
- request = value;
375
- },
376
- // Error
377
- getError() {
378
- return error;
379
- },
380
- setError(err) {
381
- error = err;
382
- manager.setError(err);
383
- },
384
- // Mutation
385
- getMutation() {
386
- return void 0;
387
- },
388
- setMutation() {
389
- }
390
- };
391
- }
392
- const eventSource = makeEventSource();
393
- const subscribe2 = eventSource.subscribe;
394
- const getCache = (key) => cacheStates.get(key);
395
- const setCache = (key, value) => {
396
- cacheStates.set(key, value);
397
- };
293
+ const store = createClientCacheStore();
398
294
  const FetcherContext = createContext(null);
399
- const CommentsEventSubscriptionContext = createContext(() => {
400
- });
401
- function getThreads() {
295
+ const RoomManagerContext = createContext(null);
296
+ function getThreads(manager) {
402
297
  const threads = manager.getCache();
403
298
  if (!threads) {
404
299
  throw new Error(
@@ -411,84 +306,118 @@ function createCommentsRoom(errorEventSource) {
411
306
  room,
412
307
  children
413
308
  }) {
414
- const commentsEventSubscribersCountRef = useRef3(0);
415
- const commentsEventDisposerRef = useRef3();
416
- const fetcher = useCallback3(async () => {
309
+ const manager = useMemo(() => {
310
+ return createRoomRevalidationManager(room.id, {
311
+ getCache: store.getThreads,
312
+ setCache: store.setThreads
313
+ });
314
+ }, [room.id]);
315
+ const fetcher = React2.useCallback(async () => {
316
+ const options = manager.getRevalidationManagers().filter(([key]) => manager.getReferenceCount(key) > 0).map(([_, manager2]) => manager2.getOptions());
417
317
  const responses = await Promise.all(
418
- Array.from(filterOptions.values()).map((info) => {
419
- return room.getThreads(info.options);
318
+ options.map(async (option) => {
319
+ return await room.getThreads(option);
420
320
  })
421
321
  );
422
322
  const threads = Array.from(
423
323
  new Map(responses.flat().map((thread) => [thread.id, thread])).values()
424
324
  );
425
325
  return threads;
426
- }, [room]);
326
+ }, [room, manager]);
427
327
  const revalidateCache = useRevalidateCache(manager, fetcher);
428
- const subscribeToCommentEvents = useCallback3(() => {
429
- const commentsEventSubscribersCount = commentsEventSubscribersCountRef.current;
430
- if (commentsEventSubscribersCount === 0) {
431
- const unsubscribe = room.events.comments.subscribe(() => {
432
- void revalidateCache({ shouldDedupe: true });
433
- });
434
- commentsEventDisposerRef.current = unsubscribe;
328
+ const status = useSyncExternalStore3(
329
+ room.events.status.subscribe,
330
+ room.getStatus,
331
+ room.getStatus
332
+ );
333
+ const isOnline = useIsOnline();
334
+ const isDocumentVisible = useIsDocumentVisible();
335
+ const refreshInterval = getPollingInterval(
336
+ isOnline,
337
+ isDocumentVisible,
338
+ status === "connected"
339
+ );
340
+ useEffect3(() => {
341
+ let revalidationTimerId;
342
+ function scheduleRevalidation() {
343
+ if (refreshInterval === 0)
344
+ return;
345
+ revalidationTimerId = window.setTimeout(() => {
346
+ if (isOnline && isDocumentVisible && !manager.getError() && manager.getTotalReferenceCount() > 0) {
347
+ void revalidateCache({ shouldDedupe: true }).then(
348
+ scheduleRevalidation
349
+ );
350
+ return;
351
+ }
352
+ scheduleRevalidation();
353
+ }, refreshInterval);
435
354
  }
436
- commentsEventSubscribersCountRef.current = commentsEventSubscribersCount + 1;
355
+ scheduleRevalidation();
437
356
  return () => {
438
- commentsEventSubscribersCountRef.current = commentsEventSubscribersCountRef.current - 1;
439
- if (commentsEventSubscribersCountRef.current > 0)
440
- return;
441
- commentsEventDisposerRef.current?.();
442
- commentsEventDisposerRef.current = void 0;
357
+ window.clearTimeout(revalidationTimerId);
443
358
  };
444
- }, [revalidateCache, room]);
359
+ }, [
360
+ revalidateCache,
361
+ refreshInterval,
362
+ isOnline,
363
+ isDocumentVisible,
364
+ manager
365
+ ]);
445
366
  useEffect3(() => {
446
- const unsubscribe = manager.subscribe("cache", (threads) => {
447
- for (const [key, info] of filterOptions.entries()) {
448
- const filtered = threads.filter((thread) => {
449
- const query = info.options.query;
450
- if (!query)
451
- return true;
452
- for (const key2 in query.metadata) {
453
- if (thread.metadata[key2] !== query.metadata[key2]) {
454
- return false;
455
- }
456
- }
457
- return true;
458
- });
459
- setCache(key, {
460
- isLoading: false,
461
- data: filtered
462
- });
463
- }
464
- for (const [key] of cacheStates.entries()) {
465
- if (filterOptions.has(key))
466
- continue;
467
- cacheStates.delete(key);
367
+ function handleIsOnline() {
368
+ if (isDocumentVisible) {
369
+ void revalidateCache({ shouldDedupe: true });
468
370
  }
469
- eventSource.notify(threads);
470
- });
371
+ }
372
+ window.addEventListener("online", handleIsOnline);
471
373
  return () => {
472
- unsubscribe();
374
+ window.removeEventListener("online", handleIsOnline);
473
375
  };
474
- }, []);
376
+ }, [revalidateCache, isDocumentVisible]);
475
377
  useEffect3(() => {
476
- const unsubscribe = manager.subscribe("error", (error) => {
477
- for (const state of cacheStates.values()) {
478
- state.error = error;
378
+ function handleVisibilityChange() {
379
+ const isVisible = document.visibilityState === "visible";
380
+ if (isVisible && isOnline) {
381
+ void revalidateCache({ shouldDedupe: true });
479
382
  }
383
+ }
384
+ document.addEventListener("visibilitychange", handleVisibilityChange);
385
+ return () => {
386
+ document.removeEventListener(
387
+ "visibilitychange",
388
+ handleVisibilityChange
389
+ );
390
+ };
391
+ }, [revalidateCache, isOnline]);
392
+ useEffect3(() => {
393
+ const unsubscribe = room.events.comments.subscribe(() => {
394
+ void revalidateCache({ shouldDedupe: false });
480
395
  });
481
396
  return () => {
482
397
  unsubscribe();
483
398
  };
484
- }, []);
485
- return /* @__PURE__ */ React2.createElement(FetcherContext.Provider, { value: fetcher }, /* @__PURE__ */ React2.createElement(
486
- CommentsEventSubscriptionContext.Provider,
487
- {
488
- value: subscribeToCommentEvents
489
- },
490
- children
491
- ));
399
+ }, [room, revalidateCache]);
400
+ return /* @__PURE__ */ React2.createElement(FetcherContext.Provider, { value: fetcher }, /* @__PURE__ */ React2.createElement(RoomManagerContext.Provider, { value: manager }, children));
401
+ }
402
+ function useRoomManager() {
403
+ const manager = useContext(RoomManagerContext);
404
+ if (manager === null) {
405
+ throw new Error("CommentsRoomProvider is missing from the React tree.");
406
+ }
407
+ return manager;
408
+ }
409
+ function getUseThreadsRevalidationManager(options, roomManager) {
410
+ const key = stringify(options);
411
+ const revalidationManager = roomManager.getRevalidationManager(key);
412
+ if (!revalidationManager) {
413
+ const useThreadsRevalidationManager = createUseThreadsRevalidationManager(
414
+ options,
415
+ roomManager
416
+ );
417
+ roomManager.setRevalidationmanager(key, useThreadsRevalidationManager);
418
+ return useThreadsRevalidationManager;
419
+ }
420
+ return revalidationManager;
492
421
  }
493
422
  function useThreadsFetcher() {
494
423
  const fetcher = useContext(FetcherContext);
@@ -497,156 +426,126 @@ function createCommentsRoom(errorEventSource) {
497
426
  }
498
427
  return fetcher;
499
428
  }
500
- function _useThreads(room, key) {
501
- const fetcher = useThreadsFetcher();
502
- const revalidateCache = useRevalidateCache(manager, fetcher);
503
- const status = useSyncExternalStore3(
504
- room.events.status.subscribe,
505
- room.getStatus,
506
- room.getStatus
507
- );
508
- const isOnline = useIsOnline();
509
- const isDocumentVisible = useIsDocumentVisible();
510
- const subscribeToCommentEvents = useContext(
511
- CommentsEventSubscriptionContext
512
- );
513
- const interval = getPollingInterval(
514
- isOnline,
515
- isDocumentVisible,
516
- status === "connected"
517
- );
518
- useAutomaticRevalidation(manager, revalidateCache, {
519
- revalidateOnFocus: true,
520
- revalidateOnReconnect: true,
521
- refreshInterval: interval
522
- });
523
- useEffect3(subscribeToCommentEvents, [subscribeToCommentEvents]);
524
- const cache = useSyncExternalStore3(
525
- subscribe2,
526
- () => getCache(key),
527
- () => getCache(key)
528
- );
529
- if (!cache || cache.isLoading) {
530
- return { isLoading: true };
531
- }
532
- return {
533
- isLoading: cache.isLoading,
534
- threads: cache.data || [],
535
- error: cache.error
536
- };
537
- }
538
429
  function useThreads(room, options = { query: { metadata: {} } }) {
539
430
  const key = useMemo(() => stringify(options), [options]);
540
- let revalidationManager = revalidationManagers.get(key);
541
- if (!revalidationManager) {
542
- revalidationManager = createThreadsRevalidationManager(key);
543
- revalidationManagers.set(key, revalidationManager);
544
- }
545
- const fetcher = useCallback3(
431
+ const manager = useRoomManager();
432
+ const useThreadsRevalidationManager = getUseThreadsRevalidationManager(
433
+ options,
434
+ manager
435
+ );
436
+ const fetcher = React2.useCallback(
546
437
  () => {
547
438
  return room.getThreads(options);
548
439
  },
549
- // eslint-disable-next-line react-hooks/exhaustive-deps
550
- [key]
440
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- The missing dependency is `options` but `key` and `normalized` are analogous, so we only include `key` as dependency. This helps minimize the number of re-renders as `options` can change on each render
441
+ [key, room]
551
442
  );
552
- const revalidateCache = useRevalidateCache(revalidationManager, fetcher);
553
- useEffect3(
554
- () => {
555
- const info = filterOptions.get(key);
556
- if (info) {
557
- info.count += 1;
558
- } else {
559
- filterOptions.set(key, {
560
- options,
561
- count: 1
562
- });
563
- cacheStates.set(key, { isLoading: true });
564
- }
565
- return () => {
566
- const info2 = filterOptions.get(key);
567
- if (!info2)
568
- return;
569
- info2.count -= 1;
570
- if (info2.count > 0)
571
- return;
572
- filterOptions.delete(key);
573
- };
574
- },
575
- // eslint-disable-next-line react-hooks/exhaustive-deps
576
- [key]
443
+ const revalidateCache = useRevalidateCache(
444
+ useThreadsRevalidationManager,
445
+ fetcher
577
446
  );
578
447
  useEffect3(() => {
579
448
  void revalidateCache({ shouldDedupe: true });
580
449
  }, [revalidateCache]);
581
- return _useThreads(room, key);
450
+ useEffect3(() => {
451
+ manager.incrementReferenceCount(key);
452
+ return () => {
453
+ manager.decrementReferenceCount(key);
454
+ };
455
+ }, [manager, key]);
456
+ const cache = _useThreads(room, options);
457
+ return cache;
582
458
  }
583
- function useThreadsSuspense(room, options = {}) {
459
+ function useThreadsSuspense(room, options = { query: { metadata: {} } }) {
584
460
  const key = useMemo(() => stringify(options), [options]);
585
- let revalidationManager = revalidationManagers.get(key);
586
- if (!revalidationManager) {
587
- revalidationManager = createThreadsRevalidationManager(key);
588
- revalidationManagers.set(key, revalidationManager);
589
- }
590
- const fetcher = useCallback3(
461
+ const manager = useRoomManager();
462
+ const useThreadsRevalidationManager = getUseThreadsRevalidationManager(
463
+ options,
464
+ manager
465
+ );
466
+ const fetcher = React2.useCallback(
591
467
  () => {
592
468
  return room.getThreads(options);
593
469
  },
594
- // eslint-disable-next-line react-hooks/exhaustive-deps
595
- [key]
470
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- The missing dependency is `options` but `key` and `normalized` are analogous, so we only include `key` as dependency. This helps minimize the number of re-renders as `options` can change on each render
471
+ [key, room]
596
472
  );
597
- const info = filterOptions.get(key);
598
- if (!info) {
599
- filterOptions.set(key, {
600
- options,
601
- count: 0
602
- });
603
- cacheStates.set(key, { isLoading: true });
604
- }
605
- const revalidateCache = useRevalidateCache(revalidationManager, fetcher);
606
- useEffect3(
607
- () => {
608
- const info2 = filterOptions.get(key);
609
- if (info2) {
610
- info2.count += 1;
611
- } else {
612
- filterOptions.set(key, {
613
- options,
614
- count: 1
615
- });
616
- }
617
- return () => {
618
- const info3 = filterOptions.get(key);
619
- if (!info3)
620
- return;
621
- info3.count -= 1;
622
- if (info3.count > 0)
623
- return;
624
- filterOptions.delete(key);
625
- };
626
- },
627
- // eslint-disable-next-line react-hooks/exhaustive-deps
628
- [key]
473
+ const revalidateCache = useRevalidateCache(
474
+ useThreadsRevalidationManager,
475
+ fetcher
629
476
  );
630
- const cache = _useThreads(room, key);
477
+ useEffect3(() => {
478
+ void revalidateCache({ shouldDedupe: true });
479
+ }, [revalidateCache]);
480
+ useEffect3(() => {
481
+ manager.incrementReferenceCount(key);
482
+ return () => {
483
+ manager.decrementReferenceCount(key);
484
+ };
485
+ }, [manager, key]);
486
+ const cache = _useThreads(room, options);
631
487
  if (cache.error) {
632
488
  throw cache.error;
633
489
  }
634
490
  if (cache.isLoading || !cache.threads) {
635
- throw revalidateCache({ shouldDedupe: true });
491
+ throw revalidateCache({
492
+ shouldDedupe: true
493
+ });
636
494
  }
637
495
  return {
496
+ isLoading: false,
638
497
  threads: cache.threads,
639
- isLoading: false
498
+ error: cache.error
640
499
  };
641
500
  }
501
+ function _useThreads(room, options) {
502
+ const manager = useRoomManager();
503
+ const useThreadsRevalidationManager = getUseThreadsRevalidationManager(
504
+ options,
505
+ manager
506
+ );
507
+ return useSyncExternalStoreWithSelector(
508
+ store.subscribe,
509
+ () => store.getThreads(),
510
+ () => store.getThreads(),
511
+ (state) => {
512
+ const isLoading = useThreadsRevalidationManager.getIsLoading();
513
+ if (isLoading) {
514
+ return {
515
+ isLoading: true
516
+ };
517
+ }
518
+ const options2 = useThreadsRevalidationManager.getOptions();
519
+ const error = useThreadsRevalidationManager.getError();
520
+ const filtered = state.filter((thread) => {
521
+ if (thread.roomId !== room.id)
522
+ return false;
523
+ const query = options2.query ?? {};
524
+ for (const key in query.metadata) {
525
+ if (thread.metadata[key] !== query.metadata[key]) {
526
+ return false;
527
+ }
528
+ }
529
+ return true;
530
+ });
531
+ return {
532
+ isLoading: false,
533
+ threads: filtered,
534
+ error
535
+ };
536
+ }
537
+ );
538
+ }
642
539
  function useEditThreadMetadata(room) {
643
- const revalidate = useRevalidateCache(manager, room.getThreads);
540
+ const manager = useRoomManager();
541
+ const fetcher = useThreadsFetcher();
542
+ const revalidate = useRevalidateCache(manager, fetcher);
644
543
  const mutate = useMutate(manager, revalidate);
645
544
  const editThreadMetadata = useCallback3(
646
545
  (options) => {
647
546
  const threadId = options.threadId;
648
547
  const metadata = "metadata" in options ? options.metadata : {};
649
- const threads = getThreads();
548
+ const threads = getThreads(manager);
650
549
  const optimisticData = threads.map(
651
550
  (thread) => thread.id === threadId ? {
652
551
  ...thread,
@@ -672,11 +571,12 @@ function createCommentsRoom(errorEventSource) {
672
571
  );
673
572
  });
674
573
  },
675
- [room, mutate]
574
+ [room, mutate, manager]
676
575
  );
677
576
  return editThreadMetadata;
678
577
  }
679
578
  function useCreateThread(room) {
579
+ const manager = useRoomManager();
680
580
  const fetcher = useThreadsFetcher();
681
581
  const revalidate = useRevalidateCache(manager, fetcher);
682
582
  const mutate = useMutate(manager, revalidate);
@@ -684,9 +584,9 @@ function createCommentsRoom(errorEventSource) {
684
584
  (options) => {
685
585
  const body = options.body;
686
586
  const metadata = "metadata" in options ? options.metadata : {};
687
- const threads = getThreads();
688
- const threadId = createOptimisticId(THREAD_ID_PREFIX);
689
- const commentId = createOptimisticId(COMMENT_ID_PREFIX);
587
+ const threads = getThreads(manager);
588
+ const threadId = createThreadId();
589
+ const commentId = createCommentId();
690
590
  const now = /* @__PURE__ */ new Date();
691
591
  const newComment = {
692
592
  id: commentId,
@@ -725,18 +625,19 @@ function createCommentsRoom(errorEventSource) {
725
625
  });
726
626
  return newThread;
727
627
  },
728
- [room, mutate]
628
+ [room, mutate, manager]
729
629
  );
730
630
  return createThread;
731
631
  }
732
632
  function useCreateComment(room) {
633
+ const manager = useRoomManager();
733
634
  const fetcher = useThreadsFetcher();
734
635
  const revalidate = useRevalidateCache(manager, fetcher);
735
636
  const mutate = useMutate(manager, revalidate);
736
637
  const createComment = useCallback3(
737
638
  ({ threadId, body }) => {
738
- const threads = getThreads();
739
- const commentId = createOptimisticId(COMMENT_ID_PREFIX);
639
+ const threads = getThreads(manager);
640
+ const commentId = createCommentId();
740
641
  const now = /* @__PURE__ */ new Date();
741
642
  const comment = {
742
643
  id: commentId,
@@ -772,16 +673,18 @@ function createCommentsRoom(errorEventSource) {
772
673
  });
773
674
  return comment;
774
675
  },
775
- [room, mutate]
676
+ [room, mutate, manager]
776
677
  );
777
678
  return createComment;
778
679
  }
779
680
  function useEditComment(room) {
780
- const revalidate = useRevalidateCache(manager, room.getThreads);
681
+ const manager = useRoomManager();
682
+ const fetcher = useThreadsFetcher();
683
+ const revalidate = useRevalidateCache(manager, fetcher);
781
684
  const mutate = useMutate(manager, revalidate);
782
685
  const editComment = useCallback3(
783
686
  ({ threadId, commentId, body }) => {
784
- const threads = getThreads();
687
+ const threads = getThreads(manager);
785
688
  const now = /* @__PURE__ */ new Date();
786
689
  const optimisticData = threads.map(
787
690
  (thread) => thread.id === threadId ? {
@@ -812,16 +715,18 @@ function createCommentsRoom(errorEventSource) {
812
715
  );
813
716
  });
814
717
  },
815
- [room, mutate]
718
+ [room, mutate, manager]
816
719
  );
817
720
  return editComment;
818
721
  }
819
722
  function useDeleteComment(room) {
820
- const revalidate = useRevalidateCache(manager, room.getThreads);
723
+ const manager = useRoomManager();
724
+ const fetcher = useThreadsFetcher();
725
+ const revalidate = useRevalidateCache(manager, fetcher);
821
726
  const mutate = useMutate(manager, revalidate);
822
727
  const deleteComment = useCallback3(
823
728
  ({ threadId, commentId }) => {
824
- const threads = getThreads();
729
+ const threads = getThreads(manager);
825
730
  const now = /* @__PURE__ */ new Date();
826
731
  const newThreads = [];
827
732
  for (const thread of threads) {
@@ -861,16 +766,18 @@ function createCommentsRoom(errorEventSource) {
861
766
  );
862
767
  });
863
768
  },
864
- [room, mutate]
769
+ [room, mutate, manager]
865
770
  );
866
771
  return deleteComment;
867
772
  }
868
773
  function useAddReaction(room) {
869
- const revalidate = useRevalidateCache(manager, room.getThreads);
774
+ const manager = useRoomManager();
775
+ const fetcher = useThreadsFetcher();
776
+ const revalidate = useRevalidateCache(manager, fetcher);
870
777
  const mutate = useMutate(manager, revalidate);
871
778
  const createComment = useCallback3(
872
779
  ({ threadId, commentId, emoji }) => {
873
- const threads = getThreads();
780
+ const threads = getThreads(manager);
874
781
  const now = /* @__PURE__ */ new Date();
875
782
  const userId = getCurrentUserId(room);
876
783
  const optimisticData = threads.map(
@@ -924,16 +831,18 @@ function createCommentsRoom(errorEventSource) {
924
831
  );
925
832
  });
926
833
  },
927
- [room, mutate]
834
+ [room, mutate, manager]
928
835
  );
929
836
  return createComment;
930
837
  }
931
838
  function useRemoveReaction(room) {
932
- const revalidate = useRevalidateCache(manager, room.getThreads);
839
+ const manager = useRoomManager();
840
+ const fetcher = useThreadsFetcher();
841
+ const revalidate = useRevalidateCache(manager, fetcher);
933
842
  const mutate = useMutate(manager, revalidate);
934
843
  const createComment = useCallback3(
935
844
  ({ threadId, commentId, emoji }) => {
936
- const threads = getThreads();
845
+ const threads = getThreads(manager);
937
846
  const userId = getCurrentUserId(room);
938
847
  const optimisticData = threads.map(
939
848
  (thread) => thread.id === threadId ? {
@@ -985,7 +894,7 @@ function createCommentsRoom(errorEventSource) {
985
894
  );
986
895
  });
987
896
  },
988
- [room, mutate]
897
+ [room, mutate, manager]
989
898
  );
990
899
  return createComment;
991
900
  }
@@ -1005,6 +914,12 @@ function createCommentsRoom(errorEventSource) {
1005
914
  function createOptimisticId(prefix) {
1006
915
  return `${prefix}_${nanoid()}`;
1007
916
  }
917
+ function createThreadId() {
918
+ return createOptimisticId(THREAD_ID_PREFIX);
919
+ }
920
+ function createCommentId() {
921
+ return createOptimisticId(COMMENT_ID_PREFIX);
922
+ }
1008
923
  function getCurrentUserId(room) {
1009
924
  const self = room.getSelf();
1010
925
  if (self === null || self.id === void 0) {
@@ -1021,31 +936,35 @@ function handleCommentsApiError(err) {
1021
936
  }
1022
937
  return new Error(message);
1023
938
  }
1024
- function getPollingInterval(isBrowserOnline, isDocumentVisible, isRoomConnected) {
1025
- if (!isBrowserOnline || !isDocumentVisible)
1026
- return;
1027
- if (isRoomConnected)
1028
- return POLLING_INTERVAL_REALTIME;
1029
- return POLLING_INTERVAL;
1030
- }
1031
- function createThreadsCacheManager() {
1032
- let cache;
939
+ function createRoomRevalidationManager(roomId, {
940
+ getCache,
941
+ setCache
942
+ }) {
1033
943
  let request;
1034
944
  let error;
1035
945
  let mutation;
1036
- const cacheEventSource = makeEventSource();
1037
- const errorEventSource = makeEventSource();
946
+ const revalidationManagerByOptions = /* @__PURE__ */ new Map();
947
+ const referenceCountByOptions = /* @__PURE__ */ new Map();
1038
948
  return {
1039
949
  // Cache
1040
950
  getCache() {
1041
- return cache;
951
+ const threads = getCache();
952
+ const filtered = threads.filter((thread) => thread.roomId === roomId);
953
+ return filtered;
1042
954
  },
1043
955
  setCache(value) {
956
+ for (const key of revalidationManagerByOptions.keys()) {
957
+ if (referenceCountByOptions.get(key) === 0) {
958
+ revalidationManagerByOptions.delete(key);
959
+ referenceCountByOptions.delete(key);
960
+ }
961
+ }
1044
962
  const sorted = value.sort(
1045
963
  (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
1046
964
  );
1047
- cache = sorted;
1048
- cacheEventSource.notify(cache);
965
+ const threads = getCache();
966
+ const newThreads = threads.filter((thread) => thread.roomId !== roomId).concat(sorted);
967
+ setCache(newThreads);
1049
968
  },
1050
969
  // Request
1051
970
  getRequest() {
@@ -1060,7 +979,6 @@ function createThreadsCacheManager() {
1060
979
  },
1061
980
  setError(err) {
1062
981
  error = err;
1063
- errorEventSource.notify(err);
1064
982
  },
1065
983
  // Mutation
1066
984
  getMutation() {
@@ -1069,25 +987,117 @@ function createThreadsCacheManager() {
1069
987
  setMutation(info) {
1070
988
  mutation = info;
1071
989
  },
1072
- // Subscription
1073
- subscribe(type, callback) {
1074
- switch (type) {
1075
- case "cache":
1076
- return cacheEventSource.subscribe(
1077
- callback
1078
- );
1079
- case "error":
1080
- return errorEventSource.subscribe(callback);
990
+ getRevalidationManagers() {
991
+ return Array.from(revalidationManagerByOptions.entries());
992
+ },
993
+ getRevalidationManager(key) {
994
+ return revalidationManagerByOptions.get(key);
995
+ },
996
+ setRevalidationmanager(key, manager) {
997
+ revalidationManagerByOptions.set(key, manager);
998
+ },
999
+ getTotalReferenceCount() {
1000
+ return Array.from(referenceCountByOptions.values()).reduce(
1001
+ (acc, count) => acc + count,
1002
+ 0
1003
+ );
1004
+ },
1005
+ incrementReferenceCount(key) {
1006
+ const count = referenceCountByOptions.get(key) ?? 0;
1007
+ referenceCountByOptions.set(key, count + 1);
1008
+ },
1009
+ decrementReferenceCount(key) {
1010
+ const count = referenceCountByOptions.get(key) ?? 0;
1011
+ referenceCountByOptions.set(key, count - 1);
1012
+ },
1013
+ getReferenceCount(key) {
1014
+ return referenceCountByOptions.get(key) ?? 0;
1015
+ }
1016
+ };
1017
+ }
1018
+ function createClientCacheStore() {
1019
+ let threads = [];
1020
+ const threadsEventSource = makeEventSource();
1021
+ return {
1022
+ getThreads() {
1023
+ return threads;
1024
+ },
1025
+ setThreads(value) {
1026
+ threads = value;
1027
+ threadsEventSource.notify(threads);
1028
+ },
1029
+ subscribe(callback) {
1030
+ return threadsEventSource.subscribe(callback);
1031
+ }
1032
+ };
1033
+ }
1034
+ function createUseThreadsRevalidationManager(options, manager) {
1035
+ let isLoading = true;
1036
+ let request;
1037
+ let error;
1038
+ return {
1039
+ // Cache
1040
+ getCache() {
1041
+ return void 0;
1042
+ },
1043
+ setCache(value) {
1044
+ const cache = new Map(
1045
+ (manager.getCache() ?? []).map((thread) => [thread.id, thread])
1046
+ );
1047
+ for (const thread of value) {
1048
+ cache.set(thread.id, thread);
1081
1049
  }
1050
+ manager.setCache(Array.from(cache.values()));
1051
+ isLoading = false;
1052
+ },
1053
+ // Request
1054
+ getRequest() {
1055
+ return request;
1056
+ },
1057
+ setRequest(value) {
1058
+ request = value;
1059
+ },
1060
+ // Error
1061
+ getError() {
1062
+ return error;
1063
+ },
1064
+ setError(err) {
1065
+ error = err;
1066
+ isLoading = false;
1067
+ const cache = manager.getCache();
1068
+ manager.setCache(cache);
1069
+ },
1070
+ // Mutation
1071
+ getMutation() {
1072
+ return void 0;
1073
+ },
1074
+ setMutation(_) {
1075
+ return;
1076
+ },
1077
+ getOptions() {
1078
+ return options;
1079
+ },
1080
+ getIsLoading() {
1081
+ return isLoading;
1082
+ },
1083
+ setIsLoading(value) {
1084
+ isLoading = value;
1082
1085
  }
1083
1086
  };
1084
1087
  }
1088
+ function getPollingInterval(isBrowserOnline, isDocumentVisible, isRoomConnected) {
1089
+ if (!isBrowserOnline || !isDocumentVisible)
1090
+ return;
1091
+ if (isRoomConnected)
1092
+ return POLLING_INTERVAL_REALTIME;
1093
+ return POLLING_INTERVAL;
1094
+ }
1085
1095
 
1086
1096
  // src/comments/lib/use-debounce.ts
1087
- import { useEffect as useEffect4, useRef as useRef4, useState as useState2 } from "react";
1097
+ import { useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
1088
1098
  var DEFAULT_DELAY = 500;
1089
1099
  function useDebounce(value, delay = DEFAULT_DELAY) {
1090
- const timeout = useRef4();
1100
+ const timeout = useRef3();
1091
1101
  const [debouncedValue, setDebouncedValue] = useState2(value);
1092
1102
  useEffect4(() => {
1093
1103
  if (delay === false) {
@@ -1108,7 +1118,7 @@ function useDebounce(value, delay = DEFAULT_DELAY) {
1108
1118
  }
1109
1119
 
1110
1120
  // src/lib/use-async-cache.ts
1111
- import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef5 } from "react";
1121
+ import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef4 } from "react";
1112
1122
  import { useSyncExternalStore as useSyncExternalStore4 } from "use-sync-external-store/shim/index.js";
1113
1123
 
1114
1124
  // src/lib/use-initial.ts
@@ -1145,7 +1155,7 @@ function useAsyncCache(cache, key, options) {
1145
1155
  );
1146
1156
  const revalidate = useCallback4(() => cacheItem?.revalidate(), [cacheItem]);
1147
1157
  const state = useSyncExternalStore4(subscribe2, getState, getState);
1148
- const previousData = useRef5();
1158
+ const previousData = useRef4();
1149
1159
  let data = state.data;
1150
1160
  useEffect5(() => {
1151
1161
  previousData.current = { key, data: state.data };
@@ -1186,9 +1196,9 @@ function useAsyncCache(cache, key, options) {
1186
1196
  }
1187
1197
 
1188
1198
  // src/lib/use-latest.ts
1189
- import { useEffect as useEffect6, useRef as useRef6 } from "react";
1199
+ import { useEffect as useEffect6, useRef as useRef5 } from "react";
1190
1200
  function useLatest(value) {
1191
- const ref = useRef6(value);
1201
+ const ref = useRef5(value);
1192
1202
  useEffect6(() => {
1193
1203
  ref.current = value;
1194
1204
  }, [value]);
@@ -1225,7 +1235,7 @@ var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\
1225
1235
  Why? Please see https://liveblocks.io/docs/platform/troubleshooting#stale-props-zombie-child for more information`;
1226
1236
  var superfluous_unstable_batchedUpdates = "You don\u2019t need to pass unstable_batchedUpdates to RoomProvider anymore, since you\u2019re on React 18+ already.";
1227
1237
  function useSyncExternalStore5(s, gs, gss) {
1228
- return useSyncExternalStoreWithSelector(s, gs, gss, identity);
1238
+ return useSyncExternalStoreWithSelector2(s, gs, gss, identity);
1229
1239
  }
1230
1240
  var STABLE_EMPTY_LIST = Object.freeze([]);
1231
1241
  function alwaysEmptyList() {
@@ -1394,7 +1404,7 @@ function createRoomContext(client, options) {
1394
1404
  const subscribe2 = room.events.others.subscribe;
1395
1405
  const getSnapshot2 = room.getOthers;
1396
1406
  const getServerSnapshot = alwaysEmptyList;
1397
- return useSyncExternalStoreWithSelector(
1407
+ return useSyncExternalStoreWithSelector2(
1398
1408
  subscribe2,
1399
1409
  getSnapshot2,
1400
1410
  getServerSnapshot,
@@ -1508,7 +1518,7 @@ function createRoomContext(client, options) {
1508
1518
  [selector]
1509
1519
  );
1510
1520
  const getServerSnapshot = alwaysNull;
1511
- return useSyncExternalStoreWithSelector(
1521
+ return useSyncExternalStoreWithSelector2(
1512
1522
  subscribe2,
1513
1523
  getSnapshot2,
1514
1524
  getServerSnapshot,
@@ -1608,7 +1618,7 @@ function createRoomContext(client, options) {
1608
1618
  }
1609
1619
  }, [rootOrNull]);
1610
1620
  const getServerSnapshot = alwaysNull;
1611
- return useSyncExternalStoreWithSelector(
1621
+ return useSyncExternalStoreWithSelector2(
1612
1622
  subscribe2,
1613
1623
  getSnapshot2,
1614
1624
  getServerSnapshot,