@liveblocks/react 2.10.0 → 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.
Files changed (33) hide show
  1. package/dist/_private.d.mts +1 -1
  2. package/dist/_private.d.ts +1 -1
  3. package/dist/_private.js +4 -6
  4. package/dist/_private.js.map +1 -1
  5. package/dist/_private.mjs +1 -3
  6. package/dist/_private.mjs.map +1 -1
  7. package/dist/{chunk-WJLAYICR.js → chunk-3MM4G6XB.js} +729 -694
  8. package/dist/chunk-3MM4G6XB.js.map +1 -0
  9. package/dist/{chunk-LMENFXCS.mjs → chunk-A7GJNN4L.mjs} +723 -688
  10. package/dist/chunk-A7GJNN4L.mjs.map +1 -0
  11. package/dist/{chunk-PXKGBTHT.mjs → chunk-DNACURSM.mjs} +2 -2
  12. package/dist/{chunk-PJN3AKDH.js → chunk-EXS4G6PT.js} +2 -2
  13. package/dist/index.d.mts +2 -2
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.js +4 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +4 -4
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/{liveblocks-X3qYz1Vg.d.mts → liveblocks-SAVcXwMX.d.mts} +105 -60
  20. package/dist/{liveblocks-X3qYz1Vg.d.ts → liveblocks-SAVcXwMX.d.ts} +105 -60
  21. package/dist/{suspense-pF_E-0CC.d.mts → suspense-W7Cp9ygn.d.ts} +18 -13
  22. package/dist/{suspense-qQ0vzMgn.d.ts → suspense-vGJE9Mgq.d.mts} +18 -13
  23. package/dist/suspense.d.mts +2 -2
  24. package/dist/suspense.d.ts +2 -2
  25. package/dist/suspense.js +4 -4
  26. package/dist/suspense.js.map +1 -1
  27. package/dist/suspense.mjs +4 -4
  28. package/dist/suspense.mjs.map +1 -1
  29. package/package.json +4 -4
  30. package/dist/chunk-LMENFXCS.mjs.map +0 -1
  31. package/dist/chunk-WJLAYICR.js.map +0 -1
  32. /package/dist/{chunk-PXKGBTHT.mjs.map → chunk-DNACURSM.mjs.map} +0 -0
  33. /package/dist/{chunk-PJN3AKDH.js.map → chunk-EXS4G6PT.js.map} +0 -0
@@ -9,18 +9,125 @@ function useIsInsideRoom() {
9
9
  return room !== null;
10
10
  }
11
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
+
12
119
  // src/umbrella-store.ts
13
120
  import {
14
121
  autoRetry,
15
122
  compactObject,
16
123
  console as console2,
17
124
  createStore,
125
+ HttpError,
18
126
  kInternal,
19
127
  makeEventSource,
20
128
  mapValues,
21
129
  nanoid,
22
130
  nn,
23
- StopRetrying,
24
131
  stringify
25
132
  } from "@liveblocks/core";
26
133
 
@@ -41,21 +148,13 @@ function autobind(self) {
41
148
  } while ((obj = Reflect.getPrototypeOf(obj)) && obj !== Object.prototype);
42
149
  }
43
150
 
44
- // src/lib/compare.ts
45
- function byFirstCreated(a, b) {
46
- return a.createdAt.getTime() - b.createdAt.getTime();
47
- }
48
- function isMoreRecentlyUpdated(a, b) {
49
- return byMostRecentlyUpdated(a, b) < 0;
50
- }
51
- function byMostRecentlyUpdated(a, b) {
52
- return b.updatedAt.getTime() - a.updatedAt.getTime();
53
- }
151
+ // src/ThreadDB.ts
152
+ import { SortedList } from "@liveblocks/core";
54
153
 
55
154
  // src/lib/guards.ts
56
- import { isPlainObject } from "@liveblocks/core";
155
+ import { isPlainObject as isPlainObject2 } from "@liveblocks/core";
57
156
  function isStartsWith(blob) {
58
- return isPlainObject(blob) && isString(blob.startsWith);
157
+ return isPlainObject2(blob) && isString(blob.startsWith);
59
158
  }
60
159
  function isString(value) {
61
160
  return typeof value === "string";
@@ -88,8 +187,124 @@ function matchesOperator(value, op) {
88
187
  }
89
188
  }
90
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
+
91
307
  // src/umbrella-store.ts
92
- var ASYNC_OK = Object.freeze({ isLoading: false, data: void 0 });
93
308
  function makeRoomThreadsQueryKey(roomId, query) {
94
309
  return `${roomId}-${stringify(query ?? {})}`;
95
310
  }
@@ -102,19 +317,6 @@ function makeNotificationSettingsQueryKey(roomId) {
102
317
  function makeVersionsQueryKey(roomId) {
103
318
  return `${roomId}-VERSIONS`;
104
319
  }
105
- function selectThreads(state, options) {
106
- let threads = state.threads;
107
- if (options.roomId !== null) {
108
- threads = threads.filter((thread) => thread.roomId === options.roomId);
109
- }
110
- const query = options.query;
111
- if (query) {
112
- threads = threads.filter(makeThreadsFilter(query));
113
- }
114
- return threads.sort(
115
- options.orderBy === "last-update" ? byMostRecentlyUpdated : byFirstCreated
116
- );
117
- }
118
320
  function usify(promise) {
119
321
  if ("status" in promise) {
120
322
  return promise;
@@ -133,7 +335,7 @@ function usify(promise) {
133
335
  );
134
336
  return usable;
135
337
  }
136
- var noop = Promise.resolve();
338
+ var noop2 = Promise.resolve();
137
339
  var ASYNC_LOADING = Object.freeze({ isLoading: true });
138
340
  var PaginatedResource = class {
139
341
  constructor(fetchPage) {
@@ -174,7 +376,7 @@ var PaginatedResource = class {
174
376
  fetchMore() {
175
377
  const state = this._paginationState;
176
378
  if (state?.cursor === null) {
177
- return noop;
379
+ return noop2;
178
380
  }
179
381
  if (!this._pendingFetchMore) {
180
382
  this._pendingFetchMore = this._fetchMore().finally(() => {
@@ -237,8 +439,54 @@ var PaginatedResource = class {
237
439
  return promise;
238
440
  }
239
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
+ };
240
487
  var UmbrellaStore = class {
241
488
  constructor(client) {
489
+ this._prevVersion = -1;
242
490
  this._prevState = null;
243
491
  this._stateCached = null;
244
492
  // Notifications
@@ -249,16 +497,16 @@ var UmbrellaStore = class {
249
497
  // User Threads
250
498
  this._userThreadsLastRequestedAt = null;
251
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();
252
506
  const inboxFetcher = async (cursor) => {
253
- if (client === void 0) {
254
- throw new StopRetrying(
255
- "Client is required in order to load threads for the room"
256
- );
257
- }
258
- const result = await client.getInboxNotifications({ cursor });
507
+ const result = await this._client.getInboxNotifications({ cursor });
259
508
  this.updateThreadsAndNotifications(
260
509
  result.threads,
261
- // TODO: Figure out how to remove this casting
262
510
  result.inboxNotifications
263
511
  );
264
512
  if (this._notificationsLastRequestedAt === null) {
@@ -267,7 +515,6 @@ var UmbrellaStore = class {
267
515
  const nextCursor = result.nextCursor;
268
516
  return nextCursor;
269
517
  };
270
- this._client = client;
271
518
  this._notifications = new PaginatedResource(inboxFetcher);
272
519
  this._notifications.observable.subscribe(
273
520
  () => (
@@ -276,10 +523,8 @@ var UmbrellaStore = class {
276
523
  this._store.set((store) => ({ ...store }))
277
524
  )
278
525
  );
526
+ this._rawThreadsDB = new ThreadDB();
279
527
  this._store = createStore({
280
- rawThreadsById: {},
281
- queries3: {},
282
- queries4: {},
283
528
  optimisticUpdates: [],
284
529
  notificationsById: {},
285
530
  settingsByRoomId: {},
@@ -289,9 +534,11 @@ var UmbrellaStore = class {
289
534
  }
290
535
  get() {
291
536
  const rawState = this._store.get();
292
- 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);
293
540
  this._prevState = rawState;
294
- this._stateCached = internalToExternalState(rawState);
541
+ this._prevVersion = this._rawThreadsDB.version;
295
542
  }
296
543
  return this._stateCached;
297
544
  }
@@ -306,8 +553,7 @@ var UmbrellaStore = class {
306
553
  * then it will return the threads that match that provided query and room id.
307
554
  *
308
555
  */
309
- // XXXX Find a better name for that doesn't associate to 'async'
310
- getRoomThreadsAsync(roomId, query) {
556
+ getRoomThreadsLoadingState(roomId, query) {
311
557
  const queryKey = makeRoomThreadsQueryKey(roomId, query);
312
558
  const paginatedResource = this._roomThreads.get(queryKey);
313
559
  if (paginatedResource === void 0) {
@@ -317,11 +563,11 @@ var UmbrellaStore = class {
317
563
  if (asyncResult.isLoading || asyncResult.error) {
318
564
  return asyncResult;
319
565
  }
320
- const threads = selectThreads(this.getFullState(), {
566
+ const threads = this.getFullState().threadsDB.findMany(
321
567
  roomId,
322
- query,
323
- orderBy: "age"
324
- });
568
+ query ?? {},
569
+ "asc"
570
+ );
325
571
  const page = asyncResult.data;
326
572
  return {
327
573
  isLoading: false,
@@ -332,8 +578,7 @@ var UmbrellaStore = class {
332
578
  fetchMore: page.fetchMore
333
579
  };
334
580
  }
335
- // XXXX - Find a better name for that doesn't associate to 'async'
336
- getUserThreadsAsync(query) {
581
+ getUserThreadsLoadingState(query) {
337
582
  const queryKey = makeUserThreadsQueryKey(query);
338
583
  const paginatedResource = this._userThreads.get(queryKey);
339
584
  if (paginatedResource === void 0) {
@@ -343,12 +588,12 @@ var UmbrellaStore = class {
343
588
  if (asyncResult.isLoading || asyncResult.error) {
344
589
  return asyncResult;
345
590
  }
346
- const threads = selectThreads(this.getFullState(), {
347
- roomId: null,
591
+ const threads = this.getFullState().threadsDB.findMany(
592
+ void 0,
348
593
  // Do _not_ filter by roomId
349
- query,
350
- orderBy: "last-update"
351
- });
594
+ query ?? {},
595
+ "desc"
596
+ );
352
597
  const page = asyncResult.data;
353
598
  return {
354
599
  isLoading: false,
@@ -360,8 +605,7 @@ var UmbrellaStore = class {
360
605
  };
361
606
  }
362
607
  // NOTE: This will read the async result, but WILL NOT start loading at the moment!
363
- // XXXX - Find a better name for that doesn't associate to 'async'
364
- getInboxNotificationsAsync() {
608
+ getInboxNotificationsLoadingState() {
365
609
  const asyncResult = this._notifications.get();
366
610
  if (asyncResult.isLoading || asyncResult.error) {
367
611
  return asyncResult;
@@ -369,7 +613,7 @@ var UmbrellaStore = class {
369
613
  const page = asyncResult.data;
370
614
  return {
371
615
  isLoading: false,
372
- inboxNotifications: this.getFullState().notifications,
616
+ inboxNotifications: this.getFullState().cleanedNotifications,
373
617
  hasFetchedAll: page.hasFetchedAll,
374
618
  isFetchingMore: page.isFetchingMore,
375
619
  fetchMoreError: page.fetchMoreError,
@@ -377,32 +621,34 @@ var UmbrellaStore = class {
377
621
  };
378
622
  }
379
623
  // NOTE: This will read the async result, but WILL NOT start loading at the moment!
380
- getNotificationSettingsAsync(roomId) {
381
- const state = this.get();
382
- const query = state.queries3[makeNotificationSettingsQueryKey(roomId)];
383
- 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) {
384
628
  return ASYNC_LOADING;
385
629
  }
386
- if (query.error !== void 0) {
387
- return query;
630
+ const asyncResult = resource.get();
631
+ if (asyncResult.isLoading || asyncResult.error) {
632
+ return asyncResult;
388
633
  }
389
634
  return {
390
635
  isLoading: false,
391
- settings: nn(state.settingsByRoomId[roomId])
636
+ settings: nn(this.get().settingsByRoomId[roomId])
392
637
  };
393
638
  }
394
- getVersionsAsync(roomId) {
395
- const state = this.get();
396
- const query = state.queries4[makeVersionsQueryKey(roomId)];
397
- 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) {
398
643
  return ASYNC_LOADING;
399
644
  }
400
- if (query.error !== void 0) {
401
- return query;
645
+ const asyncResult = resource.get();
646
+ if (asyncResult.isLoading || asyncResult.error) {
647
+ return asyncResult;
402
648
  }
403
649
  return {
404
650
  isLoading: false,
405
- versions: nn(state.versionsByRoomId[roomId])
651
+ versions: Object.values(this.get().versionsByRoomId[roomId] ?? {})
406
652
  };
407
653
  }
408
654
  /**
@@ -414,33 +660,14 @@ var UmbrellaStore = class {
414
660
  subscribe(callback) {
415
661
  return this._store.subscribe(callback);
416
662
  }
417
- /**
418
- * @private Only used by the E2E test suite.
419
- */
420
- _subscribeOptimisticUpdates(callback) {
421
- return this.subscribe(callback);
422
- }
423
- subscribeThreads(callback) {
424
- return this.subscribe(callback);
425
- }
426
- subscribeUserThreads(callback) {
427
- return this.subscribe(callback);
428
- }
429
- subscribeThreadsOrInboxNotifications(callback) {
430
- return this.subscribe(callback);
431
- }
432
- subscribeNotificationSettings(callback) {
433
- return this.subscribe(callback);
434
- }
435
- subscribeVersions(callback) {
436
- return this.subscribe(callback);
437
- }
438
663
  // Direct low-level cache mutations ------------------------------------------------- {{{
439
- updateThreadsCache(mapFn) {
440
- this._store.set((state) => {
441
- const threads = mapFn(state.rawThreadsById);
442
- return threads !== state.rawThreadsById ? { ...state, rawThreadsById: threads } : state;
443
- });
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
+ }
444
671
  }
445
672
  updateInboxNotificationsCache(mapFn) {
446
673
  this._store.set((state) => {
@@ -457,32 +684,23 @@ var UmbrellaStore = class {
457
684
  }
458
685
  }));
459
686
  }
460
- setVersions(roomId, versions) {
461
- this._store.set((state) => ({
462
- ...state,
463
- versionsByRoomId: {
464
- ...state.versionsByRoomId,
465
- [roomId]: versions
466
- }
467
- }));
468
- }
469
- setQuery3State(queryKey, queryState) {
470
- this._store.set((state) => ({
471
- ...state,
472
- queries3: {
473
- ...state.queries3,
474
- [queryKey]: queryState
475
- }
476
- }));
477
- }
478
- setQuery4State(queryKey, queryState) {
479
- this._store.set((state) => ({
480
- ...state,
481
- queries4: {
482
- ...state.queries4,
483
- [queryKey]: queryState
484
- }
485
- }));
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
+ });
486
704
  }
487
705
  updateOptimisticUpdatesCache(mapFn) {
488
706
  this._store.set((state) => ({
@@ -557,7 +775,7 @@ var UmbrellaStore = class {
557
775
  createThread(optimisticUpdateId, thread) {
558
776
  this._store.batch(() => {
559
777
  this.removeOptimisticUpdate(optimisticUpdateId);
560
- this.updateThreadsCache((cache) => ({ ...cache, [thread.id]: thread }));
778
+ this.mutateThreadsDB((db) => db.upsert(thread));
561
779
  });
562
780
  }
563
781
  /**
@@ -575,18 +793,11 @@ var UmbrellaStore = class {
575
793
  if (optimisticUpdateId !== null) {
576
794
  this.removeOptimisticUpdate(optimisticUpdateId);
577
795
  }
578
- this.updateThreadsCache((cache) => {
579
- const existing = cache[threadId];
580
- if (!existing) {
581
- return cache;
582
- }
583
- if (existing.deletedAt !== void 0) {
584
- return cache;
585
- }
586
- if (!!updatedAt && existing.updatedAt > updatedAt) {
587
- return cache;
588
- }
589
- 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));
590
801
  });
591
802
  });
592
803
  }
@@ -637,14 +848,13 @@ var UmbrellaStore = class {
637
848
  createComment(newComment, optimisticUpdateId) {
638
849
  this._store.batch(() => {
639
850
  this.removeOptimisticUpdate(optimisticUpdateId);
640
- const existingThread = this._store.get().rawThreadsById[newComment.threadId];
851
+ const existingThread = this._rawThreadsDB.get(newComment.threadId);
641
852
  if (!existingThread) {
642
853
  return;
643
854
  }
644
- this.updateThreadsCache((cache) => ({
645
- ...cache,
646
- [newComment.threadId]: applyUpsertComment(existingThread, newComment)
647
- }));
855
+ this.mutateThreadsDB(
856
+ (db) => db.upsert(applyUpsertComment(existingThread, newComment))
857
+ );
648
858
  this.updateInboxNotificationsCache((cache) => {
649
859
  const existingNotification = Object.values(cache).find(
650
860
  (notification) => notification.kind === "thread" && notification.threadId === newComment.threadId
@@ -680,10 +890,7 @@ var UmbrellaStore = class {
680
890
  }
681
891
  updateThreadAndNotification(thread, inboxNotification) {
682
892
  this._store.batch(() => {
683
- this.updateThreadsCache((cache) => {
684
- const existingThread = cache[thread.id];
685
- return existingThread === void 0 || isMoreRecentlyUpdated(thread, existingThread) ? { ...cache, [thread.id]: thread } : cache;
686
- });
893
+ this.mutateThreadsDB((db) => db.upsertIfNewer(thread));
687
894
  if (inboxNotification !== void 0) {
688
895
  this.updateInboxNotificationsCache((cache) => ({
689
896
  ...cache,
@@ -694,11 +901,8 @@ var UmbrellaStore = class {
694
901
  }
695
902
  updateThreadsAndNotifications(threads, inboxNotifications, deletedThreads = [], deletedInboxNotifications = []) {
696
903
  this._store.batch(() => {
697
- this.updateThreadsCache(
698
- (cache) => applyThreadUpdates(cache, {
699
- newThreads: threads,
700
- deletedThreads
701
- })
904
+ this.mutateThreadsDB(
905
+ (db) => applyThreadDeltaUpdates(db, { newThreads: threads, deletedThreads })
702
906
  );
703
907
  this.updateInboxNotificationsCache(
704
908
  (cache) => applyNotificationsUpdates(cache, {
@@ -712,28 +916,12 @@ var UmbrellaStore = class {
712
916
  * Updates existing notification setting for a room with a new value,
713
917
  * replacing the corresponding optimistic update.
714
918
  */
715
- // XXXX Rename this helper method
716
- updateRoomInboxNotificationSettings2(roomId, optimisticUpdateId, settings) {
919
+ updateRoomNotificationSettings_confirmOptimisticUpdate(roomId, optimisticUpdateId, settings) {
717
920
  this._store.batch(() => {
718
921
  this.removeOptimisticUpdate(optimisticUpdateId);
719
922
  this.setNotificationSettings(roomId, settings);
720
923
  });
721
924
  }
722
- // XXXX Rename this helper method
723
- updateRoomInboxNotificationSettings(roomId, settings, queryKey) {
724
- this._store.batch(() => {
725
- this.setQuery3OK(queryKey);
726
- this.setNotificationSettings(roomId, settings);
727
- });
728
- }
729
- updateRoomVersions(roomId, versions, queryKey) {
730
- this._store.batch(() => {
731
- this.setVersions(roomId, versions);
732
- if (queryKey !== void 0) {
733
- this.setQuery4OK(queryKey);
734
- }
735
- });
736
- }
737
925
  addOptimisticUpdate(optimisticUpdate) {
738
926
  const id = nanoid();
739
927
  const newUpdate = { ...optimisticUpdate, id };
@@ -745,36 +933,15 @@ var UmbrellaStore = class {
745
933
  (cache) => cache.filter((ou) => ou.id !== optimisticUpdateId)
746
934
  );
747
935
  }
748
- // Query 3
749
- setQuery3Loading(queryKey) {
750
- this.setQuery3State(queryKey, ASYNC_LOADING);
751
- }
752
- setQuery3OK(queryKey) {
753
- this.setQuery3State(queryKey, ASYNC_OK);
754
- }
755
- setQuery3Error(queryKey, error) {
756
- this.setQuery3State(queryKey, { isLoading: false, error });
757
- }
758
- // Query 4
759
- setQuery4Loading(queryKey) {
760
- this.setQuery4State(queryKey, ASYNC_LOADING);
761
- }
762
- setQuery4OK(queryKey) {
763
- this.setQuery4State(queryKey, ASYNC_OK);
764
- }
765
- setQuery4Error(queryKey, error) {
766
- this.setQuery4State(queryKey, { isLoading: false, error });
767
- }
768
- async fetchNotificationsDeltaUpdate() {
936
+ async fetchNotificationsDeltaUpdate(signal) {
769
937
  const lastRequestedAt = this._notificationsLastRequestedAt;
770
938
  if (lastRequestedAt === null) {
771
939
  return;
772
940
  }
773
- const client = nn(
774
- this._client,
775
- "Client is required in order to load notifications for the room"
776
- );
777
- const result = await client.getInboxNotificationsSince(lastRequestedAt);
941
+ const result = await this._client.getInboxNotificationsSince({
942
+ since: lastRequestedAt,
943
+ signal
944
+ });
778
945
  if (lastRequestedAt < result.requestedAt) {
779
946
  this._notificationsLastRequestedAt = result.requestedAt;
780
947
  }
@@ -790,21 +957,13 @@ var UmbrellaStore = class {
790
957
  }
791
958
  waitUntilRoomThreadsLoaded(roomId, query) {
792
959
  const threadsFetcher = async (cursor) => {
793
- if (this._client === void 0) {
794
- throw new StopRetrying(
795
- "Client is required in order to load threads for the room"
796
- );
797
- }
798
960
  const room = this._client.getRoom(roomId);
799
961
  if (room === null) {
800
- throw new StopRetrying(
801
- `Room with id ${roomId} is not available on client`
802
- );
962
+ throw new HttpError(`Room '${roomId}' is not available on client`, 479);
803
963
  }
804
964
  const result = await room.getThreads({ cursor, query });
805
965
  this.updateThreadsAndNotifications(
806
966
  result.threads,
807
- // TODO: Figure out how to remove this casting
808
967
  result.inboxNotifications
809
968
  );
810
969
  const lastRequestedAt = this._roomThreadsLastRequestedAtByRoom.get(roomId);
@@ -828,21 +987,18 @@ var UmbrellaStore = class {
828
987
  this._roomThreads.set(queryKey, paginatedResource);
829
988
  return paginatedResource.waitUntilLoaded();
830
989
  }
831
- async fetchRoomThreadsDeltaUpdate(roomId) {
990
+ async fetchRoomThreadsDeltaUpdate(roomId, signal) {
832
991
  const lastRequestedAt = this._roomThreadsLastRequestedAtByRoom.get(roomId);
833
992
  if (lastRequestedAt === void 0) {
834
993
  return;
835
994
  }
836
- const client = nn(
837
- this._client,
838
- "Client is required in order to load notifications for the room"
839
- );
840
995
  const room = nn(
841
- client.getRoom(roomId),
996
+ this._client.getRoom(roomId),
842
997
  `Room with id ${roomId} is not available on client`
843
998
  );
844
999
  const updates = await room.getThreadsSince({
845
- since: lastRequestedAt
1000
+ since: lastRequestedAt,
1001
+ signal
846
1002
  });
847
1003
  this.updateThreadsAndNotifications(
848
1004
  updates.threads.updated,
@@ -857,18 +1013,12 @@ var UmbrellaStore = class {
857
1013
  waitUntilUserThreadsLoaded(query) {
858
1014
  const queryKey = makeUserThreadsQueryKey(query);
859
1015
  const threadsFetcher = async (cursor) => {
860
- if (this._client === void 0) {
861
- throw new StopRetrying(
862
- "Client is required in order to load threads for the room"
863
- );
864
- }
865
1016
  const result = await this._client[kInternal].getUserThreads_experimental({
866
1017
  cursor,
867
1018
  query
868
1019
  });
869
1020
  this.updateThreadsAndNotifications(
870
1021
  result.threads,
871
- // TODO: Figure out how to remove this casting
872
1022
  result.inboxNotifications
873
1023
  );
874
1024
  if (this._userThreadsLastRequestedAt === null) {
@@ -890,17 +1040,14 @@ var UmbrellaStore = class {
890
1040
  this._userThreads.set(queryKey, paginatedResource);
891
1041
  return paginatedResource.waitUntilLoaded();
892
1042
  }
893
- async fetchUserThreadsDeltaUpdate() {
1043
+ async fetchUserThreadsDeltaUpdate(signal) {
894
1044
  const lastRequestedAt = this._userThreadsLastRequestedAt;
895
1045
  if (lastRequestedAt === null) {
896
1046
  return;
897
1047
  }
898
- const client = nn(
899
- this._client,
900
- "Client is required in order to load threads for the user"
901
- );
902
- const result = await client[kInternal].getUserThreadsSince_experimental({
903
- since: lastRequestedAt
1048
+ const result = await this._client[kInternal].getUserThreadsSince_experimental({
1049
+ since: lastRequestedAt,
1050
+ signal
904
1051
  });
905
1052
  if (lastRequestedAt < result.requestedAt) {
906
1053
  this._notificationsLastRequestedAt = result.requestedAt;
@@ -912,77 +1059,138 @@ var UmbrellaStore = class {
912
1059
  result.inboxNotifications.deleted
913
1060
  );
914
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
+ }
915
1149
  };
916
- function internalToExternalState(state) {
1150
+ function internalToExternalState(state, rawThreadsDB) {
1151
+ const threadsDB = rawThreadsDB.clone();
917
1152
  const computed = {
918
- threadsById: { ...state.rawThreadsById },
919
1153
  notificationsById: { ...state.notificationsById },
920
1154
  settingsByRoomId: { ...state.settingsByRoomId }
921
1155
  };
922
1156
  for (const optimisticUpdate of state.optimisticUpdates) {
923
1157
  switch (optimisticUpdate.type) {
924
1158
  case "create-thread": {
925
- computed.threadsById[optimisticUpdate.thread.id] = optimisticUpdate.thread;
1159
+ threadsDB.upsert(optimisticUpdate.thread);
926
1160
  break;
927
1161
  }
928
1162
  case "edit-thread-metadata": {
929
- const thread = computed.threadsById[optimisticUpdate.threadId];
930
- if (thread === void 0) {
931
- break;
932
- }
933
- if (thread.deletedAt !== void 0) {
934
- break;
935
- }
1163
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1164
+ if (thread === void 0) break;
936
1165
  if (thread.updatedAt > optimisticUpdate.updatedAt) {
937
1166
  break;
938
1167
  }
939
- computed.threadsById[thread.id] = {
1168
+ threadsDB.upsert({
940
1169
  ...thread,
941
1170
  updatedAt: optimisticUpdate.updatedAt,
942
1171
  metadata: {
943
1172
  ...thread.metadata,
944
1173
  ...optimisticUpdate.metadata
945
1174
  }
946
- };
1175
+ });
947
1176
  break;
948
1177
  }
949
1178
  case "mark-thread-as-resolved": {
950
- const thread = computed.threadsById[optimisticUpdate.threadId];
951
- if (thread === void 0) {
952
- break;
953
- }
954
- if (thread.deletedAt !== void 0) {
955
- break;
956
- }
957
- computed.threadsById[thread.id] = {
958
- ...thread,
959
- resolved: true
960
- };
1179
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1180
+ if (thread === void 0) break;
1181
+ threadsDB.upsert({ ...thread, resolved: true });
961
1182
  break;
962
- }
963
- case "mark-thread-as-unresolved": {
964
- const thread = computed.threadsById[optimisticUpdate.threadId];
965
- if (thread === void 0) {
966
- break;
967
- }
968
- if (thread.deletedAt !== void 0) {
969
- break;
970
- }
971
- computed.threadsById[thread.id] = {
972
- ...thread,
973
- resolved: false
974
- };
1183
+ }
1184
+ case "mark-thread-as-unresolved": {
1185
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1186
+ if (thread === void 0) break;
1187
+ threadsDB.upsert({ ...thread, resolved: false });
975
1188
  break;
976
1189
  }
977
1190
  case "create-comment": {
978
- const thread = computed.threadsById[optimisticUpdate.comment.threadId];
979
- if (thread === void 0) {
980
- break;
981
- }
982
- computed.threadsById[thread.id] = applyUpsertComment(
983
- thread,
984
- optimisticUpdate.comment
985
- );
1191
+ const thread = threadsDB.get(optimisticUpdate.comment.threadId);
1192
+ if (thread === void 0) break;
1193
+ threadsDB.upsert(applyUpsertComment(thread, optimisticUpdate.comment));
986
1194
  const inboxNotification = Object.values(
987
1195
  computed.notificationsById
988
1196
  ).find(
@@ -999,64 +1207,57 @@ function internalToExternalState(state) {
999
1207
  break;
1000
1208
  }
1001
1209
  case "edit-comment": {
1002
- const thread = computed.threadsById[optimisticUpdate.comment.threadId];
1003
- if (thread === void 0) {
1004
- break;
1005
- }
1006
- computed.threadsById[thread.id] = applyUpsertComment(
1007
- thread,
1008
- optimisticUpdate.comment
1009
- );
1210
+ const thread = threadsDB.get(optimisticUpdate.comment.threadId);
1211
+ if (thread === void 0) break;
1212
+ threadsDB.upsert(applyUpsertComment(thread, optimisticUpdate.comment));
1010
1213
  break;
1011
1214
  }
1012
1215
  case "delete-comment": {
1013
- const thread = computed.threadsById[optimisticUpdate.threadId];
1014
- if (thread === void 0) {
1015
- break;
1016
- }
1017
- computed.threadsById[thread.id] = applyDeleteComment(
1018
- thread,
1019
- optimisticUpdate.commentId,
1020
- 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
+ )
1021
1224
  );
1022
1225
  break;
1023
1226
  }
1024
1227
  case "delete-thread": {
1025
- const thread = computed.threadsById[optimisticUpdate.threadId];
1026
- if (thread === void 0) {
1027
- break;
1028
- }
1029
- computed.threadsById[optimisticUpdate.threadId] = {
1228
+ const thread = threadsDB.get(optimisticUpdate.threadId);
1229
+ if (thread === void 0) break;
1230
+ threadsDB.upsert({
1030
1231
  ...thread,
1031
1232
  deletedAt: optimisticUpdate.deletedAt,
1032
1233
  updatedAt: optimisticUpdate.deletedAt,
1033
1234
  comments: []
1034
- };
1235
+ });
1035
1236
  break;
1036
1237
  }
1037
1238
  case "add-reaction": {
1038
- const thread = computed.threadsById[optimisticUpdate.threadId];
1039
- if (thread === void 0) {
1040
- break;
1041
- }
1042
- computed.threadsById[thread.id] = applyAddReaction(
1043
- thread,
1044
- optimisticUpdate.commentId,
1045
- 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
+ )
1046
1247
  );
1047
1248
  break;
1048
1249
  }
1049
1250
  case "remove-reaction": {
1050
- const thread = computed.threadsById[optimisticUpdate.threadId];
1051
- if (thread === void 0) {
1052
- break;
1053
- }
1054
- computed.threadsById[thread.id] = applyRemoveReaction(
1055
- thread,
1056
- optimisticUpdate.commentId,
1057
- optimisticUpdate.emoji,
1058
- optimisticUpdate.userId,
1059
- 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
+ )
1060
1261
  );
1061
1262
  break;
1062
1263
  }
@@ -1104,51 +1305,27 @@ function internalToExternalState(state) {
1104
1305
  }
1105
1306
  }
1106
1307
  }
1107
- const cleanedThreads = (
1108
- // Don't expose any soft-deleted threads
1109
- Object.values(computed.threadsById).filter((thread) => !thread.deletedAt).filter(
1110
- (thread) => (
1111
- // Only keep a thread if there is at least one non-deleted comment
1112
- thread.comments.some((c) => c.deletedAt === void 0)
1113
- )
1114
- )
1115
- );
1116
1308
  const cleanedNotifications = (
1117
1309
  // Sort so that the most recent notifications are first
1118
1310
  Object.values(computed.notificationsById).filter(
1119
- (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
1120
1312
  ).sort((a, b) => b.notifiedAt.getTime() - a.notifiedAt.getTime())
1121
1313
  );
1122
1314
  return {
1123
- notifications: cleanedNotifications,
1315
+ cleanedNotifications,
1124
1316
  notificationsById: computed.notificationsById,
1125
1317
  settingsByRoomId: computed.settingsByRoomId,
1126
- queries3: state.queries3,
1127
- queries4: state.queries4,
1128
- threads: cleanedThreads,
1129
- threadsById: computed.threadsById,
1318
+ threadsDB,
1130
1319
  versionsByRoomId: state.versionsByRoomId
1131
1320
  };
1132
1321
  }
1133
- function applyThreadUpdates(existingThreads, updates) {
1134
- const updatedThreads = { ...existingThreads };
1135
- updates.newThreads.forEach((thread) => {
1136
- const existingThread = updatedThreads[thread.id];
1137
- if (existingThread) {
1138
- if (isMoreRecentlyUpdated(existingThread, thread)) {
1139
- return;
1140
- }
1141
- }
1142
- updatedThreads[thread.id] = thread;
1143
- });
1322
+ function applyThreadDeltaUpdates(db, updates) {
1323
+ updates.newThreads.forEach((thread) => db.upsertIfNewer(thread));
1144
1324
  updates.deletedThreads.forEach(({ id, deletedAt }) => {
1145
- const existingThread = updatedThreads[id];
1146
- if (existingThread === void 0) return;
1147
- existingThread.deletedAt = deletedAt;
1148
- existingThread.updatedAt = deletedAt;
1149
- existingThread.comments = [];
1325
+ const existing = db.getEvenIfDeleted(id);
1326
+ if (!existing) return;
1327
+ db.delete(id, deletedAt);
1150
1328
  });
1151
- return updatedThreads;
1152
1329
  }
1153
1330
  function applyNotificationsUpdates(existingInboxNotifications, updates) {
1154
1331
  const updatedInboxNotifications = { ...existingInboxNotifications };
@@ -1346,97 +1523,6 @@ function upsertReaction(reactions, reaction) {
1346
1523
  return reactions;
1347
1524
  }
1348
1525
 
1349
- // src/liveblocks.tsx
1350
- import {
1351
- assert,
1352
- createClient,
1353
- kInternal as kInternal2,
1354
- makePoller,
1355
- raise,
1356
- shallow as shallow3
1357
- } from "@liveblocks/core";
1358
- import React2, {
1359
- createContext as createContext2,
1360
- useCallback as useCallback2,
1361
- useContext as useContext2,
1362
- useEffect as useEffect2,
1363
- useMemo
1364
- } from "react";
1365
- import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
1366
- import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
1367
-
1368
- // src/lib/shallow2.ts
1369
- import { isPlainObject as isPlainObject2, shallow } from "@liveblocks/core";
1370
- function shallow2(a, b) {
1371
- if (!isPlainObject2(a) || !isPlainObject2(b)) {
1372
- return shallow(a, b);
1373
- }
1374
- const keysA = Object.keys(a);
1375
- if (keysA.length !== Object.keys(b).length) {
1376
- return false;
1377
- }
1378
- return keysA.every(
1379
- (key) => Object.prototype.hasOwnProperty.call(b, key) && shallow(a[key], b[key])
1380
- );
1381
- }
1382
-
1383
- // src/lib/use-initial.ts
1384
- import { useCallback, useReducer } from "react";
1385
-
1386
- // src/lib/use-latest.ts
1387
- import { useEffect, useRef } from "react";
1388
- function useLatest(value) {
1389
- const ref = useRef(value);
1390
- useEffect(() => {
1391
- ref.current = value;
1392
- }, [value]);
1393
- return ref;
1394
- }
1395
-
1396
- // src/lib/use-initial.ts
1397
- var noop2 = (state) => state;
1398
- function useInitial(value) {
1399
- return useReducer(noop2, value)[0];
1400
- }
1401
- function useInitialUnlessFunction(latestValue) {
1402
- const frozenValue = useInitial(latestValue);
1403
- if (typeof frozenValue === "function") {
1404
- const ref = useLatest(latestValue);
1405
- return useCallback((...args) => ref.current(...args), [
1406
- ref
1407
- ]);
1408
- } else {
1409
- return frozenValue;
1410
- }
1411
- }
1412
-
1413
- // src/lib/use-polyfill.ts
1414
- var use = (
1415
- // React.use ||
1416
- (promise) => {
1417
- if (promise.status === "pending") {
1418
- throw promise;
1419
- } else if (promise.status === "fulfilled") {
1420
- return promise.value;
1421
- } else if (promise.status === "rejected") {
1422
- throw promise.reason;
1423
- } else {
1424
- promise.status = "pending";
1425
- promise.then(
1426
- (v) => {
1427
- promise.status = "fulfilled";
1428
- promise.value = v;
1429
- },
1430
- (e) => {
1431
- promise.status = "rejected";
1432
- promise.reason = e;
1433
- }
1434
- );
1435
- throw promise;
1436
- }
1437
- }
1438
- );
1439
-
1440
1526
  // src/liveblocks.tsx
1441
1527
  var ClientContext = createContext2(null);
1442
1528
  function missingUserError(userId) {
@@ -1453,7 +1539,6 @@ function identity(x) {
1453
1539
  var _umbrellaStores = /* @__PURE__ */ new WeakMap();
1454
1540
  var _extras = /* @__PURE__ */ new WeakMap();
1455
1541
  var _bundles = /* @__PURE__ */ new WeakMap();
1456
- var POLLING_INTERVAL = 60 * 1e3;
1457
1542
  function selectUnreadInboxNotificationsCount(inboxNotifications) {
1458
1543
  let count = 0;
1459
1544
  for (const notification of inboxNotifications) {
@@ -1532,61 +1617,36 @@ function getLiveblocksExtrasForClient(client) {
1532
1617
  }
1533
1618
  return extras;
1534
1619
  }
1535
- function makeDeltaPoller_Notifications(store) {
1536
- const poller = makePoller(async () => {
1537
- try {
1538
- await store.waitUntilNotificationsLoaded();
1539
- await store.fetchNotificationsDeltaUpdate();
1540
- } catch (err) {
1541
- console.warn(`Polling new inbox notifications failed: ${String(err)}`);
1542
- }
1543
- }, POLLING_INTERVAL);
1544
- let pollerSubscribers = 0;
1545
- return () => {
1546
- pollerSubscribers++;
1547
- poller.enable(pollerSubscribers > 0);
1548
- return () => {
1549
- pollerSubscribers--;
1550
- poller.enable(pollerSubscribers > 0);
1551
- };
1552
- };
1553
- }
1554
- function makeDeltaPoller_UserThreads(store) {
1555
- const poller = makePoller(async () => {
1556
- try {
1557
- await store.fetchUserThreadsDeltaUpdate();
1558
- } catch (err) {
1559
- console.warn(`Polling new user threads failed: ${String(err)}`);
1560
- }
1561
- }, POLLING_INTERVAL);
1562
- let pollerSubscribers = 0;
1563
- return () => {
1564
- pollerSubscribers++;
1565
- poller.enable(pollerSubscribers > 0);
1566
- return () => {
1567
- pollerSubscribers--;
1568
- poller.enable(pollerSubscribers > 0);
1569
- };
1570
- };
1571
- }
1572
1620
  function makeLiveblocksExtrasForClient(client) {
1573
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
+ );
1574
1646
  return {
1575
1647
  store,
1576
- /**
1577
- * Sub/unsub pair to start the process of watching for new incoming inbox
1578
- * notifications through a stream of delta updates. Call the unsub function
1579
- * returned to stop this subscription when unmounting. Currently
1580
- * implemented by a periodic poller.
1581
- */
1582
- subscribeToNotificationsDeltaUpdates: makeDeltaPoller_Notifications(store),
1583
- /**
1584
- * Sub/unsub pair to start the process of watching for new user threads
1585
- * through a stream of delta updates. Call the unsub function returned to
1586
- * stop this subscription when unmounting. Currently implemented by
1587
- * a periodic poller.
1588
- */
1589
- subscribeToUserThreadsDeltaUpdates: makeDeltaPoller_UserThreads(store)
1648
+ notificationsPoller,
1649
+ userThreadsPoller
1590
1650
  };
1591
1651
  }
1592
1652
  function makeLiveblocksContextBundle(client) {
@@ -1627,19 +1687,21 @@ function makeLiveblocksContextBundle(client) {
1627
1687
  return bundle;
1628
1688
  }
1629
1689
  function useInboxNotifications_withClient(client, selector, isEqual) {
1630
- const {
1631
- store,
1632
- subscribeToNotificationsDeltaUpdates: subscribeToDeltaUpdates
1633
- } = getLiveblocksExtrasForClient(client);
1690
+ const { store, notificationsPoller: poller } = getLiveblocksExtrasForClient(client);
1634
1691
  useEffect2(() => {
1635
- void store.waitUntilNotificationsLoaded().catch(() => {
1636
- });
1692
+ void store.waitUntilNotificationsLoaded();
1637
1693
  });
1638
- useEffect2(subscribeToDeltaUpdates, [subscribeToDeltaUpdates]);
1694
+ useEffect2(() => {
1695
+ poller.inc();
1696
+ poller.pollNowIfStale();
1697
+ return () => {
1698
+ poller.dec();
1699
+ };
1700
+ }, [poller]);
1639
1701
  return useSyncExternalStoreWithSelector(
1640
- store.subscribeThreadsOrInboxNotifications,
1641
- store.getInboxNotificationsAsync,
1642
- store.getInboxNotificationsAsync,
1702
+ store.subscribe,
1703
+ store.getInboxNotificationsLoadingState,
1704
+ store.getInboxNotificationsLoadingState,
1643
1705
  selector,
1644
1706
  isEqual
1645
1707
  );
@@ -1768,7 +1830,7 @@ function useInboxNotificationThread_withClient(client, inboxNotificationId) {
1768
1830
  `Inbox notification with ID "${inboxNotificationId}" is not of kind "thread"`
1769
1831
  );
1770
1832
  }
1771
- const thread = state.threadsById[inboxNotification.threadId] ?? raise(
1833
+ const thread = state.threadsDB.get(inboxNotification.threadId) ?? raise(
1772
1834
  `Thread with ID "${inboxNotification.threadId}" not found, this inbox notification might not be of kind "thread"`
1773
1835
  );
1774
1836
  return thread;
@@ -1776,7 +1838,7 @@ function useInboxNotificationThread_withClient(client, inboxNotificationId) {
1776
1838
  [inboxNotificationId]
1777
1839
  );
1778
1840
  return useSyncExternalStoreWithSelector(
1779
- store.subscribeThreadsOrInboxNotifications,
1841
+ store.subscribe,
1780
1842
  // Re-evaluate if we need to update any time the notification changes over time
1781
1843
  getter,
1782
1844
  getter,
@@ -1960,11 +2022,10 @@ function useUserThreads_experimental(options = {
1960
2022
  }
1961
2023
  }) {
1962
2024
  const client = useClient();
1963
- const { store, subscribeToUserThreadsDeltaUpdates: subscribeToDeltaUpdates } = getLiveblocksExtrasForClient(client);
2025
+ const { store, userThreadsPoller: poller } = getLiveblocksExtrasForClient(client);
1964
2026
  useEffect2(
1965
2027
  () => {
1966
- void store.waitUntilUserThreadsLoaded(options.query).catch(() => {
1967
- });
2028
+ void store.waitUntilUserThreadsLoaded(options.query);
1968
2029
  }
1969
2030
  // NOTE: Deliberately *not* using a dependency array here!
1970
2031
  //
@@ -1975,13 +2036,19 @@ function useUserThreads_experimental(options = {
1975
2036
  // 3. If ever the promise would fail, then after 5 seconds it would reset, and on the very
1976
2037
  // *next* render after that, a *new* fetch/promise will get created.
1977
2038
  );
1978
- useEffect2(subscribeToDeltaUpdates, [subscribeToDeltaUpdates]);
2039
+ useEffect2(() => {
2040
+ poller.inc();
2041
+ poller.pollNowIfStale();
2042
+ return () => {
2043
+ poller.dec();
2044
+ };
2045
+ }, [poller]);
1979
2046
  const getter = useCallback2(
1980
- () => store.getUserThreadsAsync(options.query),
2047
+ () => store.getUserThreadsLoadingState(options.query),
1981
2048
  [store, options.query]
1982
2049
  );
1983
2050
  return useSyncExternalStoreWithSelector(
1984
- store.subscribeUserThreads,
2051
+ store.subscribe,
1985
2052
  getter,
1986
2053
  getter,
1987
2054
  identity,
@@ -2154,32 +2221,20 @@ var UpdateNotificationSettingsError = class extends Error {
2154
2221
  import { shallow as shallow4 } from "@liveblocks/client";
2155
2222
  import {
2156
2223
  assert as assert2,
2157
- CommentsApiError,
2158
2224
  console as console3,
2159
2225
  createCommentId,
2160
2226
  createThreadId,
2161
2227
  deprecateIf,
2162
2228
  errorIf,
2229
+ HttpError as HttpError2,
2163
2230
  kInternal as kInternal3,
2164
2231
  makeEventSource as makeEventSource2,
2165
2232
  makePoller as makePoller2,
2166
- NotificationsApiError,
2167
2233
  ServerMsgCode
2168
2234
  } from "@liveblocks/core";
2169
2235
  import * as React4 from "react";
2170
2236
  import { useSyncExternalStoreWithSelector as useSyncExternalStoreWithSelector2 } from "use-sync-external-store/shim/with-selector.js";
2171
2237
 
2172
- // src/lib/retry-error.ts
2173
- var MAX_ERROR_RETRY_COUNT = 5;
2174
- var ERROR_RETRY_INTERVAL = 5e3;
2175
- function retryError(action, retryCount) {
2176
- if (retryCount >= MAX_ERROR_RETRY_COUNT) return;
2177
- const timeout = Math.pow(2, retryCount) * ERROR_RETRY_INTERVAL;
2178
- setTimeout(() => {
2179
- void action();
2180
- }, timeout);
2181
- }
2182
-
2183
2238
  // src/use-scroll-to-comment-on-load-effect.ts
2184
2239
  import * as React3 from "react";
2185
2240
  function handleScrollToCommentOnLoad(shouldScrollOnLoad, state) {
@@ -2230,7 +2285,6 @@ function useSyncExternalStore2(s, gs, gss) {
2230
2285
  return useSyncExternalStoreWithSelector2(s, gs, gss, identity2);
2231
2286
  }
2232
2287
  var STABLE_EMPTY_LIST = Object.freeze([]);
2233
- var POLLING_INTERVAL2 = 5 * 60 * 1e3;
2234
2288
  function alwaysEmptyList() {
2235
2289
  return STABLE_EMPTY_LIST;
2236
2290
  }
@@ -2285,28 +2339,6 @@ function handleApiError(err) {
2285
2339
  }
2286
2340
  return new Error(message);
2287
2341
  }
2288
- function makeDeltaPoller_RoomThreads(client) {
2289
- const store = getUmbrellaStoreForClient(client);
2290
- const poller = makePoller2(async () => {
2291
- const roomIds = client[kInternal3].getRoomIds();
2292
- await Promise.allSettled(
2293
- roomIds.map((roomId) => {
2294
- const room = client.getRoom(roomId);
2295
- if (room === null) return;
2296
- return store.fetchRoomThreadsDeltaUpdate(room.id);
2297
- })
2298
- );
2299
- }, POLLING_INTERVAL2);
2300
- let pollerSubscribers = 0;
2301
- return () => {
2302
- pollerSubscribers++;
2303
- poller.enable(pollerSubscribers > 0);
2304
- return () => {
2305
- pollerSubscribers--;
2306
- poller.enable(pollerSubscribers > 0);
2307
- };
2308
- };
2309
- }
2310
2342
  var _extras2 = /* @__PURE__ */ new WeakMap();
2311
2343
  var _bundles2 = /* @__PURE__ */ new WeakMap();
2312
2344
  function getOrCreateRoomContextBundle(client) {
@@ -2327,78 +2359,87 @@ function getRoomExtrasForClient(client) {
2327
2359
  }
2328
2360
  function makeRoomExtrasForClient(client) {
2329
2361
  const store = getUmbrellaStoreForClient(client);
2330
- const requestsByQuery = /* @__PURE__ */ new Map();
2331
- async function getRoomVersions(room, { retryCount } = { retryCount: 0 }) {
2332
- const queryKey = makeVersionsQueryKey(room.id);
2333
- const existingRequest = requestsByQuery.get(queryKey);
2334
- if (existingRequest !== void 0) return existingRequest;
2335
- const request = room[kInternal3].listTextVersions();
2336
- requestsByQuery.set(queryKey, request);
2337
- store.setQuery4Loading(queryKey);
2338
- try {
2339
- const result = await request;
2340
- const data = await result.json();
2341
- const versions = data.versions.map(({ createdAt, ...version2 }) => {
2342
- return {
2343
- createdAt: new Date(createdAt),
2344
- ...version2
2345
- };
2346
- });
2347
- store.updateRoomVersions(room.id, versions, queryKey);
2348
- requestsByQuery.delete(queryKey);
2349
- } catch (err) {
2350
- requestsByQuery.delete(queryKey);
2351
- retryError(() => {
2352
- void getRoomVersions(room, {
2353
- retryCount: retryCount + 1
2354
- });
2355
- }, retryCount);
2356
- store.setQuery4Error(queryKey, err);
2357
- }
2358
- return;
2359
- }
2360
- async function getInboxNotificationSettings(room, { retryCount } = { retryCount: 0 }) {
2361
- const queryKey = makeNotificationSettingsQueryKey(room.id);
2362
- const existingRequest = requestsByQuery.get(queryKey);
2363
- if (existingRequest !== void 0) return existingRequest;
2364
- try {
2365
- const request = room.getNotificationSettings();
2366
- requestsByQuery.set(queryKey, request);
2367
- store.setQuery3Loading(queryKey);
2368
- const settings = await request;
2369
- store.updateRoomInboxNotificationSettings(room.id, settings, queryKey);
2370
- } catch (err) {
2371
- requestsByQuery.delete(queryKey);
2372
- retryError(() => {
2373
- void getInboxNotificationSettings(room, {
2374
- retryCount: retryCount + 1
2375
- });
2376
- }, retryCount);
2377
- store.setQuery3Error(queryKey, err);
2378
- }
2379
- return;
2380
- }
2381
2362
  const commentsErrorEventSource = makeEventSource2();
2382
2363
  function onMutationFailure(innerError, optimisticUpdateId, createPublicError) {
2383
2364
  store.removeOptimisticUpdate(optimisticUpdateId);
2384
- if (innerError instanceof CommentsApiError) {
2365
+ if (innerError instanceof HttpError2) {
2385
2366
  const error = handleApiError(innerError);
2386
2367
  commentsErrorEventSource.notify(createPublicError(error));
2387
2368
  return;
2388
2369
  }
2389
- if (innerError instanceof NotificationsApiError) {
2370
+ if (innerError instanceof HttpError2) {
2390
2371
  handleApiError(innerError);
2391
2372
  return;
2392
2373
  }
2393
2374
  throw innerError;
2394
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
+ }
2395
2436
  return {
2396
2437
  store,
2397
- subscribeToRoomThreadsDeltaUpdates: makeDeltaPoller_RoomThreads(client),
2398
- commentsErrorEventSource,
2399
- getInboxNotificationSettings,
2400
- getRoomVersions,
2401
- onMutationFailure
2438
+ commentsErrorEventSource: commentsErrorEventSource.observable,
2439
+ onMutationFailure,
2440
+ getOrCreateThreadsPollerForRoomId,
2441
+ getOrCreateVersionsPollerForRoomId,
2442
+ getOrCreateNotificationsSettingsPollerForRoomId
2402
2443
  };
2403
2444
  }
2404
2445
  function makeRoomContextBundle(client) {
@@ -2582,7 +2623,7 @@ function RoomProviderInner(props) {
2582
2623
  return;
2583
2624
  }
2584
2625
  const { thread, inboxNotification } = info;
2585
- const existingThread = store.getFullState().threadsById[message.threadId];
2626
+ const existingThread = store.getFullState().threadsDB.getEvenIfDeleted(message.threadId);
2586
2627
  switch (message.type) {
2587
2628
  case ServerMsgCode.COMMENT_EDITED:
2588
2629
  case ServerMsgCode.THREAD_METADATA_UPDATED:
@@ -2604,22 +2645,6 @@ function RoomProviderInner(props) {
2604
2645
  (message) => void handleCommentEvent(message)
2605
2646
  );
2606
2647
  }, [client, room]);
2607
- React4.useEffect(() => {
2608
- const store = getRoomExtrasForClient(client).store;
2609
- void store.fetchRoomThreadsDeltaUpdate(room.id).catch(() => {
2610
- });
2611
- }, [client, room.id]);
2612
- React4.useEffect(() => {
2613
- function handleIsOnline() {
2614
- const store = getRoomExtrasForClient(client).store;
2615
- void store.fetchRoomThreadsDeltaUpdate(room.id).catch(() => {
2616
- });
2617
- }
2618
- window.addEventListener("online", handleIsOnline);
2619
- return () => {
2620
- window.removeEventListener("online", handleIsOnline);
2621
- };
2622
- }, [client, room.id]);
2623
2648
  React4.useEffect(() => {
2624
2649
  const pair = stableEnterRoom(roomId, frozenProps);
2625
2650
  setRoomLeavePair(pair);
@@ -2907,11 +2932,11 @@ function useThreads(options = {
2907
2932
  const { scrollOnLoad = true } = options;
2908
2933
  const client = useClient();
2909
2934
  const room = useRoom();
2910
- const { store, subscribeToRoomThreadsDeltaUpdates: subscribeToDeltaUpdates } = getRoomExtrasForClient(client);
2935
+ const { store, getOrCreateThreadsPollerForRoomId } = getRoomExtrasForClient(client);
2936
+ const poller = getOrCreateThreadsPollerForRoomId(room.id);
2911
2937
  React4.useEffect(
2912
2938
  () => {
2913
- void store.waitUntilRoomThreadsLoaded(room.id, options.query).catch(() => {
2914
- });
2939
+ void store.waitUntilRoomThreadsLoaded(room.id, options.query);
2915
2940
  }
2916
2941
  // NOTE: Deliberately *not* using a dependency array here!
2917
2942
  //
@@ -2922,13 +2947,17 @@ function useThreads(options = {
2922
2947
  // 3. If ever the promise would fail, then after 5 seconds it would reset, and on the very
2923
2948
  // *next* render after that, a *new* fetch/promise will get created.
2924
2949
  );
2925
- React4.useEffect(subscribeToDeltaUpdates, [subscribeToDeltaUpdates]);
2950
+ React4.useEffect(() => {
2951
+ poller.inc();
2952
+ poller.pollNowIfStale();
2953
+ return () => poller.dec();
2954
+ }, [poller]);
2926
2955
  const getter = React4.useCallback(
2927
- () => store.getRoomThreadsAsync(room.id, options.query),
2956
+ () => store.getRoomThreadsLoadingState(room.id, options.query),
2928
2957
  [store, room.id, options.query]
2929
2958
  );
2930
2959
  const state = useSyncExternalStoreWithSelector2(
2931
- store.subscribeThreads,
2960
+ store.subscribe,
2932
2961
  getter,
2933
2962
  getter,
2934
2963
  identity2,
@@ -3012,9 +3041,9 @@ function useDeleteThread() {
3012
3041
  return React4.useCallback(
3013
3042
  (threadId) => {
3014
3043
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
3015
- const thread = store.getFullState().threadsById[threadId];
3016
3044
  const userId = getCurrentUserId(room);
3017
- if (thread?.comments?.[0]?.userId !== userId) {
3045
+ const existing = store.getFullState().threadsDB.get(threadId);
3046
+ if (existing?.comments?.[0]?.userId !== userId) {
3018
3047
  throw new Error("Only the thread creator can delete the thread");
3019
3048
  }
3020
3049
  const optimisticUpdateId = store.addOptimisticUpdate({
@@ -3130,14 +3159,14 @@ function useEditComment() {
3130
3159
  ({ threadId, commentId, body, attachments }) => {
3131
3160
  const editedAt = /* @__PURE__ */ new Date();
3132
3161
  const { store, onMutationFailure } = getRoomExtrasForClient(client);
3133
- const thread = store.getFullState().threadsById[threadId];
3134
- if (thread === void 0) {
3162
+ const existing = store.getFullState().threadsDB.getEvenIfDeleted(threadId);
3163
+ if (existing === void 0) {
3135
3164
  console3.warn(
3136
3165
  `Internal unexpected behavior. Cannot edit comment in thread "${threadId}" because the thread does not exist in the cache.`
3137
3166
  );
3138
3167
  return;
3139
3168
  }
3140
- const comment = thread.comments.find(
3169
+ const comment = existing.comments.find(
3141
3170
  (comment2) => comment2.id === commentId
3142
3171
  );
3143
3172
  if (comment === void 0 || comment.deletedAt !== void 0) {
@@ -3411,24 +3440,22 @@ function useThreadSubscription(threadId) {
3411
3440
  const { store } = getRoomExtrasForClient(client);
3412
3441
  const selector = React4.useCallback(
3413
3442
  (state) => {
3414
- const inboxNotification = state.notifications.find(
3415
- (inboxNotification2) => inboxNotification2.kind === "thread" && inboxNotification2.threadId === threadId
3443
+ const notification = state.cleanedNotifications.find(
3444
+ (inboxNotification) => inboxNotification.kind === "thread" && inboxNotification.threadId === threadId
3416
3445
  );
3417
- const thread = state.threadsById[threadId];
3418
- if (inboxNotification === void 0 || thread === void 0) {
3419
- return {
3420
- status: "not-subscribed"
3421
- };
3446
+ const thread = state.threadsDB.get(threadId);
3447
+ if (notification === void 0 || thread === void 0) {
3448
+ return { status: "not-subscribed" };
3422
3449
  }
3423
3450
  return {
3424
3451
  status: "subscribed",
3425
- unreadSince: inboxNotification.readAt
3452
+ unreadSince: notification.readAt
3426
3453
  };
3427
3454
  },
3428
3455
  [threadId]
3429
3456
  );
3430
3457
  return useSyncExternalStoreWithSelector2(
3431
- store.subscribeThreads,
3458
+ store.subscribe,
3432
3459
  store.getFullState,
3433
3460
  store.getFullState,
3434
3461
  selector
@@ -3438,48 +3465,51 @@ function useRoomNotificationSettings() {
3438
3465
  const updateRoomNotificationSettings = useUpdateRoomNotificationSettings();
3439
3466
  const client = useClient();
3440
3467
  const room = useRoom();
3441
- const { store } = getRoomExtrasForClient(client);
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]);
3442
3490
  const getter = React4.useCallback(
3443
- () => store.getNotificationSettingsAsync(room.id),
3491
+ () => store.getNotificationSettingsLoadingState(room.id),
3444
3492
  [store, room.id]
3445
3493
  );
3446
- React4.useEffect(() => {
3447
- const { getInboxNotificationSettings } = getRoomExtrasForClient(client);
3448
- void getInboxNotificationSettings(room);
3449
- }, [client, room]);
3450
3494
  const settings = useSyncExternalStoreWithSelector2(
3451
- store.subscribeNotificationSettings,
3495
+ store.subscribe,
3452
3496
  getter,
3453
3497
  getter,
3454
3498
  identity2,
3455
- shallow4
3499
+ shallow2
3456
3500
  );
3457
3501
  return React4.useMemo(() => {
3458
3502
  return [settings, updateRoomNotificationSettings];
3459
3503
  }, [settings, updateRoomNotificationSettings]);
3460
3504
  }
3461
3505
  function useRoomNotificationSettingsSuspense() {
3462
- const updateRoomNotificationSettings = useUpdateRoomNotificationSettings();
3463
3506
  const client = useClient();
3507
+ const store = getRoomExtrasForClient(client).store;
3464
3508
  const room = useRoom();
3465
- const { store } = getRoomExtrasForClient(client);
3466
- const getter = React4.useCallback(
3467
- () => store.getNotificationSettingsAsync(room.id),
3468
- [store, room.id]
3469
- );
3470
- const settings = useSyncExternalStoreWithSelector2(
3471
- store.subscribeNotificationSettings,
3472
- getter,
3473
- getter,
3474
- identity2,
3475
- shallow4
3476
- );
3477
- if (settings.isLoading) {
3478
- const { getInboxNotificationSettings } = getRoomExtrasForClient(client);
3479
- throw getInboxNotificationSettings(room);
3480
- } else if (settings.error) {
3481
- throw settings.error;
3482
- }
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");
3483
3513
  return React4.useMemo(() => {
3484
3514
  return [settings, updateRoomNotificationSettings];
3485
3515
  }, [settings, updateRoomNotificationSettings]);
@@ -3516,45 +3546,48 @@ function useHistoryVersionData(versionId) {
3516
3546
  function useHistoryVersions() {
3517
3547
  const client = useClient();
3518
3548
  const room = useRoom();
3519
- const { store, getRoomVersions } = getRoomExtrasForClient(client);
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]);
3520
3556
  const getter = React4.useCallback(
3521
- () => store.getVersionsAsync(room.id),
3557
+ () => store.getRoomVersionsLoadingState(room.id),
3522
3558
  [store, room.id]
3523
3559
  );
3524
- React4.useEffect(() => {
3525
- void getRoomVersions(room);
3526
- }, [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
+ );
3527
3573
  const state = useSyncExternalStoreWithSelector2(
3528
- store.subscribeVersions,
3574
+ store.subscribe,
3529
3575
  getter,
3530
3576
  getter,
3531
3577
  identity2,
3532
- shallow4
3578
+ shallow2
3533
3579
  );
3534
3580
  return state;
3535
3581
  }
3536
3582
  function useHistoryVersionsSuspense() {
3537
3583
  const client = useClient();
3538
3584
  const room = useRoom();
3539
- const { store } = getRoomExtrasForClient(client);
3540
- const getter = React4.useCallback(
3541
- () => store.getVersionsAsync(room.id),
3542
- [store, room.id]
3543
- );
3544
- const state = useSyncExternalStoreWithSelector2(
3545
- store.subscribeVersions,
3546
- getter,
3547
- getter,
3548
- identity2,
3549
- shallow4
3550
- );
3551
- if (state.isLoading) {
3552
- const { getRoomVersions } = getRoomExtrasForClient(client);
3553
- throw getRoomVersions(room);
3554
- } else if (state.error) {
3555
- throw state.error;
3556
- }
3557
- 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;
3558
3591
  }
3559
3592
  function useUpdateRoomNotificationSettings() {
3560
3593
  const client = useClient();
@@ -3569,7 +3602,7 @@ function useUpdateRoomNotificationSettings() {
3569
3602
  });
3570
3603
  room.updateNotificationSettings(settings).then(
3571
3604
  (settings2) => {
3572
- store.updateRoomInboxNotificationSettings2(
3605
+ store.updateRoomNotificationSettings_confirmOptimisticUpdate(
3573
3606
  room.id,
3574
3607
  optimisticUpdateId,
3575
3608
  settings2
@@ -3731,6 +3764,8 @@ var _useOthersMapped = useOthersMapped;
3731
3764
  var _useOthersMappedSuspense = useOthersMappedSuspense;
3732
3765
  var _useThreads = useThreads;
3733
3766
  var _useThreadsSuspense = useThreadsSuspense;
3767
+ var _useRoomNotificationSettings = useRoomNotificationSettings;
3768
+ var _useRoomNotificationSettingsSuspense = useRoomNotificationSettingsSuspense;
3734
3769
  var _useHistoryVersions = useHistoryVersions;
3735
3770
  var _useHistoryVersionsSuspense = useHistoryVersionsSuspense;
3736
3771
  var _useOther = useOther;
@@ -3754,7 +3789,6 @@ var _useUpdateMyPresence = useUpdateMyPresence;
3754
3789
 
3755
3790
  export {
3756
3791
  RoomContext,
3757
- selectThreads,
3758
3792
  ClientContext,
3759
3793
  getUmbrellaStoreForClient,
3760
3794
  useClient,
@@ -3796,7 +3830,6 @@ export {
3796
3830
  useMarkThreadAsResolved,
3797
3831
  useMarkThreadAsUnresolved,
3798
3832
  useThreadSubscription,
3799
- useRoomNotificationSettings,
3800
3833
  useHistoryVersionData,
3801
3834
  useUpdateRoomNotificationSettings,
3802
3835
  useOthersConnectionIdsSuspense,
@@ -3820,6 +3853,8 @@ export {
3820
3853
  _useOthersMappedSuspense,
3821
3854
  _useThreads,
3822
3855
  _useThreadsSuspense,
3856
+ _useRoomNotificationSettings,
3857
+ _useRoomNotificationSettingsSuspense,
3823
3858
  _useHistoryVersions,
3824
3859
  _useHistoryVersionsSuspense,
3825
3860
  _useOther,
@@ -3833,4 +3868,4 @@ export {
3833
3868
  _useStorageRoot,
3834
3869
  _useUpdateMyPresence
3835
3870
  };
3836
- //# sourceMappingURL=chunk-LMENFXCS.mjs.map
3871
+ //# sourceMappingURL=chunk-A7GJNN4L.mjs.map