@liveblocks/react 2.15.0 → 2.15.2

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.
@@ -85,6 +85,18 @@ function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnaps
85
85
  return value;
86
86
  }
87
87
 
88
+ // src/use-signal.ts
89
+ var identity = (value) => value;
90
+ function useSignal(signal, selector, isEqual) {
91
+ return useSyncExternalStoreWithSelector(
92
+ signal.subscribe,
93
+ signal.get,
94
+ signal.get,
95
+ selector ?? identity,
96
+ isEqual
97
+ );
98
+ }
99
+
88
100
  // src/liveblocks.tsx
89
101
  import {
90
102
  assert,
@@ -92,7 +104,7 @@ import {
92
104
  kInternal as kInternal2,
93
105
  makePoller,
94
106
  raise,
95
- shallow as shallow3
107
+ shallow as shallow4
96
108
  } from "@liveblocks/core";
97
109
  import {
98
110
  createContext as createContext2,
@@ -121,19 +133,30 @@ var config = {
121
133
  NOTIFICATION_SETTINGS_MAX_STALE_TIME: 5 * SECONDS
122
134
  };
123
135
 
124
- // src/lib/shallow2.ts
125
- import { isPlainObject, shallow } from "@liveblocks/core";
126
- function shallow2(a, b) {
127
- if (!isPlainObject(a) || !isPlainObject(b)) {
128
- return shallow(a, b);
136
+ // src/lib/AsyncResult.ts
137
+ var ASYNC_LOADING = Object.freeze({ isLoading: true });
138
+ var ASYNC_ERR = (error) => Object.freeze({ isLoading: false, error });
139
+ function ASYNC_OK(fieldOrData, data) {
140
+ if (arguments.length === 1) {
141
+ return Object.freeze({ isLoading: false, data: fieldOrData });
142
+ } else {
143
+ return Object.freeze({ isLoading: false, [fieldOrData]: data });
129
144
  }
130
- const keysA = Object.keys(a);
131
- if (keysA.length !== Object.keys(b).length) {
132
- return false;
145
+ }
146
+
147
+ // src/lib/itertools.ts
148
+ function find(it, predicate) {
149
+ for (const item of it) {
150
+ if (predicate(item)) return item;
133
151
  }
134
- return keysA.every(
135
- (key) => Object.prototype.hasOwnProperty.call(b, key) && shallow(a[key], b[key])
136
- );
152
+ return void 0;
153
+ }
154
+ function count(it, predicate) {
155
+ let total = 0;
156
+ for (const item of it) {
157
+ if (predicate(item)) total++;
158
+ }
159
+ return total;
137
160
  }
138
161
 
139
162
  // src/lib/use-initial.ts
@@ -199,15 +222,17 @@ import {
199
222
  batch as batch2,
200
223
  compactObject,
201
224
  console as console2,
225
+ DefaultMap,
202
226
  DerivedSignal,
203
227
  HttpError,
204
228
  kInternal,
205
- makeEventSource,
206
229
  MutableSignal as MutableSignal2,
207
230
  nanoid,
208
231
  nn,
232
+ shallow as shallow3,
209
233
  Signal,
210
- stringify
234
+ stringify,
235
+ unstringify
211
236
  } from "@liveblocks/core";
212
237
 
213
238
  // src/lib/autobind.ts
@@ -227,12 +252,19 @@ function autobind(self) {
227
252
  } while ((obj = Reflect.getPrototypeOf(obj)) && obj !== Object.prototype);
228
253
  }
229
254
 
230
- // src/lib/itertools.ts
231
- function find(it, predicate) {
232
- for (const item of it) {
233
- if (predicate(item)) return item;
255
+ // src/lib/shallow2.ts
256
+ import { isPlainObject, shallow } from "@liveblocks/core";
257
+ function shallow2(a, b) {
258
+ if (!isPlainObject(a) || !isPlainObject(b)) {
259
+ return shallow(a, b);
234
260
  }
235
- return void 0;
261
+ const keysA = Object.keys(a);
262
+ if (keysA.length !== Object.keys(b).length) {
263
+ return false;
264
+ }
265
+ return keysA.every(
266
+ (key) => Object.prototype.hasOwnProperty.call(b, key) && shallow(a[key], b[key])
267
+ );
236
268
  }
237
269
 
238
270
  // src/ThreadDB.ts
@@ -354,12 +386,12 @@ var ThreadDB = class _ThreadDB {
354
386
  this.upsert(thread);
355
387
  }
356
388
  }
357
- applyDelta(updates) {
389
+ applyDelta(newThreads, deletedThreads) {
358
390
  batch(() => {
359
- for (const thread of updates.newThreads) {
391
+ for (const thread of newThreads) {
360
392
  this.upsertIfNewer(thread);
361
393
  }
362
- for (const { id, deletedAt } of updates.deletedThreads) {
394
+ for (const { id, deletedAt } of deletedThreads) {
363
395
  const existing = this.getEvenIfDeleted(id);
364
396
  if (!existing) continue;
365
397
  this.delete(id, deletedAt);
@@ -400,16 +432,10 @@ var ThreadDB = class _ThreadDB {
400
432
 
401
433
  // src/umbrella-store.ts
402
434
  function makeRoomThreadsQueryKey(roomId, query) {
403
- return `${roomId}-${stringify(query ?? {})}`;
435
+ return stringify([roomId, query ?? {}]);
404
436
  }
405
437
  function makeUserThreadsQueryKey(query) {
406
- return `USER_THREADS:${stringify(query ?? {})}`;
407
- }
408
- function makeNotificationSettingsQueryKey(roomId) {
409
- return `${roomId}:NOTIFICATION_SETTINGS`;
410
- }
411
- function makeVersionsQueryKey(roomId) {
412
- return `${roomId}-VERSIONS`;
438
+ return stringify(query ?? {});
413
439
  }
414
440
  function usify(promise) {
415
441
  if ("status" in promise) {
@@ -430,53 +456,50 @@ function usify(promise) {
430
456
  return usable;
431
457
  }
432
458
  var noop2 = Promise.resolve();
433
- var ASYNC_LOADING = Object.freeze({ isLoading: true });
434
459
  var PaginatedResource = class {
435
- observable;
436
- #eventSource;
460
+ #signal;
461
+ signal;
437
462
  #fetchPage;
438
- #paginationState;
439
- // Should be null while in loading or error state!
440
463
  #pendingFetchMore;
441
464
  constructor(fetchPage) {
442
- this.#paginationState = null;
465
+ this.#signal = new Signal(ASYNC_LOADING);
443
466
  this.#fetchPage = fetchPage;
444
- this.#eventSource = makeEventSource();
445
467
  this.#pendingFetchMore = null;
446
- this.observable = this.#eventSource.observable;
468
+ this.signal = this.#signal.asReadonly();
447
469
  autobind(this);
448
470
  }
449
- #patchPaginationState(patch) {
450
- const state = this.#paginationState;
451
- if (state === null) return;
452
- this.#paginationState = { ...state, ...patch };
453
- this.#eventSource.notify();
471
+ get() {
472
+ return this.#signal.get();
473
+ }
474
+ #patch(patch) {
475
+ const state = this.#signal.get();
476
+ if (state.data === void 0) return;
477
+ this.#signal.set(ASYNC_OK({ ...state.data, ...patch }));
454
478
  }
455
479
  async #fetchMore() {
456
- const state = this.#paginationState;
457
- if (!state?.cursor) {
480
+ const state = this.#signal.get();
481
+ if (!state.data?.cursor || state.data.isFetchingMore) {
458
482
  return;
459
483
  }
460
- this.#patchPaginationState({ isFetchingMore: true });
484
+ this.#patch({ isFetchingMore: true });
461
485
  try {
462
- const nextCursor = await this.#fetchPage(state.cursor);
463
- this.#patchPaginationState({
486
+ const nextCursor = await this.#fetchPage(state.data.cursor);
487
+ this.#patch({
464
488
  cursor: nextCursor,
489
+ hasFetchedAll: nextCursor === null,
465
490
  fetchMoreError: void 0,
466
491
  isFetchingMore: false
467
492
  });
468
493
  } catch (err) {
469
- this.#patchPaginationState({
494
+ this.#patch({
470
495
  isFetchingMore: false,
471
496
  fetchMoreError: err
472
497
  });
473
498
  }
474
499
  }
475
500
  fetchMore() {
476
- const state = this.#paginationState;
477
- if (state?.cursor === null) {
478
- return noop2;
479
- }
501
+ const state = this.#signal.get();
502
+ if (!state.data?.cursor) return noop2;
480
503
  if (!this.#pendingFetchMore) {
481
504
  this.#pendingFetchMore = this.#fetchMore().finally(() => {
482
505
  this.#pendingFetchMore = null;
@@ -484,31 +507,12 @@ var PaginatedResource = class {
484
507
  }
485
508
  return this.#pendingFetchMore;
486
509
  }
487
- get() {
488
- const usable = this.#cachedPromise;
489
- if (usable === null || usable.status === "pending") {
490
- return ASYNC_LOADING;
491
- }
492
- if (usable.status === "rejected") {
493
- return { isLoading: false, error: usable.reason };
494
- }
495
- const state = this.#paginationState;
496
- return {
497
- isLoading: false,
498
- data: {
499
- fetchMore: this.fetchMore,
500
- isFetchingMore: state.isFetchingMore,
501
- fetchMoreError: state.fetchMoreError,
502
- hasFetchedAll: state.cursor === null
503
- }
504
- };
505
- }
506
510
  #cachedPromise = null;
507
511
  waitUntilLoaded() {
508
512
  if (this.#cachedPromise) {
509
513
  return this.#cachedPromise;
510
514
  }
511
- const initialFetcher = autoRetry(
515
+ const initialPageFetch$ = autoRetry(
512
516
  () => this.#fetchPage(
513
517
  /* cursor */
514
518
  void 0
@@ -516,67 +520,64 @@ var PaginatedResource = class {
516
520
  5,
517
521
  [5e3, 5e3, 1e4, 15e3]
518
522
  );
519
- const promise = usify(
520
- initialFetcher.then((cursor) => {
521
- this.#paginationState = {
522
- cursor,
523
- isFetchingMore: false,
524
- fetchMoreError: void 0
525
- };
526
- })
527
- );
523
+ const promise = usify(initialPageFetch$);
528
524
  promise.then(
529
- () => this.#eventSource.notify(),
530
- () => {
531
- this.#eventSource.notify();
525
+ (cursor) => {
526
+ this.#signal.set(
527
+ ASYNC_OK({
528
+ cursor,
529
+ hasFetchedAll: cursor === null,
530
+ isFetchingMore: false,
531
+ fetchMoreError: void 0,
532
+ fetchMore: this.fetchMore
533
+ })
534
+ );
535
+ },
536
+ (err) => {
537
+ this.#signal.set(ASYNC_ERR(err));
532
538
  setTimeout(() => {
533
539
  this.#cachedPromise = null;
534
- this.#eventSource.notify();
540
+ this.#signal.set(ASYNC_LOADING);
535
541
  }, 5e3);
536
542
  }
537
543
  );
538
544
  this.#cachedPromise = promise;
539
- return promise;
545
+ return this.#cachedPromise;
540
546
  }
541
547
  };
542
548
  var SinglePageResource = class {
543
- observable;
544
- #eventSource;
549
+ #signal;
550
+ signal;
545
551
  #fetchPage;
546
552
  constructor(fetchPage) {
553
+ this.#signal = new Signal(ASYNC_LOADING);
554
+ this.signal = this.#signal.asReadonly();
547
555
  this.#fetchPage = fetchPage;
548
- this.#eventSource = makeEventSource();
549
- this.observable = this.#eventSource.observable;
550
556
  autobind(this);
551
557
  }
552
558
  get() {
553
- const usable = this.#cachedPromise;
554
- if (usable === null || usable.status === "pending") {
555
- return ASYNC_LOADING;
556
- } else if (usable.status === "rejected") {
557
- return { isLoading: false, error: usable.reason };
558
- } else {
559
- return { isLoading: false, data: void 0 };
560
- }
559
+ return this.#signal.get();
561
560
  }
562
561
  #cachedPromise = null;
563
562
  waitUntilLoaded() {
564
563
  if (this.#cachedPromise) {
565
564
  return this.#cachedPromise;
566
565
  }
567
- const initialFetcher = autoRetry(
566
+ const initialFetcher$ = autoRetry(
568
567
  () => this.#fetchPage(),
569
568
  5,
570
569
  [5e3, 5e3, 1e4, 15e3]
571
570
  );
572
- const promise = usify(initialFetcher);
571
+ const promise = usify(initialFetcher$);
573
572
  promise.then(
574
- () => this.#eventSource.notify(),
575
573
  () => {
576
- this.#eventSource.notify();
574
+ this.#signal.set(ASYNC_OK(void 0));
575
+ },
576
+ (err) => {
577
+ this.#signal.set(ASYNC_ERR(err));
577
578
  setTimeout(() => {
578
579
  this.#cachedPromise = null;
579
- this.#eventSource.notify();
580
+ this.#signal.set(ASYNC_LOADING);
580
581
  }, 5e3);
581
582
  }
582
583
  );
@@ -597,7 +598,7 @@ function createStore_forNotifications() {
597
598
  });
598
599
  }
599
600
  function markAllRead(readAt) {
600
- return signal.mutate((lut) => {
601
+ signal.mutate((lut) => {
601
602
  for (const n of lut.values()) {
602
603
  n.readAt = readAt;
603
604
  }
@@ -609,10 +610,10 @@ function createStore_forNotifications() {
609
610
  function clear() {
610
611
  signal.mutate((lut) => lut.clear());
611
612
  }
612
- function applyDelta(newInboxNotifications, deletedNotifications) {
613
+ function applyDelta(newNotifications, deletedNotifications) {
613
614
  signal.mutate((lut) => {
614
615
  let mutated = false;
615
- for (const n of newInboxNotifications) {
616
+ for (const n of newNotifications) {
616
617
  const existing = lut.get(n.id);
617
618
  if (existing) {
618
619
  const result = compareInboxNotifications(existing, n);
@@ -643,6 +644,11 @@ function createStore_forNotifications() {
643
644
  return true;
644
645
  });
645
646
  }
647
+ function upsert(notification) {
648
+ signal.mutate((lut) => {
649
+ lut.set(notification.id, notification);
650
+ });
651
+ }
646
652
  return {
647
653
  signal: signal.asReadonly(),
648
654
  // Mutations
@@ -652,66 +658,70 @@ function createStore_forNotifications() {
652
658
  applyDelta,
653
659
  clear,
654
660
  updateAssociatedNotification,
655
- // XXX_vincent Remove this eventually
656
- force_set: (mutationCallback) => signal.mutate(mutationCallback),
657
- invalidate: () => signal.mutate()
661
+ upsert
658
662
  };
659
663
  }
660
- function createStore_forRoomNotificationSettings() {
661
- const signal = new MutableSignal2(/* @__PURE__ */ new Map());
664
+ function createStore_forRoomNotificationSettings(updates) {
665
+ const baseSignal = new MutableSignal2(/* @__PURE__ */ new Map());
662
666
  function update(roomId, settings) {
663
- signal.mutate((lut) => {
667
+ baseSignal.mutate((lut) => {
664
668
  lut.set(roomId, settings);
665
669
  });
666
670
  }
667
671
  return {
668
- signal: signal.asReadonly(),
672
+ signal: DerivedSignal.from(
673
+ baseSignal,
674
+ updates,
675
+ (base, updates2) => applyOptimisticUpdates_forSettings(base, updates2)
676
+ ),
669
677
  // Mutations
670
- update,
671
- // XXX_vincent Remove this eventually
672
- invalidate: () => signal.mutate()
678
+ update
673
679
  };
674
680
  }
675
681
  function createStore_forHistoryVersions() {
676
- const signal = new MutableSignal2(/* @__PURE__ */ new Map());
682
+ const baseSignal = new MutableSignal2(
683
+ new DefaultMap(() => /* @__PURE__ */ new Map())
684
+ );
677
685
  function update(roomId, versions) {
678
- signal.mutate((lut) => {
679
- const versionsById = lut.get(roomId) ?? (lut.set(roomId, /* @__PURE__ */ new Map()), lut.get(roomId));
686
+ baseSignal.mutate((lut) => {
687
+ const versionsById = lut.getOrCreate(roomId);
680
688
  for (const version of versions) {
681
689
  versionsById.set(version.id, version);
682
690
  }
683
691
  });
684
692
  }
685
693
  return {
686
- signal: signal.asReadonly(),
694
+ signal: DerivedSignal.from(
695
+ baseSignal,
696
+ (hv) => Object.fromEntries(
697
+ [...hv].map(([roomId, versions]) => [
698
+ roomId,
699
+ Object.fromEntries(versions)
700
+ ])
701
+ )
702
+ ),
687
703
  // Mutations
688
- update,
689
- // XXX_vincent Remove these eventually
690
- force_set: (callback) => signal.mutate(callback),
691
- invalidate: () => signal.mutate()
704
+ update
692
705
  };
693
706
  }
694
707
  function createStore_forPermissionHints() {
695
- const signal = new Signal({});
708
+ const signal = new MutableSignal2(
709
+ new DefaultMap(() => /* @__PURE__ */ new Set())
710
+ );
696
711
  function update(newHints) {
697
- signal.set((prev) => {
698
- const permissionsByRoom = { ...prev };
712
+ signal.mutate((lut) => {
699
713
  for (const [roomId, newPermissions] of Object.entries(newHints)) {
700
- const existing = permissionsByRoom[roomId] ?? /* @__PURE__ */ new Set();
714
+ const existing = lut.getOrCreate(roomId);
701
715
  for (const permission of newPermissions) {
702
716
  existing.add(permission);
703
717
  }
704
- permissionsByRoom[roomId] = existing;
705
718
  }
706
- return permissionsByRoom;
707
719
  });
708
720
  }
709
721
  return {
710
722
  signal: signal.asReadonly(),
711
723
  // Mutations
712
- update,
713
- // XXX_vincent Remove this eventually
714
- invalidate: () => signal.set((store) => ({ ...store }))
724
+ update
715
725
  };
716
726
  }
717
727
  function createStore_forOptimistic(client) {
@@ -735,9 +745,7 @@ function createStore_forOptimistic(client) {
735
745
  signal: signal.asReadonly(),
736
746
  // Mutations
737
747
  add,
738
- remove,
739
- // XXX_vincent Remove this eventually
740
- invalidate: () => signal.set((store) => [...store])
748
+ remove
741
749
  };
742
750
  }
743
751
  var UmbrellaStore = class {
@@ -787,48 +795,37 @@ var UmbrellaStore = class {
787
795
  // threads and notifications separately, but the threadifications signal will
788
796
  // be updated whenever either of them change.
789
797
  //
790
- // XXX_vincent APIs like getRoomThreadsLoadingState should really also be modeled as output signals.
791
- //
792
798
  outputs;
793
799
  // Notifications
794
800
  #notificationsLastRequestedAt = null;
795
801
  // Keeps track of when we successfully requested an inbox notifications update for the last time. Will be `null` as long as the first successful fetch hasn't happened yet.
796
- #notifications;
802
+ #notificationsPaginationState;
797
803
  // Room Threads
798
804
  #roomThreadsLastRequestedAtByRoom = /* @__PURE__ */ new Map();
799
- #roomThreads = /* @__PURE__ */ new Map();
800
805
  // User Threads
801
806
  #userThreadsLastRequestedAt = null;
802
- #userThreads = /* @__PURE__ */ new Map();
803
807
  // Room versions
804
- #roomVersions = /* @__PURE__ */ new Map();
805
808
  #roomVersionsLastRequestedAtByRoom = /* @__PURE__ */ new Map();
806
- // Room notification settings
807
- #roomNotificationSettings = /* @__PURE__ */ new Map();
808
809
  constructor(client) {
809
810
  this.#client = client[kInternal].as();
810
811
  this.optimisticUpdates = createStore_forOptimistic(this.#client);
811
812
  this.permissionHints = createStore_forPermissionHints();
812
- const inboxFetcher = async (cursor) => {
813
- const result = await this.#client.getInboxNotifications({ cursor });
814
- this.updateThreadifications(result.threads, result.inboxNotifications);
815
- if (this.#notificationsLastRequestedAt === null) {
816
- this.#notificationsLastRequestedAt = result.requestedAt;
813
+ this.#notificationsPaginationState = new PaginatedResource(
814
+ async (cursor) => {
815
+ const result = await this.#client.getInboxNotifications({ cursor });
816
+ this.updateThreadifications(result.threads, result.inboxNotifications);
817
+ if (this.#notificationsLastRequestedAt === null) {
818
+ this.#notificationsLastRequestedAt = result.requestedAt;
819
+ }
820
+ const nextCursor = result.nextCursor;
821
+ return nextCursor;
817
822
  }
818
- const nextCursor = result.nextCursor;
819
- return nextCursor;
820
- };
821
- this.#notifications = new PaginatedResource(inboxFetcher);
822
- this.#notifications.observable.subscribe(
823
- () => (
824
- // Note that the store itself does not change, but it's only vehicle at
825
- // the moment to trigger a re-render, so we'll do a no-op update here.
826
- this.invalidateEntireStore()
827
- )
828
823
  );
829
824
  this.threads = new ThreadDB();
830
825
  this.notifications = createStore_forNotifications();
831
- this.roomNotificationSettings = createStore_forRoomNotificationSettings();
826
+ this.roomNotificationSettings = createStore_forRoomNotificationSettings(
827
+ this.optimisticUpdates.signal
828
+ );
832
829
  this.historyVersions = createStore_forHistoryVersions();
833
830
  const threadifications = DerivedSignal.from(
834
831
  this.threads.signal,
@@ -836,184 +833,190 @@ var UmbrellaStore = class {
836
833
  this.optimisticUpdates.signal,
837
834
  (ts, ns, updates) => applyOptimisticUpdates_forThreadifications(ts, ns, updates)
838
835
  );
839
- const threads = DerivedSignal.from(threadifications, (s) => ({
840
- threadsDB: s.threadsDB
841
- }));
842
- const notifications = DerivedSignal.from(threadifications, (s) => ({
843
- sortedNotifications: s.sortedNotifications,
844
- notificationsById: s.notificationsById
845
- }));
846
- const settingsByRoomId = DerivedSignal.from(
847
- this.roomNotificationSettings.signal,
848
- this.optimisticUpdates.signal,
849
- (settings, updates) => applyOptimisticUpdates_forSettings(settings, updates)
836
+ const threads = DerivedSignal.from(threadifications, (s) => s.threadsDB);
837
+ const notifications = DerivedSignal.from(
838
+ threadifications,
839
+ (s) => ({
840
+ sortedNotifications: s.sortedNotifications,
841
+ notificationsById: s.notificationsById
842
+ }),
843
+ shallow3
850
844
  );
851
- const versionsByRoomId = DerivedSignal.from(
852
- this.historyVersions.signal,
853
- (hv) => Object.fromEntries(
854
- [...hv].map(([roomId, versions]) => [
855
- roomId,
856
- Object.fromEntries(versions)
857
- ])
858
- )
845
+ const loadingUserThreads = new DefaultMap(
846
+ (queryKey) => {
847
+ const query = unstringify(queryKey);
848
+ const resource = new PaginatedResource(async (cursor) => {
849
+ const result = await this.#client[kInternal].httpClient.getUserThreads_experimental({
850
+ cursor,
851
+ query
852
+ });
853
+ this.updateThreadifications(
854
+ result.threads,
855
+ result.inboxNotifications
856
+ );
857
+ this.permissionHints.update(result.permissionHints);
858
+ if (this.#userThreadsLastRequestedAt === null) {
859
+ this.#userThreadsLastRequestedAt = result.requestedAt;
860
+ }
861
+ return result.nextCursor;
862
+ });
863
+ const signal = DerivedSignal.from(() => {
864
+ const result = resource.get();
865
+ if (result.isLoading || result.error) {
866
+ return result;
867
+ }
868
+ const threads2 = this.outputs.threads.get().findMany(
869
+ void 0,
870
+ // Do _not_ filter by roomId
871
+ query ?? {},
872
+ "desc"
873
+ );
874
+ const page = result.data;
875
+ return {
876
+ isLoading: false,
877
+ threads: threads2,
878
+ hasFetchedAll: page.hasFetchedAll,
879
+ isFetchingMore: page.isFetchingMore,
880
+ fetchMoreError: page.fetchMoreError,
881
+ fetchMore: page.fetchMore
882
+ };
883
+ }, shallow2);
884
+ return { signal, waitUntilLoaded: resource.waitUntilLoaded };
885
+ }
886
+ );
887
+ const loadingRoomThreads = new DefaultMap(
888
+ (queryKey) => {
889
+ const [roomId, query] = unstringify(queryKey);
890
+ const resource = new PaginatedResource(async (cursor) => {
891
+ const result = await this.#client[kInternal].httpClient.getThreads({
892
+ roomId,
893
+ cursor,
894
+ query
895
+ });
896
+ this.updateThreadifications(
897
+ result.threads,
898
+ result.inboxNotifications
899
+ );
900
+ this.permissionHints.update(result.permissionHints);
901
+ const lastRequestedAt = this.#roomThreadsLastRequestedAtByRoom.get(roomId);
902
+ if (lastRequestedAt === void 0 || lastRequestedAt > result.requestedAt) {
903
+ this.#roomThreadsLastRequestedAtByRoom.set(
904
+ roomId,
905
+ result.requestedAt
906
+ );
907
+ }
908
+ return result.nextCursor;
909
+ });
910
+ const signal = DerivedSignal.from(() => {
911
+ const result = resource.get();
912
+ if (result.isLoading || result.error) {
913
+ return result;
914
+ }
915
+ const threads2 = this.outputs.threads.get().findMany(roomId, query ?? {}, "asc");
916
+ const page = result.data;
917
+ return {
918
+ isLoading: false,
919
+ threads: threads2,
920
+ hasFetchedAll: page.hasFetchedAll,
921
+ isFetchingMore: page.isFetchingMore,
922
+ fetchMoreError: page.fetchMoreError,
923
+ fetchMore: page.fetchMore
924
+ };
925
+ }, shallow2);
926
+ return { signal, waitUntilLoaded: resource.waitUntilLoaded };
927
+ }
928
+ );
929
+ const loadingNotifications = {
930
+ signal: DerivedSignal.from(() => {
931
+ const result = this.#notificationsPaginationState.get();
932
+ if (result.isLoading || result.error) {
933
+ return result;
934
+ }
935
+ const page = result.data;
936
+ return {
937
+ isLoading: false,
938
+ inboxNotifications: this.outputs.notifications.get().sortedNotifications,
939
+ hasFetchedAll: page.hasFetchedAll,
940
+ isFetchingMore: page.isFetchingMore,
941
+ fetchMoreError: page.fetchMoreError,
942
+ fetchMore: page.fetchMore
943
+ };
944
+ }),
945
+ waitUntilLoaded: this.#notificationsPaginationState.waitUntilLoaded
946
+ };
947
+ const settingsByRoomId = new DefaultMap((roomId) => {
948
+ const resource = new SinglePageResource(async () => {
949
+ const room = this.#client.getRoom(roomId);
950
+ if (room === null) {
951
+ throw new HttpError(
952
+ `Room '${roomId}' is not available on client`,
953
+ 479
954
+ );
955
+ }
956
+ const result = await room.getNotificationSettings();
957
+ this.roomNotificationSettings.update(roomId, result);
958
+ });
959
+ const signal = DerivedSignal.from(() => {
960
+ const result = resource.get();
961
+ if (result.isLoading || result.error) {
962
+ return result;
963
+ } else {
964
+ return ASYNC_OK(
965
+ "settings",
966
+ nn(this.roomNotificationSettings.signal.get()[roomId])
967
+ );
968
+ }
969
+ }, shallow3);
970
+ return { signal, waitUntilLoaded: resource.waitUntilLoaded };
971
+ });
972
+ const versionsByRoomId = new DefaultMap(
973
+ (roomId) => {
974
+ const resource = new SinglePageResource(async () => {
975
+ const room = this.#client.getRoom(roomId);
976
+ if (room === null) {
977
+ throw new HttpError(
978
+ `Room '${roomId}' is not available on client`,
979
+ 479
980
+ );
981
+ }
982
+ const result = await room[kInternal].listTextVersions();
983
+ this.historyVersions.update(roomId, result.versions);
984
+ const lastRequestedAt = this.#roomVersionsLastRequestedAtByRoom.get(roomId);
985
+ if (lastRequestedAt === void 0 || lastRequestedAt > result.requestedAt) {
986
+ this.#roomVersionsLastRequestedAtByRoom.set(
987
+ roomId,
988
+ result.requestedAt
989
+ );
990
+ }
991
+ });
992
+ const signal = DerivedSignal.from(() => {
993
+ const result = resource.get();
994
+ if (result.isLoading || result.error) {
995
+ return result;
996
+ } else {
997
+ return {
998
+ isLoading: false,
999
+ versions: Object.values(
1000
+ this.historyVersions.signal.get()[roomId] ?? {}
1001
+ )
1002
+ };
1003
+ }
1004
+ }, shallow3);
1005
+ return { signal, waitUntilLoaded: resource.waitUntilLoaded };
1006
+ }
859
1007
  );
860
1008
  this.outputs = {
861
1009
  threadifications,
862
1010
  threads,
1011
+ loadingRoomThreads,
1012
+ loadingUserThreads,
863
1013
  notifications,
1014
+ loadingNotifications,
864
1015
  settingsByRoomId,
865
1016
  versionsByRoomId
866
1017
  };
867
1018
  autobind(this);
868
1019
  }
869
- get1_both() {
870
- return this.outputs.threadifications.get();
871
- }
872
- subscribe1_both(callback) {
873
- return this.outputs.threadifications.subscribe(callback);
874
- }
875
- get1_threads() {
876
- return this.outputs.threads.get();
877
- }
878
- subscribe1_threads(callback) {
879
- return this.outputs.threads.subscribe(callback);
880
- }
881
- get1_notifications() {
882
- return this.outputs.notifications.get();
883
- }
884
- subscribe1_notifications(callback) {
885
- return this.outputs.notifications.subscribe(callback);
886
- }
887
- get2() {
888
- return this.outputs.settingsByRoomId.get();
889
- }
890
- subscribe2(callback) {
891
- return this.outputs.settingsByRoomId.subscribe(callback);
892
- }
893
- get3() {
894
- return this.outputs.versionsByRoomId.get();
895
- }
896
- subscribe3(callback) {
897
- return this.outputs.versionsByRoomId.subscribe(callback);
898
- }
899
- /**
900
- * Returns the async result of the given query and room id. If the query is success,
901
- * then it will return the threads that match that provided query and room id.
902
- *
903
- */
904
- getRoomThreadsLoadingState(roomId, query) {
905
- const queryKey = makeRoomThreadsQueryKey(roomId, query);
906
- const paginatedResource = this.#roomThreads.get(queryKey);
907
- if (paginatedResource === void 0) {
908
- return ASYNC_LOADING;
909
- }
910
- const asyncResult = paginatedResource.get();
911
- if (asyncResult.isLoading || asyncResult.error) {
912
- return asyncResult;
913
- }
914
- const threads = this.get1_threads().threadsDB.findMany(
915
- roomId,
916
- query ?? {},
917
- "asc"
918
- );
919
- const page = asyncResult.data;
920
- return {
921
- isLoading: false,
922
- threads,
923
- hasFetchedAll: page.hasFetchedAll,
924
- isFetchingMore: page.isFetchingMore,
925
- fetchMoreError: page.fetchMoreError,
926
- fetchMore: page.fetchMore
927
- };
928
- }
929
- getUserThreadsLoadingState(query) {
930
- const queryKey = makeUserThreadsQueryKey(query);
931
- const paginatedResource = this.#userThreads.get(queryKey);
932
- if (paginatedResource === void 0) {
933
- return ASYNC_LOADING;
934
- }
935
- const asyncResult = paginatedResource.get();
936
- if (asyncResult.isLoading || asyncResult.error) {
937
- return asyncResult;
938
- }
939
- const threads = this.get1_threads().threadsDB.findMany(
940
- void 0,
941
- // Do _not_ filter by roomId
942
- query ?? {},
943
- "desc"
944
- );
945
- const page = asyncResult.data;
946
- return {
947
- isLoading: false,
948
- threads,
949
- hasFetchedAll: page.hasFetchedAll,
950
- isFetchingMore: page.isFetchingMore,
951
- fetchMoreError: page.fetchMoreError,
952
- fetchMore: page.fetchMore
953
- };
954
- }
955
- // NOTE: This will read the async result, but WILL NOT start loading at the moment!
956
- getInboxNotificationsLoadingState() {
957
- const asyncResult = this.#notifications.get();
958
- if (asyncResult.isLoading || asyncResult.error) {
959
- return asyncResult;
960
- }
961
- const page = asyncResult.data;
962
- return {
963
- isLoading: false,
964
- inboxNotifications: this.get1_notifications().sortedNotifications,
965
- hasFetchedAll: page.hasFetchedAll,
966
- isFetchingMore: page.isFetchingMore,
967
- fetchMoreError: page.fetchMoreError,
968
- fetchMore: page.fetchMore
969
- };
970
- }
971
- // NOTE: This will read the async result, but WILL NOT start loading at the moment!
972
- // XXX_vincent This should really be a derived Signal!
973
- getNotificationSettingsLoadingState(roomId) {
974
- const queryKey = makeNotificationSettingsQueryKey(roomId);
975
- const resource = this.#roomNotificationSettings.get(queryKey);
976
- if (resource === void 0) {
977
- return ASYNC_LOADING;
978
- }
979
- const asyncResult = resource.get();
980
- if (asyncResult.isLoading || asyncResult.error) {
981
- return asyncResult;
982
- }
983
- return {
984
- isLoading: false,
985
- settings: nn(this.get2()[roomId])
986
- };
987
- }
988
- getRoomVersionsLoadingState(roomId) {
989
- const queryKey = makeVersionsQueryKey(roomId);
990
- const resource = this.#roomVersions.get(queryKey);
991
- if (resource === void 0) {
992
- return ASYNC_LOADING;
993
- }
994
- const asyncResult = resource.get();
995
- if (asyncResult.isLoading || asyncResult.error) {
996
- return asyncResult;
997
- }
998
- return {
999
- isLoading: false,
1000
- versions: Object.values(this.get3()[roomId] ?? {})
1001
- };
1002
- }
1003
- /** @internal - Only call this method from unit tests. */
1004
- force_set_versions(callback) {
1005
- batch2(() => {
1006
- this.historyVersions.force_set(callback);
1007
- this.invalidateEntireStore();
1008
- });
1009
- }
1010
- /** @internal - Only call this method from unit tests. */
1011
- force_set_notifications(callback) {
1012
- batch2(() => {
1013
- this.notifications.force_set(callback);
1014
- this.invalidateEntireStore();
1015
- });
1016
- }
1017
1020
  /**
1018
1021
  * Updates an existing inbox notification with a new value, replacing the
1019
1022
  * corresponding optimistic update.
@@ -1155,7 +1158,7 @@ var UmbrellaStore = class {
1155
1158
  }
1156
1159
  updateThreadifications(threads, notifications, deletedThreads = [], deletedNotifications = []) {
1157
1160
  batch2(() => {
1158
- this.threads.applyDelta({ newThreads: threads, deletedThreads });
1161
+ this.threads.applyDelta(threads, deletedThreads);
1159
1162
  this.notifications.applyDelta(notifications, deletedNotifications);
1160
1163
  });
1161
1164
  }
@@ -1188,39 +1191,6 @@ var UmbrellaStore = class {
1188
1191
  result.inboxNotifications.deleted
1189
1192
  );
1190
1193
  }
1191
- waitUntilNotificationsLoaded() {
1192
- return this.#notifications.waitUntilLoaded();
1193
- }
1194
- waitUntilRoomThreadsLoaded(roomId, query) {
1195
- const threadsFetcher = async (cursor) => {
1196
- const result = await this.#client[kInternal].httpClient.getThreads({
1197
- roomId,
1198
- cursor,
1199
- query
1200
- });
1201
- this.updateThreadifications(result.threads, result.inboxNotifications);
1202
- this.permissionHints.update(result.permissionHints);
1203
- const lastRequestedAt = this.#roomThreadsLastRequestedAtByRoom.get(roomId);
1204
- if (lastRequestedAt === void 0 || lastRequestedAt > result.requestedAt) {
1205
- this.#roomThreadsLastRequestedAtByRoom.set(roomId, result.requestedAt);
1206
- }
1207
- return result.nextCursor;
1208
- };
1209
- const queryKey = makeRoomThreadsQueryKey(roomId, query);
1210
- let paginatedResource = this.#roomThreads.get(queryKey);
1211
- if (paginatedResource === void 0) {
1212
- paginatedResource = new PaginatedResource(threadsFetcher);
1213
- }
1214
- paginatedResource.observable.subscribe(
1215
- () => (
1216
- // Note that the store itself does not change, but it's only vehicle at
1217
- // the moment to trigger a re-render, so we'll do a no-op update here.
1218
- this.invalidateEntireStore()
1219
- )
1220
- );
1221
- this.#roomThreads.set(queryKey, paginatedResource);
1222
- return paginatedResource.waitUntilLoaded();
1223
- }
1224
1194
  async fetchRoomThreadsDeltaUpdate(roomId, signal) {
1225
1195
  const lastRequestedAt = this.#roomThreadsLastRequestedAtByRoom.get(roomId);
1226
1196
  if (lastRequestedAt === void 0) {
@@ -1242,45 +1212,6 @@ var UmbrellaStore = class {
1242
1212
  this.#roomThreadsLastRequestedAtByRoom.set(roomId, updates.requestedAt);
1243
1213
  }
1244
1214
  }
1245
- waitUntilUserThreadsLoaded(query) {
1246
- const queryKey = makeUserThreadsQueryKey(query);
1247
- const threadsFetcher = async (cursor) => {
1248
- const result = await this.#client[kInternal].httpClient.getUserThreads_experimental({
1249
- cursor,
1250
- query
1251
- });
1252
- this.updateThreadifications(result.threads, result.inboxNotifications);
1253
- this.permissionHints.update(result.permissionHints);
1254
- if (this.#userThreadsLastRequestedAt === null) {
1255
- this.#userThreadsLastRequestedAt = result.requestedAt;
1256
- }
1257
- return result.nextCursor;
1258
- };
1259
- let paginatedResource = this.#userThreads.get(queryKey);
1260
- if (paginatedResource === void 0) {
1261
- paginatedResource = new PaginatedResource(threadsFetcher);
1262
- }
1263
- paginatedResource.observable.subscribe(
1264
- () => (
1265
- // Note that the store itself does not change, but it's only vehicle at
1266
- // the moment to trigger a re-render, so we'll do a no-op update here.
1267
- this.invalidateEntireStore()
1268
- )
1269
- );
1270
- this.#userThreads.set(queryKey, paginatedResource);
1271
- return paginatedResource.waitUntilLoaded();
1272
- }
1273
- // XXX_vincent We should really be going over all call sites, and replace this call
1274
- // with a more specific invalidation!
1275
- invalidateEntireStore() {
1276
- batch2(() => {
1277
- this.historyVersions.invalidate();
1278
- this.notifications.invalidate();
1279
- this.optimisticUpdates.invalidate();
1280
- this.permissionHints.invalidate();
1281
- this.roomNotificationSettings.invalidate();
1282
- });
1283
- }
1284
1215
  async fetchUserThreadsDeltaUpdate(signal) {
1285
1216
  const lastRequestedAt = this.#userThreadsLastRequestedAt;
1286
1217
  if (lastRequestedAt === null) {
@@ -1301,40 +1232,6 @@ var UmbrellaStore = class {
1301
1232
  );
1302
1233
  this.permissionHints.update(result.permissionHints);
1303
1234
  }
1304
- waitUntilRoomVersionsLoaded(roomId) {
1305
- const queryKey = makeVersionsQueryKey(roomId);
1306
- let resource = this.#roomVersions.get(queryKey);
1307
- if (resource === void 0) {
1308
- const versionsFetcher = async () => {
1309
- const room = this.#client.getRoom(roomId);
1310
- if (room === null) {
1311
- throw new HttpError(
1312
- `Room '${roomId}' is not available on client`,
1313
- 479
1314
- );
1315
- }
1316
- const result = await room[kInternal].listTextVersions();
1317
- this.historyVersions.update(roomId, result.versions);
1318
- const lastRequestedAt = this.#roomVersionsLastRequestedAtByRoom.get(roomId);
1319
- if (lastRequestedAt === void 0 || lastRequestedAt > result.requestedAt) {
1320
- this.#roomVersionsLastRequestedAtByRoom.set(
1321
- roomId,
1322
- result.requestedAt
1323
- );
1324
- }
1325
- };
1326
- resource = new SinglePageResource(versionsFetcher);
1327
- }
1328
- resource.observable.subscribe(
1329
- () => (
1330
- // Note that the store itself does not change, but it's only vehicle at
1331
- // the moment to trigger a re-render, so we'll do a no-op update here.
1332
- this.invalidateEntireStore()
1333
- )
1334
- );
1335
- this.#roomVersions.set(queryKey, resource);
1336
- return resource.waitUntilLoaded();
1337
- }
1338
1235
  async fetchRoomVersionsDeltaUpdate(roomId, signal) {
1339
1236
  const lastRequestedAt = this.#roomVersionsLastRequestedAtByRoom.get(roomId);
1340
1237
  if (lastRequestedAt === void 0) {
@@ -1353,33 +1250,6 @@ var UmbrellaStore = class {
1353
1250
  this.#roomVersionsLastRequestedAtByRoom.set(roomId, updates.requestedAt);
1354
1251
  }
1355
1252
  }
1356
- waitUntilRoomNotificationSettingsLoaded(roomId) {
1357
- const queryKey = makeNotificationSettingsQueryKey(roomId);
1358
- let resource = this.#roomNotificationSettings.get(queryKey);
1359
- if (resource === void 0) {
1360
- const notificationSettingsFetcher = async () => {
1361
- const room = this.#client.getRoom(roomId);
1362
- if (room === null) {
1363
- throw new HttpError(
1364
- `Room '${roomId}' is not available on client`,
1365
- 479
1366
- );
1367
- }
1368
- const result = await room.getNotificationSettings();
1369
- this.roomNotificationSettings.update(roomId, result);
1370
- };
1371
- resource = new SinglePageResource(notificationSettingsFetcher);
1372
- }
1373
- resource.observable.subscribe(
1374
- () => (
1375
- // Note that the store itself does not change, but it's only vehicle at
1376
- // the moment to trigger a re-render, so we'll do a no-op update here.
1377
- this.invalidateEntireStore()
1378
- )
1379
- );
1380
- this.#roomNotificationSettings.set(queryKey, resource);
1381
- return resource.waitUntilLoaded();
1382
- }
1383
1253
  async refreshRoomNotificationSettings(roomId, signal) {
1384
1254
  const room = nn(
1385
1255
  this.#client.getRoom(roomId),
@@ -1751,29 +1621,23 @@ function missingRoomInfoError(roomId) {
1751
1621
  `resolveRoomsInfo didn't return anything for room '${roomId}'`
1752
1622
  );
1753
1623
  }
1754
- function identity(x) {
1624
+ function identity2(x) {
1755
1625
  return x;
1756
1626
  }
1757
1627
  var _umbrellaStores = /* @__PURE__ */ new WeakMap();
1758
1628
  var _extras = /* @__PURE__ */ new WeakMap();
1759
1629
  var _bundles = /* @__PURE__ */ new WeakMap();
1760
- function selectUnreadInboxNotificationsCount(inboxNotifications) {
1761
- let count = 0;
1762
- for (const notification of inboxNotifications) {
1763
- if (notification.readAt === null || notification.readAt < notification.notifiedAt) {
1764
- count++;
1765
- }
1766
- }
1767
- return count;
1768
- }
1769
1630
  function selectorFor_useUnreadInboxNotificationsCount(result) {
1770
1631
  if (!result.inboxNotifications) {
1771
1632
  return result;
1772
1633
  }
1773
- return {
1774
- isLoading: false,
1775
- count: selectUnreadInboxNotificationsCount(result.inboxNotifications)
1776
- };
1634
+ return ASYNC_OK(
1635
+ "count",
1636
+ count(
1637
+ result.inboxNotifications,
1638
+ (n) => n.readAt === null || n.readAt < n.notifiedAt
1639
+ )
1640
+ );
1777
1641
  }
1778
1642
  function selectorFor_useUser(state, userId) {
1779
1643
  if (state === void 0 || state?.isLoading) {
@@ -1880,7 +1744,7 @@ function makeLiveblocksContextBundle(client) {
1880
1744
  const shared = createSharedContext(client);
1881
1745
  const bundle = {
1882
1746
  LiveblocksProvider: LiveblocksProvider2,
1883
- useInboxNotifications: () => useInboxNotifications_withClient(client, identity, shallow3),
1747
+ useInboxNotifications: () => useInboxNotifications_withClient(client, identity2, shallow4),
1884
1748
  useUnreadInboxNotificationsCount: () => useUnreadInboxNotificationsCount_withClient(client),
1885
1749
  useMarkInboxNotificationAsRead: useMarkInboxNotificationAsRead2,
1886
1750
  useMarkAllInboxNotificationsAsRead: useMarkAllInboxNotificationsAsRead2,
@@ -1906,9 +1770,17 @@ function makeLiveblocksContextBundle(client) {
1906
1770
  }
1907
1771
  function useInboxNotifications_withClient(client, selector, isEqual) {
1908
1772
  const { store, notificationsPoller: poller } = getLiveblocksExtrasForClient(client);
1909
- useEffect3(() => {
1910
- void store.waitUntilNotificationsLoaded();
1911
- });
1773
+ useEffect3(
1774
+ () => void store.outputs.loadingNotifications.waitUntilLoaded()
1775
+ // NOTE: Deliberately *not* using a dependency array here!
1776
+ //
1777
+ // It is important to call waitUntil on *every* render.
1778
+ // This is harmless though, on most renders, except:
1779
+ // 1. The very first render, in which case we'll want to trigger the initial page fetch.
1780
+ // 2. All other subsequent renders now "just" return the same promise (a quick operation).
1781
+ // 3. If ever the promise would fail, then after 5 seconds it would reset, and on the very
1782
+ // *next* render after that, a *new* fetch/promise will get created.
1783
+ );
1912
1784
  useEffect3(() => {
1913
1785
  poller.inc();
1914
1786
  poller.pollNowIfStale();
@@ -1916,18 +1788,16 @@ function useInboxNotifications_withClient(client, selector, isEqual) {
1916
1788
  poller.dec();
1917
1789
  };
1918
1790
  }, [poller]);
1919
- return useSyncExternalStoreWithSelector(
1920
- store.subscribe1_threads,
1921
- store.getInboxNotificationsLoadingState,
1922
- store.getInboxNotificationsLoadingState,
1791
+ return useSignal(
1792
+ store.outputs.loadingNotifications.signal,
1923
1793
  selector,
1924
1794
  isEqual
1925
1795
  );
1926
1796
  }
1927
1797
  function useInboxNotificationsSuspense_withClient(client) {
1928
1798
  const store = getLiveblocksExtrasForClient(client).store;
1929
- use(store.waitUntilNotificationsLoaded());
1930
- const result = useInboxNotifications_withClient(client, identity, shallow3);
1799
+ use(store.outputs.loadingNotifications.waitUntilLoaded());
1800
+ const result = useInboxNotifications_withClient(client, identity2, shallow4);
1931
1801
  assert(!result.error, "Did not expect error");
1932
1802
  assert(!result.isLoading, "Did not expect loading");
1933
1803
  return result;
@@ -1936,12 +1806,12 @@ function useUnreadInboxNotificationsCount_withClient(client) {
1936
1806
  return useInboxNotifications_withClient(
1937
1807
  client,
1938
1808
  selectorFor_useUnreadInboxNotificationsCount,
1939
- shallow3
1809
+ shallow4
1940
1810
  );
1941
1811
  }
1942
1812
  function useUnreadInboxNotificationsCountSuspense_withClient(client) {
1943
1813
  const store = getLiveblocksExtrasForClient(client).store;
1944
- use(store.waitUntilNotificationsLoaded());
1814
+ use(store.outputs.loadingNotifications.waitUntilLoaded());
1945
1815
  const result = useUnreadInboxNotificationsCount_withClient(client);
1946
1816
  assert(!result.isLoading, "Did not expect loading");
1947
1817
  assert(!result.error, "Did not expect error");
@@ -2033,34 +1903,31 @@ function useDeleteAllInboxNotifications_withClient(client) {
2033
1903
  }
2034
1904
  function useInboxNotificationThread_withClient(client, inboxNotificationId) {
2035
1905
  const { store } = getLiveblocksExtrasForClient(client);
2036
- const getter = store.get1_both;
2037
- const selector = useCallback2(
2038
- (state) => {
2039
- const inboxNotification = state.notificationsById[inboxNotificationId] ?? raise(`Inbox notification with ID "${inboxNotificationId}" not found`);
2040
- if (inboxNotification.kind !== "thread") {
2041
- raise(
2042
- `Inbox notification with ID "${inboxNotificationId}" is not of kind "thread"`
1906
+ return useSignal(
1907
+ store.outputs.threadifications,
1908
+ useCallback2(
1909
+ (state) => {
1910
+ const inboxNotification = state.notificationsById[inboxNotificationId] ?? raise(
1911
+ `Inbox notification with ID "${inboxNotificationId}" not found`
2043
1912
  );
2044
- }
2045
- const thread = state.threadsDB.get(inboxNotification.threadId) ?? raise(
2046
- `Thread with ID "${inboxNotification.threadId}" not found, this inbox notification might not be of kind "thread"`
2047
- );
2048
- return thread;
2049
- },
2050
- [inboxNotificationId]
2051
- );
2052
- return useSyncExternalStoreWithSelector(
2053
- store.subscribe1_both,
2054
- // Re-evaluate if we need to update any time the notification changes over time
2055
- getter,
2056
- getter,
2057
- selector
1913
+ if (inboxNotification.kind !== "thread") {
1914
+ raise(
1915
+ `Inbox notification with ID "${inboxNotificationId}" is not of kind "thread"`
1916
+ );
1917
+ }
1918
+ const thread = state.threadsDB.get(inboxNotification.threadId) ?? raise(
1919
+ `Thread with ID "${inboxNotification.threadId}" not found, this inbox notification might not be of kind "thread"`
1920
+ );
1921
+ return thread;
1922
+ },
1923
+ [inboxNotificationId]
1924
+ )
2058
1925
  );
2059
1926
  }
2060
1927
  function useUser_withClient(client, userId) {
2061
1928
  const usersStore = client[kInternal2].usersStore;
2062
1929
  const getUserState = useCallback2(
2063
- () => usersStore.getState(userId),
1930
+ () => usersStore.getItemState(userId),
2064
1931
  [usersStore, userId]
2065
1932
  );
2066
1933
  const selector = useCallback2(
@@ -2072,22 +1939,31 @@ function useUser_withClient(client, userId) {
2072
1939
  getUserState,
2073
1940
  getUserState,
2074
1941
  selector,
2075
- shallow3
1942
+ shallow4
1943
+ );
1944
+ useEffect3(
1945
+ () => void usersStore.enqueue(userId)
1946
+ // NOTE: Deliberately *not* using a dependency array here!
1947
+ //
1948
+ // It is important to call usersStore.enqueue on *every* render.
1949
+ // This is harmless though, on most renders, except:
1950
+ // 1. The very first render, in which case we'll want to trigger evaluation
1951
+ // of the userId.
1952
+ // 2. All other subsequent renders now are a no-op (from the implementation
1953
+ // of .enqueue)
1954
+ // 3. If ever the userId gets invalidated, the user would be fetched again.
2076
1955
  );
2077
- useEffect3(() => {
2078
- void usersStore.get(userId);
2079
- }, [usersStore, userId, result]);
2080
1956
  return result;
2081
1957
  }
2082
1958
  function useUserSuspense_withClient(client, userId) {
2083
1959
  const usersStore = client[kInternal2].usersStore;
2084
1960
  const getUserState = useCallback2(
2085
- () => usersStore.getState(userId),
1961
+ () => usersStore.getItemState(userId),
2086
1962
  [usersStore, userId]
2087
1963
  );
2088
1964
  const userState = getUserState();
2089
1965
  if (!userState || userState.isLoading) {
2090
- throw usersStore.get(userId);
1966
+ throw usersStore.enqueue(userId);
2091
1967
  }
2092
1968
  if (userState.error) {
2093
1969
  throw userState.error;
@@ -2112,7 +1988,7 @@ function useUserSuspense_withClient(client, userId) {
2112
1988
  function useRoomInfo_withClient(client, roomId) {
2113
1989
  const roomsInfoStore = client[kInternal2].roomsInfoStore;
2114
1990
  const getRoomInfoState = useCallback2(
2115
- () => roomsInfoStore.getState(roomId),
1991
+ () => roomsInfoStore.getItemState(roomId),
2116
1992
  [roomsInfoStore, roomId]
2117
1993
  );
2118
1994
  const selector = useCallback2(
@@ -2124,22 +2000,31 @@ function useRoomInfo_withClient(client, roomId) {
2124
2000
  getRoomInfoState,
2125
2001
  getRoomInfoState,
2126
2002
  selector,
2127
- shallow3
2003
+ shallow4
2004
+ );
2005
+ useEffect3(
2006
+ () => void roomsInfoStore.enqueue(roomId)
2007
+ // NOTE: Deliberately *not* using a dependency array here!
2008
+ //
2009
+ // It is important to call roomsInfoStore.enqueue on *every* render.
2010
+ // This is harmless though, on most renders, except:
2011
+ // 1. The very first render, in which case we'll want to trigger evaluation
2012
+ // of the roomId.
2013
+ // 2. All other subsequent renders now are a no-op (from the implementation
2014
+ // of .enqueue)
2015
+ // 3. If ever the roomId gets invalidated, the room info would be fetched again.
2128
2016
  );
2129
- useEffect3(() => {
2130
- void roomsInfoStore.get(roomId);
2131
- }, [roomsInfoStore, roomId, result]);
2132
2017
  return result;
2133
2018
  }
2134
2019
  function useRoomInfoSuspense_withClient(client, roomId) {
2135
2020
  const roomsInfoStore = client[kInternal2].roomsInfoStore;
2136
2021
  const getRoomInfoState = useCallback2(
2137
- () => roomsInfoStore.getState(roomId),
2022
+ () => roomsInfoStore.getItemState(roomId),
2138
2023
  [roomsInfoStore, roomId]
2139
2024
  );
2140
2025
  const roomInfoState = getRoomInfoState();
2141
2026
  if (!roomInfoState || roomInfoState.isLoading) {
2142
- throw roomsInfoStore.get(roomId);
2027
+ throw roomsInfoStore.enqueue(roomId);
2143
2028
  }
2144
2029
  if (roomInfoState.error) {
2145
2030
  throw roomInfoState.error;
@@ -2241,10 +2126,9 @@ function useUserThreads_experimental(options = {
2241
2126
  }) {
2242
2127
  const client = useClient();
2243
2128
  const { store, userThreadsPoller: poller } = getLiveblocksExtrasForClient(client);
2129
+ const queryKey = makeUserThreadsQueryKey(options.query);
2244
2130
  useEffect3(
2245
- () => {
2246
- void store.waitUntilUserThreadsLoaded(options.query);
2247
- }
2131
+ () => void store.outputs.loadingUserThreads.getOrCreate(queryKey).waitUntilLoaded()
2248
2132
  // NOTE: Deliberately *not* using a dependency array here!
2249
2133
  //
2250
2134
  // It is important to call waitUntil on *every* render.
@@ -2261,17 +2145,8 @@ function useUserThreads_experimental(options = {
2261
2145
  poller.dec();
2262
2146
  };
2263
2147
  }, [poller]);
2264
- const getter = useCallback2(
2265
- () => store.getUserThreadsLoadingState(options.query),
2266
- [store, options.query]
2267
- );
2268
- return useSyncExternalStoreWithSelector(
2269
- store.subscribe1_threads,
2270
- getter,
2271
- getter,
2272
- identity,
2273
- shallow2
2274
- // NOTE: Using 2-level-deep shallow check here, because the result of selectThreads() is not stable!
2148
+ return useSignal(
2149
+ store.outputs.loadingUserThreads.getOrCreate(queryKey).signal
2275
2150
  );
2276
2151
  }
2277
2152
  function useUserThreadsSuspense_experimental(options = {
@@ -2281,14 +2156,15 @@ function useUserThreadsSuspense_experimental(options = {
2281
2156
  }) {
2282
2157
  const client = useClient();
2283
2158
  const { store } = getLiveblocksExtrasForClient(client);
2284
- use(store.waitUntilUserThreadsLoaded(options.query));
2159
+ const queryKey = makeUserThreadsQueryKey(options.query);
2160
+ use(store.outputs.loadingUserThreads.getOrCreate(queryKey).waitUntilLoaded());
2285
2161
  const result = useUserThreads_experimental(options);
2286
2162
  assert(!result.error, "Did not expect error");
2287
2163
  assert(!result.isLoading, "Did not expect loading");
2288
2164
  return result;
2289
2165
  }
2290
2166
  function useInboxNotifications() {
2291
- return useInboxNotifications_withClient(useClient(), identity, shallow3);
2167
+ return useInboxNotifications_withClient(useClient(), identity2, shallow4);
2292
2168
  }
2293
2169
  function useInboxNotificationsSuspense() {
2294
2170
  return useInboxNotificationsSuspense_withClient(useClient());
@@ -2475,29 +2351,18 @@ var UpdateNotificationSettingsError = class extends Error {
2475
2351
  }
2476
2352
  };
2477
2353
 
2478
- // src/use-signal.ts
2479
- var identity2 = (value) => value;
2480
- function useSignal(signal, selector, isEqual) {
2481
- return useSyncExternalStoreWithSelector(
2482
- signal.subscribe,
2483
- signal.get,
2484
- signal.get,
2485
- selector ?? identity2,
2486
- isEqual
2487
- );
2488
- }
2489
-
2490
2354
  // src/room.tsx
2491
- import { shallow as shallow4 } from "@liveblocks/client";
2355
+ import { shallow as shallow5 } from "@liveblocks/client";
2492
2356
  import {
2493
2357
  assert as assert2,
2494
2358
  console as console3,
2495
2359
  createCommentId,
2496
2360
  createThreadId,
2361
+ DefaultMap as DefaultMap2,
2497
2362
  errorIf,
2498
2363
  HttpError as HttpError2,
2499
2364
  kInternal as kInternal3,
2500
- makeEventSource as makeEventSource2,
2365
+ makeEventSource,
2501
2366
  makePoller as makePoller2,
2502
2367
  ServerMsgCode
2503
2368
  } from "@liveblocks/core";
@@ -2619,7 +2484,7 @@ function getRoomExtrasForClient(client) {
2619
2484
  }
2620
2485
  function makeRoomExtrasForClient(client) {
2621
2486
  const store = getUmbrellaStoreForClient(client);
2622
- const commentsErrorEventSource = makeEventSource2();
2487
+ const commentsErrorEventSource = makeEventSource();
2623
2488
  function onMutationFailure(innerError, optimisticId, createPublicError) {
2624
2489
  store.optimisticUpdates.remove(optimisticId);
2625
2490
  if (innerError instanceof HttpError2) {
@@ -2633,73 +2498,59 @@ function makeRoomExtrasForClient(client) {
2633
2498
  }
2634
2499
  throw innerError;
2635
2500
  }
2636
- const threadsPollersByRoomId = /* @__PURE__ */ new Map();
2637
- const versionsPollersByRoomId = /* @__PURE__ */ new Map();
2638
- const roomNotificationSettingsPollersByRoomId = /* @__PURE__ */ new Map();
2639
- function getOrCreateThreadsPollerForRoomId(roomId) {
2640
- let poller = threadsPollersByRoomId.get(roomId);
2641
- if (!poller) {
2642
- poller = makePoller2(
2643
- async (signal) => {
2644
- try {
2645
- return await store.fetchRoomThreadsDeltaUpdate(roomId, signal);
2646
- } catch (err) {
2647
- console3.warn(`Polling new threads for '${roomId}' failed: ${String(err)}`);
2648
- throw err;
2649
- }
2650
- },
2651
- config.ROOM_THREADS_POLL_INTERVAL,
2652
- { maxStaleTimeMs: config.ROOM_THREADS_MAX_STALE_TIME }
2653
- );
2654
- threadsPollersByRoomId.set(roomId, poller);
2655
- }
2656
- return poller;
2657
- }
2658
- function getOrCreateVersionsPollerForRoomId(roomId) {
2659
- let poller = versionsPollersByRoomId.get(roomId);
2660
- if (!poller) {
2661
- poller = makePoller2(
2662
- async (signal) => {
2663
- try {
2664
- return await store.fetchRoomVersionsDeltaUpdate(roomId, signal);
2665
- } catch (err) {
2666
- console3.warn(`Polling new history versions for '${roomId}' failed: ${String(err)}`);
2667
- throw err;
2668
- }
2669
- },
2670
- config.HISTORY_VERSIONS_POLL_INTERVAL,
2671
- { maxStaleTimeMs: config.HISTORY_VERSIONS_MAX_STALE_TIME }
2672
- );
2673
- versionsPollersByRoomId.set(roomId, poller);
2674
- }
2675
- return poller;
2676
- }
2677
- function getOrCreateNotificationsSettingsPollerForRoomId(roomId) {
2678
- let poller = roomNotificationSettingsPollersByRoomId.get(roomId);
2679
- if (!poller) {
2680
- poller = makePoller2(
2681
- async (signal) => {
2682
- try {
2683
- return await store.refreshRoomNotificationSettings(roomId, signal);
2684
- } catch (err) {
2685
- console3.warn(`Polling notification settings for '${roomId}' failed: ${String(err)}`);
2686
- throw err;
2687
- }
2688
- },
2689
- config.NOTIFICATION_SETTINGS_POLL_INTERVAL,
2690
- { maxStaleTimeMs: config.NOTIFICATION_SETTINGS_MAX_STALE_TIME }
2691
- );
2692
- roomNotificationSettingsPollersByRoomId.set(roomId, poller);
2693
- }
2694
- return poller;
2695
- }
2501
+ const threadsPollersByRoomId = new DefaultMap2(
2502
+ (roomId) => makePoller2(
2503
+ async (signal) => {
2504
+ try {
2505
+ return await store.fetchRoomThreadsDeltaUpdate(roomId, signal);
2506
+ } catch (err) {
2507
+ console3.warn(`Polling new threads for '${roomId}' failed: ${String(err)}`);
2508
+ throw err;
2509
+ }
2510
+ },
2511
+ config.ROOM_THREADS_POLL_INTERVAL,
2512
+ { maxStaleTimeMs: config.ROOM_THREADS_MAX_STALE_TIME }
2513
+ )
2514
+ );
2515
+ const versionsPollersByRoomId = new DefaultMap2(
2516
+ (roomId) => makePoller2(
2517
+ async (signal) => {
2518
+ try {
2519
+ return await store.fetchRoomVersionsDeltaUpdate(roomId, signal);
2520
+ } catch (err) {
2521
+ console3.warn(`Polling new history versions for '${roomId}' failed: ${String(err)}`);
2522
+ throw err;
2523
+ }
2524
+ },
2525
+ config.HISTORY_VERSIONS_POLL_INTERVAL,
2526
+ { maxStaleTimeMs: config.HISTORY_VERSIONS_MAX_STALE_TIME }
2527
+ )
2528
+ );
2529
+ const roomNotificationSettingsPollersByRoomId = new DefaultMap2(
2530
+ (roomId) => makePoller2(
2531
+ async (signal) => {
2532
+ try {
2533
+ return await store.refreshRoomNotificationSettings(roomId, signal);
2534
+ } catch (err) {
2535
+ console3.warn(`Polling notification settings for '${roomId}' failed: ${String(err)}`);
2536
+ throw err;
2537
+ }
2538
+ },
2539
+ config.NOTIFICATION_SETTINGS_POLL_INTERVAL,
2540
+ { maxStaleTimeMs: config.NOTIFICATION_SETTINGS_MAX_STALE_TIME }
2541
+ )
2542
+ );
2696
2543
  return {
2697
2544
  store,
2698
2545
  commentsErrorEventSource: commentsErrorEventSource.observable,
2699
2546
  onMutationFailure,
2700
- getOrCreateThreadsPollerForRoomId,
2701
- getOrCreateVersionsPollerForRoomId,
2702
- getOrCreateNotificationsSettingsPollerForRoomId
2547
+ getOrCreateThreadsPollerForRoomId: threadsPollersByRoomId.getOrCreate.bind(
2548
+ threadsPollersByRoomId
2549
+ ),
2550
+ getOrCreateVersionsPollerForRoomId: versionsPollersByRoomId.getOrCreate.bind(versionsPollersByRoomId),
2551
+ getOrCreateNotificationsSettingsPollerForRoomId: roomNotificationSettingsPollersByRoomId.getOrCreate.bind(
2552
+ roomNotificationSettingsPollersByRoomId
2553
+ )
2703
2554
  };
2704
2555
  }
2705
2556
  function makeRoomContextBundle(client) {
@@ -2878,7 +2729,7 @@ function RoomProviderInner(props) {
2878
2729
  return;
2879
2730
  }
2880
2731
  const { thread, inboxNotification: maybeNotification } = info;
2881
- const existingThread = store.get1_threads().threadsDB.getEvenIfDeleted(message.threadId);
2732
+ const existingThread = store.outputs.threads.get().getEvenIfDeleted(message.threadId);
2882
2733
  switch (message.type) {
2883
2734
  case ServerMsgCode.COMMENT_EDITED:
2884
2735
  case ServerMsgCode.THREAD_METADATA_UPDATED:
@@ -3160,7 +3011,7 @@ function useOthersMapped(itemSelector, itemIsEqual) {
3160
3011
  return useOthers(wrappedSelector, wrappedIsEqual);
3161
3012
  }
3162
3013
  function useOthersConnectionIds() {
3163
- return useOthers(selectorFor_useOthersConnectionIds, shallow4);
3014
+ return useOthers(selectorFor_useOthersConnectionIds, shallow5);
3164
3015
  }
3165
3016
  var NOT_FOUND = Symbol();
3166
3017
  function useOther(connectionId, selector, isEqual) {
@@ -3249,18 +3100,15 @@ function useMutation(callback, deps) {
3249
3100
  [room, ...deps]
3250
3101
  );
3251
3102
  }
3252
- function useThreads(options = {
3253
- query: { metadata: {} }
3254
- }) {
3103
+ function useThreads(options = {}) {
3255
3104
  const { scrollOnLoad = true } = options;
3256
3105
  const client = useClient();
3257
3106
  const room = useRoom();
3258
3107
  const { store, getOrCreateThreadsPollerForRoomId } = getRoomExtrasForClient(client);
3108
+ const queryKey = makeRoomThreadsQueryKey(room.id, options.query);
3259
3109
  const poller = getOrCreateThreadsPollerForRoomId(room.id);
3260
3110
  useEffect5(
3261
- () => {
3262
- void store.waitUntilRoomThreadsLoaded(room.id, options.query);
3263
- }
3111
+ () => void store.outputs.loadingRoomThreads.getOrCreate(queryKey).waitUntilLoaded()
3264
3112
  // NOTE: Deliberately *not* using a dependency array here!
3265
3113
  //
3266
3114
  // It is important to call waitUntil on *every* render.
@@ -3275,20 +3123,11 @@ function useThreads(options = {
3275
3123
  poller.pollNowIfStale();
3276
3124
  return () => poller.dec();
3277
3125
  }, [poller]);
3278
- const getter = useCallback3(
3279
- () => store.getRoomThreadsLoadingState(room.id, options.query),
3280
- [store, room.id, options.query]
3281
- );
3282
- const state = useSyncExternalStoreWithSelector(
3283
- store.subscribe1_threads,
3284
- getter,
3285
- getter,
3286
- identity3,
3287
- shallow2
3288
- // NOTE: Using 2-level-deep shallow check here, because the result of selectThreads() is not stable!
3126
+ const result = useSignal(
3127
+ store.outputs.loadingRoomThreads.getOrCreate(queryKey).signal
3289
3128
  );
3290
- useScrollToCommentOnLoadEffect(scrollOnLoad, state);
3291
- return state;
3129
+ useScrollToCommentOnLoadEffect(scrollOnLoad, result);
3130
+ return result;
3292
3131
  }
3293
3132
  function useCommentsErrorListener(callback) {
3294
3133
  const client = useClient();
@@ -3376,7 +3215,7 @@ function useDeleteRoomThread(roomId) {
3376
3215
  (threadId) => {
3377
3216
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
3378
3217
  const userId = getCurrentUserId(client);
3379
- const existing = store.get1_threads().threadsDB.get(threadId);
3218
+ const existing = store.outputs.threads.get().get(threadId);
3380
3219
  if (existing?.comments?.[0]?.userId !== userId) {
3381
3220
  throw new Error("Only the thread creator can delete the thread");
3382
3221
  }
@@ -3494,7 +3333,7 @@ function useEditRoomComment(roomId) {
3494
3333
  ({ threadId, commentId, body, attachments }) => {
3495
3334
  const editedAt = /* @__PURE__ */ new Date();
3496
3335
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
3497
- const existing = store.get1_threads().threadsDB.getEvenIfDeleted(threadId);
3336
+ const existing = store.outputs.threads.get().getEvenIfDeleted(threadId);
3498
3337
  if (existing === void 0) {
3499
3338
  console3.warn(
3500
3339
  `Internal unexpected behavior. Cannot edit comment in thread "${threadId}" because the thread does not exist in the cache.`
@@ -3671,7 +3510,7 @@ function useMarkRoomThreadAsRead(roomId) {
3671
3510
  (threadId) => {
3672
3511
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
3673
3512
  const inboxNotification = Object.values(
3674
- store.get1_notifications().notificationsById
3513
+ store.outputs.notifications.get().notificationsById
3675
3514
  ).find(
3676
3515
  (inboxNotification2) => inboxNotification2.kind === "thread" && inboxNotification2.threadId === threadId
3677
3516
  );
@@ -3800,7 +3639,7 @@ function useThreadSubscription(threadId) {
3800
3639
  },
3801
3640
  [threadId]
3802
3641
  );
3803
- return useSignal(signal, selector, shallow4);
3642
+ return useSignal(signal, selector, shallow5);
3804
3643
  }
3805
3644
  function useRoomNotificationSettings() {
3806
3645
  const updateRoomNotificationSettings = useUpdateRoomNotificationSettings();
@@ -3809,9 +3648,7 @@ function useRoomNotificationSettings() {
3809
3648
  const { store, getOrCreateNotificationsSettingsPollerForRoomId } = getRoomExtrasForClient(client);
3810
3649
  const poller = getOrCreateNotificationsSettingsPollerForRoomId(room.id);
3811
3650
  useEffect5(
3812
- () => {
3813
- void store.waitUntilRoomNotificationSettingsLoaded(room.id);
3814
- }
3651
+ () => void store.outputs.settingsByRoomId.getOrCreate(room.id).waitUntilLoaded()
3815
3652
  // NOTE: Deliberately *not* using a dependency array here!
3816
3653
  //
3817
3654
  // It is important to call waitUntil on *every* render.
@@ -3828,16 +3665,8 @@ function useRoomNotificationSettings() {
3828
3665
  poller.dec();
3829
3666
  };
3830
3667
  }, [poller]);
3831
- const getter = useCallback3(
3832
- () => store.getNotificationSettingsLoadingState(room.id),
3833
- [store, room.id]
3834
- );
3835
- const settings = useSyncExternalStoreWithSelector(
3836
- store.subscribe2,
3837
- getter,
3838
- getter,
3839
- identity3,
3840
- shallow2
3668
+ const settings = useSignal(
3669
+ store.outputs.settingsByRoomId.getOrCreate(room.id).signal
3841
3670
  );
3842
3671
  return useMemo3(() => {
3843
3672
  return [settings, updateRoomNotificationSettings];
@@ -3847,7 +3676,7 @@ function useRoomNotificationSettingsSuspense() {
3847
3676
  const client = useClient();
3848
3677
  const store = getRoomExtrasForClient(client).store;
3849
3678
  const room = useRoom();
3850
- use(store.waitUntilRoomNotificationSettingsLoaded(room.id));
3679
+ use(store.outputs.settingsByRoomId.getOrCreate(room.id).waitUntilLoaded());
3851
3680
  const [settings, updateRoomNotificationSettings] = useRoomNotificationSettings();
3852
3681
  assert2(!settings.error, "Did not expect error");
3853
3682
  assert2(!settings.isLoading, "Did not expect loading");
@@ -3894,14 +3723,8 @@ function useHistoryVersions() {
3894
3723
  poller.pollNowIfStale();
3895
3724
  return () => poller.dec();
3896
3725
  }, [poller]);
3897
- const getter = useCallback3(
3898
- () => store.getRoomVersionsLoadingState(room.id),
3899
- [store, room.id]
3900
- );
3901
3726
  useEffect5(
3902
- () => {
3903
- void store.waitUntilRoomVersionsLoaded(room.id);
3904
- }
3727
+ () => void store.outputs.versionsByRoomId.getOrCreate(room.id).waitUntilLoaded()
3905
3728
  // NOTE: Deliberately *not* using a dependency array here!
3906
3729
  //
3907
3730
  // It is important to call waitUntil on *every* render.
@@ -3911,20 +3734,13 @@ function useHistoryVersions() {
3911
3734
  // 3. If ever the promise would fail, then after 5 seconds it would reset, and on the very
3912
3735
  // *next* render after that, a *new* fetch/promise will get created.
3913
3736
  );
3914
- const state = useSyncExternalStoreWithSelector(
3915
- store.subscribe3,
3916
- getter,
3917
- getter,
3918
- identity3,
3919
- shallow2
3920
- );
3921
- return state;
3737
+ return useSignal(store.outputs.versionsByRoomId.getOrCreate(room.id).signal);
3922
3738
  }
3923
3739
  function useHistoryVersionsSuspense() {
3924
3740
  const client = useClient();
3925
3741
  const room = useRoom();
3926
3742
  const store = getRoomExtrasForClient(client).store;
3927
- use(store.waitUntilRoomVersionsLoaded(room.id));
3743
+ use(store.outputs.versionsByRoomId.getOrCreate(room.id).waitUntilLoaded());
3928
3744
  const result = useHistoryVersions();
3929
3745
  assert2(!result.error, "Did not expect error");
3930
3746
  assert2(!result.isLoading, "Did not expect loading");
@@ -4011,13 +3827,12 @@ function useStorageStatusSuspense(options) {
4011
3827
  useSuspendUntilStorageReady();
4012
3828
  return useStorageStatus(options);
4013
3829
  }
4014
- function useThreadsSuspense(options = {
4015
- query: { metadata: {} }
4016
- }) {
3830
+ function useThreadsSuspense(options = {}) {
4017
3831
  const client = useClient();
4018
3832
  const room = useRoom();
4019
3833
  const { store } = getRoomExtrasForClient(client);
4020
- use(store.waitUntilRoomThreadsLoaded(room.id, options.query));
3834
+ const queryKey = makeRoomThreadsQueryKey(room.id, options.query);
3835
+ use(store.outputs.loadingRoomThreads.getOrCreate(queryKey).waitUntilLoaded());
4021
3836
  const result = useThreads(options);
4022
3837
  assert2(!result.error, "Did not expect error");
4023
3838
  assert2(!result.isLoading, "Did not expect loading");
@@ -4044,30 +3859,30 @@ function useRoomAttachmentUrl(attachmentId, roomId) {
4044
3859
  const client = useClient();
4045
3860
  const store = client[kInternal3].httpClient.getOrCreateAttachmentUrlsStore(roomId);
4046
3861
  const getAttachmentUrlState = useCallback3(
4047
- () => store.getState(attachmentId),
3862
+ () => store.getItemState(attachmentId),
4048
3863
  [store, attachmentId]
4049
3864
  );
4050
3865
  useEffect5(() => {
4051
- void store.get(attachmentId);
3866
+ void store.enqueue(attachmentId);
4052
3867
  }, [store, attachmentId]);
4053
3868
  return useSyncExternalStoreWithSelector(
4054
3869
  store.subscribe,
4055
3870
  getAttachmentUrlState,
4056
3871
  getAttachmentUrlState,
4057
3872
  selectorFor_useAttachmentUrl,
4058
- shallow4
3873
+ shallow5
4059
3874
  );
4060
3875
  }
4061
3876
  function useAttachmentUrlSuspense(attachmentId) {
4062
3877
  const room = useRoom();
4063
3878
  const { attachmentUrlsStore } = room[kInternal3];
4064
3879
  const getAttachmentUrlState = useCallback3(
4065
- () => attachmentUrlsStore.getState(attachmentId),
3880
+ () => attachmentUrlsStore.getItemState(attachmentId),
4066
3881
  [attachmentUrlsStore, attachmentId]
4067
3882
  );
4068
3883
  const attachmentUrlState = getAttachmentUrlState();
4069
3884
  if (!attachmentUrlState || attachmentUrlState.isLoading) {
4070
- throw attachmentUrlsStore.get(attachmentId);
3885
+ throw attachmentUrlsStore.enqueue(attachmentId);
4071
3886
  }
4072
3887
  if (attachmentUrlState.error) {
4073
3888
  throw attachmentUrlState.error;
@@ -4086,12 +3901,13 @@ function useAttachmentUrlSuspense(attachmentId) {
4086
3901
  error: void 0
4087
3902
  };
4088
3903
  }
3904
+ var NO_PERMISSIONS = /* @__PURE__ */ new Set();
4089
3905
  function useRoomPermissions(roomId) {
4090
3906
  const client = useClient();
4091
3907
  const store = getRoomExtrasForClient(client).store;
4092
3908
  return useSignal(
4093
3909
  store.permissionHints.signal,
4094
- (hints) => hints[roomId] ?? /* @__PURE__ */ new Set()
3910
+ (hints) => hints.get(roomId) ?? NO_PERMISSIONS
4095
3911
  );
4096
3912
  }
4097
3913
  function createRoomContext(client) {
@@ -4140,6 +3956,7 @@ export {
4140
3956
  RoomContext,
4141
3957
  useRoomOrNull,
4142
3958
  useSyncExternalStoreWithSelector,
3959
+ useSignal,
4143
3960
  ClientContext,
4144
3961
  getUmbrellaStoreForClient,
4145
3962
  useClientOrNull,
@@ -4163,7 +3980,6 @@ export {
4163
3980
  _useUserThreadsSuspense_experimental,
4164
3981
  useSyncStatus,
4165
3982
  CreateThreadError,
4166
- useSignal,
4167
3983
  useStatus,
4168
3984
  useReportTextEditor,
4169
3985
  useYjsProvider,
@@ -4241,4 +4057,4 @@ export {
4241
4057
  _useStorageRoot,
4242
4058
  _useUpdateMyPresence
4243
4059
  };
4244
- //# sourceMappingURL=chunk-NZZCQLBR.mjs.map
4060
+ //# sourceMappingURL=chunk-EEYUKRIA.mjs.map