@liveblocks/react 2.9.3-experimental1 → 2.10.1-react19

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.
@@ -1,41 +1,133 @@
1
- // src/version.ts
2
- var PKG_NAME = "@liveblocks/react";
3
- var PKG_VERSION = "2.9.3-experimental1";
4
- var PKG_FORMAT = "esm";
5
-
6
- // src/ClientSideSuspense.tsx
7
- import * as React from "react";
8
- function ClientSideSuspense(props) {
9
- const [mounted, setMounted] = React.useState(false);
10
- React.useEffect(() => {
11
- setMounted(true);
12
- }, []);
13
- return /* @__PURE__ */ React.createElement(React.Suspense, { fallback: props.fallback }, mounted ? typeof props.children === "function" ? props.children() : props.children : props.fallback);
14
- }
15
-
16
1
  // src/contexts.ts
17
- import * as React2 from "react";
18
- var RoomContext = React2.createContext(null);
2
+ import * as React from "react";
3
+ var RoomContext = React.createContext(null);
19
4
  function useRoomOrNull() {
20
- return React2.useContext(RoomContext);
5
+ return React.useContext(RoomContext);
21
6
  }
22
7
  function useIsInsideRoom() {
23
8
  const room = useRoomOrNull();
24
9
  return room !== null;
25
10
  }
26
11
 
12
+ // src/liveblocks.tsx
13
+ import {
14
+ assert,
15
+ createClient,
16
+ kInternal as kInternal2,
17
+ makePoller,
18
+ raise,
19
+ shallow as shallow3
20
+ } from "@liveblocks/core";
21
+ import React2, {
22
+ createContext as createContext2,
23
+ useCallback as useCallback2,
24
+ useContext as useContext2,
25
+ useEffect as useEffect2,
26
+ useMemo
27
+ } from "react";
28
+ import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
29
+ import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
30
+
31
+ // src/config.ts
32
+ var SECONDS = 1e3;
33
+ var MINUTES = 60 * SECONDS;
34
+ var config = {
35
+ NOTIFICATIONS_POLL_INTERVAL: 1 * MINUTES,
36
+ NOTIFICATIONS_MAX_STALE_TIME: 5 * SECONDS,
37
+ ROOM_THREADS_POLL_INTERVAL: 5 * MINUTES,
38
+ ROOM_THREADS_MAX_STALE_TIME: 5 * SECONDS,
39
+ USER_THREADS_POLL_INTERVAL: 1 * MINUTES,
40
+ USER_THREADS_MAX_STALE_TIME: 5 * SECONDS,
41
+ HISTORY_VERSIONS_POLL_INTERVAL: 1 * MINUTES,
42
+ HISTORY_VERSIONS_MAX_STALE_TIME: 5 * SECONDS,
43
+ NOTIFICATION_SETTINGS_POLL_INTERVAL: 1 * MINUTES,
44
+ NOTIFICATION_SETTINGS_MAX_STALE_TIME: 5 * SECONDS
45
+ };
46
+
47
+ // src/lib/shallow2.ts
48
+ import { isPlainObject, shallow } from "@liveblocks/core";
49
+ function shallow2(a, b) {
50
+ if (!isPlainObject(a) || !isPlainObject(b)) {
51
+ return shallow(a, b);
52
+ }
53
+ const keysA = Object.keys(a);
54
+ if (keysA.length !== Object.keys(b).length) {
55
+ return false;
56
+ }
57
+ return keysA.every(
58
+ (key) => Object.prototype.hasOwnProperty.call(b, key) && shallow(a[key], b[key])
59
+ );
60
+ }
61
+
62
+ // src/lib/use-initial.ts
63
+ import { useCallback, useReducer } from "react";
64
+
65
+ // src/lib/use-latest.ts
66
+ import { useEffect, useRef } from "react";
67
+ function useLatest(value) {
68
+ const ref = useRef(value);
69
+ useEffect(() => {
70
+ ref.current = value;
71
+ }, [value]);
72
+ return ref;
73
+ }
74
+
75
+ // src/lib/use-initial.ts
76
+ var noop = (state) => state;
77
+ function useInitial(value) {
78
+ return useReducer(noop, value)[0];
79
+ }
80
+ function useInitialUnlessFunction(latestValue) {
81
+ const frozenValue = useInitial(latestValue);
82
+ if (typeof frozenValue === "function") {
83
+ const ref = useLatest(latestValue);
84
+ return useCallback((...args) => ref.current(...args), [
85
+ ref
86
+ ]);
87
+ } else {
88
+ return frozenValue;
89
+ }
90
+ }
91
+
92
+ // src/lib/use-polyfill.ts
93
+ var use = (
94
+ // React.use ||
95
+ (promise) => {
96
+ if (promise.status === "pending") {
97
+ throw promise;
98
+ } else if (promise.status === "fulfilled") {
99
+ return promise.value;
100
+ } else if (promise.status === "rejected") {
101
+ throw promise.reason;
102
+ } else {
103
+ promise.status = "pending";
104
+ promise.then(
105
+ (v) => {
106
+ promise.status = "fulfilled";
107
+ promise.value = v;
108
+ },
109
+ (e) => {
110
+ promise.status = "rejected";
111
+ promise.reason = e;
112
+ }
113
+ );
114
+ throw promise;
115
+ }
116
+ }
117
+ );
118
+
27
119
  // src/umbrella-store.ts
28
120
  import {
29
121
  autoRetry,
30
122
  compactObject,
31
123
  console as console2,
32
124
  createStore,
125
+ HttpError,
33
126
  kInternal,
34
127
  makeEventSource,
35
128
  mapValues,
36
129
  nanoid,
37
130
  nn,
38
- StopRetrying,
39
131
  stringify
40
132
  } from "@liveblocks/core";
41
133
 
@@ -56,21 +148,13 @@ function autobind(self) {
56
148
  } while ((obj = Reflect.getPrototypeOf(obj)) && obj !== Object.prototype);
57
149
  }
58
150
 
59
- // src/lib/compare.ts
60
- function byFirstCreated(a, b) {
61
- return a.createdAt.getTime() - b.createdAt.getTime();
62
- }
63
- function isMoreRecentlyUpdated(a, b) {
64
- return byMostRecentlyUpdated(a, b) < 0;
65
- }
66
- function byMostRecentlyUpdated(a, b) {
67
- return b.updatedAt.getTime() - a.updatedAt.getTime();
68
- }
151
+ // src/ThreadDB.ts
152
+ import { SortedList } from "@liveblocks/core";
69
153
 
70
154
  // src/lib/guards.ts
71
- import { isPlainObject } from "@liveblocks/core";
155
+ import { isPlainObject as isPlainObject2 } from "@liveblocks/core";
72
156
  function isStartsWith(blob) {
73
- return isPlainObject(blob) && isString(blob.startsWith);
157
+ return isPlainObject2(blob) && isString(blob.startsWith);
74
158
  }
75
159
  function isString(value) {
76
160
  return typeof value === "string";
@@ -103,8 +187,124 @@ function matchesOperator(value, op) {
103
187
  }
104
188
  }
105
189
 
190
+ // src/ThreadDB.ts
191
+ function sanitizeThread(thread) {
192
+ if (thread.deletedAt) {
193
+ if (thread.comments.length > 0) {
194
+ return { ...thread, comments: [] };
195
+ }
196
+ }
197
+ const hasComment = thread.comments.some((c) => !c.deletedAt);
198
+ if (!hasComment) {
199
+ return { ...thread, deletedAt: /* @__PURE__ */ new Date(), comments: [] };
200
+ }
201
+ return thread;
202
+ }
203
+ var ThreadDB = class _ThreadDB {
204
+ // The version is auto-incremented on every mutation and can be used as a reliable indicator to tell if the contents of the thread pool has changed
205
+ constructor() {
206
+ this._asc = SortedList.from([], (t1, t2) => {
207
+ const d1 = t1.createdAt;
208
+ const d2 = t2.createdAt;
209
+ return d1 < d2 ? true : d1 === d2 ? t1.id < t2.id : false;
210
+ });
211
+ this._desc = SortedList.from([], (t1, t2) => {
212
+ const d2 = t2.updatedAt;
213
+ const d1 = t1.updatedAt;
214
+ return d2 < d1 ? true : d2 === d1 ? t2.id < t1.id : false;
215
+ });
216
+ this._byId = /* @__PURE__ */ new Map();
217
+ this._version = 0;
218
+ }
219
+ //
220
+ // Public APIs
221
+ //
222
+ clone() {
223
+ const newPool = new _ThreadDB();
224
+ newPool._byId = new Map(this._byId);
225
+ newPool._asc = this._asc.clone();
226
+ newPool._desc = this._desc.clone();
227
+ newPool._version = this._version;
228
+ return newPool;
229
+ }
230
+ /** Gets the transaction count for this DB. Increments any time the DB is modified. */
231
+ get version() {
232
+ return this._version;
233
+ }
234
+ /** Returns an existing thread by ID. Will never return a deleted thread. */
235
+ get(threadId) {
236
+ const thread = this.getEvenIfDeleted(threadId);
237
+ return thread?.deletedAt ? void 0 : thread;
238
+ }
239
+ /** Returns the (possibly deleted) thread by ID. */
240
+ getEvenIfDeleted(threadId) {
241
+ return this._byId.get(threadId);
242
+ }
243
+ /** Adds or updates a thread in the DB. If the newly given thread is a deleted one, it will get deleted. */
244
+ upsert(thread) {
245
+ thread = sanitizeThread(thread);
246
+ const id = thread.id;
247
+ const toRemove = this._byId.get(id);
248
+ if (toRemove) {
249
+ if (toRemove.deletedAt) return;
250
+ this._asc.remove(toRemove);
251
+ this._desc.remove(toRemove);
252
+ }
253
+ if (!thread.deletedAt) {
254
+ this._asc.add(thread);
255
+ this._desc.add(thread);
256
+ }
257
+ this._byId.set(id, thread);
258
+ this.touch();
259
+ }
260
+ /** Like .upsert(), except it won't update if a thread by this ID already exists. */
261
+ // TODO Consider renaming this to just .upsert(). I'm not sure if we really
262
+ // TODO need the raw .upsert(). Would be nice if this behavior was the default.
263
+ upsertIfNewer(thread) {
264
+ const existing = this.get(thread.id);
265
+ if (!existing || thread.updatedAt >= existing.updatedAt) {
266
+ this.upsert(thread);
267
+ }
268
+ }
269
+ /**
270
+ * Marks a thread as deleted. It will no longer pop up in .findMany()
271
+ * queries, but it can still be accessed via `.getEvenIfDeleted()`.
272
+ */
273
+ delete(threadId, deletedAt) {
274
+ const existing = this._byId.get(threadId);
275
+ if (existing && !existing.deletedAt) {
276
+ this.upsert({ ...existing, deletedAt, updatedAt: deletedAt });
277
+ }
278
+ }
279
+ /**
280
+ * Returns all threads matching a given roomId and query. If roomId is not
281
+ * specified, it will return all threads matching the query, across all
282
+ * rooms.
283
+ *
284
+ * Returns the results in the requested order. Please note:
285
+ * 'asc' means by createdAt ASC
286
+ * 'desc' means by updatedAt DESC
287
+ *
288
+ * Will never return deleted threads in the result.
289
+ */
290
+ findMany(roomId, query, direction) {
291
+ const index = direction === "desc" ? this._desc : this._asc;
292
+ const crit = [];
293
+ if (roomId !== void 0) {
294
+ crit.push((t) => t.roomId === roomId);
295
+ }
296
+ crit.push(makeThreadsFilter(query));
297
+ return Array.from(index.filter((t) => crit.every((pred) => pred(t))));
298
+ }
299
+ //
300
+ // Private APIs
301
+ //
302
+ touch() {
303
+ ++this._version;
304
+ }
305
+ };
306
+
106
307
  // src/umbrella-store.ts
107
- var ASYNC_OK = Object.freeze({ isLoading: false, data: void 0 });
108
308
  function makeRoomThreadsQueryKey(roomId, query) {
109
309
  return `${roomId}-${stringify(query ?? {})}`;
110
310
  }
@@ -117,19 +317,6 @@ function makeNotificationSettingsQueryKey(roomId) {
117
317
  function makeVersionsQueryKey(roomId) {
118
318
  return `${roomId}-VERSIONS`;
119
319
  }
120
- function selectThreads(state, options) {
121
- let threads = state.threads;
122
- if (options.roomId !== null) {
123
- threads = threads.filter((thread) => thread.roomId === options.roomId);
124
- }
125
- const query = options.query;
126
- if (query) {
127
- threads = threads.filter(makeThreadsFilter(query));
128
- }
129
- return threads.sort(
130
- options.orderBy === "last-update" ? byMostRecentlyUpdated : byFirstCreated
131
- );
132
- }
133
320
  function usify(promise) {
134
321
  if ("status" in promise) {
135
322
  return promise;
@@ -148,7 +335,7 @@ function usify(promise) {
148
335
  );
149
336
  return usable;
150
337
  }
151
- var noop = Promise.resolve();
338
+ var noop2 = Promise.resolve();
152
339
  var ASYNC_LOADING = Object.freeze({ isLoading: true });
153
340
  var PaginatedResource = class {
154
341
  constructor(fetchPage) {
@@ -189,7 +376,7 @@ var PaginatedResource = class {
189
376
  fetchMore() {
190
377
  const state = this._paginationState;
191
378
  if (state?.cursor === null) {
192
- return noop;
379
+ return noop2;
193
380
  }
194
381
  if (!this._pendingFetchMore) {
195
382
  this._pendingFetchMore = this._fetchMore().finally(() => {
@@ -252,8 +439,54 @@ var PaginatedResource = class {
252
439
  return promise;
253
440
  }
254
441
  };
442
+ var SinglePageResource = class {
443
+ constructor(fetchPage) {
444
+ this._cachedPromise = null;
445
+ this._fetchPage = fetchPage;
446
+ this._eventSource = makeEventSource();
447
+ this.observable = this._eventSource.observable;
448
+ autobind(this);
449
+ }
450
+ get() {
451
+ const usable = this._cachedPromise;
452
+ if (usable === null || usable.status === "pending") {
453
+ return ASYNC_LOADING;
454
+ }
455
+ if (usable.status === "rejected") {
456
+ return { isLoading: false, error: usable.reason };
457
+ }
458
+ return {
459
+ isLoading: false,
460
+ data: void 0
461
+ };
462
+ }
463
+ waitUntilLoaded() {
464
+ if (this._cachedPromise) {
465
+ return this._cachedPromise;
466
+ }
467
+ const initialFetcher = autoRetry(
468
+ () => this._fetchPage(),
469
+ 5,
470
+ [5e3, 5e3, 1e4, 15e3]
471
+ );
472
+ const promise = usify(initialFetcher);
473
+ promise.then(
474
+ () => this._eventSource.notify(),
475
+ () => {
476
+ this._eventSource.notify();
477
+ setTimeout(() => {
478
+ this._cachedPromise = null;
479
+ this._eventSource.notify();
480
+ }, 5e3);
481
+ }
482
+ );
483
+ this._cachedPromise = promise;
484
+ return promise;
485
+ }
486
+ };
255
487
  var UmbrellaStore = class {
256
488
  constructor(client) {
489
+ this._prevVersion = -1;
257
490
  this._prevState = null;
258
491
  this._stateCached = null;
259
492
  // Notifications
@@ -264,16 +497,16 @@ var UmbrellaStore = class {
264
497
  // User Threads
265
498
  this._userThreadsLastRequestedAt = null;
266
499
  this._userThreads = /* @__PURE__ */ new Map();
500
+ // Room versions
501
+ this._roomVersions = /* @__PURE__ */ new Map();
502
+ this._roomVersionsLastRequestedAtByRoom = /* @__PURE__ */ new Map();
503
+ // Room notification settings
504
+ this._roomNotificationSettings = /* @__PURE__ */ new Map();
505
+ this._client = client[kInternal].as();
267
506
  const inboxFetcher = async (cursor) => {
268
- if (client === void 0) {
269
- throw new StopRetrying(
270
- "Client is required in order to load threads for the room"
271
- );
272
- }
273
- const result = await client.getInboxNotifications({ cursor });
507
+ const result = await this._client.getInboxNotifications({ cursor });
274
508
  this.updateThreadsAndNotifications(
275
509
  result.threads,
276
- // TODO: Figure out how to remove this casting
277
510
  result.inboxNotifications
278
511
  );
279
512
  if (this._notificationsLastRequestedAt === null) {
@@ -282,7 +515,6 @@ var UmbrellaStore = class {
282
515
  const nextCursor = result.nextCursor;
283
516
  return nextCursor;
284
517
  };
285
- this._client = client;
286
518
  this._notifications = new PaginatedResource(inboxFetcher);
287
519
  this._notifications.observable.subscribe(
288
520
  () => (
@@ -291,10 +523,8 @@ var UmbrellaStore = class {
291
523
  this._store.set((store) => ({ ...store }))
292
524
  )
293
525
  );
526
+ this._rawThreadsDB = new ThreadDB();
294
527
  this._store = createStore({
295
- rawThreadsById: {},
296
- queries3: {},
297
- queries4: {},
298
528
  optimisticUpdates: [],
299
529
  notificationsById: {},
300
530
  settingsByRoomId: {},
@@ -304,9 +534,11 @@ var UmbrellaStore = class {
304
534
  }
305
535
  get() {
306
536
  const rawState = this._store.get();
307
- if (this._prevState !== rawState || this._stateCached === null) {
537
+ if (this._prevVersion !== this._rawThreadsDB.version || // Note: Version check is only needed temporarily, until we can get rid of the Zustand-like update model
538
+ this._prevState !== rawState || this._stateCached === null) {
539
+ this._stateCached = internalToExternalState(rawState, this._rawThreadsDB);
308
540
  this._prevState = rawState;
309
- this._stateCached = internalToExternalState(rawState);
541
+ this._prevVersion = this._rawThreadsDB.version;
310
542
  }
311
543
  return this._stateCached;
312
544
  }
@@ -321,8 +553,7 @@ var UmbrellaStore = class {
321
553
  * then it will return the threads that match that provided query and room id.
322
554
  *
323
555
  */
324
- // XXXX Find a better name for that doesn't associate to 'async'
325
- getRoomThreadsAsync(roomId, query) {
556
+ getRoomThreadsLoadingState(roomId, query) {
326
557
  const queryKey = makeRoomThreadsQueryKey(roomId, query);
327
558
  const paginatedResource = this._roomThreads.get(queryKey);
328
559
  if (paginatedResource === void 0) {
@@ -332,11 +563,11 @@ var UmbrellaStore = class {
332
563
  if (asyncResult.isLoading || asyncResult.error) {
333
564
  return asyncResult;
334
565
  }
335
- const threads = selectThreads(this.getFullState(), {
566
+ const threads = this.getFullState().threadsDB.findMany(
336
567
  roomId,
337
- query,
338
- orderBy: "age"
339
- });
568
+ query ?? {},
569
+ "asc"
570
+ );
340
571
  const page = asyncResult.data;
341
572
  return {
342
573
  isLoading: false,
@@ -347,8 +578,7 @@ var UmbrellaStore = class {
347
578
  fetchMore: page.fetchMore
348
579
  };
349
580
  }
350
- // XXXX - Find a better name for that doesn't associate to 'async'
351
- getUserThreadsAsync(query) {
581
+ getUserThreadsLoadingState(query) {
352
582
  const queryKey = makeUserThreadsQueryKey(query);
353
583
  const paginatedResource = this._userThreads.get(queryKey);
354
584
  if (paginatedResource === void 0) {
@@ -358,12 +588,12 @@ var UmbrellaStore = class {
358
588
  if (asyncResult.isLoading || asyncResult.error) {
359
589
  return asyncResult;
360
590
  }
361
- const threads = selectThreads(this.getFullState(), {
362
- roomId: null,
591
+ const threads = this.getFullState().threadsDB.findMany(
592
+ void 0,
363
593
  // Do _not_ filter by roomId
364
- query,
365
- orderBy: "last-update"
366
- });
594
+ query ?? {},
595
+ "desc"
596
+ );
367
597
  const page = asyncResult.data;
368
598
  return {
369
599
  isLoading: false,
@@ -375,8 +605,7 @@ var UmbrellaStore = class {
375
605
  };
376
606
  }
377
607
  // NOTE: This will read the async result, but WILL NOT start loading at the moment!
378
- // XXXX - Find a better name for that doesn't associate to 'async'
379
- getInboxNotificationsAsync() {
608
+ getInboxNotificationsLoadingState() {
380
609
  const asyncResult = this._notifications.get();
381
610
  if (asyncResult.isLoading || asyncResult.error) {
382
611
  return asyncResult;
@@ -384,7 +613,7 @@ var UmbrellaStore = class {
384
613
  const page = asyncResult.data;
385
614
  return {
386
615
  isLoading: false,
387
- inboxNotifications: this.getFullState().notifications,
616
+ inboxNotifications: this.getFullState().cleanedNotifications,
388
617
  hasFetchedAll: page.hasFetchedAll,
389
618
  isFetchingMore: page.isFetchingMore,
390
619
  fetchMoreError: page.fetchMoreError,
@@ -392,32 +621,34 @@ var UmbrellaStore = class {
392
621
  };
393
622
  }
394
623
  // NOTE: This will read the async result, but WILL NOT start loading at the moment!
395
- getNotificationSettingsAsync(roomId) {
396
- const state = this.get();
397
- const query = state.queries3[makeNotificationSettingsQueryKey(roomId)];
398
- if (query === void 0 || query.isLoading) {
624
+ getNotificationSettingsLoadingState(roomId) {
625
+ const queryKey = makeNotificationSettingsQueryKey(roomId);
626
+ const resource = this._roomNotificationSettings.get(queryKey);
627
+ if (resource === void 0) {
399
628
  return ASYNC_LOADING;
400
629
  }
401
- if (query.error !== void 0) {
402
- return query;
630
+ const asyncResult = resource.get();
631
+ if (asyncResult.isLoading || asyncResult.error) {
632
+ return asyncResult;
403
633
  }
404
634
  return {
405
635
  isLoading: false,
406
- settings: nn(state.settingsByRoomId[roomId])
636
+ settings: nn(this.get().settingsByRoomId[roomId])
407
637
  };
408
638
  }
409
- getVersionsAsync(roomId) {
410
- const state = this.get();
411
- const query = state.queries4[makeVersionsQueryKey(roomId)];
412
- if (query === void 0 || query.isLoading) {
639
+ getRoomVersionsLoadingState(roomId) {
640
+ const queryKey = makeVersionsQueryKey(roomId);
641
+ const resource = this._roomVersions.get(queryKey);
642
+ if (resource === void 0) {
413
643
  return ASYNC_LOADING;
414
644
  }
415
- if (query.error !== void 0) {
416
- return query;
645
+ const asyncResult = resource.get();
646
+ if (asyncResult.isLoading || asyncResult.error) {
647
+ return asyncResult;
417
648
  }
418
649
  return {
419
650
  isLoading: false,
420
- versions: nn(state.versionsByRoomId[roomId])
651
+ versions: Object.values(this.get().versionsByRoomId[roomId] ?? {})
421
652
  };
422
653
  }
423
654
  /**
@@ -429,33 +660,14 @@ var UmbrellaStore = class {
429
660
  subscribe(callback) {
430
661
  return this._store.subscribe(callback);
431
662
  }
432
- /**
433
- * @private Only used by the E2E test suite.
434
- */
435
- _subscribeOptimisticUpdates(callback) {
436
- return this.subscribe(callback);
437
- }
438
- subscribeThreads(callback) {
439
- return this.subscribe(callback);
440
- }
441
- subscribeUserThreads(callback) {
442
- return this.subscribe(callback);
443
- }
444
- subscribeThreadsOrInboxNotifications(callback) {
445
- return this.subscribe(callback);
446
- }
447
- subscribeNotificationSettings(callback) {
448
- return this.subscribe(callback);
449
- }
450
- subscribeVersions(callback) {
451
- return this.subscribe(callback);
452
- }
453
663
  // Direct low-level cache mutations ------------------------------------------------- {{{
454
- updateThreadsCache(mapFn) {
455
- this._store.set((state) => {
456
- const threads = mapFn(state.rawThreadsById);
457
- return threads !== state.rawThreadsById ? { ...state, rawThreadsById: threads } : state;
458
- });
664
+ mutateThreadsDB(mutate) {
665
+ const db = this._rawThreadsDB;
666
+ const old = db.version;
667
+ mutate(db);
668
+ if (old !== db.version) {
669
+ this._store.set((state) => ({ ...state }));
670
+ }
459
671
  }
460
672
  updateInboxNotificationsCache(mapFn) {
461
673
  this._store.set((state) => {
@@ -472,32 +684,23 @@ var UmbrellaStore = class {
472
684
  }
473
685
  }));
474
686
  }
475
- setVersions(roomId, versions) {
476
- this._store.set((state) => ({
477
- ...state,
478
- versionsByRoomId: {
479
- ...state.versionsByRoomId,
480
- [roomId]: versions
481
- }
482
- }));
483
- }
484
- setQuery3State(queryKey, queryState) {
485
- this._store.set((state) => ({
486
- ...state,
487
- queries3: {
488
- ...state.queries3,
489
- [queryKey]: queryState
490
- }
491
- }));
492
- }
493
- setQuery4State(queryKey, queryState) {
494
- this._store.set((state) => ({
495
- ...state,
496
- queries4: {
497
- ...state.queries4,
498
- [queryKey]: queryState
499
- }
500
- }));
687
+ updateRoomVersions(roomId, versions) {
688
+ this._store.set((state) => {
689
+ const versionsById = Object.fromEntries(
690
+ versions.map((version2) => [version2.id, version2])
691
+ );
692
+ return {
693
+ ...state,
694
+ versionsByRoomId: {
695
+ ...state.versionsByRoomId,
696
+ [roomId]: {
697
+ // Merge with existing versions for the room, or start with an empty object
698
+ ...state.versionsByRoomId[roomId] ?? {},
699
+ ...versionsById
700
+ }
701
+ }
702
+ };
703
+ });
501
704
  }
502
705
  updateOptimisticUpdatesCache(mapFn) {
503
706
  this._store.set((state) => ({
@@ -572,7 +775,7 @@ var UmbrellaStore = class {
572
775
  createThread(optimisticUpdateId, thread) {
573
776
  this._store.batch(() => {
574
777
  this.removeOptimisticUpdate(optimisticUpdateId);
575
- this.updateThreadsCache((cache) => ({ ...cache, [thread.id]: thread }));
778
+ this.mutateThreadsDB((db) => db.upsert(thread));
576
779
  });
577
780
  }
578
781
  /**
@@ -590,18 +793,11 @@ var UmbrellaStore = class {
590
793
  if (optimisticUpdateId !== null) {
591
794
  this.removeOptimisticUpdate(optimisticUpdateId);
592
795
  }
593
- this.updateThreadsCache((cache) => {
594
- const existing = cache[threadId];
595
- if (!existing) {
596
- return cache;
597
- }
598
- if (existing.deletedAt !== void 0) {
599
- return cache;
600
- }
601
- if (!!updatedAt && existing.updatedAt > updatedAt) {
602
- return cache;
603
- }
604
- return { ...cache, [threadId]: callback(existing) };
796
+ this.mutateThreadsDB((db) => {
797
+ const existing = db.get(threadId);
798
+ if (!existing) return;
799
+ if (!!updatedAt && existing.updatedAt > updatedAt) return;
800
+ db.upsert(callback(existing));
605
801
  });
606
802
  });
607
803
  }
@@ -652,14 +848,13 @@ var UmbrellaStore = class {
652
848
  createComment(newComment, optimisticUpdateId) {
653
849
  this._store.batch(() => {
654
850
  this.removeOptimisticUpdate(optimisticUpdateId);
655
- const existingThread = this._store.get().rawThreadsById[newComment.threadId];
851
+ const existingThread = this._rawThreadsDB.get(newComment.threadId);
656
852
  if (!existingThread) {
657
853
  return;
658
854
  }
659
- this.updateThreadsCache((cache) => ({
660
- ...cache,
661
- [newComment.threadId]: applyUpsertComment(existingThread, newComment)
662
- }));
855
+ this.mutateThreadsDB(
856
+ (db) => db.upsert(applyUpsertComment(existingThread, newComment))
857
+ );
663
858
  this.updateInboxNotificationsCache((cache) => {
664
859
  const existingNotification = Object.values(cache).find(
665
860
  (notification) => notification.kind === "thread" && notification.threadId === newComment.threadId
@@ -695,10 +890,7 @@ var UmbrellaStore = class {
695
890
  }
696
891
  updateThreadAndNotification(thread, inboxNotification) {
697
892
  this._store.batch(() => {
698
- this.updateThreadsCache((cache) => {
699
- const existingThread = cache[thread.id];
700
- return existingThread === void 0 || isMoreRecentlyUpdated(thread, existingThread) ? { ...cache, [thread.id]: thread } : cache;
701
- });
893
+ this.mutateThreadsDB((db) => db.upsertIfNewer(thread));
702
894
  if (inboxNotification !== void 0) {
703
895
  this.updateInboxNotificationsCache((cache) => ({
704
896
  ...cache,
@@ -709,11 +901,8 @@ var UmbrellaStore = class {
709
901
  }
710
902
  updateThreadsAndNotifications(threads, inboxNotifications, deletedThreads = [], deletedInboxNotifications = []) {
711
903
  this._store.batch(() => {
712
- this.updateThreadsCache(
713
- (cache) => applyThreadUpdates(cache, {
714
- newThreads: threads,
715
- deletedThreads
716
- })
904
+ this.mutateThreadsDB(
905
+ (db) => applyThreadDeltaUpdates(db, { newThreads: threads, deletedThreads })
717
906
  );
718
907
  this.updateInboxNotificationsCache(
719
908
  (cache) => applyNotificationsUpdates(cache, {
@@ -727,28 +916,12 @@ var UmbrellaStore = class {
727
916
  * Updates existing notification setting for a room with a new value,
728
917
  * replacing the corresponding optimistic update.
729
918
  */
730
- // XXXX Rename this helper method
731
- updateRoomInboxNotificationSettings2(roomId, optimisticUpdateId, settings) {
919
+ updateRoomNotificationSettings_confirmOptimisticUpdate(roomId, optimisticUpdateId, settings) {
732
920
  this._store.batch(() => {
733
921
  this.removeOptimisticUpdate(optimisticUpdateId);
734
922
  this.setNotificationSettings(roomId, settings);
735
923
  });
736
924
  }
737
- // XXXX Rename this helper method
738
- updateRoomInboxNotificationSettings(roomId, settings, queryKey) {
739
- this._store.batch(() => {
740
- this.setQuery3OK(queryKey);
741
- this.setNotificationSettings(roomId, settings);
742
- });
743
- }
744
- updateRoomVersions(roomId, versions, queryKey) {
745
- this._store.batch(() => {
746
- this.setVersions(roomId, versions);
747
- if (queryKey !== void 0) {
748
- this.setQuery4OK(queryKey);
749
- }
750
- });
751
- }
752
925
  addOptimisticUpdate(optimisticUpdate) {
753
926
  const id = nanoid();
754
927
  const newUpdate = { ...optimisticUpdate, id };
@@ -760,36 +933,15 @@ var UmbrellaStore = class {
760
933
  (cache) => cache.filter((ou) => ou.id !== optimisticUpdateId)
761
934
  );
762
935
  }
763
- // Query 3
764
- setQuery3Loading(queryKey) {
765
- this.setQuery3State(queryKey, ASYNC_LOADING);
766
- }
767
- setQuery3OK(queryKey) {
768
- this.setQuery3State(queryKey, ASYNC_OK);
769
- }
770
- setQuery3Error(queryKey, error) {
771
- this.setQuery3State(queryKey, { isLoading: false, error });
772
- }
773
- // Query 4
774
- setQuery4Loading(queryKey) {
775
- this.setQuery4State(queryKey, ASYNC_LOADING);
776
- }
777
- setQuery4OK(queryKey) {
778
- this.setQuery4State(queryKey, ASYNC_OK);
779
- }
780
- setQuery4Error(queryKey, error) {
781
- this.setQuery4State(queryKey, { isLoading: false, error });
782
- }
783
- async fetchNotificationsDeltaUpdate() {
936
+ async fetchNotificationsDeltaUpdate(signal) {
784
937
  const lastRequestedAt = this._notificationsLastRequestedAt;
785
938
  if (lastRequestedAt === null) {
786
939
  return;
787
940
  }
788
- const client = nn(
789
- this._client,
790
- "Client is required in order to load notifications for the room"
791
- );
792
- const result = await client.getInboxNotificationsSince(lastRequestedAt);
941
+ const result = await this._client.getInboxNotificationsSince({
942
+ since: lastRequestedAt,
943
+ signal
944
+ });
793
945
  if (lastRequestedAt < result.requestedAt) {
794
946
  this._notificationsLastRequestedAt = result.requestedAt;
795
947
  }
@@ -805,21 +957,13 @@ var UmbrellaStore = class {
805
957
  }
806
958
  waitUntilRoomThreadsLoaded(roomId, query) {
807
959
  const threadsFetcher = async (cursor) => {
808
- if (this._client === void 0) {
809
- throw new StopRetrying(
810
- "Client is required in order to load threads for the room"
811
- );
812
- }
813
960
  const room = this._client.getRoom(roomId);
814
961
  if (room === null) {
815
- throw new StopRetrying(
816
- `Room with id ${roomId} is not available on client`
817
- );
962
+ throw new HttpError(`Room '${roomId}' is not available on client`, 479);
818
963
  }
819
964
  const result = await room.getThreads({ cursor, query });
820
965
  this.updateThreadsAndNotifications(
821
966
  result.threads,
822
- // TODO: Figure out how to remove this casting
823
967
  result.inboxNotifications
824
968
  );
825
969
  const lastRequestedAt = this._roomThreadsLastRequestedAtByRoom.get(roomId);
@@ -843,21 +987,18 @@ var UmbrellaStore = class {
843
987
  this._roomThreads.set(queryKey, paginatedResource);
844
988
  return paginatedResource.waitUntilLoaded();
845
989
  }
846
- async fetchRoomThreadsDeltaUpdate(roomId) {
990
+ async fetchRoomThreadsDeltaUpdate(roomId, signal) {
847
991
  const lastRequestedAt = this._roomThreadsLastRequestedAtByRoom.get(roomId);
848
992
  if (lastRequestedAt === void 0) {
849
993
  return;
850
994
  }
851
- const client = nn(
852
- this._client,
853
- "Client is required in order to load notifications for the room"
854
- );
855
995
  const room = nn(
856
- client.getRoom(roomId),
996
+ this._client.getRoom(roomId),
857
997
  `Room with id ${roomId} is not available on client`
858
998
  );
859
999
  const updates = await room.getThreadsSince({
860
- since: lastRequestedAt
1000
+ since: lastRequestedAt,
1001
+ signal
861
1002
  });
862
1003
  this.updateThreadsAndNotifications(
863
1004
  updates.threads.updated,
@@ -872,18 +1013,12 @@ var UmbrellaStore = class {
872
1013
  waitUntilUserThreadsLoaded(query) {
873
1014
  const queryKey = makeUserThreadsQueryKey(query);
874
1015
  const threadsFetcher = async (cursor) => {
875
- if (this._client === void 0) {
876
- throw new StopRetrying(
877
- "Client is required in order to load threads for the room"
878
- );
879
- }
880
1016
  const result = await this._client[kInternal].getUserThreads_experimental({
881
1017
  cursor,
882
1018
  query
883
1019
  });
884
1020
  this.updateThreadsAndNotifications(
885
1021
  result.threads,
886
- // TODO: Figure out how to remove this casting
887
1022
  result.inboxNotifications
888
1023
  );
889
1024
  if (this._userThreadsLastRequestedAt === null) {
@@ -905,17 +1040,14 @@ var UmbrellaStore = class {
905
1040
  this._userThreads.set(queryKey, paginatedResource);
906
1041
  return paginatedResource.waitUntilLoaded();
907
1042
  }
908
- async fetchUserThreadsDeltaUpdate() {
1043
+ async fetchUserThreadsDeltaUpdate(signal) {
909
1044
  const lastRequestedAt = this._userThreadsLastRequestedAt;
910
1045
  if (lastRequestedAt === null) {
911
1046
  return;
912
1047
  }
913
- const client = nn(
914
- this._client,
915
- "Client is required in order to load threads for the user"
916
- );
917
- const result = await client[kInternal].getUserThreadsSince_experimental({
918
- since: lastRequestedAt
1048
+ const result = await this._client[kInternal].getUserThreadsSince_experimental({
1049
+ since: lastRequestedAt,
1050
+ signal
919
1051
  });
920
1052
  if (lastRequestedAt < result.requestedAt) {
921
1053
  this._notificationsLastRequestedAt = result.requestedAt;
@@ -927,77 +1059,138 @@ var UmbrellaStore = class {
927
1059
  result.inboxNotifications.deleted
928
1060
  );
929
1061
  }
1062
+ waitUntilRoomVersionsLoaded(roomId) {
1063
+ const queryKey = makeVersionsQueryKey(roomId);
1064
+ let resource = this._roomVersions.get(queryKey);
1065
+ if (resource === void 0) {
1066
+ const versionsFetcher = async () => {
1067
+ const room = this._client.getRoom(roomId);
1068
+ if (room === null) {
1069
+ throw new HttpError(
1070
+ `Room '${roomId}' is not available on client`,
1071
+ 479
1072
+ );
1073
+ }
1074
+ const result = await room[kInternal].listTextVersions();
1075
+ this.updateRoomVersions(roomId, result.versions);
1076
+ const lastRequestedAt = this._roomVersionsLastRequestedAtByRoom.get(roomId);
1077
+ if (lastRequestedAt === void 0 || lastRequestedAt > result.requestedAt) {
1078
+ this._roomVersionsLastRequestedAtByRoom.set(
1079
+ roomId,
1080
+ result.requestedAt
1081
+ );
1082
+ }
1083
+ };
1084
+ resource = new SinglePageResource(versionsFetcher);
1085
+ }
1086
+ resource.observable.subscribe(
1087
+ () => (
1088
+ // Note that the store itself does not change, but it's only vehicle at
1089
+ // the moment to trigger a re-render, so we'll do a no-op update here.
1090
+ this._store.set((store) => ({ ...store }))
1091
+ )
1092
+ );
1093
+ this._roomVersions.set(queryKey, resource);
1094
+ return resource.waitUntilLoaded();
1095
+ }
1096
+ async fetchRoomVersionsDeltaUpdate(roomId, signal) {
1097
+ const lastRequestedAt = this._roomVersionsLastRequestedAtByRoom.get(roomId);
1098
+ if (lastRequestedAt === void 0) {
1099
+ return;
1100
+ }
1101
+ const room = nn(
1102
+ this._client.getRoom(roomId),
1103
+ `Room with id ${roomId} is not available on client`
1104
+ );
1105
+ const updates = await room[kInternal].listTextVersionsSince({
1106
+ since: lastRequestedAt,
1107
+ signal
1108
+ });
1109
+ this.updateRoomVersions(roomId, updates.versions);
1110
+ if (lastRequestedAt < updates.requestedAt) {
1111
+ this._roomVersionsLastRequestedAtByRoom.set(roomId, updates.requestedAt);
1112
+ }
1113
+ }
1114
+ waitUntilRoomNotificationSettingsLoaded(roomId) {
1115
+ const queryKey = makeNotificationSettingsQueryKey(roomId);
1116
+ let resource = this._roomNotificationSettings.get(queryKey);
1117
+ if (resource === void 0) {
1118
+ const notificationSettingsFetcher = async () => {
1119
+ const room = this._client.getRoom(roomId);
1120
+ if (room === null) {
1121
+ throw new HttpError(
1122
+ `Room '${roomId}' is not available on client`,
1123
+ 479
1124
+ );
1125
+ }
1126
+ const result = await room.getNotificationSettings();
1127
+ this.setNotificationSettings(roomId, result);
1128
+ };
1129
+ resource = new SinglePageResource(notificationSettingsFetcher);
1130
+ }
1131
+ resource.observable.subscribe(
1132
+ () => (
1133
+ // Note that the store itself does not change, but it's only vehicle at
1134
+ // the moment to trigger a re-render, so we'll do a no-op update here.
1135
+ this._store.set((store) => ({ ...store }))
1136
+ )
1137
+ );
1138
+ this._roomNotificationSettings.set(queryKey, resource);
1139
+ return resource.waitUntilLoaded();
1140
+ }
1141
+ async refreshRoomNotificationSettings(roomId, signal) {
1142
+ const room = nn(
1143
+ this._client.getRoom(roomId),
1144
+ `Room with id ${roomId} is not available on client`
1145
+ );
1146
+ const result = await room.getNotificationSettings({ signal });
1147
+ this.setNotificationSettings(roomId, result);
1148
+ }
930
1149
  };
931
- function internalToExternalState(state) {
1150
+ function internalToExternalState(state, rawThreadsDB) {
1151
+ const threadsDB = rawThreadsDB.clone();
932
1152
  const computed = {
933
- threadsById: { ...state.rawThreadsById },
934
1153
  notificationsById: { ...state.notificationsById },
935
1154
  settingsByRoomId: { ...state.settingsByRoomId }
936
1155
  };
937
1156
  for (const optimisticUpdate of state.optimisticUpdates) {
938
1157
  switch (optimisticUpdate.type) {
939
1158
  case "create-thread": {
940
- computed.threadsById[optimisticUpdate.thread.id] = optimisticUpdate.thread;
1159
+ threadsDB.upsert(optimisticUpdate.thread);
941
1160
  break;
942
1161
  }
943
1162
  case "edit-thread-metadata": {
944
- const thread = computed.threadsById[optimisticUpdate.threadId];
945
- if (thread === void 0) {
946
- break;
947
- }
948
- if (thread.deletedAt !== void 0) {
949
- break;
950
- }
1163
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1164
+ if (thread === void 0) break;
951
1165
  if (thread.updatedAt > optimisticUpdate.updatedAt) {
952
1166
  break;
953
1167
  }
954
- computed.threadsById[thread.id] = {
1168
+ threadsDB.upsert({
955
1169
  ...thread,
956
1170
  updatedAt: optimisticUpdate.updatedAt,
957
1171
  metadata: {
958
1172
  ...thread.metadata,
959
1173
  ...optimisticUpdate.metadata
960
1174
  }
961
- };
1175
+ });
962
1176
  break;
963
1177
  }
964
1178
  case "mark-thread-as-resolved": {
965
- const thread = computed.threadsById[optimisticUpdate.threadId];
966
- if (thread === void 0) {
967
- break;
968
- }
969
- if (thread.deletedAt !== void 0) {
970
- break;
971
- }
972
- computed.threadsById[thread.id] = {
973
- ...thread,
974
- resolved: true
975
- };
1179
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1180
+ if (thread === void 0) break;
1181
+ threadsDB.upsert({ ...thread, resolved: true });
976
1182
  break;
977
1183
  }
978
1184
  case "mark-thread-as-unresolved": {
979
- const thread = computed.threadsById[optimisticUpdate.threadId];
980
- if (thread === void 0) {
981
- break;
982
- }
983
- if (thread.deletedAt !== void 0) {
984
- break;
985
- }
986
- computed.threadsById[thread.id] = {
987
- ...thread,
988
- resolved: false
989
- };
1185
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1186
+ if (thread === void 0) break;
1187
+ threadsDB.upsert({ ...thread, resolved: false });
990
1188
  break;
991
1189
  }
992
1190
  case "create-comment": {
993
- const thread = computed.threadsById[optimisticUpdate.comment.threadId];
994
- if (thread === void 0) {
995
- break;
996
- }
997
- computed.threadsById[thread.id] = applyUpsertComment(
998
- thread,
999
- optimisticUpdate.comment
1000
- );
1191
+ const thread = threadsDB.get(optimisticUpdate.comment.threadId);
1192
+ if (thread === void 0) break;
1193
+ threadsDB.upsert(applyUpsertComment(thread, optimisticUpdate.comment));
1001
1194
  const inboxNotification = Object.values(
1002
1195
  computed.notificationsById
1003
1196
  ).find(
@@ -1012,66 +1205,59 @@ function internalToExternalState(state) {
1012
1205
  readAt: optimisticUpdate.comment.createdAt
1013
1206
  };
1014
1207
  break;
1015
- }
1016
- case "edit-comment": {
1017
- const thread = computed.threadsById[optimisticUpdate.comment.threadId];
1018
- if (thread === void 0) {
1019
- break;
1020
- }
1021
- computed.threadsById[thread.id] = applyUpsertComment(
1022
- thread,
1023
- optimisticUpdate.comment
1024
- );
1208
+ }
1209
+ case "edit-comment": {
1210
+ const thread = threadsDB.get(optimisticUpdate.comment.threadId);
1211
+ if (thread === void 0) break;
1212
+ threadsDB.upsert(applyUpsertComment(thread, optimisticUpdate.comment));
1025
1213
  break;
1026
1214
  }
1027
1215
  case "delete-comment": {
1028
- const thread = computed.threadsById[optimisticUpdate.threadId];
1029
- if (thread === void 0) {
1030
- break;
1031
- }
1032
- computed.threadsById[thread.id] = applyDeleteComment(
1033
- thread,
1034
- optimisticUpdate.commentId,
1035
- optimisticUpdate.deletedAt
1216
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1217
+ if (thread === void 0) break;
1218
+ threadsDB.upsert(
1219
+ applyDeleteComment(
1220
+ thread,
1221
+ optimisticUpdate.commentId,
1222
+ optimisticUpdate.deletedAt
1223
+ )
1036
1224
  );
1037
1225
  break;
1038
1226
  }
1039
1227
  case "delete-thread": {
1040
- const thread = computed.threadsById[optimisticUpdate.threadId];
1041
- if (thread === void 0) {
1042
- break;
1043
- }
1044
- computed.threadsById[optimisticUpdate.threadId] = {
1228
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1229
+ if (thread === void 0) break;
1230
+ threadsDB.upsert({
1045
1231
  ...thread,
1046
1232
  deletedAt: optimisticUpdate.deletedAt,
1047
1233
  updatedAt: optimisticUpdate.deletedAt,
1048
1234
  comments: []
1049
- };
1235
+ });
1050
1236
  break;
1051
1237
  }
1052
1238
  case "add-reaction": {
1053
- const thread = computed.threadsById[optimisticUpdate.threadId];
1054
- if (thread === void 0) {
1055
- break;
1056
- }
1057
- computed.threadsById[thread.id] = applyAddReaction(
1058
- thread,
1059
- optimisticUpdate.commentId,
1060
- optimisticUpdate.reaction
1239
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1240
+ if (thread === void 0) break;
1241
+ threadsDB.upsert(
1242
+ applyAddReaction(
1243
+ thread,
1244
+ optimisticUpdate.commentId,
1245
+ optimisticUpdate.reaction
1246
+ )
1061
1247
  );
1062
1248
  break;
1063
1249
  }
1064
1250
  case "remove-reaction": {
1065
- const thread = computed.threadsById[optimisticUpdate.threadId];
1066
- if (thread === void 0) {
1067
- break;
1068
- }
1069
- computed.threadsById[thread.id] = applyRemoveReaction(
1070
- thread,
1071
- optimisticUpdate.commentId,
1072
- optimisticUpdate.emoji,
1073
- optimisticUpdate.userId,
1074
- optimisticUpdate.removedAt
1251
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1252
+ if (thread === void 0) break;
1253
+ threadsDB.upsert(
1254
+ applyRemoveReaction(
1255
+ thread,
1256
+ optimisticUpdate.commentId,
1257
+ optimisticUpdate.emoji,
1258
+ optimisticUpdate.userId,
1259
+ optimisticUpdate.removedAt
1260
+ )
1075
1261
  );
1076
1262
  break;
1077
1263
  }
@@ -1119,51 +1305,27 @@ function internalToExternalState(state) {
1119
1305
  }
1120
1306
  }
1121
1307
  }
1122
- const cleanedThreads = (
1123
- // Don't expose any soft-deleted threads
1124
- Object.values(computed.threadsById).filter((thread) => !thread.deletedAt).filter(
1125
- (thread) => (
1126
- // Only keep a thread if there is at least one non-deleted comment
1127
- thread.comments.some((c) => c.deletedAt === void 0)
1128
- )
1129
- )
1130
- );
1131
1308
  const cleanedNotifications = (
1132
1309
  // Sort so that the most recent notifications are first
1133
1310
  Object.values(computed.notificationsById).filter(
1134
- (ibn) => ibn.kind === "thread" ? computed.threadsById[ibn.threadId] && computed.threadsById[ibn.threadId]?.deletedAt === void 0 : true
1311
+ (ibn) => ibn.kind === "thread" ? threadsDB.get(ibn.threadId) !== void 0 : true
1135
1312
  ).sort((a, b) => b.notifiedAt.getTime() - a.notifiedAt.getTime())
1136
1313
  );
1137
1314
  return {
1138
- notifications: cleanedNotifications,
1315
+ cleanedNotifications,
1139
1316
  notificationsById: computed.notificationsById,
1140
1317
  settingsByRoomId: computed.settingsByRoomId,
1141
- queries3: state.queries3,
1142
- queries4: state.queries4,
1143
- threads: cleanedThreads,
1144
- threadsById: computed.threadsById,
1318
+ threadsDB,
1145
1319
  versionsByRoomId: state.versionsByRoomId
1146
1320
  };
1147
1321
  }
1148
- function applyThreadUpdates(existingThreads, updates) {
1149
- const updatedThreads = { ...existingThreads };
1150
- updates.newThreads.forEach((thread) => {
1151
- const existingThread = updatedThreads[thread.id];
1152
- if (existingThread) {
1153
- if (isMoreRecentlyUpdated(existingThread, thread)) {
1154
- return;
1155
- }
1156
- }
1157
- updatedThreads[thread.id] = thread;
1158
- });
1322
+ function applyThreadDeltaUpdates(db, updates) {
1323
+ updates.newThreads.forEach((thread) => db.upsertIfNewer(thread));
1159
1324
  updates.deletedThreads.forEach(({ id, deletedAt }) => {
1160
- const existingThread = updatedThreads[id];
1161
- if (existingThread === void 0) return;
1162
- existingThread.deletedAt = deletedAt;
1163
- existingThread.updatedAt = deletedAt;
1164
- existingThread.comments = [];
1325
+ const existing = db.getEvenIfDeleted(id);
1326
+ if (!existing) return;
1327
+ db.delete(id, deletedAt);
1165
1328
  });
1166
- return updatedThreads;
1167
1329
  }
1168
1330
  function applyNotificationsUpdates(existingInboxNotifications, updates) {
1169
1331
  const updatedInboxNotifications = { ...existingInboxNotifications };
@@ -1361,97 +1523,6 @@ function upsertReaction(reactions, reaction) {
1361
1523
  return reactions;
1362
1524
  }
1363
1525
 
1364
- // src/liveblocks.tsx
1365
- import {
1366
- assert,
1367
- createClient,
1368
- kInternal as kInternal2,
1369
- makePoller,
1370
- raise,
1371
- shallow as shallow3
1372
- } from "@liveblocks/core";
1373
- import React3, {
1374
- createContext as createContext2,
1375
- useCallback as useCallback2,
1376
- useContext as useContext2,
1377
- useEffect as useEffect3,
1378
- useMemo
1379
- } from "react";
1380
- import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
1381
- import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
1382
-
1383
- // src/lib/shallow2.ts
1384
- import { isPlainObject as isPlainObject2, shallow } from "@liveblocks/core";
1385
- function shallow2(a, b) {
1386
- if (!isPlainObject2(a) || !isPlainObject2(b)) {
1387
- return shallow(a, b);
1388
- }
1389
- const keysA = Object.keys(a);
1390
- if (keysA.length !== Object.keys(b).length) {
1391
- return false;
1392
- }
1393
- return keysA.every(
1394
- (key) => Object.prototype.hasOwnProperty.call(b, key) && shallow(a[key], b[key])
1395
- );
1396
- }
1397
-
1398
- // src/lib/use-initial.ts
1399
- import { useCallback, useReducer } from "react";
1400
-
1401
- // src/lib/use-latest.ts
1402
- import { useEffect as useEffect2, useRef } from "react";
1403
- function useLatest(value) {
1404
- const ref = useRef(value);
1405
- useEffect2(() => {
1406
- ref.current = value;
1407
- }, [value]);
1408
- return ref;
1409
- }
1410
-
1411
- // src/lib/use-initial.ts
1412
- var noop2 = (state) => state;
1413
- function useInitial(value) {
1414
- return useReducer(noop2, value)[0];
1415
- }
1416
- function useInitialUnlessFunction(latestValue) {
1417
- const frozenValue = useInitial(latestValue);
1418
- if (typeof frozenValue === "function") {
1419
- const ref = useLatest(latestValue);
1420
- return useCallback((...args) => ref.current(...args), [
1421
- ref
1422
- ]);
1423
- } else {
1424
- return frozenValue;
1425
- }
1426
- }
1427
-
1428
- // src/lib/use-polyfill.ts
1429
- var use = (
1430
- // React.use ||
1431
- (promise) => {
1432
- if (promise.status === "pending") {
1433
- throw promise;
1434
- } else if (promise.status === "fulfilled") {
1435
- return promise.value;
1436
- } else if (promise.status === "rejected") {
1437
- throw promise.reason;
1438
- } else {
1439
- promise.status = "pending";
1440
- promise.then(
1441
- (v) => {
1442
- promise.status = "fulfilled";
1443
- promise.value = v;
1444
- },
1445
- (e) => {
1446
- promise.status = "rejected";
1447
- promise.reason = e;
1448
- }
1449
- );
1450
- throw promise;
1451
- }
1452
- }
1453
- );
1454
-
1455
1526
  // src/liveblocks.tsx
1456
1527
  var ClientContext = createContext2(null);
1457
1528
  function missingUserError(userId) {
@@ -1468,7 +1539,6 @@ function identity(x) {
1468
1539
  var _umbrellaStores = /* @__PURE__ */ new WeakMap();
1469
1540
  var _extras = /* @__PURE__ */ new WeakMap();
1470
1541
  var _bundles = /* @__PURE__ */ new WeakMap();
1471
- var POLLING_INTERVAL = 60 * 1e3;
1472
1542
  function selectUnreadInboxNotificationsCount(inboxNotifications) {
1473
1543
  let count = 0;
1474
1544
  for (const notification of inboxNotifications) {
@@ -1547,61 +1617,36 @@ function getLiveblocksExtrasForClient(client) {
1547
1617
  }
1548
1618
  return extras;
1549
1619
  }
1550
- function makeDeltaPoller_Notifications(store) {
1551
- const poller = makePoller(async () => {
1552
- try {
1553
- await store.waitUntilNotificationsLoaded();
1554
- await store.fetchNotificationsDeltaUpdate();
1555
- } catch (err) {
1556
- console.warn(`Polling new inbox notifications failed: ${String(err)}`);
1557
- }
1558
- }, POLLING_INTERVAL);
1559
- let pollerSubscribers = 0;
1560
- return () => {
1561
- pollerSubscribers++;
1562
- poller.enable(pollerSubscribers > 0);
1563
- return () => {
1564
- pollerSubscribers--;
1565
- poller.enable(pollerSubscribers > 0);
1566
- };
1567
- };
1568
- }
1569
- function makeDeltaPoller_UserThreads(store) {
1570
- const poller = makePoller(async () => {
1571
- try {
1572
- await store.fetchUserThreadsDeltaUpdate();
1573
- } catch (err) {
1574
- console.warn(`Polling new user threads failed: ${String(err)}`);
1575
- }
1576
- }, POLLING_INTERVAL);
1577
- let pollerSubscribers = 0;
1578
- return () => {
1579
- pollerSubscribers++;
1580
- poller.enable(pollerSubscribers > 0);
1581
- return () => {
1582
- pollerSubscribers--;
1583
- poller.enable(pollerSubscribers > 0);
1584
- };
1585
- };
1586
- }
1587
1620
  function makeLiveblocksExtrasForClient(client) {
1588
1621
  const store = getUmbrellaStoreForClient(client);
1622
+ const notificationsPoller = makePoller(
1623
+ async (signal) => {
1624
+ try {
1625
+ return await store.fetchNotificationsDeltaUpdate(signal);
1626
+ } catch (err) {
1627
+ console.warn(`Polling new inbox notifications failed: ${String(err)}`);
1628
+ throw err;
1629
+ }
1630
+ },
1631
+ config.NOTIFICATIONS_POLL_INTERVAL,
1632
+ { maxStaleTimeMs: config.NOTIFICATIONS_MAX_STALE_TIME }
1633
+ );
1634
+ const userThreadsPoller = makePoller(
1635
+ async (signal) => {
1636
+ try {
1637
+ return await store.fetchUserThreadsDeltaUpdate(signal);
1638
+ } catch (err) {
1639
+ console.warn(`Polling new user threads failed: ${String(err)}`);
1640
+ throw err;
1641
+ }
1642
+ },
1643
+ config.USER_THREADS_POLL_INTERVAL,
1644
+ { maxStaleTimeMs: config.USER_THREADS_MAX_STALE_TIME }
1645
+ );
1589
1646
  return {
1590
1647
  store,
1591
- /**
1592
- * Sub/unsub pair to start the process of watching for new incoming inbox
1593
- * notifications through a stream of delta updates. Call the unsub function
1594
- * returned to stop this subscription when unmounting. Currently
1595
- * implemented by a periodic poller.
1596
- */
1597
- subscribeToNotificationsDeltaUpdates: makeDeltaPoller_Notifications(store),
1598
- /**
1599
- * Sub/unsub pair to start the process of watching for new user threads
1600
- * through a stream of delta updates. Call the unsub function returned to
1601
- * stop this subscription when unmounting. Currently implemented by
1602
- * a periodic poller.
1603
- */
1604
- subscribeToUserThreadsDeltaUpdates: makeDeltaPoller_UserThreads(store)
1648
+ notificationsPoller,
1649
+ userThreadsPoller
1605
1650
  };
1606
1651
  }
1607
1652
  function makeLiveblocksContextBundle(client) {
@@ -1612,7 +1657,7 @@ function makeLiveblocksContextBundle(client) {
1612
1657
  const useDeleteAllInboxNotifications2 = () => useDeleteAllInboxNotifications_withClient(client);
1613
1658
  function LiveblocksProvider2(props) {
1614
1659
  useEnsureNoLiveblocksProvider();
1615
- return /* @__PURE__ */ React3.createElement(ClientContext.Provider, { value: client }, props.children);
1660
+ return /* @__PURE__ */ React2.createElement(ClientContext.Provider, { value: client }, props.children);
1616
1661
  }
1617
1662
  const shared = createSharedContext(client);
1618
1663
  const bundle = {
@@ -1642,19 +1687,21 @@ function makeLiveblocksContextBundle(client) {
1642
1687
  return bundle;
1643
1688
  }
1644
1689
  function useInboxNotifications_withClient(client, selector, isEqual) {
1645
- const {
1646
- store,
1647
- subscribeToNotificationsDeltaUpdates: subscribeToDeltaUpdates
1648
- } = getLiveblocksExtrasForClient(client);
1649
- useEffect3(() => {
1650
- void store.waitUntilNotificationsLoaded().catch(() => {
1651
- });
1690
+ const { store, notificationsPoller: poller } = getLiveblocksExtrasForClient(client);
1691
+ useEffect2(() => {
1692
+ void store.waitUntilNotificationsLoaded();
1652
1693
  });
1653
- useEffect3(subscribeToDeltaUpdates, [subscribeToDeltaUpdates]);
1694
+ useEffect2(() => {
1695
+ poller.inc();
1696
+ poller.pollNowIfStale();
1697
+ return () => {
1698
+ poller.dec();
1699
+ };
1700
+ }, [poller]);
1654
1701
  return useSyncExternalStoreWithSelector(
1655
- store.subscribeThreadsOrInboxNotifications,
1656
- store.getInboxNotificationsAsync,
1657
- store.getInboxNotificationsAsync,
1702
+ store.subscribe,
1703
+ store.getInboxNotificationsLoadingState,
1704
+ store.getInboxNotificationsLoadingState,
1658
1705
  selector,
1659
1706
  isEqual
1660
1707
  );
@@ -1783,7 +1830,7 @@ function useInboxNotificationThread_withClient(client, inboxNotificationId) {
1783
1830
  `Inbox notification with ID "${inboxNotificationId}" is not of kind "thread"`
1784
1831
  );
1785
1832
  }
1786
- const thread = state.threadsById[inboxNotification.threadId] ?? raise(
1833
+ const thread = state.threadsDB.get(inboxNotification.threadId) ?? raise(
1787
1834
  `Thread with ID "${inboxNotification.threadId}" not found, this inbox notification might not be of kind "thread"`
1788
1835
  );
1789
1836
  return thread;
@@ -1791,7 +1838,7 @@ function useInboxNotificationThread_withClient(client, inboxNotificationId) {
1791
1838
  [inboxNotificationId]
1792
1839
  );
1793
1840
  return useSyncExternalStoreWithSelector(
1794
- store.subscribeThreadsOrInboxNotifications,
1841
+ store.subscribe,
1795
1842
  // Re-evaluate if we need to update any time the notification changes over time
1796
1843
  getter,
1797
1844
  getter,
@@ -1804,20 +1851,21 @@ function useUser_withClient(client, userId) {
1804
1851
  () => usersStore.getState(userId),
1805
1852
  [usersStore, userId]
1806
1853
  );
1807
- useEffect3(() => {
1808
- void usersStore.get(userId);
1809
- }, [usersStore, userId]);
1810
1854
  const selector = useCallback2(
1811
1855
  (state) => selectorFor_useUser(state, userId),
1812
1856
  [userId]
1813
1857
  );
1814
- return useSyncExternalStoreWithSelector(
1858
+ const result = useSyncExternalStoreWithSelector(
1815
1859
  usersStore.subscribe,
1816
1860
  getUserState,
1817
1861
  getUserState,
1818
1862
  selector,
1819
1863
  shallow3
1820
1864
  );
1865
+ useEffect2(() => {
1866
+ void usersStore.get(userId);
1867
+ }, [usersStore, userId, result]);
1868
+ return result;
1821
1869
  }
1822
1870
  function useUserSuspense_withClient(client, userId) {
1823
1871
  const usersStore = client[kInternal2].usersStore;
@@ -1859,16 +1907,17 @@ function useRoomInfo_withClient(client, roomId) {
1859
1907
  (state) => selectorFor_useRoomInfo(state, roomId),
1860
1908
  [roomId]
1861
1909
  );
1862
- useEffect3(() => {
1863
- void roomsInfoStore.get(roomId);
1864
- }, [roomsInfoStore, roomId]);
1865
- return useSyncExternalStoreWithSelector(
1910
+ const result = useSyncExternalStoreWithSelector(
1866
1911
  roomsInfoStore.subscribe,
1867
1912
  getRoomInfoState,
1868
1913
  getRoomInfoState,
1869
1914
  selector,
1870
1915
  shallow3
1871
1916
  );
1917
+ useEffect2(() => {
1918
+ void roomsInfoStore.get(roomId);
1919
+ }, [roomsInfoStore, roomId, result]);
1920
+ return result;
1872
1921
  }
1873
1922
  function useRoomInfoSuspense_withClient(client, roomId) {
1874
1923
  const roomsInfoStore = client[kInternal2].roomsInfoStore;
@@ -1934,7 +1983,7 @@ function useClient() {
1934
1983
  }
1935
1984
  function LiveblocksProviderWithClient(props) {
1936
1985
  useEnsureNoLiveblocksProvider(props);
1937
- return /* @__PURE__ */ React3.createElement(ClientContext.Provider, { value: props.client }, props.children);
1986
+ return /* @__PURE__ */ React2.createElement(ClientContext.Provider, { value: props.client }, props.children);
1938
1987
  }
1939
1988
  function LiveblocksProvider(props) {
1940
1989
  const { children, ...o } = props;
@@ -1962,7 +2011,7 @@ function LiveblocksProvider(props) {
1962
2011
  )
1963
2012
  };
1964
2013
  const client = useMemo(() => createClient(options), []);
1965
- return /* @__PURE__ */ React3.createElement(LiveblocksProviderWithClient, { client }, children);
2014
+ return /* @__PURE__ */ React2.createElement(LiveblocksProviderWithClient, { client }, children);
1966
2015
  }
1967
2016
  function createLiveblocksContext(client) {
1968
2017
  return getOrCreateContextBundle(client);
@@ -1973,11 +2022,10 @@ function useUserThreads_experimental(options = {
1973
2022
  }
1974
2023
  }) {
1975
2024
  const client = useClient();
1976
- const { store, subscribeToUserThreadsDeltaUpdates: subscribeToDeltaUpdates } = getLiveblocksExtrasForClient(client);
1977
- useEffect3(
2025
+ const { store, userThreadsPoller: poller } = getLiveblocksExtrasForClient(client);
2026
+ useEffect2(
1978
2027
  () => {
1979
- void store.waitUntilUserThreadsLoaded(options.query).catch(() => {
1980
- });
2028
+ void store.waitUntilUserThreadsLoaded(options.query);
1981
2029
  }
1982
2030
  // NOTE: Deliberately *not* using a dependency array here!
1983
2031
  //
@@ -1988,13 +2036,19 @@ function useUserThreads_experimental(options = {
1988
2036
  // 3. If ever the promise would fail, then after 5 seconds it would reset, and on the very
1989
2037
  // *next* render after that, a *new* fetch/promise will get created.
1990
2038
  );
1991
- useEffect3(subscribeToDeltaUpdates, [subscribeToDeltaUpdates]);
2039
+ useEffect2(() => {
2040
+ poller.inc();
2041
+ poller.pollNowIfStale();
2042
+ return () => {
2043
+ poller.dec();
2044
+ };
2045
+ }, [poller]);
1992
2046
  const getter = useCallback2(
1993
- () => store.getUserThreadsAsync(options.query),
2047
+ () => store.getUserThreadsLoadingState(options.query),
1994
2048
  [store, options.query]
1995
2049
  );
1996
2050
  return useSyncExternalStoreWithSelector(
1997
- store.subscribeUserThreads,
2051
+ store.subscribe,
1998
2052
  getter,
1999
2053
  getter,
2000
2054
  identity,
@@ -2167,34 +2221,22 @@ var UpdateNotificationSettingsError = class extends Error {
2167
2221
  import { shallow as shallow4 } from "@liveblocks/client";
2168
2222
  import {
2169
2223
  assert as assert2,
2170
- CommentsApiError,
2171
2224
  console as console3,
2172
2225
  createCommentId,
2173
2226
  createThreadId,
2174
2227
  deprecateIf,
2175
2228
  errorIf,
2229
+ HttpError as HttpError2,
2176
2230
  kInternal as kInternal3,
2177
2231
  makeEventSource as makeEventSource2,
2178
2232
  makePoller as makePoller2,
2179
- NotificationsApiError,
2180
2233
  ServerMsgCode
2181
2234
  } from "@liveblocks/core";
2182
- import * as React5 from "react";
2235
+ import * as React4 from "react";
2183
2236
  import { useSyncExternalStoreWithSelector as useSyncExternalStoreWithSelector2 } from "use-sync-external-store/shim/with-selector.js";
2184
2237
 
2185
- // src/lib/retry-error.ts
2186
- var MAX_ERROR_RETRY_COUNT = 5;
2187
- var ERROR_RETRY_INTERVAL = 5e3;
2188
- function retryError(action, retryCount) {
2189
- if (retryCount >= MAX_ERROR_RETRY_COUNT) return;
2190
- const timeout = Math.pow(2, retryCount) * ERROR_RETRY_INTERVAL;
2191
- setTimeout(() => {
2192
- void action();
2193
- }, timeout);
2194
- }
2195
-
2196
2238
  // src/use-scroll-to-comment-on-load-effect.ts
2197
- import * as React4 from "react";
2239
+ import * as React3 from "react";
2198
2240
  function handleScrollToCommentOnLoad(shouldScrollOnLoad, state) {
2199
2241
  if (shouldScrollOnLoad === false) return;
2200
2242
  if (!state.threads) return;
@@ -2213,7 +2255,7 @@ function handleScrollToCommentOnLoad(shouldScrollOnLoad, state) {
2213
2255
  comment.scrollIntoView();
2214
2256
  }
2215
2257
  function useScrollToCommentOnLoadEffect(shouldScrollOnLoad, state) {
2216
- React4.useEffect(
2258
+ React3.useEffect(
2217
2259
  () => {
2218
2260
  handleScrollToCommentOnLoad(shouldScrollOnLoad, state);
2219
2261
  },
@@ -2243,7 +2285,6 @@ function useSyncExternalStore2(s, gs, gss) {
2243
2285
  return useSyncExternalStoreWithSelector2(s, gs, gss, identity2);
2244
2286
  }
2245
2287
  var STABLE_EMPTY_LIST = Object.freeze([]);
2246
- var POLLING_INTERVAL2 = 5 * 60 * 1e3;
2247
2288
  function alwaysEmptyList() {
2248
2289
  return STABLE_EMPTY_LIST;
2249
2290
  }
@@ -2298,28 +2339,6 @@ function handleApiError(err) {
2298
2339
  }
2299
2340
  return new Error(message);
2300
2341
  }
2301
- function makeDeltaPoller_RoomThreads(client) {
2302
- const store = getUmbrellaStoreForClient(client);
2303
- const poller = makePoller2(async () => {
2304
- const roomIds = client[kInternal3].getRoomIds();
2305
- await Promise.allSettled(
2306
- roomIds.map((roomId) => {
2307
- const room = client.getRoom(roomId);
2308
- if (room === null) return;
2309
- return store.fetchRoomThreadsDeltaUpdate(room.id);
2310
- })
2311
- );
2312
- }, POLLING_INTERVAL2);
2313
- let pollerSubscribers = 0;
2314
- return () => {
2315
- pollerSubscribers++;
2316
- poller.enable(pollerSubscribers > 0);
2317
- return () => {
2318
- pollerSubscribers--;
2319
- poller.enable(pollerSubscribers > 0);
2320
- };
2321
- };
2322
- }
2323
2342
  var _extras2 = /* @__PURE__ */ new WeakMap();
2324
2343
  var _bundles2 = /* @__PURE__ */ new WeakMap();
2325
2344
  function getOrCreateRoomContextBundle(client) {
@@ -2340,83 +2359,92 @@ function getRoomExtrasForClient(client) {
2340
2359
  }
2341
2360
  function makeRoomExtrasForClient(client) {
2342
2361
  const store = getUmbrellaStoreForClient(client);
2343
- const requestsByQuery = /* @__PURE__ */ new Map();
2344
- async function getRoomVersions(room, { retryCount } = { retryCount: 0 }) {
2345
- const queryKey = makeVersionsQueryKey(room.id);
2346
- const existingRequest = requestsByQuery.get(queryKey);
2347
- if (existingRequest !== void 0) return existingRequest;
2348
- const request = room[kInternal3].listTextVersions();
2349
- requestsByQuery.set(queryKey, request);
2350
- store.setQuery4Loading(queryKey);
2351
- try {
2352
- const result = await request;
2353
- const data = await result.json();
2354
- const versions = data.versions.map(({ createdAt, ...version2 }) => {
2355
- return {
2356
- createdAt: new Date(createdAt),
2357
- ...version2
2358
- };
2359
- });
2360
- store.updateRoomVersions(room.id, versions, queryKey);
2361
- requestsByQuery.delete(queryKey);
2362
- } catch (err) {
2363
- requestsByQuery.delete(queryKey);
2364
- retryError(() => {
2365
- void getRoomVersions(room, {
2366
- retryCount: retryCount + 1
2367
- });
2368
- }, retryCount);
2369
- store.setQuery4Error(queryKey, err);
2370
- }
2371
- return;
2372
- }
2373
- async function getInboxNotificationSettings(room, { retryCount } = { retryCount: 0 }) {
2374
- const queryKey = makeNotificationSettingsQueryKey(room.id);
2375
- const existingRequest = requestsByQuery.get(queryKey);
2376
- if (existingRequest !== void 0) return existingRequest;
2377
- try {
2378
- const request = room.getNotificationSettings();
2379
- requestsByQuery.set(queryKey, request);
2380
- store.setQuery3Loading(queryKey);
2381
- const settings = await request;
2382
- store.updateRoomInboxNotificationSettings(room.id, settings, queryKey);
2383
- } catch (err) {
2384
- requestsByQuery.delete(queryKey);
2385
- retryError(() => {
2386
- void getInboxNotificationSettings(room, {
2387
- retryCount: retryCount + 1
2388
- });
2389
- }, retryCount);
2390
- store.setQuery3Error(queryKey, err);
2391
- }
2392
- return;
2393
- }
2394
2362
  const commentsErrorEventSource = makeEventSource2();
2395
2363
  function onMutationFailure(innerError, optimisticUpdateId, createPublicError) {
2396
2364
  store.removeOptimisticUpdate(optimisticUpdateId);
2397
- if (innerError instanceof CommentsApiError) {
2365
+ if (innerError instanceof HttpError2) {
2398
2366
  const error = handleApiError(innerError);
2399
2367
  commentsErrorEventSource.notify(createPublicError(error));
2400
2368
  return;
2401
2369
  }
2402
- if (innerError instanceof NotificationsApiError) {
2370
+ if (innerError instanceof HttpError2) {
2403
2371
  handleApiError(innerError);
2404
2372
  return;
2405
2373
  }
2406
2374
  throw innerError;
2407
2375
  }
2376
+ const threadsPollersByRoomId = /* @__PURE__ */ new Map();
2377
+ const versionsPollersByRoomId = /* @__PURE__ */ new Map();
2378
+ const roomNotificationSettingsPollersByRoomId = /* @__PURE__ */ new Map();
2379
+ function getOrCreateThreadsPollerForRoomId(roomId) {
2380
+ let poller = threadsPollersByRoomId.get(roomId);
2381
+ if (!poller) {
2382
+ poller = makePoller2(
2383
+ async (signal) => {
2384
+ try {
2385
+ return await store.fetchRoomThreadsDeltaUpdate(roomId, signal);
2386
+ } catch (err) {
2387
+ console3.warn(`Polling new threads for '${roomId}' failed: ${String(err)}`);
2388
+ throw err;
2389
+ }
2390
+ },
2391
+ config.ROOM_THREADS_POLL_INTERVAL,
2392
+ { maxStaleTimeMs: config.ROOM_THREADS_MAX_STALE_TIME }
2393
+ );
2394
+ threadsPollersByRoomId.set(roomId, poller);
2395
+ }
2396
+ return poller;
2397
+ }
2398
+ function getOrCreateVersionsPollerForRoomId(roomId) {
2399
+ let poller = versionsPollersByRoomId.get(roomId);
2400
+ if (!poller) {
2401
+ poller = makePoller2(
2402
+ async (signal) => {
2403
+ try {
2404
+ return await store.fetchRoomVersionsDeltaUpdate(roomId, signal);
2405
+ } catch (err) {
2406
+ console3.warn(`Polling new history versions for '${roomId}' failed: ${String(err)}`);
2407
+ throw err;
2408
+ }
2409
+ },
2410
+ config.HISTORY_VERSIONS_POLL_INTERVAL,
2411
+ { maxStaleTimeMs: config.HISTORY_VERSIONS_MAX_STALE_TIME }
2412
+ );
2413
+ versionsPollersByRoomId.set(roomId, poller);
2414
+ }
2415
+ return poller;
2416
+ }
2417
+ function getOrCreateNotificationsSettingsPollerForRoomId(roomId) {
2418
+ let poller = roomNotificationSettingsPollersByRoomId.get(roomId);
2419
+ if (!poller) {
2420
+ poller = makePoller2(
2421
+ async (signal) => {
2422
+ try {
2423
+ return await store.refreshRoomNotificationSettings(roomId, signal);
2424
+ } catch (err) {
2425
+ console3.warn(`Polling notification settings for '${roomId}' failed: ${String(err)}`);
2426
+ throw err;
2427
+ }
2428
+ },
2429
+ config.NOTIFICATION_SETTINGS_POLL_INTERVAL,
2430
+ { maxStaleTimeMs: config.NOTIFICATION_SETTINGS_MAX_STALE_TIME }
2431
+ );
2432
+ roomNotificationSettingsPollersByRoomId.set(roomId, poller);
2433
+ }
2434
+ return poller;
2435
+ }
2408
2436
  return {
2409
2437
  store,
2410
- subscribeToRoomThreadsDeltaUpdates: makeDeltaPoller_RoomThreads(client),
2411
- commentsErrorEventSource,
2412
- getInboxNotificationSettings,
2413
- getRoomVersions,
2414
- onMutationFailure
2438
+ commentsErrorEventSource: commentsErrorEventSource.observable,
2439
+ onMutationFailure,
2440
+ getOrCreateThreadsPollerForRoomId,
2441
+ getOrCreateVersionsPollerForRoomId,
2442
+ getOrCreateNotificationsSettingsPollerForRoomId
2415
2443
  };
2416
2444
  }
2417
2445
  function makeRoomContextBundle(client) {
2418
2446
  function RoomProvider_withImplicitLiveblocksProvider(props) {
2419
- return /* @__PURE__ */ React5.createElement(LiveblocksProviderWithClient, { client, allowNesting: true }, /* @__PURE__ */ React5.createElement(RoomProvider, { ...props }));
2447
+ return /* @__PURE__ */ React4.createElement(LiveblocksProviderWithClient, { client, allowNesting: true }, /* @__PURE__ */ React4.createElement(RoomProvider, { ...props }));
2420
2448
  }
2421
2449
  const shared = createSharedContext(client);
2422
2450
  const bundle = {
@@ -2520,10 +2548,10 @@ function makeRoomContextBundle(client) {
2520
2548
  }
2521
2549
  function RoomProvider(props) {
2522
2550
  const client = useClient();
2523
- const [cache] = React5.useState(
2551
+ const [cache] = React4.useState(
2524
2552
  () => /* @__PURE__ */ new Map()
2525
2553
  );
2526
- const stableEnterRoom = React5.useCallback(
2554
+ const stableEnterRoom = React4.useCallback(
2527
2555
  (roomId, options) => {
2528
2556
  const cached = cache.get(roomId);
2529
2557
  if (cached) return cached;
@@ -2538,7 +2566,7 @@ function RoomProvider(props) {
2538
2566
  },
2539
2567
  [client, cache]
2540
2568
  );
2541
- return /* @__PURE__ */ React5.createElement(
2569
+ return /* @__PURE__ */ React4.createElement(
2542
2570
  RoomProviderInner,
2543
2571
  {
2544
2572
  ...props,
@@ -2558,7 +2586,7 @@ function RoomProviderInner(props) {
2558
2586
  if (!isString(roomId)) {
2559
2587
  throw new Error("RoomProvider id property should be a string.");
2560
2588
  }
2561
- const majorReactVersion = parseInt(React5.version) || 1;
2589
+ const majorReactVersion = parseInt(React4.version) || 1;
2562
2590
  const oldReactVersion = majorReactVersion < 18;
2563
2591
  errorIf(
2564
2592
  oldReactVersion && props.unstable_batchedUpdates === void 0,
@@ -2575,14 +2603,14 @@ function RoomProviderInner(props) {
2575
2603
  unstable_batchedUpdates: props.unstable_batchedUpdates,
2576
2604
  autoConnect: props.autoConnect ?? typeof window !== "undefined"
2577
2605
  });
2578
- const [{ room }, setRoomLeavePair] = React5.useState(
2606
+ const [{ room }, setRoomLeavePair] = React4.useState(
2579
2607
  () => stableEnterRoom(roomId, {
2580
2608
  ...frozenProps,
2581
2609
  autoConnect: false
2582
2610
  // Deliberately using false here on the first render, see below
2583
2611
  })
2584
2612
  );
2585
- React5.useEffect(() => {
2613
+ React4.useEffect(() => {
2586
2614
  const { store } = getRoomExtrasForClient(client);
2587
2615
  async function handleCommentEvent(message) {
2588
2616
  if (message.type === ServerMsgCode.THREAD_DELETED) {
@@ -2595,7 +2623,7 @@ function RoomProviderInner(props) {
2595
2623
  return;
2596
2624
  }
2597
2625
  const { thread, inboxNotification } = info;
2598
- const existingThread = store.getFullState().threadsById[message.threadId];
2626
+ const existingThread = store.getFullState().threadsDB.getEvenIfDeleted(message.threadId);
2599
2627
  switch (message.type) {
2600
2628
  case ServerMsgCode.COMMENT_EDITED:
2601
2629
  case ServerMsgCode.THREAD_METADATA_UPDATED:
@@ -2617,23 +2645,7 @@ function RoomProviderInner(props) {
2617
2645
  (message) => void handleCommentEvent(message)
2618
2646
  );
2619
2647
  }, [client, room]);
2620
- React5.useEffect(() => {
2621
- const store = getRoomExtrasForClient(client).store;
2622
- void store.fetchRoomThreadsDeltaUpdate(room.id).catch(() => {
2623
- });
2624
- }, [client, room.id]);
2625
- React5.useEffect(() => {
2626
- function handleIsOnline() {
2627
- const store = getRoomExtrasForClient(client).store;
2628
- void store.fetchRoomThreadsDeltaUpdate(room.id).catch(() => {
2629
- });
2630
- }
2631
- window.addEventListener("online", handleIsOnline);
2632
- return () => {
2633
- window.removeEventListener("online", handleIsOnline);
2634
- };
2635
- }, [client, room.id]);
2636
- React5.useEffect(() => {
2648
+ React4.useEffect(() => {
2637
2649
  const pair = stableEnterRoom(roomId, frozenProps);
2638
2650
  setRoomLeavePair(pair);
2639
2651
  const { room: room2, leave } = pair;
@@ -2644,7 +2656,7 @@ function RoomProviderInner(props) {
2644
2656
  leave();
2645
2657
  };
2646
2658
  }, [roomId, frozenProps, stableEnterRoom]);
2647
- return /* @__PURE__ */ React5.createElement(RoomContext.Provider, { value: room }, props.children);
2659
+ return /* @__PURE__ */ React4.createElement(RoomContext.Provider, { value: room }, props.children);
2648
2660
  }
2649
2661
  function useRoom() {
2650
2662
  const room = useRoomOrNull();
@@ -2677,9 +2689,9 @@ function useStorageStatusImmediate() {
2677
2689
  }
2678
2690
  function useStorageStatusSmooth() {
2679
2691
  const room = useRoom();
2680
- const [status, setStatus] = React5.useState(room.getStorageStatus);
2692
+ const [status, setStatus] = React4.useState(room.getStorageStatus);
2681
2693
  const oldStatus = useLatest(room.getStorageStatus());
2682
- React5.useEffect(() => {
2694
+ React4.useEffect(() => {
2683
2695
  let timeoutId;
2684
2696
  const unsub = room.events.storageStatus.subscribe((newStatus) => {
2685
2697
  if (oldStatus.current === "synchronizing" && newStatus === "synchronized") {
@@ -2701,7 +2713,7 @@ function useBatch() {
2701
2713
  }
2702
2714
  function useBroadcastEvent() {
2703
2715
  const room = useRoom();
2704
- return React5.useCallback(
2716
+ return React4.useCallback(
2705
2717
  (event, options = { shouldQueueEventIfNotReady: false }) => {
2706
2718
  room.broadcastEvent(event, options);
2707
2719
  },
@@ -2711,7 +2723,7 @@ function useBroadcastEvent() {
2711
2723
  function useOthersListener(callback) {
2712
2724
  const room = useRoom();
2713
2725
  const savedCallback = useLatest(callback);
2714
- React5.useEffect(
2726
+ React4.useEffect(
2715
2727
  () => room.events.others.subscribe((event) => savedCallback.current(event)),
2716
2728
  [room, savedCallback]
2717
2729
  );
@@ -2719,7 +2731,7 @@ function useOthersListener(callback) {
2719
2731
  function useLostConnectionListener(callback) {
2720
2732
  const room = useRoom();
2721
2733
  const savedCallback = useLatest(callback);
2722
- React5.useEffect(
2734
+ React4.useEffect(
2723
2735
  () => room.events.lostConnection.subscribe(
2724
2736
  (event) => savedCallback.current(event)
2725
2737
  ),
@@ -2729,7 +2741,7 @@ function useLostConnectionListener(callback) {
2729
2741
  function useErrorListener(callback) {
2730
2742
  const room = useRoom();
2731
2743
  const savedCallback = useLatest(callback);
2732
- React5.useEffect(
2744
+ React4.useEffect(
2733
2745
  () => room.events.error.subscribe((e) => savedCallback.current(e)),
2734
2746
  [room, savedCallback]
2735
2747
  );
@@ -2737,7 +2749,7 @@ function useErrorListener(callback) {
2737
2749
  function useEventListener(callback) {
2738
2750
  const room = useRoom();
2739
2751
  const savedCallback = useLatest(callback);
2740
- React5.useEffect(() => {
2752
+ React4.useEffect(() => {
2741
2753
  const listener = (eventData) => {
2742
2754
  savedCallback.current(eventData);
2743
2755
  };
@@ -2770,7 +2782,7 @@ function useSelf(maybeSelector, isEqual) {
2770
2782
  const subscribe = room.events.self.subscribe;
2771
2783
  const getSnapshot = room.getSelf;
2772
2784
  const selector = maybeSelector ?? identity2;
2773
- const wrappedSelector = React5.useCallback(
2785
+ const wrappedSelector = React4.useCallback(
2774
2786
  (me) => me !== null ? selector(me) : null,
2775
2787
  [selector]
2776
2788
  );
@@ -2808,11 +2820,11 @@ function useOthers(selector, isEqual) {
2808
2820
  );
2809
2821
  }
2810
2822
  function useOthersMapped(itemSelector, itemIsEqual) {
2811
- const wrappedSelector = React5.useCallback(
2823
+ const wrappedSelector = React4.useCallback(
2812
2824
  (others) => others.map((other) => [other.connectionId, itemSelector(other)]),
2813
2825
  [itemSelector]
2814
2826
  );
2815
- const wrappedIsEqual = React5.useCallback(
2827
+ const wrappedIsEqual = React4.useCallback(
2816
2828
  (a, b) => {
2817
2829
  const eq = itemIsEqual ?? Object.is;
2818
2830
  return a.length === b.length && a.every((atuple, index) => {
@@ -2829,14 +2841,14 @@ function useOthersConnectionIds() {
2829
2841
  }
2830
2842
  var NOT_FOUND = Symbol();
2831
2843
  function useOther(connectionId, selector, isEqual) {
2832
- const wrappedSelector = React5.useCallback(
2844
+ const wrappedSelector = React4.useCallback(
2833
2845
  (others) => {
2834
2846
  const other2 = others.find((other3) => other3.connectionId === connectionId);
2835
2847
  return other2 !== void 0 ? selector(other2) : NOT_FOUND;
2836
2848
  },
2837
2849
  [connectionId, selector]
2838
2850
  );
2839
- const wrappedIsEqual = React5.useCallback(
2851
+ const wrappedIsEqual = React4.useCallback(
2840
2852
  (prev, curr) => {
2841
2853
  if (prev === NOT_FOUND || curr === NOT_FOUND) {
2842
2854
  return prev === curr;
@@ -2867,15 +2879,15 @@ function useStorageRoot() {
2867
2879
  function useStorage(selector, isEqual) {
2868
2880
  const room = useRoom();
2869
2881
  const rootOrNull = useMutableStorageRoot();
2870
- const wrappedSelector = React5.useCallback(
2882
+ const wrappedSelector = React4.useCallback(
2871
2883
  (rootOrNull2) => rootOrNull2 !== null ? selector(rootOrNull2) : null,
2872
2884
  [selector]
2873
2885
  );
2874
- const subscribe = React5.useCallback(
2886
+ const subscribe = React4.useCallback(
2875
2887
  (onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop3,
2876
2888
  [room, rootOrNull]
2877
2889
  );
2878
- const getSnapshot = React5.useCallback(() => {
2890
+ const getSnapshot = React4.useCallback(() => {
2879
2891
  if (rootOrNull === null) {
2880
2892
  return null;
2881
2893
  } else {
@@ -2895,7 +2907,7 @@ function useStorage(selector, isEqual) {
2895
2907
  }
2896
2908
  function useMutation(callback, deps) {
2897
2909
  const room = useRoom();
2898
- return React5.useMemo(
2910
+ return React4.useMemo(
2899
2911
  () => {
2900
2912
  return (...args) => (
2901
2913
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
@@ -2920,11 +2932,11 @@ function useThreads(options = {
2920
2932
  const { scrollOnLoad = true } = options;
2921
2933
  const client = useClient();
2922
2934
  const room = useRoom();
2923
- const { store, subscribeToRoomThreadsDeltaUpdates: subscribeToDeltaUpdates } = getRoomExtrasForClient(client);
2924
- React5.useEffect(
2935
+ const { store, getOrCreateThreadsPollerForRoomId } = getRoomExtrasForClient(client);
2936
+ const poller = getOrCreateThreadsPollerForRoomId(room.id);
2937
+ React4.useEffect(
2925
2938
  () => {
2926
- void store.waitUntilRoomThreadsLoaded(room.id, options.query).catch(() => {
2927
- });
2939
+ void store.waitUntilRoomThreadsLoaded(room.id, options.query);
2928
2940
  }
2929
2941
  // NOTE: Deliberately *not* using a dependency array here!
2930
2942
  //
@@ -2935,13 +2947,17 @@ function useThreads(options = {
2935
2947
  // 3. If ever the promise would fail, then after 5 seconds it would reset, and on the very
2936
2948
  // *next* render after that, a *new* fetch/promise will get created.
2937
2949
  );
2938
- React5.useEffect(subscribeToDeltaUpdates, [subscribeToDeltaUpdates]);
2939
- const getter = React5.useCallback(
2940
- () => store.getRoomThreadsAsync(room.id, options.query),
2950
+ React4.useEffect(() => {
2951
+ poller.inc();
2952
+ poller.pollNowIfStale();
2953
+ return () => poller.dec();
2954
+ }, [poller]);
2955
+ const getter = React4.useCallback(
2956
+ () => store.getRoomThreadsLoadingState(room.id, options.query),
2941
2957
  [store, room.id, options.query]
2942
2958
  );
2943
2959
  const state = useSyncExternalStoreWithSelector2(
2944
- store.subscribeThreads,
2960
+ store.subscribe,
2945
2961
  getter,
2946
2962
  getter,
2947
2963
  identity2,
@@ -2955,14 +2971,14 @@ function useCommentsErrorListener(callback) {
2955
2971
  const client = useClient();
2956
2972
  const savedCallback = useLatest(callback);
2957
2973
  const { commentsErrorEventSource } = getRoomExtrasForClient(client);
2958
- React5.useEffect(() => {
2974
+ React4.useEffect(() => {
2959
2975
  return commentsErrorEventSource.subscribe(savedCallback.current);
2960
2976
  }, [savedCallback, commentsErrorEventSource]);
2961
2977
  }
2962
2978
  function useCreateThread() {
2963
2979
  const client = useClient();
2964
2980
  const room = useRoom();
2965
- return React5.useCallback(
2981
+ return React4.useCallback(
2966
2982
  (options) => {
2967
2983
  const body = options.body;
2968
2984
  const metadata = options.metadata ?? {};
@@ -3022,12 +3038,12 @@ function useCreateThread() {
3022
3038
  function useDeleteThread() {
3023
3039
  const client = useClient();
3024
3040
  const room = useRoom();
3025
- return React5.useCallback(
3041
+ return React4.useCallback(
3026
3042
  (threadId) => {
3027
3043
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
3028
- const thread = store.getFullState().threadsById[threadId];
3029
3044
  const userId = getCurrentUserId(room);
3030
- if (thread?.comments?.[0]?.userId !== userId) {
3045
+ const existing = store.getFullState().threadsDB.get(threadId);
3046
+ if (existing?.comments?.[0]?.userId !== userId) {
3031
3047
  throw new Error("Only the thread creator can delete the thread");
3032
3048
  }
3033
3049
  const optimisticUpdateId = store.addOptimisticUpdate({
@@ -3053,7 +3069,7 @@ function useDeleteThread() {
3053
3069
  function useEditThreadMetadata() {
3054
3070
  const client = useClient();
3055
3071
  const room = useRoom();
3056
- return React5.useCallback(
3072
+ return React4.useCallback(
3057
3073
  (options) => {
3058
3074
  if (!options.metadata) {
3059
3075
  return;
@@ -3095,7 +3111,7 @@ function useEditThreadMetadata() {
3095
3111
  function useCreateComment() {
3096
3112
  const client = useClient();
3097
3113
  const room = useRoom();
3098
- return React5.useCallback(
3114
+ return React4.useCallback(
3099
3115
  ({ threadId, body, attachments }) => {
3100
3116
  const commentId = createCommentId();
3101
3117
  const createdAt = /* @__PURE__ */ new Date();
@@ -3139,18 +3155,18 @@ function useCreateComment() {
3139
3155
  function useEditComment() {
3140
3156
  const client = useClient();
3141
3157
  const room = useRoom();
3142
- return React5.useCallback(
3158
+ return React4.useCallback(
3143
3159
  ({ threadId, commentId, body, attachments }) => {
3144
3160
  const editedAt = /* @__PURE__ */ new Date();
3145
3161
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
3146
- const thread = store.getFullState().threadsById[threadId];
3147
- if (thread === void 0) {
3162
+ const existing = store.getFullState().threadsDB.getEvenIfDeleted(threadId);
3163
+ if (existing === void 0) {
3148
3164
  console3.warn(
3149
3165
  `Internal unexpected behavior. Cannot edit comment in thread "${threadId}" because the thread does not exist in the cache.`
3150
3166
  );
3151
3167
  return;
3152
3168
  }
3153
- const comment = thread.comments.find(
3169
+ const comment = existing.comments.find(
3154
3170
  (comment2) => comment2.id === commentId
3155
3171
  );
3156
3172
  if (comment === void 0 || comment.deletedAt !== void 0) {
@@ -3191,7 +3207,7 @@ function useEditComment() {
3191
3207
  function useDeleteComment() {
3192
3208
  const client = useClient();
3193
3209
  const room = useRoom();
3194
- return React5.useCallback(
3210
+ return React4.useCallback(
3195
3211
  ({ threadId, commentId }) => {
3196
3212
  const deletedAt = /* @__PURE__ */ new Date();
3197
3213
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
@@ -3228,7 +3244,7 @@ function useDeleteComment() {
3228
3244
  function useAddReaction() {
3229
3245
  const client = useClient();
3230
3246
  const room = useRoom();
3231
- return React5.useCallback(
3247
+ return React4.useCallback(
3232
3248
  ({ threadId, commentId, emoji }) => {
3233
3249
  const createdAt = /* @__PURE__ */ new Date();
3234
3250
  const userId = getCurrentUserId(room);
@@ -3271,7 +3287,7 @@ function useAddReaction() {
3271
3287
  function useRemoveReaction() {
3272
3288
  const client = useClient();
3273
3289
  const room = useRoom();
3274
- return React5.useCallback(
3290
+ return React4.useCallback(
3275
3291
  ({ threadId, commentId, emoji }) => {
3276
3292
  const userId = getCurrentUserId(room);
3277
3293
  const removedAt = /* @__PURE__ */ new Date();
@@ -3313,7 +3329,7 @@ function useRemoveReaction() {
3313
3329
  function useMarkThreadAsRead() {
3314
3330
  const client = useClient();
3315
3331
  const room = useRoom();
3316
- return React5.useCallback(
3332
+ return React4.useCallback(
3317
3333
  (threadId) => {
3318
3334
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
3319
3335
  const inboxNotification = Object.values(
@@ -3354,7 +3370,7 @@ function useMarkThreadAsRead() {
3354
3370
  function useMarkThreadAsResolved() {
3355
3371
  const client = useClient();
3356
3372
  const room = useRoom();
3357
- return React5.useCallback(
3373
+ return React4.useCallback(
3358
3374
  (threadId) => {
3359
3375
  const updatedAt = /* @__PURE__ */ new Date();
3360
3376
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
@@ -3388,7 +3404,7 @@ function useMarkThreadAsResolved() {
3388
3404
  function useMarkThreadAsUnresolved() {
3389
3405
  const client = useClient();
3390
3406
  const room = useRoom();
3391
- return React5.useCallback(
3407
+ return React4.useCallback(
3392
3408
  (threadId) => {
3393
3409
  const updatedAt = /* @__PURE__ */ new Date();
3394
3410
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
@@ -3422,26 +3438,24 @@ function useMarkThreadAsUnresolved() {
3422
3438
  function useThreadSubscription(threadId) {
3423
3439
  const client = useClient();
3424
3440
  const { store } = getRoomExtrasForClient(client);
3425
- const selector = React5.useCallback(
3441
+ const selector = React4.useCallback(
3426
3442
  (state) => {
3427
- const inboxNotification = state.notifications.find(
3428
- (inboxNotification2) => inboxNotification2.kind === "thread" && inboxNotification2.threadId === threadId
3443
+ const notification = state.cleanedNotifications.find(
3444
+ (inboxNotification) => inboxNotification.kind === "thread" && inboxNotification.threadId === threadId
3429
3445
  );
3430
- const thread = state.threadsById[threadId];
3431
- if (inboxNotification === void 0 || thread === void 0) {
3432
- return {
3433
- status: "not-subscribed"
3434
- };
3446
+ const thread = state.threadsDB.get(threadId);
3447
+ if (notification === void 0 || thread === void 0) {
3448
+ return { status: "not-subscribed" };
3435
3449
  }
3436
3450
  return {
3437
3451
  status: "subscribed",
3438
- unreadSince: inboxNotification.readAt
3452
+ unreadSince: notification.readAt
3439
3453
  };
3440
3454
  },
3441
3455
  [threadId]
3442
3456
  );
3443
3457
  return useSyncExternalStoreWithSelector2(
3444
- store.subscribeThreads,
3458
+ store.subscribe,
3445
3459
  store.getFullState,
3446
3460
  store.getFullState,
3447
3461
  selector
@@ -3451,58 +3465,61 @@ function useRoomNotificationSettings() {
3451
3465
  const updateRoomNotificationSettings = useUpdateRoomNotificationSettings();
3452
3466
  const client = useClient();
3453
3467
  const room = useRoom();
3454
- const { store } = getRoomExtrasForClient(client);
3455
- const getter = React5.useCallback(
3456
- () => store.getNotificationSettingsAsync(room.id),
3468
+ const { store, getOrCreateNotificationsSettingsPollerForRoomId } = getRoomExtrasForClient(client);
3469
+ const poller = getOrCreateNotificationsSettingsPollerForRoomId(room.id);
3470
+ React4.useEffect(
3471
+ () => {
3472
+ void store.waitUntilRoomNotificationSettingsLoaded(room.id);
3473
+ }
3474
+ // NOTE: Deliberately *not* using a dependency array here!
3475
+ //
3476
+ // It is important to call waitUntil on *every* render.
3477
+ // This is harmless though, on most renders, except:
3478
+ // 1. The very first render, in which case we'll want to trigger the initial page fetch.
3479
+ // 2. All other subsequent renders now "just" return the same promise (a quick operation).
3480
+ // 3. If ever the promise would fail, then after 5 seconds it would reset, and on the very
3481
+ // *next* render after that, a *new* fetch/promise will get created.
3482
+ );
3483
+ React4.useEffect(() => {
3484
+ poller.inc();
3485
+ poller.pollNowIfStale();
3486
+ return () => {
3487
+ poller.dec();
3488
+ };
3489
+ }, [poller]);
3490
+ const getter = React4.useCallback(
3491
+ () => store.getNotificationSettingsLoadingState(room.id),
3457
3492
  [store, room.id]
3458
3493
  );
3459
- React5.useEffect(() => {
3460
- const { getInboxNotificationSettings } = getRoomExtrasForClient(client);
3461
- void getInboxNotificationSettings(room);
3462
- }, [client, room]);
3463
3494
  const settings = useSyncExternalStoreWithSelector2(
3464
- store.subscribeNotificationSettings,
3495
+ store.subscribe,
3465
3496
  getter,
3466
3497
  getter,
3467
3498
  identity2,
3468
- shallow4
3499
+ shallow2
3469
3500
  );
3470
- return React5.useMemo(() => {
3501
+ return React4.useMemo(() => {
3471
3502
  return [settings, updateRoomNotificationSettings];
3472
3503
  }, [settings, updateRoomNotificationSettings]);
3473
3504
  }
3474
3505
  function useRoomNotificationSettingsSuspense() {
3475
- const updateRoomNotificationSettings = useUpdateRoomNotificationSettings();
3476
3506
  const client = useClient();
3507
+ const store = getRoomExtrasForClient(client).store;
3477
3508
  const room = useRoom();
3478
- const { store } = getRoomExtrasForClient(client);
3479
- const getter = React5.useCallback(
3480
- () => store.getNotificationSettingsAsync(room.id),
3481
- [store, room.id]
3482
- );
3483
- const settings = useSyncExternalStoreWithSelector2(
3484
- store.subscribeNotificationSettings,
3485
- getter,
3486
- getter,
3487
- identity2,
3488
- shallow4
3489
- );
3490
- if (settings.isLoading) {
3491
- const { getInboxNotificationSettings } = getRoomExtrasForClient(client);
3492
- throw getInboxNotificationSettings(room);
3493
- } else if (settings.error) {
3494
- throw settings.error;
3495
- }
3496
- return React5.useMemo(() => {
3509
+ use(store.waitUntilRoomNotificationSettingsLoaded(room.id));
3510
+ const [settings, updateRoomNotificationSettings] = useRoomNotificationSettings();
3511
+ assert2(!settings.error, "Did not expect error");
3512
+ assert2(!settings.isLoading, "Did not expect loading");
3513
+ return React4.useMemo(() => {
3497
3514
  return [settings, updateRoomNotificationSettings];
3498
3515
  }, [settings, updateRoomNotificationSettings]);
3499
3516
  }
3500
3517
  function useHistoryVersionData(versionId) {
3501
- const [state, setState] = React5.useState({
3518
+ const [state, setState] = React4.useState({
3502
3519
  isLoading: true
3503
3520
  });
3504
3521
  const room = useRoom();
3505
- React5.useEffect(() => {
3522
+ React4.useEffect(() => {
3506
3523
  setState({ isLoading: true });
3507
3524
  const load = async () => {
3508
3525
  try {
@@ -3529,50 +3546,53 @@ function useHistoryVersionData(versionId) {
3529
3546
  function useHistoryVersions() {
3530
3547
  const client = useClient();
3531
3548
  const room = useRoom();
3532
- const { store, getRoomVersions } = getRoomExtrasForClient(client);
3533
- const getter = React5.useCallback(
3534
- () => store.getVersionsAsync(room.id),
3549
+ const { store, getOrCreateVersionsPollerForRoomId } = getRoomExtrasForClient(client);
3550
+ const poller = getOrCreateVersionsPollerForRoomId(room.id);
3551
+ React4.useEffect(() => {
3552
+ poller.inc();
3553
+ poller.pollNowIfStale();
3554
+ return () => poller.dec();
3555
+ }, [poller]);
3556
+ const getter = React4.useCallback(
3557
+ () => store.getRoomVersionsLoadingState(room.id),
3535
3558
  [store, room.id]
3536
3559
  );
3537
- React5.useEffect(() => {
3538
- void getRoomVersions(room);
3539
- }, [room]);
3560
+ React4.useEffect(
3561
+ () => {
3562
+ void store.waitUntilRoomVersionsLoaded(room.id);
3563
+ }
3564
+ // NOTE: Deliberately *not* using a dependency array here!
3565
+ //
3566
+ // It is important to call waitUntil on *every* render.
3567
+ // This is harmless though, on most renders, except:
3568
+ // 1. The very first render, in which case we'll want to trigger the initial page fetch.
3569
+ // 2. All other subsequent renders now "just" return the same promise (a quick operation).
3570
+ // 3. If ever the promise would fail, then after 5 seconds it would reset, and on the very
3571
+ // *next* render after that, a *new* fetch/promise will get created.
3572
+ );
3540
3573
  const state = useSyncExternalStoreWithSelector2(
3541
- store.subscribeVersions,
3574
+ store.subscribe,
3542
3575
  getter,
3543
3576
  getter,
3544
3577
  identity2,
3545
- shallow4
3578
+ shallow2
3546
3579
  );
3547
3580
  return state;
3548
3581
  }
3549
3582
  function useHistoryVersionsSuspense() {
3550
3583
  const client = useClient();
3551
3584
  const room = useRoom();
3552
- const { store } = getRoomExtrasForClient(client);
3553
- const getter = React5.useCallback(
3554
- () => store.getVersionsAsync(room.id),
3555
- [store, room.id]
3556
- );
3557
- const state = useSyncExternalStoreWithSelector2(
3558
- store.subscribeVersions,
3559
- getter,
3560
- getter,
3561
- identity2,
3562
- shallow4
3563
- );
3564
- if (state.isLoading) {
3565
- const { getRoomVersions } = getRoomExtrasForClient(client);
3566
- throw getRoomVersions(room);
3567
- } else if (state.error) {
3568
- throw state.error;
3569
- }
3570
- return state;
3585
+ const store = getRoomExtrasForClient(client).store;
3586
+ use(store.waitUntilRoomVersionsLoaded(room.id));
3587
+ const result = useHistoryVersions();
3588
+ assert2(!result.error, "Did not expect error");
3589
+ assert2(!result.isLoading, "Did not expect loading");
3590
+ return result;
3571
3591
  }
3572
3592
  function useUpdateRoomNotificationSettings() {
3573
3593
  const client = useClient();
3574
3594
  const room = useRoom();
3575
- return React5.useCallback(
3595
+ return React4.useCallback(
3576
3596
  (settings) => {
3577
3597
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
3578
3598
  const optimisticUpdateId = store.addOptimisticUpdate({
@@ -3582,7 +3602,7 @@ function useUpdateRoomNotificationSettings() {
3582
3602
  });
3583
3603
  room.updateNotificationSettings(settings).then(
3584
3604
  (settings2) => {
3585
- store.updateRoomInboxNotificationSettings2(
3605
+ store.updateRoomNotificationSettings_confirmOptimisticUpdate(
3586
3606
  room.id,
3587
3607
  optimisticUpdateId,
3588
3608
  settings2
@@ -3682,11 +3702,11 @@ function selectorFor_useAttachmentUrl(state) {
3682
3702
  function useAttachmentUrl(attachmentId) {
3683
3703
  const room = useRoom();
3684
3704
  const { attachmentUrlsStore } = room[kInternal3];
3685
- const getAttachmentUrlState = React5.useCallback(
3705
+ const getAttachmentUrlState = React4.useCallback(
3686
3706
  () => attachmentUrlsStore.getState(attachmentId),
3687
3707
  [attachmentUrlsStore, attachmentId]
3688
3708
  );
3689
- React5.useEffect(() => {
3709
+ React4.useEffect(() => {
3690
3710
  void attachmentUrlsStore.get(attachmentId);
3691
3711
  }, [attachmentUrlsStore, attachmentId]);
3692
3712
  return useSyncExternalStoreWithSelector2(
@@ -3700,7 +3720,7 @@ function useAttachmentUrl(attachmentId) {
3700
3720
  function useAttachmentUrlSuspense(attachmentId) {
3701
3721
  const room = useRoom();
3702
3722
  const { attachmentUrlsStore } = room[kInternal3];
3703
- const getAttachmentUrlState = React5.useCallback(
3723
+ const getAttachmentUrlState = React4.useCallback(
3704
3724
  () => attachmentUrlsStore.getState(attachmentId),
3705
3725
  [attachmentUrlsStore, attachmentId]
3706
3726
  );
@@ -3744,6 +3764,8 @@ var _useOthersMapped = useOthersMapped;
3744
3764
  var _useOthersMappedSuspense = useOthersMappedSuspense;
3745
3765
  var _useThreads = useThreads;
3746
3766
  var _useThreadsSuspense = useThreadsSuspense;
3767
+ var _useRoomNotificationSettings = useRoomNotificationSettings;
3768
+ var _useRoomNotificationSettingsSuspense = useRoomNotificationSettingsSuspense;
3747
3769
  var _useHistoryVersions = useHistoryVersions;
3748
3770
  var _useHistoryVersionsSuspense = useHistoryVersionsSuspense;
3749
3771
  var _useOther = useOther;
@@ -3766,12 +3788,7 @@ var _useStorageRoot = useStorageRoot;
3766
3788
  var _useUpdateMyPresence = useUpdateMyPresence;
3767
3789
 
3768
3790
  export {
3769
- PKG_NAME,
3770
- PKG_VERSION,
3771
- PKG_FORMAT,
3772
- ClientSideSuspense,
3773
3791
  RoomContext,
3774
- selectThreads,
3775
3792
  ClientContext,
3776
3793
  getUmbrellaStoreForClient,
3777
3794
  useClient,
@@ -3813,7 +3830,6 @@ export {
3813
3830
  useMarkThreadAsResolved,
3814
3831
  useMarkThreadAsUnresolved,
3815
3832
  useThreadSubscription,
3816
- useRoomNotificationSettings,
3817
3833
  useHistoryVersionData,
3818
3834
  useUpdateRoomNotificationSettings,
3819
3835
  useOthersConnectionIdsSuspense,
@@ -3837,6 +3853,8 @@ export {
3837
3853
  _useOthersMappedSuspense,
3838
3854
  _useThreads,
3839
3855
  _useThreadsSuspense,
3856
+ _useRoomNotificationSettings,
3857
+ _useRoomNotificationSettingsSuspense,
3840
3858
  _useHistoryVersions,
3841
3859
  _useHistoryVersionsSuspense,
3842
3860
  _useOther,
@@ -3850,4 +3868,4 @@ export {
3850
3868
  _useStorageRoot,
3851
3869
  _useUpdateMyPresence
3852
3870
  };
3853
- //# sourceMappingURL=chunk-SNEUTGU4.mjs.map
3871
+ //# sourceMappingURL=chunk-A7GJNN4L.mjs.map