@liveblocks/react 2.8.3-tiptap1 → 2.8.3-tiptap2

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,6 +1,6 @@
1
1
  // src/version.ts
2
2
  var PKG_NAME = "@liveblocks/react";
3
- var PKG_VERSION = "2.8.3-tiptap1";
3
+ var PKG_VERSION = "2.8.3-tiptap2";
4
4
  var PKG_FORMAT = "esm";
5
5
 
6
6
  // src/ClientSideSuspense.tsx
@@ -24,27 +24,37 @@ function useIsInsideRoom() {
24
24
  return room !== null;
25
25
  }
26
26
 
27
- // src/liveblocks.tsx
27
+ // src/umbrella-store.ts
28
28
  import {
29
- assert,
30
29
  autoRetry,
31
- createClient,
30
+ compactObject,
31
+ console as console2,
32
+ createStore,
32
33
  kInternal,
33
- makePoller,
34
- memoizeOnSuccess,
35
- raise,
36
- shallow as shallow3,
34
+ makeEventSource,
35
+ mapValues,
36
+ nanoid,
37
+ nn,
38
+ StopRetrying,
37
39
  stringify
38
40
  } from "@liveblocks/core";
39
- import React3, {
40
- createContext as createContext2,
41
- useCallback as useCallback2,
42
- useContext as useContext2,
43
- useEffect as useEffect3,
44
- useMemo
45
- } from "react";
46
- import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
47
- import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
41
+
42
+ // src/lib/autobind.ts
43
+ function autobind(self) {
44
+ const seen = /* @__PURE__ */ new Set();
45
+ seen.add("constructor");
46
+ let obj = self.constructor.prototype;
47
+ do {
48
+ for (const key of Reflect.ownKeys(obj)) {
49
+ if (seen.has(key)) continue;
50
+ const descriptor = Reflect.getOwnPropertyDescriptor(obj, key);
51
+ if (typeof descriptor?.value === "function") {
52
+ seen.add(key);
53
+ self[key] = self[key].bind(self);
54
+ }
55
+ }
56
+ } while ((obj = Reflect.getPrototypeOf(obj)) && obj !== Object.prototype);
57
+ }
48
58
 
49
59
  // src/lib/compare.ts
50
60
  function byFirstCreated(a, b) {
@@ -54,7 +64,7 @@ function isMoreRecentlyUpdated(a, b) {
54
64
  return byMostRecentlyUpdated(a, b) < 0;
55
65
  }
56
66
  function byMostRecentlyUpdated(a, b) {
57
- return (b.updatedAt ?? b.createdAt).getTime() - (a.updatedAt ?? a.createdAt).getTime();
67
+ return b.updatedAt.getTime() - a.updatedAt.getTime();
58
68
  }
59
69
 
60
70
  // src/lib/guards.ts
@@ -77,8 +87,11 @@ function matchesMetadata(thread, q) {
77
87
  const metadata = thread.metadata;
78
88
  return q.metadata === void 0 || Object.entries(q.metadata).every(
79
89
  ([key, op]) => (
80
- // Boolean logic: op? => value matches the operator
81
- op === void 0 || matchesOperator(metadata[key], op)
90
+ // NOTE: `op` can be explicitly-`undefined` here, which ideally would not
91
+ // mean "filter for absence" like it does now, as this does not match the
92
+ // backend behavior at the moment. For an in-depth discussion, see
93
+ // https://liveblocks.slack.com/archives/C02PZL7QAAW/p1728546988505989
94
+ matchesOperator(metadata[key], op)
82
95
  )
83
96
  );
84
97
  }
@@ -90,131 +103,204 @@ function matchesOperator(value, op) {
90
103
  }
91
104
  }
92
105
 
93
- // src/lib/retry-error.ts
94
- var MAX_ERROR_RETRY_COUNT = 5;
95
- var ERROR_RETRY_INTERVAL = 5e3;
96
- function retryError(action, retryCount) {
97
- if (retryCount >= MAX_ERROR_RETRY_COUNT) return;
98
- const timeout = Math.pow(2, retryCount) * ERROR_RETRY_INTERVAL;
99
- setTimeout(() => {
100
- void action();
101
- }, timeout);
106
+ // src/umbrella-store.ts
107
+ var ASYNC_OK = Object.freeze({ isLoading: false, data: void 0 });
108
+ function makeRoomThreadsQueryKey(roomId, query) {
109
+ return `${roomId}-${stringify(query ?? {})}`;
102
110
  }
103
-
104
- // src/lib/shallow2.ts
105
- import { isPlainObject as isPlainObject2, shallow } from "@liveblocks/core";
106
- function shallow2(a, b) {
107
- if (!isPlainObject2(a) || !isPlainObject2(b)) {
108
- return shallow(a, b);
111
+ function makeUserThreadsQueryKey(query) {
112
+ return `USER_THREADS:${stringify(query ?? {})}`;
113
+ }
114
+ function makeNotificationSettingsQueryKey(roomId) {
115
+ return `${roomId}:NOTIFICATION_SETTINGS`;
116
+ }
117
+ function makeVersionsQueryKey(roomId) {
118
+ return `${roomId}-VERSIONS`;
119
+ }
120
+ function selectThreads(state, options) {
121
+ let threads = state.threads;
122
+ if (options.roomId !== null) {
123
+ threads = threads.filter((thread) => thread.roomId === options.roomId);
109
124
  }
110
- const keysA = Object.keys(a);
111
- if (keysA.length !== Object.keys(b).length) {
112
- return false;
125
+ const query = options.query;
126
+ if (query) {
127
+ threads = threads.filter(makeThreadsFilter(query));
113
128
  }
114
- return keysA.every(
115
- (key) => Object.prototype.hasOwnProperty.call(b, key) && shallow(a[key], b[key])
129
+ return threads.sort(
130
+ options.orderBy === "last-update" ? byMostRecentlyUpdated : byFirstCreated
116
131
  );
117
132
  }
118
-
119
- // src/lib/use-initial.ts
120
- import { useCallback, useReducer } from "react";
121
-
122
- // src/lib/use-latest.ts
123
- import { useEffect as useEffect2, useRef } from "react";
124
- function useLatest(value) {
125
- const ref = useRef(value);
126
- useEffect2(() => {
127
- ref.current = value;
128
- }, [value]);
129
- return ref;
130
- }
131
-
132
- // src/lib/use-initial.ts
133
- var noop = (state) => state;
134
- function useInitial(value) {
135
- return useReducer(noop, value)[0];
136
- }
137
- function useInitialUnlessFunction(latestValue) {
138
- const frozenValue = useInitial(latestValue);
139
- if (typeof frozenValue === "function") {
140
- const ref = useLatest(latestValue);
141
- return useCallback((...args) => ref.current(...args), [
142
- ref
143
- ]);
144
- } else {
145
- return frozenValue;
133
+ function usify(promise) {
134
+ if ("status" in promise) {
135
+ return promise;
146
136
  }
137
+ const usable = promise;
138
+ usable.status = "pending";
139
+ usable.then(
140
+ (value) => {
141
+ usable.status = "fulfilled";
142
+ usable.value = value;
143
+ },
144
+ (err) => {
145
+ usable.status = "rejected";
146
+ usable.reason = err;
147
+ }
148
+ );
149
+ return usable;
147
150
  }
148
-
149
- // src/lib/use-polyfill.ts
150
- var use = (
151
- // React.use ||
152
- (promise) => {
153
- if (promise.status === "pending") {
154
- throw promise;
155
- } else if (promise.status === "fulfilled") {
156
- return promise.value;
157
- } else if (promise.status === "rejected") {
158
- throw promise.reason;
159
- } else {
160
- promise.status = "pending";
161
- promise.then(
162
- (v) => {
163
- promise.status = "fulfilled";
164
- promise.value = v;
165
- },
166
- (e) => {
167
- promise.status = "rejected";
168
- promise.reason = e;
169
- }
170
- );
171
- throw promise;
151
+ var noop = Promise.resolve();
152
+ var ASYNC_LOADING = Object.freeze({ isLoading: true });
153
+ var PaginatedResource = class {
154
+ constructor(fetchPage) {
155
+ this._cachedPromise = null;
156
+ this._paginationState = null;
157
+ this._fetchPage = fetchPage;
158
+ this._eventSource = makeEventSource();
159
+ this._pendingFetchMore = null;
160
+ this.observable = this._eventSource.observable;
161
+ autobind(this);
162
+ }
163
+ patchPaginationState(patch) {
164
+ const state = this._paginationState;
165
+ if (state === null) return;
166
+ this._paginationState = { ...state, ...patch };
167
+ this._eventSource.notify();
168
+ }
169
+ async _fetchMore() {
170
+ const state = this._paginationState;
171
+ if (!state?.cursor) {
172
+ return;
173
+ }
174
+ this.patchPaginationState({ isFetchingMore: true });
175
+ try {
176
+ const nextCursor = await this._fetchPage(state.cursor);
177
+ this.patchPaginationState({
178
+ cursor: nextCursor,
179
+ fetchMoreError: void 0,
180
+ isFetchingMore: false
181
+ });
182
+ } catch (err) {
183
+ this.patchPaginationState({
184
+ isFetchingMore: false,
185
+ fetchMoreError: err
186
+ });
172
187
  }
173
188
  }
174
- );
175
-
176
- // src/umbrella-store.ts
177
- import {
178
- compactObject,
179
- console as console2,
180
- createStore,
181
- mapValues,
182
- nanoid,
183
- nn
184
- } from "@liveblocks/core";
185
- var ASYNC_LOADING = Object.freeze({ isLoading: true });
186
- var ASYNC_OK = Object.freeze({ isLoading: false, data: void 0 });
187
- function makeNotificationSettingsQueryKey(roomId) {
188
- return `${roomId}:NOTIFICATION_SETTINGS`;
189
- }
190
- function makeVersionsQueryKey(roomId) {
191
- return `${roomId}-VERSIONS`;
192
- }
189
+ fetchMore() {
190
+ const state = this._paginationState;
191
+ if (state?.cursor === null) {
192
+ return noop;
193
+ }
194
+ if (!this._pendingFetchMore) {
195
+ this._pendingFetchMore = this._fetchMore().finally(() => {
196
+ this._pendingFetchMore = null;
197
+ });
198
+ }
199
+ return this._pendingFetchMore;
200
+ }
201
+ get() {
202
+ const usable = this._cachedPromise;
203
+ if (usable === null || usable.status === "pending") {
204
+ return ASYNC_LOADING;
205
+ }
206
+ if (usable.status === "rejected") {
207
+ return { isLoading: false, error: usable.reason };
208
+ }
209
+ const state = this._paginationState;
210
+ return {
211
+ isLoading: false,
212
+ data: {
213
+ fetchMore: this.fetchMore,
214
+ isFetchingMore: state.isFetchingMore,
215
+ fetchMoreError: state.fetchMoreError,
216
+ hasFetchedAll: state.cursor === null
217
+ }
218
+ };
219
+ }
220
+ waitUntilLoaded() {
221
+ if (this._cachedPromise) {
222
+ return this._cachedPromise;
223
+ }
224
+ const initialFetcher = autoRetry(
225
+ () => this._fetchPage(
226
+ /* cursor */
227
+ void 0
228
+ ),
229
+ 5,
230
+ [5e3, 5e3, 1e4, 15e3]
231
+ );
232
+ const promise = usify(
233
+ initialFetcher.then((cursor) => {
234
+ this._paginationState = {
235
+ cursor,
236
+ isFetchingMore: false,
237
+ fetchMoreError: void 0
238
+ };
239
+ })
240
+ );
241
+ promise.then(
242
+ () => this._eventSource.notify(),
243
+ () => {
244
+ this._eventSource.notify();
245
+ setTimeout(() => {
246
+ this._cachedPromise = null;
247
+ this._eventSource.notify();
248
+ }, 5e3);
249
+ }
250
+ );
251
+ this._cachedPromise = promise;
252
+ return promise;
253
+ }
254
+ };
193
255
  var UmbrellaStore = class {
194
- constructor() {
256
+ constructor(client) {
195
257
  this._prevState = null;
196
258
  this._stateCached = null;
259
+ // Notifications
260
+ this._notificationsLastRequestedAt = null;
261
+ // Room Threads
262
+ this._roomThreadsLastRequestedAtByRoom = /* @__PURE__ */ new Map();
263
+ this._roomThreads = /* @__PURE__ */ new Map();
264
+ // User Threads
265
+ this._userThreadsLastRequestedAt = null;
266
+ this._userThreads = /* @__PURE__ */ new Map();
267
+ 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 });
274
+ this.updateThreadsAndNotifications(
275
+ result.threads,
276
+ // TODO: Figure out how to remove this casting
277
+ result.inboxNotifications
278
+ );
279
+ if (this._notificationsLastRequestedAt === null) {
280
+ this._notificationsLastRequestedAt = result.requestedAt;
281
+ }
282
+ const nextCursor = result.nextCursor;
283
+ return nextCursor;
284
+ };
285
+ this._client = client;
286
+ this._notifications = new PaginatedResource(inboxFetcher);
287
+ this._notifications.observable.subscribe(
288
+ () => (
289
+ // Note that the store itself does not change, but it's only vehicle at
290
+ // the moment to trigger a re-render, so we'll do a no-op update here.
291
+ this._store.set((store) => ({ ...store }))
292
+ )
293
+ );
197
294
  this._store = createStore({
198
295
  rawThreadsById: {},
199
- // queries: {},
200
- query1: void 0,
201
- queries2: {},
202
296
  queries3: {},
203
297
  queries4: {},
204
298
  optimisticUpdates: [],
205
- inboxNotificationsById: {},
206
- notificationSettingsByRoomId: {},
299
+ notificationsById: {},
300
+ settingsByRoomId: {},
207
301
  versionsByRoomId: {}
208
302
  });
209
- this.getFullState = this.getFullState.bind(this);
210
- this.getInboxNotificationsAsync = this.getInboxNotificationsAsync.bind(this);
211
- this.subscribeThreads = this.subscribeThreads.bind(this);
212
- this.subscribeUserThreads = this.subscribeUserThreads.bind(this);
213
- this.subscribeThreadsOrInboxNotifications = this.subscribeThreadsOrInboxNotifications.bind(this);
214
- this.subscribeNotificationSettings = this.subscribeNotificationSettings.bind(this);
215
- this.subscribeVersions = this.subscribeVersions.bind(this);
216
- this._hasOptimisticUpdates = this._hasOptimisticUpdates.bind(this);
217
- this._subscribeOptimisticUpdates = this._subscribeOptimisticUpdates.bind(this);
303
+ autobind(this);
218
304
  }
219
305
  get() {
220
306
  const rawState = this._store.get();
@@ -231,45 +317,79 @@ var UmbrellaStore = class {
231
317
  return this.get();
232
318
  }
233
319
  /**
234
- * Returns the async result of the given queryKey. If the query is success,
235
- * then it will return the entire store's state in the payload.
320
+ * Returns the async result of the given query and room id. If the query is success,
321
+ * then it will return the threads that match that provided query and room id.
322
+ *
236
323
  */
237
- // TODO: This return type is a bit weird! Feels like we haven't found the
238
- // right abstraction here yet.
239
- getThreadsAsync(queryKey) {
240
- const internalState = this._store.get();
241
- const query = internalState.queries2[queryKey];
242
- if (query === void 0 || query.isLoading) {
324
+ // XXXX Find a better name for that doesn't associate to 'async'
325
+ getRoomThreadsAsync(roomId, query) {
326
+ const queryKey = makeRoomThreadsQueryKey(roomId, query);
327
+ const paginatedResource = this._roomThreads.get(queryKey);
328
+ if (paginatedResource === void 0) {
243
329
  return ASYNC_LOADING;
244
330
  }
245
- if (query.error) {
246
- return query;
331
+ const asyncResult = paginatedResource.get();
332
+ if (asyncResult.isLoading || asyncResult.error) {
333
+ return asyncResult;
247
334
  }
248
- return { isLoading: false, fullState: this.getFullState() };
335
+ const threads = selectThreads(this.getFullState(), {
336
+ roomId,
337
+ query,
338
+ orderBy: "age"
339
+ });
340
+ const page = asyncResult.data;
341
+ return {
342
+ isLoading: false,
343
+ threads,
344
+ hasFetchedAll: page.hasFetchedAll,
345
+ isFetchingMore: page.isFetchingMore,
346
+ fetchMoreError: page.fetchMoreError,
347
+ fetchMore: page.fetchMore
348
+ };
249
349
  }
250
- getUserThreadsAsync(queryKey) {
251
- const internalState = this._store.get();
252
- const query = internalState.queries2[queryKey];
253
- if (query === void 0 || query.isLoading) {
350
+ // XXXX - Find a better name for that doesn't associate to 'async'
351
+ getUserThreadsAsync(query) {
352
+ const queryKey = makeUserThreadsQueryKey(query);
353
+ const paginatedResource = this._userThreads.get(queryKey);
354
+ if (paginatedResource === void 0) {
254
355
  return ASYNC_LOADING;
255
356
  }
256
- if (query.error) {
257
- return query;
357
+ const asyncResult = paginatedResource.get();
358
+ if (asyncResult.isLoading || asyncResult.error) {
359
+ return asyncResult;
258
360
  }
259
- return { isLoading: false, fullState: this.getFullState() };
361
+ const threads = selectThreads(this.getFullState(), {
362
+ roomId: null,
363
+ // Do _not_ filter by roomId
364
+ query,
365
+ orderBy: "last-update"
366
+ });
367
+ const page = asyncResult.data;
368
+ return {
369
+ isLoading: false,
370
+ threads,
371
+ hasFetchedAll: page.hasFetchedAll,
372
+ isFetchingMore: page.isFetchingMore,
373
+ fetchMoreError: page.fetchMoreError,
374
+ fetchMore: page.fetchMore
375
+ };
260
376
  }
261
377
  // 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'
262
379
  getInboxNotificationsAsync() {
263
- const internalState = this._store.get();
264
- const query = internalState.query1;
265
- if (query === void 0 || query.isLoading) {
266
- return ASYNC_LOADING;
380
+ const asyncResult = this._notifications.get();
381
+ if (asyncResult.isLoading || asyncResult.error) {
382
+ return asyncResult;
267
383
  }
268
- if (query.error !== void 0) {
269
- return query;
270
- }
271
- const inboxNotifications = this.getFullState().inboxNotifications;
272
- return { isLoading: false, inboxNotifications };
384
+ const page = asyncResult.data;
385
+ return {
386
+ isLoading: false,
387
+ inboxNotifications: this.getFullState().notifications,
388
+ hasFetchedAll: page.hasFetchedAll,
389
+ isFetchingMore: page.isFetchingMore,
390
+ fetchMoreError: page.fetchMoreError,
391
+ fetchMore: page.fetchMore
392
+ };
273
393
  }
274
394
  // NOTE: This will read the async result, but WILL NOT start loading at the moment!
275
395
  getNotificationSettingsAsync(roomId) {
@@ -283,7 +403,7 @@ var UmbrellaStore = class {
283
403
  }
284
404
  return {
285
405
  isLoading: false,
286
- settings: nn(state.notificationSettingsByRoomId[roomId])
406
+ settings: nn(state.settingsByRoomId[roomId])
287
407
  };
288
408
  }
289
409
  getVersionsAsync(roomId) {
@@ -339,15 +459,15 @@ var UmbrellaStore = class {
339
459
  }
340
460
  updateInboxNotificationsCache(mapFn) {
341
461
  this._store.set((state) => {
342
- const inboxNotifications = mapFn(state.inboxNotificationsById);
343
- return inboxNotifications !== state.inboxNotificationsById ? { ...state, inboxNotificationsById: inboxNotifications } : state;
462
+ const inboxNotifications = mapFn(state.notificationsById);
463
+ return inboxNotifications !== state.notificationsById ? { ...state, notificationsById: inboxNotifications } : state;
344
464
  });
345
465
  }
346
466
  setNotificationSettings(roomId, settings) {
347
467
  this._store.set((state) => ({
348
468
  ...state,
349
- notificationSettingsByRoomId: {
350
- ...state.notificationSettingsByRoomId,
469
+ settingsByRoomId: {
470
+ ...state.settingsByRoomId,
351
471
  [roomId]: settings
352
472
  }
353
473
  }));
@@ -361,21 +481,6 @@ var UmbrellaStore = class {
361
481
  }
362
482
  }));
363
483
  }
364
- setQuery1State(queryState) {
365
- this._store.set((state) => ({
366
- ...state,
367
- query1: queryState
368
- }));
369
- }
370
- setQuery2State(queryKey, queryState) {
371
- this._store.set((state) => ({
372
- ...state,
373
- queries2: {
374
- ...state.queries2,
375
- [queryKey]: queryState
376
- }
377
- }));
378
- }
379
484
  setQuery3State(queryKey, queryState) {
380
485
  this._store.set((state) => ({
381
486
  ...state,
@@ -493,7 +598,7 @@ var UmbrellaStore = class {
493
598
  if (existing.deletedAt !== void 0) {
494
599
  return cache;
495
600
  }
496
- if (!!updatedAt && !!existing.updatedAt && existing.updatedAt > updatedAt) {
601
+ if (!!updatedAt && existing.updatedAt > updatedAt) {
497
602
  return cache;
498
603
  }
499
604
  return { ...cache, [threadId]: callback(existing) };
@@ -602,7 +707,7 @@ var UmbrellaStore = class {
602
707
  }
603
708
  });
604
709
  }
605
- updateThreadsAndNotifications(threads, inboxNotifications, deletedThreads, deletedInboxNotifications) {
710
+ updateThreadsAndNotifications(threads, inboxNotifications, deletedThreads = [], deletedInboxNotifications = []) {
606
711
  this._store.batch(() => {
607
712
  this.updateThreadsCache(
608
713
  (cache) => applyThreadUpdates(cache, {
@@ -622,12 +727,14 @@ var UmbrellaStore = class {
622
727
  * Updates existing notification setting for a room with a new value,
623
728
  * replacing the corresponding optimistic update.
624
729
  */
730
+ // XXXX Rename this helper method
625
731
  updateRoomInboxNotificationSettings2(roomId, optimisticUpdateId, settings) {
626
732
  this._store.batch(() => {
627
733
  this.removeOptimisticUpdate(optimisticUpdateId);
628
734
  this.setNotificationSettings(roomId, settings);
629
735
  });
630
736
  }
737
+ // XXXX Rename this helper method
631
738
  updateRoomInboxNotificationSettings(roomId, settings, queryKey) {
632
739
  this._store.batch(() => {
633
740
  this.setQuery3OK(queryKey);
@@ -653,29 +760,6 @@ var UmbrellaStore = class {
653
760
  (cache) => cache.filter((ou) => ou.id !== optimisticUpdateId)
654
761
  );
655
762
  }
656
- //
657
- // Query State APIs
658
- //
659
- // Query 1
660
- setQuery1Loading() {
661
- this.setQuery1State(ASYNC_LOADING);
662
- }
663
- setQuery1OK() {
664
- this.setQuery1State(ASYNC_OK);
665
- }
666
- setQuery1Error(error) {
667
- this.setQuery1State({ isLoading: false, error });
668
- }
669
- // Query 2
670
- setQuery2Loading(queryKey) {
671
- this.setQuery2State(queryKey, ASYNC_LOADING);
672
- }
673
- setQuery2OK(queryKey) {
674
- this.setQuery2State(queryKey, ASYNC_OK);
675
- }
676
- setQuery2Error(queryKey, error) {
677
- this.setQuery2State(queryKey, { isLoading: false, error });
678
- }
679
763
  // Query 3
680
764
  setQuery3Loading(queryKey) {
681
765
  this.setQuery3State(queryKey, ASYNC_LOADING);
@@ -696,12 +780,159 @@ var UmbrellaStore = class {
696
780
  setQuery4Error(queryKey, error) {
697
781
  this.setQuery4State(queryKey, { isLoading: false, error });
698
782
  }
783
+ async fetchNotificationsDeltaUpdate() {
784
+ const lastRequestedAt = this._notificationsLastRequestedAt;
785
+ if (lastRequestedAt === null) {
786
+ return;
787
+ }
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);
793
+ if (lastRequestedAt < result.requestedAt) {
794
+ this._notificationsLastRequestedAt = result.requestedAt;
795
+ }
796
+ this.updateThreadsAndNotifications(
797
+ result.threads.updated,
798
+ result.inboxNotifications.updated,
799
+ result.threads.deleted,
800
+ result.inboxNotifications.deleted
801
+ );
802
+ }
803
+ waitUntilNotificationsLoaded() {
804
+ return this._notifications.waitUntilLoaded();
805
+ }
806
+ waitUntilRoomThreadsLoaded(roomId, query) {
807
+ 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
+ const room = this._client.getRoom(roomId);
814
+ if (room === null) {
815
+ throw new StopRetrying(
816
+ `Room with id ${roomId} is not available on client`
817
+ );
818
+ }
819
+ const result = await room.getThreads({ cursor, query });
820
+ this.updateThreadsAndNotifications(
821
+ result.threads,
822
+ // TODO: Figure out how to remove this casting
823
+ result.inboxNotifications
824
+ );
825
+ const lastRequestedAt = this._roomThreadsLastRequestedAtByRoom.get(roomId);
826
+ if (lastRequestedAt === void 0 || lastRequestedAt > result.requestedAt) {
827
+ this._roomThreadsLastRequestedAtByRoom.set(roomId, result.requestedAt);
828
+ }
829
+ return result.nextCursor;
830
+ };
831
+ const queryKey = makeRoomThreadsQueryKey(roomId, query);
832
+ let paginatedResource = this._roomThreads.get(queryKey);
833
+ if (paginatedResource === void 0) {
834
+ paginatedResource = new PaginatedResource(threadsFetcher);
835
+ }
836
+ paginatedResource.observable.subscribe(
837
+ () => (
838
+ // Note that the store itself does not change, but it's only vehicle at
839
+ // the moment to trigger a re-render, so we'll do a no-op update here.
840
+ this._store.set((store) => ({ ...store }))
841
+ )
842
+ );
843
+ this._roomThreads.set(queryKey, paginatedResource);
844
+ return paginatedResource.waitUntilLoaded();
845
+ }
846
+ async fetchRoomThreadsDeltaUpdate(roomId) {
847
+ const lastRequestedAt = this._roomThreadsLastRequestedAtByRoom.get(roomId);
848
+ if (lastRequestedAt === void 0) {
849
+ return;
850
+ }
851
+ const client = nn(
852
+ this._client,
853
+ "Client is required in order to load notifications for the room"
854
+ );
855
+ const room = nn(
856
+ client.getRoom(roomId),
857
+ `Room with id ${roomId} is not available on client`
858
+ );
859
+ const updates = await room.getThreadsSince({
860
+ since: lastRequestedAt
861
+ });
862
+ this.updateThreadsAndNotifications(
863
+ updates.threads.updated,
864
+ updates.inboxNotifications.updated,
865
+ updates.threads.deleted,
866
+ updates.inboxNotifications.deleted
867
+ );
868
+ if (lastRequestedAt < updates.requestedAt) {
869
+ this._roomThreadsLastRequestedAtByRoom.set(roomId, updates.requestedAt);
870
+ }
871
+ }
872
+ waitUntilUserThreadsLoaded(query) {
873
+ const queryKey = makeUserThreadsQueryKey(query);
874
+ 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
+ const result = await this._client[kInternal].getUserThreads_experimental({
881
+ cursor,
882
+ query
883
+ });
884
+ this.updateThreadsAndNotifications(
885
+ result.threads,
886
+ // TODO: Figure out how to remove this casting
887
+ result.inboxNotifications
888
+ );
889
+ if (this._userThreadsLastRequestedAt === null) {
890
+ this._userThreadsLastRequestedAt = result.requestedAt;
891
+ }
892
+ return result.nextCursor;
893
+ };
894
+ let paginatedResource = this._userThreads.get(queryKey);
895
+ if (paginatedResource === void 0) {
896
+ paginatedResource = new PaginatedResource(threadsFetcher);
897
+ }
898
+ paginatedResource.observable.subscribe(
899
+ () => (
900
+ // Note that the store itself does not change, but it's only vehicle at
901
+ // the moment to trigger a re-render, so we'll do a no-op update here.
902
+ this._store.set((store) => ({ ...store }))
903
+ )
904
+ );
905
+ this._userThreads.set(queryKey, paginatedResource);
906
+ return paginatedResource.waitUntilLoaded();
907
+ }
908
+ async fetchUserThreadsDeltaUpdate() {
909
+ const lastRequestedAt = this._userThreadsLastRequestedAt;
910
+ if (lastRequestedAt === null) {
911
+ return;
912
+ }
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
919
+ });
920
+ if (lastRequestedAt < result.requestedAt) {
921
+ this._notificationsLastRequestedAt = result.requestedAt;
922
+ }
923
+ this.updateThreadsAndNotifications(
924
+ result.threads.updated,
925
+ result.inboxNotifications.updated,
926
+ result.threads.deleted,
927
+ result.inboxNotifications.deleted
928
+ );
929
+ }
699
930
  };
700
931
  function internalToExternalState(state) {
701
932
  const computed = {
702
933
  threadsById: { ...state.rawThreadsById },
703
- inboxNotificationsById: { ...state.inboxNotificationsById },
704
- notificationSettingsByRoomId: { ...state.notificationSettingsByRoomId }
934
+ notificationsById: { ...state.notificationsById },
935
+ settingsByRoomId: { ...state.settingsByRoomId }
705
936
  };
706
937
  for (const optimisticUpdate of state.optimisticUpdates) {
707
938
  switch (optimisticUpdate.type) {
@@ -717,7 +948,7 @@ function internalToExternalState(state) {
717
948
  if (thread.deletedAt !== void 0) {
718
949
  break;
719
950
  }
720
- if (thread.updatedAt !== void 0 && thread.updatedAt > optimisticUpdate.updatedAt) {
951
+ if (thread.updatedAt > optimisticUpdate.updatedAt) {
721
952
  break;
722
953
  }
723
954
  computed.threadsById[thread.id] = {
@@ -768,14 +999,14 @@ function internalToExternalState(state) {
768
999
  optimisticUpdate.comment
769
1000
  );
770
1001
  const inboxNotification = Object.values(
771
- computed.inboxNotificationsById
1002
+ computed.notificationsById
772
1003
  ).find(
773
1004
  (notification) => notification.kind === "thread" && notification.threadId === thread.id
774
1005
  );
775
1006
  if (inboxNotification === void 0) {
776
1007
  break;
777
1008
  }
778
- computed.inboxNotificationsById[inboxNotification.id] = {
1009
+ computed.notificationsById[inboxNotification.id] = {
779
1010
  ...inboxNotification,
780
1011
  notifiedAt: optimisticUpdate.comment.createdAt,
781
1012
  readAt: optimisticUpdate.comment.createdAt
@@ -845,20 +1076,23 @@ function internalToExternalState(state) {
845
1076
  break;
846
1077
  }
847
1078
  case "mark-inbox-notification-as-read": {
848
- const ibn = computed.inboxNotificationsById[optimisticUpdate.inboxNotificationId];
1079
+ const ibn = computed.notificationsById[optimisticUpdate.inboxNotificationId];
849
1080
  if (ibn === void 0) {
850
1081
  break;
851
1082
  }
852
- computed.inboxNotificationsById[optimisticUpdate.inboxNotificationId] = { ...ibn, readAt: optimisticUpdate.readAt };
1083
+ computed.notificationsById[optimisticUpdate.inboxNotificationId] = {
1084
+ ...ibn,
1085
+ readAt: optimisticUpdate.readAt
1086
+ };
853
1087
  break;
854
1088
  }
855
1089
  case "mark-all-inbox-notifications-as-read": {
856
- for (const id in computed.inboxNotificationsById) {
857
- const ibn = computed.inboxNotificationsById[id];
1090
+ for (const id in computed.notificationsById) {
1091
+ const ibn = computed.notificationsById[id];
858
1092
  if (ibn === void 0) {
859
1093
  break;
860
1094
  }
861
- computed.inboxNotificationsById[id] = {
1095
+ computed.notificationsById[id] = {
862
1096
  ...ibn,
863
1097
  readAt: optimisticUpdate.readAt
864
1098
  };
@@ -866,19 +1100,19 @@ function internalToExternalState(state) {
866
1100
  break;
867
1101
  }
868
1102
  case "delete-inbox-notification": {
869
- delete computed.inboxNotificationsById[optimisticUpdate.inboxNotificationId];
1103
+ delete computed.notificationsById[optimisticUpdate.inboxNotificationId];
870
1104
  break;
871
1105
  }
872
1106
  case "delete-all-inbox-notifications": {
873
- computed.inboxNotificationsById = {};
1107
+ computed.notificationsById = {};
874
1108
  break;
875
1109
  }
876
1110
  case "update-notification-settings": {
877
- const settings = computed.notificationSettingsByRoomId[optimisticUpdate.roomId];
1111
+ const settings = computed.settingsByRoomId[optimisticUpdate.roomId];
878
1112
  if (settings === void 0) {
879
1113
  break;
880
1114
  }
881
- computed.notificationSettingsByRoomId[optimisticUpdate.roomId] = {
1115
+ computed.settingsByRoomId[optimisticUpdate.roomId] = {
882
1116
  ...settings,
883
1117
  ...optimisticUpdate.settings
884
1118
  };
@@ -896,15 +1130,14 @@ function internalToExternalState(state) {
896
1130
  );
897
1131
  const cleanedNotifications = (
898
1132
  // Sort so that the most recent notifications are first
899
- Object.values(computed.inboxNotificationsById).filter(
1133
+ Object.values(computed.notificationsById).filter(
900
1134
  (ibn) => ibn.kind === "thread" ? computed.threadsById[ibn.threadId] && computed.threadsById[ibn.threadId]?.deletedAt === void 0 : true
901
1135
  ).sort((a, b) => b.notifiedAt.getTime() - a.notifiedAt.getTime())
902
1136
  );
903
1137
  return {
904
- inboxNotifications: cleanedNotifications,
905
- inboxNotificationsById: computed.inboxNotificationsById,
906
- notificationSettingsByRoomId: computed.notificationSettingsByRoomId,
907
- queries2: state.queries2,
1138
+ notifications: cleanedNotifications,
1139
+ notificationsById: computed.notificationsById,
1140
+ settingsByRoomId: computed.settingsByRoomId,
908
1141
  queries3: state.queries3,
909
1142
  queries4: state.queries4,
910
1143
  threads: cleanedThreads,
@@ -978,7 +1211,7 @@ function applyUpsertComment(thread, comment) {
978
1211
  );
979
1212
  if (existingComment === void 0) {
980
1213
  const updatedAt = new Date(
981
- Math.max(thread.updatedAt?.getTime() || 0, comment.createdAt.getTime())
1214
+ Math.max(thread.updatedAt.getTime(), comment.createdAt.getTime())
982
1215
  );
983
1216
  const updatedThread = {
984
1217
  ...thread,
@@ -998,7 +1231,7 @@ function applyUpsertComment(thread, comment) {
998
1231
  ...thread,
999
1232
  updatedAt: new Date(
1000
1233
  Math.max(
1001
- thread.updatedAt?.getTime() || 0,
1234
+ thread.updatedAt.getTime(),
1002
1235
  comment.editedAt?.getTime() || comment.createdAt.getTime()
1003
1236
  )
1004
1237
  ),
@@ -1065,7 +1298,7 @@ function applyAddReaction(thread, commentId, reaction) {
1065
1298
  return {
1066
1299
  ...thread,
1067
1300
  updatedAt: new Date(
1068
- Math.max(reaction.createdAt.getTime(), thread.updatedAt?.getTime() || 0)
1301
+ Math.max(reaction.createdAt.getTime(), thread.updatedAt.getTime())
1069
1302
  ),
1070
1303
  comments: updatedComments
1071
1304
  };
@@ -1098,7 +1331,7 @@ function applyRemoveReaction(thread, commentId, emoji, userId, removedAt) {
1098
1331
  return {
1099
1332
  ...thread,
1100
1333
  updatedAt: new Date(
1101
- Math.max(removedAt.getTime(), thread.updatedAt?.getTime() || 0)
1334
+ Math.max(removedAt.getTime(), thread.updatedAt.getTime())
1102
1335
  ),
1103
1336
  comments: updatedComments
1104
1337
  };
@@ -1125,9 +1358,100 @@ function upsertReaction(reactions, reaction) {
1125
1358
  } : existingReaction2
1126
1359
  );
1127
1360
  }
1128
- return reactions;
1361
+ return reactions;
1362
+ }
1363
+
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
+ }
1129
1426
  }
1130
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
+
1131
1455
  // src/liveblocks.tsx
1132
1456
  var ClientContext = createContext2(null);
1133
1457
  function missingUserError(userId) {
@@ -1145,7 +1469,6 @@ var _umbrellaStores = /* @__PURE__ */ new WeakMap();
1145
1469
  var _extras = /* @__PURE__ */ new WeakMap();
1146
1470
  var _bundles = /* @__PURE__ */ new WeakMap();
1147
1471
  var POLLING_INTERVAL = 60 * 1e3;
1148
- var USER_THREADS_QUERY = "USER_THREADS";
1149
1472
  function selectUnreadInboxNotificationsCount(inboxNotifications) {
1150
1473
  let count = 0;
1151
1474
  for (const notification of inboxNotifications) {
@@ -1200,19 +1523,6 @@ function selectorFor_useRoomInfo(state, roomId) {
1200
1523
  info: state.data
1201
1524
  };
1202
1525
  }
1203
- function selectThreads(state, options) {
1204
- let threads = state.threads;
1205
- if (options.roomId !== null) {
1206
- threads = threads.filter((thread) => thread.roomId === options.roomId);
1207
- }
1208
- const query = options.query;
1209
- if (query) {
1210
- threads = threads.filter(makeThreadsFilter(query));
1211
- }
1212
- return threads.sort(
1213
- options.orderBy === "last-update" ? byMostRecentlyUpdated : byFirstCreated
1214
- );
1215
- }
1216
1526
  function getOrCreateContextBundle(client) {
1217
1527
  let bundle = _bundles.get(client);
1218
1528
  if (!bundle) {
@@ -1224,187 +1534,74 @@ function getOrCreateContextBundle(client) {
1224
1534
  function getUmbrellaStoreForClient(client) {
1225
1535
  let store = _umbrellaStores.get(client);
1226
1536
  if (!store) {
1227
- store = new UmbrellaStore();
1537
+ store = new UmbrellaStore(client);
1228
1538
  _umbrellaStores.set(client, store);
1229
1539
  }
1230
1540
  return store;
1231
1541
  }
1232
- function getExtrasForClient(client) {
1542
+ function getLiveblocksExtrasForClient(client) {
1233
1543
  let extras = _extras.get(client);
1234
1544
  if (!extras) {
1235
- extras = makeExtrasForClient(client);
1545
+ extras = makeLiveblocksExtrasForClient(client);
1236
1546
  _extras.set(client, extras);
1237
1547
  }
1238
1548
  return extras;
1239
1549
  }
1240
- function makeExtrasForClient(client) {
1241
- const store = getUmbrellaStoreForClient(client);
1242
- let lastRequestedAt;
1243
- async function fetchInboxNotifications() {
1244
- if (lastRequestedAt === void 0) {
1245
- const result = await client.getInboxNotifications();
1246
- store.batch(() => {
1247
- store.updateThreadsAndNotifications(
1248
- result.threads,
1249
- result.inboxNotifications,
1250
- [],
1251
- []
1252
- );
1253
- store.setQuery1OK();
1254
- });
1255
- lastRequestedAt = result.requestedAt;
1256
- } else {
1257
- const result = await client.getInboxNotificationsSince({
1258
- since: lastRequestedAt
1259
- });
1260
- store.batch(() => {
1261
- store.updateThreadsAndNotifications(
1262
- result.threads.updated,
1263
- result.inboxNotifications.updated,
1264
- result.threads.deleted,
1265
- result.inboxNotifications.deleted
1266
- );
1267
- store.setQuery1OK();
1268
- });
1269
- if (lastRequestedAt < result.requestedAt) {
1270
- lastRequestedAt = result.requestedAt;
1271
- }
1272
- }
1273
- }
1274
- let pollerSubscribers = 0;
1550
+ function makeDeltaPoller_Notifications(store) {
1275
1551
  const poller = makePoller(async () => {
1276
1552
  try {
1277
- await waitUntilInboxNotificationsLoaded();
1278
- await fetchInboxNotifications();
1553
+ await store.waitUntilNotificationsLoaded();
1554
+ await store.fetchNotificationsDeltaUpdate();
1279
1555
  } catch (err) {
1280
1556
  console.warn(`Polling new inbox notifications failed: ${String(err)}`);
1281
1557
  }
1282
- });
1283
- const waitUntilInboxNotificationsLoaded = memoizeOnSuccess(async () => {
1284
- store.setQuery1Loading();
1285
- try {
1286
- await autoRetry(
1287
- () => fetchInboxNotifications(),
1288
- 5,
1289
- [5e3, 5e3, 1e4, 15e3]
1290
- );
1291
- } catch (err) {
1292
- store.setQuery1Error(err);
1293
- throw err;
1294
- }
1295
- });
1296
- function loadInboxNotifications() {
1297
- void waitUntilInboxNotificationsLoaded().catch(() => {
1298
- });
1299
- }
1300
- function startPolling() {
1558
+ }, POLLING_INTERVAL);
1559
+ let pollerSubscribers = 0;
1560
+ return () => {
1301
1561
  pollerSubscribers++;
1302
- poller.start(POLLING_INTERVAL);
1562
+ poller.enable(pollerSubscribers > 0);
1303
1563
  return () => {
1304
- if (pollerSubscribers <= 0) {
1305
- console.warn(
1306
- "Unexpected internal error: cannot decrease subscriber count for inbox notifications."
1307
- );
1308
- return;
1309
- }
1310
1564
  pollerSubscribers--;
1311
- if (pollerSubscribers <= 0) {
1312
- poller.stop();
1313
- }
1565
+ poller.enable(pollerSubscribers > 0);
1314
1566
  };
1315
- }
1316
- const userThreadsPoller = makePoller(refreshUserThreads);
1317
- let isFetchingUserThreadsUpdates = false;
1318
- async function refreshUserThreads() {
1319
- const since = userThreadslastRequestedAt;
1320
- if (since === void 0 || isFetchingUserThreadsUpdates) {
1321
- return;
1322
- }
1567
+ };
1568
+ }
1569
+ function makeDeltaPoller_UserThreads(store) {
1570
+ const poller = makePoller(async () => {
1323
1571
  try {
1324
- isFetchingUserThreadsUpdates = true;
1325
- const updates = await client[kInternal].getThreadsSince({
1326
- since
1327
- });
1328
- isFetchingUserThreadsUpdates = false;
1329
- store.batch(() => {
1330
- store.updateThreadsAndNotifications(
1331
- updates.threads.updated,
1332
- [],
1333
- updates.threads.deleted,
1334
- []
1335
- );
1336
- store.setQuery2OK(USER_THREADS_QUERY);
1337
- });
1338
- userThreadslastRequestedAt = updates.requestedAt;
1572
+ await store.fetchUserThreadsDeltaUpdate();
1339
1573
  } catch (err) {
1340
- isFetchingUserThreadsUpdates = false;
1341
- return;
1574
+ console.warn(`Polling new user threads failed: ${String(err)}`);
1342
1575
  }
1343
- }
1344
- const userThreadsSubscribersByQuery = /* @__PURE__ */ new Map();
1345
- const userThreadsRequestsByQuery = /* @__PURE__ */ new Map();
1346
- function incrementUserThreadsQuerySubscribers(queryKey) {
1347
- const subscribers = userThreadsSubscribersByQuery.get(queryKey) ?? 0;
1348
- userThreadsSubscribersByQuery.set(queryKey, subscribers + 1);
1349
- userThreadsPoller.start(POLLING_INTERVAL);
1576
+ }, POLLING_INTERVAL);
1577
+ let pollerSubscribers = 0;
1578
+ return () => {
1579
+ pollerSubscribers++;
1580
+ poller.enable(pollerSubscribers > 0);
1350
1581
  return () => {
1351
- const subscribers2 = userThreadsSubscribersByQuery.get(queryKey);
1352
- if (subscribers2 === void 0 || subscribers2 <= 0) {
1353
- console.warn(
1354
- `Internal unexpected behavior. Cannot decrease subscriber count for query "${queryKey}"`
1355
- );
1356
- return;
1357
- }
1358
- userThreadsSubscribersByQuery.set(queryKey, subscribers2 - 1);
1359
- let totalSubscribers = 0;
1360
- for (const subscribers3 of userThreadsSubscribersByQuery.values()) {
1361
- totalSubscribers += subscribers3;
1362
- }
1363
- if (totalSubscribers <= 0) {
1364
- userThreadsPoller.stop();
1365
- }
1582
+ pollerSubscribers--;
1583
+ poller.enable(pollerSubscribers > 0);
1366
1584
  };
1367
- }
1368
- let userThreadslastRequestedAt;
1369
- async function getUserThreads(queryKey, options, { retryCount } = { retryCount: 0 }) {
1370
- const existingRequest = userThreadsRequestsByQuery.get(queryKey);
1371
- if (existingRequest !== void 0) return existingRequest;
1372
- const request = client[kInternal].getThreads(options);
1373
- userThreadsRequestsByQuery.set(queryKey, request);
1374
- store.setQuery2Loading(queryKey);
1375
- try {
1376
- const result = await request;
1377
- store.batch(() => {
1378
- store.updateThreadsAndNotifications(
1379
- result.threads,
1380
- result.inboxNotifications,
1381
- [],
1382
- []
1383
- );
1384
- store.setQuery2OK(queryKey);
1385
- });
1386
- if (userThreadslastRequestedAt === void 0 || userThreadslastRequestedAt < result.requestedAt) {
1387
- userThreadslastRequestedAt = result.requestedAt;
1388
- }
1389
- userThreadsPoller.start(POLLING_INTERVAL);
1390
- } catch (err) {
1391
- userThreadsRequestsByQuery.delete(queryKey);
1392
- retryError(() => {
1393
- void getUserThreads(queryKey, options, {
1394
- retryCount: retryCount + 1
1395
- });
1396
- }, retryCount);
1397
- store.setQuery2Error(queryKey, err);
1398
- }
1399
- return;
1400
- }
1585
+ };
1586
+ }
1587
+ function makeLiveblocksExtrasForClient(client) {
1588
+ const store = getUmbrellaStoreForClient(client);
1401
1589
  return {
1402
1590
  store,
1403
- startPolling,
1404
- waitUntilInboxNotificationsLoaded,
1405
- loadInboxNotifications,
1406
- incrementUserThreadsQuerySubscribers,
1407
- getUserThreads
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)
1408
1605
  };
1409
1606
  }
1410
1607
  function makeLiveblocksContextBundle(client) {
@@ -1420,7 +1617,7 @@ function makeLiveblocksContextBundle(client) {
1420
1617
  const shared = createSharedContext(client);
1421
1618
  const bundle = {
1422
1619
  LiveblocksProvider: LiveblocksProvider2,
1423
- useInboxNotifications: () => useInboxNotifications_withClient(client),
1620
+ useInboxNotifications: () => useInboxNotifications_withClient(client, identity, shallow3),
1424
1621
  useUnreadInboxNotificationsCount: () => useUnreadInboxNotificationsCount_withClient(client),
1425
1622
  useMarkInboxNotificationAsRead: useMarkInboxNotificationAsRead2,
1426
1623
  useMarkAllInboxNotificationsAsRead: useMarkAllInboxNotificationsAsRead2,
@@ -1444,41 +1641,42 @@ function makeLiveblocksContextBundle(client) {
1444
1641
  };
1445
1642
  return bundle;
1446
1643
  }
1447
- function useInboxNotifications_withClient(client) {
1448
- const { loadInboxNotifications, store, startPolling } = getExtrasForClient(client);
1449
- useEffect3(loadInboxNotifications, [loadInboxNotifications]);
1450
- useEffect3(startPolling, [startPolling]);
1644
+ function useInboxNotifications_withClient(client, selector, isEqual) {
1645
+ const {
1646
+ store,
1647
+ subscribeToNotificationsDeltaUpdates: subscribeToDeltaUpdates
1648
+ } = getLiveblocksExtrasForClient(client);
1649
+ useEffect3(() => {
1650
+ void store.waitUntilNotificationsLoaded().catch(() => {
1651
+ });
1652
+ });
1653
+ useEffect3(subscribeToDeltaUpdates, [subscribeToDeltaUpdates]);
1451
1654
  return useSyncExternalStoreWithSelector(
1452
1655
  store.subscribeThreadsOrInboxNotifications,
1453
1656
  store.getInboxNotificationsAsync,
1454
1657
  store.getInboxNotificationsAsync,
1455
- identity,
1456
- shallow3
1658
+ selector,
1659
+ isEqual
1457
1660
  );
1458
1661
  }
1459
1662
  function useInboxNotificationsSuspense_withClient(client) {
1460
- const { waitUntilInboxNotificationsLoaded } = getExtrasForClient(client);
1461
- use(waitUntilInboxNotificationsLoaded());
1462
- const result = useInboxNotifications_withClient(client);
1663
+ const store = getLiveblocksExtrasForClient(client).store;
1664
+ use(store.waitUntilNotificationsLoaded());
1665
+ const result = useInboxNotifications_withClient(client, identity, shallow3);
1463
1666
  assert(!result.error, "Did not expect error");
1464
1667
  assert(!result.isLoading, "Did not expect loading");
1465
1668
  return result;
1466
1669
  }
1467
1670
  function useUnreadInboxNotificationsCount_withClient(client) {
1468
- const { store, loadInboxNotifications, startPolling } = getExtrasForClient(client);
1469
- useEffect3(loadInboxNotifications, [loadInboxNotifications]);
1470
- useEffect3(startPolling, [startPolling]);
1471
- return useSyncExternalStoreWithSelector(
1472
- store.subscribeThreadsOrInboxNotifications,
1473
- store.getInboxNotificationsAsync,
1474
- store.getInboxNotificationsAsync,
1671
+ return useInboxNotifications_withClient(
1672
+ client,
1475
1673
  selectorFor_useUnreadInboxNotificationsCount,
1476
1674
  shallow3
1477
1675
  );
1478
1676
  }
1479
1677
  function useUnreadInboxNotificationsCountSuspense_withClient(client) {
1480
- const { waitUntilInboxNotificationsLoaded } = getExtrasForClient(client);
1481
- use(waitUntilInboxNotificationsLoaded());
1678
+ const store = getLiveblocksExtrasForClient(client).store;
1679
+ use(store.waitUntilNotificationsLoaded());
1482
1680
  const result = useUnreadInboxNotificationsCount_withClient(client);
1483
1681
  assert(!result.isLoading, "Did not expect loading");
1484
1682
  assert(!result.error, "Did not expect error");
@@ -1487,7 +1685,7 @@ function useUnreadInboxNotificationsCountSuspense_withClient(client) {
1487
1685
  function useMarkInboxNotificationAsRead_withClient(client) {
1488
1686
  return useCallback2(
1489
1687
  (inboxNotificationId) => {
1490
- const { store } = getExtrasForClient(client);
1688
+ const { store } = getLiveblocksExtrasForClient(client);
1491
1689
  const readAt = /* @__PURE__ */ new Date();
1492
1690
  const optimisticUpdateId = store.addOptimisticUpdate({
1493
1691
  type: "mark-inbox-notification-as-read",
@@ -1512,7 +1710,7 @@ function useMarkInboxNotificationAsRead_withClient(client) {
1512
1710
  }
1513
1711
  function useMarkAllInboxNotificationsAsRead_withClient(client) {
1514
1712
  return useCallback2(() => {
1515
- const { store } = getExtrasForClient(client);
1713
+ const { store } = getLiveblocksExtrasForClient(client);
1516
1714
  const readAt = /* @__PURE__ */ new Date();
1517
1715
  const optimisticUpdateId = store.addOptimisticUpdate({
1518
1716
  type: "mark-all-inbox-notifications-as-read",
@@ -1534,7 +1732,7 @@ function useMarkAllInboxNotificationsAsRead_withClient(client) {
1534
1732
  function useDeleteInboxNotification_withClient(client) {
1535
1733
  return useCallback2(
1536
1734
  (inboxNotificationId) => {
1537
- const { store } = getExtrasForClient(client);
1735
+ const { store } = getLiveblocksExtrasForClient(client);
1538
1736
  const deletedAt = /* @__PURE__ */ new Date();
1539
1737
  const optimisticUpdateId = store.addOptimisticUpdate({
1540
1738
  type: "delete-inbox-notification",
@@ -1558,7 +1756,7 @@ function useDeleteInboxNotification_withClient(client) {
1558
1756
  }
1559
1757
  function useDeleteAllInboxNotifications_withClient(client) {
1560
1758
  return useCallback2(() => {
1561
- const { store } = getExtrasForClient(client);
1759
+ const { store } = getLiveblocksExtrasForClient(client);
1562
1760
  const deletedAt = /* @__PURE__ */ new Date();
1563
1761
  const optimisticUpdateId = store.addOptimisticUpdate({
1564
1762
  type: "delete-all-inbox-notifications",
@@ -1575,11 +1773,11 @@ function useDeleteAllInboxNotifications_withClient(client) {
1575
1773
  }, [client]);
1576
1774
  }
1577
1775
  function useInboxNotificationThread_withClient(client, inboxNotificationId) {
1578
- const { store } = getExtrasForClient(client);
1776
+ const { store } = getLiveblocksExtrasForClient(client);
1579
1777
  const getter = store.getFullState;
1580
1778
  const selector = useCallback2(
1581
1779
  (state) => {
1582
- const inboxNotification = state.inboxNotificationsById[inboxNotificationId] ?? raise(`Inbox notification with ID "${inboxNotificationId}" not found`);
1780
+ const inboxNotification = state.notificationsById[inboxNotificationId] ?? raise(`Inbox notification with ID "${inboxNotificationId}" not found`);
1583
1781
  if (inboxNotification.kind !== "thread") {
1584
1782
  raise(
1585
1783
  `Inbox notification with ID "${inboxNotificationId}" is not of kind "thread"`
@@ -1601,7 +1799,7 @@ function useInboxNotificationThread_withClient(client, inboxNotificationId) {
1601
1799
  );
1602
1800
  }
1603
1801
  function useUser_withClient(client, userId) {
1604
- const usersStore = client[kInternal].usersStore;
1802
+ const usersStore = client[kInternal2].usersStore;
1605
1803
  const getUserState = useCallback2(
1606
1804
  () => usersStore.getState(userId),
1607
1805
  [usersStore, userId]
@@ -1622,7 +1820,7 @@ function useUser_withClient(client, userId) {
1622
1820
  );
1623
1821
  }
1624
1822
  function useUserSuspense_withClient(client, userId) {
1625
- const usersStore = client[kInternal].usersStore;
1823
+ const usersStore = client[kInternal2].usersStore;
1626
1824
  const getUserState = useCallback2(
1627
1825
  () => usersStore.getState(userId),
1628
1826
  [usersStore, userId]
@@ -1652,7 +1850,7 @@ function useUserSuspense_withClient(client, userId) {
1652
1850
  };
1653
1851
  }
1654
1852
  function useRoomInfo_withClient(client, roomId) {
1655
- const roomsInfoStore = client[kInternal].roomsInfoStore;
1853
+ const roomsInfoStore = client[kInternal2].roomsInfoStore;
1656
1854
  const getRoomInfoState = useCallback2(
1657
1855
  () => roomsInfoStore.getState(roomId),
1658
1856
  [roomsInfoStore, roomId]
@@ -1673,7 +1871,7 @@ function useRoomInfo_withClient(client, roomId) {
1673
1871
  );
1674
1872
  }
1675
1873
  function useRoomInfoSuspense_withClient(client, roomId) {
1676
- const roomsInfoStore = client[kInternal].roomsInfoStore;
1874
+ const roomsInfoStore = client[kInternal2].roomsInfoStore;
1677
1875
  const getRoomInfoState = useCallback2(
1678
1876
  () => roomsInfoStore.getState(roomId),
1679
1877
  [roomsInfoStore, roomId]
@@ -1774,40 +1972,32 @@ function useUserThreads_experimental(options = {
1774
1972
  metadata: {}
1775
1973
  }
1776
1974
  }) {
1777
- const queryKey = React3.useMemo(
1778
- () => makeUserThreadsQueryKey(options.query),
1779
- [options]
1780
- );
1781
1975
  const client = useClient();
1782
- const { store, incrementUserThreadsQuerySubscribers, getUserThreads } = getExtrasForClient(client);
1783
- useEffect3(() => {
1784
- void getUserThreads(queryKey, options);
1785
- return incrementUserThreadsQuerySubscribers(queryKey);
1786
- }, [queryKey, incrementUserThreadsQuerySubscribers, getUserThreads, options]);
1787
- const getter = useCallback2(
1788
- () => store.getUserThreadsAsync(queryKey),
1789
- [store, queryKey]
1790
- );
1791
- const selector = useCallback2(
1792
- (result) => {
1793
- if (!result.fullState) {
1794
- return result;
1795
- }
1796
- const threads = selectThreads(result.fullState, {
1797
- roomId: null,
1798
- // Do _not_ filter by roomId
1799
- query: options.query,
1800
- orderBy: "last-update"
1976
+ const { store, subscribeToUserThreadsDeltaUpdates: subscribeToDeltaUpdates } = getLiveblocksExtrasForClient(client);
1977
+ useEffect3(
1978
+ () => {
1979
+ void store.waitUntilUserThreadsLoaded(options.query).catch(() => {
1801
1980
  });
1802
- return { isLoading: false, threads };
1803
- },
1804
- [options]
1981
+ }
1982
+ // NOTE: Deliberately *not* using a dependency array here!
1983
+ //
1984
+ // It is important to call waitUntil on *every* render.
1985
+ // This is harmless though, on most renders, except:
1986
+ // 1. The very first render, in which case we'll want to trigger the initial page fetch.
1987
+ // 2. All other subsequent renders now "just" return the same promise (a quick operation).
1988
+ // 3. If ever the promise would fail, then after 5 seconds it would reset, and on the very
1989
+ // *next* render after that, a *new* fetch/promise will get created.
1990
+ );
1991
+ useEffect3(subscribeToDeltaUpdates, [subscribeToDeltaUpdates]);
1992
+ const getter = useCallback2(
1993
+ () => store.getUserThreadsAsync(options.query),
1994
+ [store, options.query]
1805
1995
  );
1806
1996
  return useSyncExternalStoreWithSelector(
1807
1997
  store.subscribeUserThreads,
1808
1998
  getter,
1809
1999
  getter,
1810
- selector,
2000
+ identity,
1811
2001
  shallow2
1812
2002
  // NOTE: Using 2-level-deep shallow check here, because the result of selectThreads() is not stable!
1813
2003
  );
@@ -1817,49 +2007,16 @@ function useUserThreadsSuspense_experimental(options = {
1817
2007
  metadata: {}
1818
2008
  }
1819
2009
  }) {
1820
- const queryKey = React3.useMemo(
1821
- () => makeUserThreadsQueryKey(options.query),
1822
- [options]
1823
- );
1824
2010
  const client = useClient();
1825
- const { store, getUserThreads } = getExtrasForClient(client);
1826
- React3.useEffect(() => {
1827
- const { incrementUserThreadsQuerySubscribers } = getExtrasForClient(client);
1828
- return incrementUserThreadsQuerySubscribers(queryKey);
1829
- }, [client, queryKey]);
1830
- const query = store.getFullState().queries2[queryKey];
1831
- if (query === void 0 || query.isLoading) {
1832
- throw getUserThreads(queryKey, options);
1833
- }
1834
- if (query.error) {
1835
- throw query.error;
1836
- }
1837
- const getter = store.getFullState;
1838
- const selector = useCallback2(
1839
- (state) => {
1840
- return {
1841
- threads: selectThreads(state, {
1842
- roomId: null,
1843
- // Do _not_ filter by roomId
1844
- query: options.query,
1845
- orderBy: "last-update"
1846
- }),
1847
- isLoading: false
1848
- };
1849
- },
1850
- [options]
1851
- );
1852
- return useSyncExternalStoreWithSelector(
1853
- store.subscribeUserThreads,
1854
- getter,
1855
- getter,
1856
- selector,
1857
- shallow2
1858
- // NOTE: Using 2-level-deep shallow check here, because the result of selectThreads() is not stable!
1859
- );
2011
+ const { store } = getLiveblocksExtrasForClient(client);
2012
+ use(store.waitUntilUserThreadsLoaded(options.query));
2013
+ const result = useUserThreads_experimental(options);
2014
+ assert(!result.error, "Did not expect error");
2015
+ assert(!result.isLoading, "Did not expect loading");
2016
+ return result;
1860
2017
  }
1861
2018
  function useInboxNotifications() {
1862
- return useInboxNotifications_withClient(useClient());
2019
+ return useInboxNotifications_withClient(useClient(), identity, shallow3);
1863
2020
  }
1864
2021
  function useInboxNotificationsSuspense() {
1865
2022
  return useInboxNotificationsSuspense_withClient(useClient());
@@ -1907,7 +2064,6 @@ var _useUser = useUser;
1907
2064
  var _useUserSuspense = useUserSuspense;
1908
2065
  var _useUserThreads_experimental = useUserThreads_experimental;
1909
2066
  var _useUserThreadsSuspense_experimental = useUserThreadsSuspense_experimental;
1910
- var makeUserThreadsQueryKey = (options) => `${USER_THREADS_QUERY}:${stringify(options)}`;
1911
2067
 
1912
2068
  // src/types/errors.ts
1913
2069
  var CreateThreadError = class extends Error {
@@ -2017,16 +2173,26 @@ import {
2017
2173
  createThreadId,
2018
2174
  deprecateIf,
2019
2175
  errorIf,
2020
- kInternal as kInternal2,
2021
- makeEventSource,
2176
+ kInternal as kInternal3,
2177
+ makeEventSource as makeEventSource2,
2022
2178
  makePoller as makePoller2,
2023
2179
  NotificationsApiError,
2024
- ServerMsgCode,
2025
- stringify as stringify2
2180
+ ServerMsgCode
2026
2181
  } from "@liveblocks/core";
2027
2182
  import * as React5 from "react";
2028
2183
  import { useSyncExternalStoreWithSelector as useSyncExternalStoreWithSelector2 } from "use-sync-external-store/shim/with-selector.js";
2029
2184
 
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
+
2030
2196
  // src/use-scroll-to-comment-on-load-effect.ts
2031
2197
  import * as React4 from "react";
2032
2198
  function handleScrollToCommentOnLoad(shouldScrollOnLoad, state) {
@@ -2058,7 +2224,7 @@ function useScrollToCommentOnLoadEffect(shouldScrollOnLoad, state) {
2058
2224
 
2059
2225
  // src/room.tsx
2060
2226
  var SMOOTH_DELAY = 1e3;
2061
- var noop2 = () => {
2227
+ var noop3 = () => {
2062
2228
  };
2063
2229
  var identity2 = (x) => x;
2064
2230
  var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\u2019re using React ${reactVersion}. Please pass unstable_batchedUpdates at the RoomProvider level until you\u2019re ready to upgrade to React 18:
@@ -2132,6 +2298,28 @@ function handleApiError(err) {
2132
2298
  }
2133
2299
  return new Error(message);
2134
2300
  }
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
+ }
2135
2323
  var _extras2 = /* @__PURE__ */ new WeakMap();
2136
2324
  var _bundles2 = /* @__PURE__ */ new WeakMap();
2137
2325
  function getOrCreateRoomContextBundle(client) {
@@ -2142,83 +2330,22 @@ function getOrCreateRoomContextBundle(client) {
2142
2330
  }
2143
2331
  return bundle;
2144
2332
  }
2145
- function getExtrasForClient2(client) {
2333
+ function getRoomExtrasForClient(client) {
2146
2334
  let extras = _extras2.get(client);
2147
2335
  if (!extras) {
2148
- extras = makeExtrasForClient2(client);
2336
+ extras = makeRoomExtrasForClient(client);
2149
2337
  _extras2.set(client, extras);
2150
2338
  }
2151
2339
  return extras;
2152
2340
  }
2153
- function makeExtrasForClient2(client) {
2341
+ function makeRoomExtrasForClient(client) {
2154
2342
  const store = getUmbrellaStoreForClient(client);
2155
- const DEFAULT_DEDUPING_INTERVAL = 2e3;
2156
- const lastRequestedAtByRoom = /* @__PURE__ */ new Map();
2157
2343
  const requestsByQuery = /* @__PURE__ */ new Map();
2158
- const requestStatusByRoom = /* @__PURE__ */ new Map();
2159
- const subscribersByQuery = /* @__PURE__ */ new Map();
2160
- const poller = makePoller2(refreshThreadsAndNotifications);
2161
- async function refreshThreadsAndNotifications() {
2162
- const requests = [];
2163
- client[kInternal2].getRoomIds().map((roomId) => {
2164
- const room = client.getRoom(roomId);
2165
- if (room === null) return;
2166
- requests.push(getThreadsUpdates(room.id));
2167
- });
2168
- await Promise.allSettled(requests);
2169
- }
2170
- function incrementQuerySubscribers(queryKey) {
2171
- const subscribers = subscribersByQuery.get(queryKey) ?? 0;
2172
- subscribersByQuery.set(queryKey, subscribers + 1);
2173
- poller.start(POLLING_INTERVAL2);
2174
- return () => {
2175
- const subscribers2 = subscribersByQuery.get(queryKey);
2176
- if (subscribers2 === void 0 || subscribers2 <= 0) {
2177
- console3.warn(
2178
- `Internal unexpected behavior. Cannot decrease subscriber count for query "${queryKey}"`
2179
- );
2180
- return;
2181
- }
2182
- subscribersByQuery.set(queryKey, subscribers2 - 1);
2183
- let totalSubscribers = 0;
2184
- for (const subscribers3 of subscribersByQuery.values()) {
2185
- totalSubscribers += subscribers3;
2186
- }
2187
- if (totalSubscribers <= 0) {
2188
- poller.stop();
2189
- }
2190
- };
2191
- }
2192
- async function getThreadsUpdates(roomId) {
2193
- const room = client.getRoom(roomId);
2194
- if (room === null) return;
2195
- const since = lastRequestedAtByRoom.get(room.id);
2196
- if (since === void 0) return;
2197
- const isFetchingThreadsUpdates = requestStatusByRoom.get(room.id) ?? false;
2198
- if (isFetchingThreadsUpdates === true) return;
2199
- try {
2200
- requestStatusByRoom.set(room.id, true);
2201
- const updates = await room.getThreadsSince({ since });
2202
- setTimeout(() => {
2203
- requestStatusByRoom.set(room.id, false);
2204
- }, DEFAULT_DEDUPING_INTERVAL);
2205
- store.updateThreadsAndNotifications(
2206
- updates.threads.updated,
2207
- updates.inboxNotifications.updated,
2208
- updates.threads.deleted,
2209
- updates.inboxNotifications.deleted
2210
- );
2211
- lastRequestedAtByRoom.set(room.id, updates.requestedAt);
2212
- } catch (err) {
2213
- requestStatusByRoom.set(room.id, false);
2214
- return;
2215
- }
2216
- }
2217
2344
  async function getRoomVersions(room, { retryCount } = { retryCount: 0 }) {
2218
2345
  const queryKey = makeVersionsQueryKey(room.id);
2219
2346
  const existingRequest = requestsByQuery.get(queryKey);
2220
2347
  if (existingRequest !== void 0) return existingRequest;
2221
- const request = room[kInternal2].listTextVersions();
2348
+ const request = room[kInternal3].listTextVersions();
2222
2349
  requestsByQuery.set(queryKey, request);
2223
2350
  store.setQuery4Loading(queryKey);
2224
2351
  try {
@@ -2243,40 +2370,6 @@ function makeExtrasForClient2(client) {
2243
2370
  }
2244
2371
  return;
2245
2372
  }
2246
- async function getThreadsAndInboxNotifications(room, queryKey, options, { retryCount } = { retryCount: 0 }) {
2247
- const existingRequest = requestsByQuery.get(queryKey);
2248
- if (existingRequest !== void 0) return existingRequest;
2249
- const request = room.getThreads(options);
2250
- requestsByQuery.set(queryKey, request);
2251
- store.setQuery2Loading(queryKey);
2252
- try {
2253
- const result = await request;
2254
- store.batch(() => {
2255
- store.updateThreadsAndNotifications(
2256
- result.threads,
2257
- // TODO: Figure out how to remove this casting
2258
- result.inboxNotifications,
2259
- [],
2260
- []
2261
- );
2262
- store.setQuery2OK(queryKey);
2263
- });
2264
- const lastRequestedAt = lastRequestedAtByRoom.get(room.id);
2265
- if (lastRequestedAt === void 0 || lastRequestedAt > result.requestedAt) {
2266
- lastRequestedAtByRoom.set(room.id, result.requestedAt);
2267
- }
2268
- poller.start(POLLING_INTERVAL2);
2269
- } catch (err) {
2270
- requestsByQuery.delete(queryKey);
2271
- retryError(() => {
2272
- void getThreadsAndInboxNotifications(room, queryKey, options, {
2273
- retryCount: retryCount + 1
2274
- });
2275
- }, retryCount);
2276
- store.setQuery2Error(queryKey, err);
2277
- }
2278
- return;
2279
- }
2280
2373
  async function getInboxNotificationSettings(room, { retryCount } = { retryCount: 0 }) {
2281
2374
  const queryKey = makeNotificationSettingsQueryKey(room.id);
2282
2375
  const existingRequest = requestsByQuery.get(queryKey);
@@ -2298,7 +2391,7 @@ function makeExtrasForClient2(client) {
2298
2391
  }
2299
2392
  return;
2300
2393
  }
2301
- const commentsErrorEventSource = makeEventSource();
2394
+ const commentsErrorEventSource = makeEventSource2();
2302
2395
  function onMutationFailure(innerError, optimisticUpdateId, createPublicError) {
2303
2396
  store.removeOptimisticUpdate(optimisticUpdateId);
2304
2397
  if (innerError instanceof CommentsApiError) {
@@ -2314,10 +2407,8 @@ function makeExtrasForClient2(client) {
2314
2407
  }
2315
2408
  return {
2316
2409
  store,
2317
- incrementQuerySubscribers,
2410
+ subscribeToRoomThreadsDeltaUpdates: makeDeltaPoller_RoomThreads(client),
2318
2411
  commentsErrorEventSource,
2319
- getThreadsUpdates,
2320
- getThreadsAndInboxNotifications,
2321
2412
  getInboxNotificationSettings,
2322
2413
  getRoomVersions,
2323
2414
  onMutationFailure
@@ -2423,7 +2514,7 @@ function makeRoomContextBundle(client) {
2423
2514
  },
2424
2515
  useCommentsErrorListener
2425
2516
  };
2426
- return Object.defineProperty(bundle, kInternal2, {
2517
+ return Object.defineProperty(bundle, kInternal3, {
2427
2518
  enumerable: false
2428
2519
  });
2429
2520
  }
@@ -2492,7 +2583,7 @@ function RoomProviderInner(props) {
2492
2583
  })
2493
2584
  );
2494
2585
  React5.useEffect(() => {
2495
- const { store } = getExtrasForClient2(client);
2586
+ const { store } = getRoomExtrasForClient(client);
2496
2587
  async function handleCommentEvent(message) {
2497
2588
  if (message.type === ServerMsgCode.THREAD_DELETED) {
2498
2589
  store.deleteThread(message.threadId, null);
@@ -2527,13 +2618,15 @@ function RoomProviderInner(props) {
2527
2618
  );
2528
2619
  }, [client, room]);
2529
2620
  React5.useEffect(() => {
2530
- const { getThreadsUpdates } = getExtrasForClient2(client);
2531
- void getThreadsUpdates(room.id);
2621
+ const store = getRoomExtrasForClient(client).store;
2622
+ void store.fetchRoomThreadsDeltaUpdate(room.id).catch(() => {
2623
+ });
2532
2624
  }, [client, room.id]);
2533
2625
  React5.useEffect(() => {
2534
2626
  function handleIsOnline() {
2535
- const { getThreadsUpdates } = getExtrasForClient2(client);
2536
- void getThreadsUpdates(room.id);
2627
+ const store = getRoomExtrasForClient(client).store;
2628
+ void store.fetchRoomThreadsDeltaUpdate(room.id).catch(() => {
2629
+ });
2537
2630
  }
2538
2631
  window.addEventListener("online", handleIsOnline);
2539
2632
  return () => {
@@ -2779,7 +2872,7 @@ function useStorage(selector, isEqual) {
2779
2872
  [selector]
2780
2873
  );
2781
2874
  const subscribe = React5.useCallback(
2782
- (onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop2,
2875
+ (onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop3,
2783
2876
  [room, rootOrNull]
2784
2877
  );
2785
2878
  const getSnapshot = React5.useCallback(() => {
@@ -2827,40 +2920,33 @@ function useThreads(options = {
2827
2920
  const { scrollOnLoad = true } = options;
2828
2921
  const client = useClient();
2829
2922
  const room = useRoom();
2830
- const queryKey = React5.useMemo(
2831
- () => generateQueryKey(room.id, options.query),
2832
- [room, options]
2833
- );
2834
- const { store, getThreadsAndInboxNotifications, incrementQuerySubscribers } = getExtrasForClient2(client);
2835
- React5.useEffect(() => {
2836
- void getThreadsAndInboxNotifications(room, queryKey, options);
2837
- return incrementQuerySubscribers(queryKey);
2838
- }, [room, queryKey]);
2839
- const getter = React5.useCallback(
2840
- () => store.getThreadsAsync(queryKey),
2841
- [store, queryKey]
2842
- );
2843
- const selector = React5.useCallback(
2844
- (result) => {
2845
- if (!result.fullState) {
2846
- return result;
2847
- }
2848
- const threads = selectThreads(result.fullState, {
2849
- roomId: room.id,
2850
- query: options.query,
2851
- orderBy: "age"
2923
+ const { store, subscribeToRoomThreadsDeltaUpdates: subscribeToDeltaUpdates } = getRoomExtrasForClient(client);
2924
+ React5.useEffect(
2925
+ () => {
2926
+ void store.waitUntilRoomThreadsLoaded(room.id, options.query).catch(() => {
2852
2927
  });
2853
- return { isLoading: false, threads };
2854
- },
2855
- [room.id, queryKey]
2856
- // eslint-disable-line react-hooks/exhaustive-deps
2928
+ }
2929
+ // NOTE: Deliberately *not* using a dependency array here!
2930
+ //
2931
+ // It is important to call waitUntil on *every* render.
2932
+ // This is harmless though, on most renders, except:
2933
+ // 1. The very first render, in which case we'll want to trigger the initial page fetch.
2934
+ // 2. All other subsequent renders now "just" return the same promise (a quick operation).
2935
+ // 3. If ever the promise would fail, then after 5 seconds it would reset, and on the very
2936
+ // *next* render after that, a *new* fetch/promise will get created.
2937
+ );
2938
+ React5.useEffect(subscribeToDeltaUpdates, [subscribeToDeltaUpdates]);
2939
+ const getter = React5.useCallback(
2940
+ () => store.getRoomThreadsAsync(room.id, options.query),
2941
+ [store, room.id, options.query]
2857
2942
  );
2858
2943
  const state = useSyncExternalStoreWithSelector2(
2859
2944
  store.subscribeThreads,
2860
2945
  getter,
2861
2946
  getter,
2862
- selector,
2947
+ identity2,
2863
2948
  shallow2
2949
+ // NOTE: Using 2-level-deep shallow check here, because the result of selectThreads() is not stable!
2864
2950
  );
2865
2951
  useScrollToCommentOnLoadEffect(scrollOnLoad, state);
2866
2952
  return state;
@@ -2868,7 +2954,7 @@ function useThreads(options = {
2868
2954
  function useCommentsErrorListener(callback) {
2869
2955
  const client = useClient();
2870
2956
  const savedCallback = useLatest(callback);
2871
- const { commentsErrorEventSource } = getExtrasForClient2(client);
2957
+ const { commentsErrorEventSource } = getRoomExtrasForClient(client);
2872
2958
  React5.useEffect(() => {
2873
2959
  return commentsErrorEventSource.subscribe(savedCallback.current);
2874
2960
  }, [savedCallback, commentsErrorEventSource]);
@@ -2905,7 +2991,7 @@ function useCreateThread() {
2905
2991
  comments: [newComment],
2906
2992
  resolved: false
2907
2993
  };
2908
- const { store, onMutationFailure } = getExtrasForClient2(client);
2994
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
2909
2995
  const optimisticUpdateId = store.addOptimisticUpdate({
2910
2996
  type: "create-thread",
2911
2997
  thread: newThread,
@@ -2938,7 +3024,7 @@ function useDeleteThread() {
2938
3024
  const room = useRoom();
2939
3025
  return React5.useCallback(
2940
3026
  (threadId) => {
2941
- const { store, onMutationFailure } = getExtrasForClient2(client);
3027
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
2942
3028
  const thread = store.getFullState().threadsById[threadId];
2943
3029
  const userId = getCurrentUserId(room);
2944
3030
  if (thread?.comments?.[0]?.userId !== userId) {
@@ -2975,7 +3061,7 @@ function useEditThreadMetadata() {
2975
3061
  const threadId = options.threadId;
2976
3062
  const metadata = options.metadata;
2977
3063
  const updatedAt = /* @__PURE__ */ new Date();
2978
- const { store, onMutationFailure } = getExtrasForClient2(client);
3064
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
2979
3065
  const optimisticUpdateId = store.addOptimisticUpdate({
2980
3066
  type: "edit-thread-metadata",
2981
3067
  metadata,
@@ -3024,7 +3110,7 @@ function useCreateComment() {
3024
3110
  reactions: [],
3025
3111
  attachments: attachments ?? []
3026
3112
  };
3027
- const { store, onMutationFailure } = getExtrasForClient2(client);
3113
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
3028
3114
  const optimisticUpdateId = store.addOptimisticUpdate({
3029
3115
  type: "create-comment",
3030
3116
  comment
@@ -3056,7 +3142,7 @@ function useEditComment() {
3056
3142
  return React5.useCallback(
3057
3143
  ({ threadId, commentId, body, attachments }) => {
3058
3144
  const editedAt = /* @__PURE__ */ new Date();
3059
- const { store, onMutationFailure } = getExtrasForClient2(client);
3145
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
3060
3146
  const thread = store.getFullState().threadsById[threadId];
3061
3147
  if (thread === void 0) {
3062
3148
  console3.warn(
@@ -3108,7 +3194,7 @@ function useDeleteComment() {
3108
3194
  return React5.useCallback(
3109
3195
  ({ threadId, commentId }) => {
3110
3196
  const deletedAt = /* @__PURE__ */ new Date();
3111
- const { store, onMutationFailure } = getExtrasForClient2(client);
3197
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
3112
3198
  const optimisticUpdateId = store.addOptimisticUpdate({
3113
3199
  type: "delete-comment",
3114
3200
  threadId,
@@ -3146,7 +3232,7 @@ function useAddReaction() {
3146
3232
  ({ threadId, commentId, emoji }) => {
3147
3233
  const createdAt = /* @__PURE__ */ new Date();
3148
3234
  const userId = getCurrentUserId(room);
3149
- const { store, onMutationFailure } = getExtrasForClient2(client);
3235
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
3150
3236
  const optimisticUpdateId = store.addOptimisticUpdate({
3151
3237
  type: "add-reaction",
3152
3238
  threadId,
@@ -3189,7 +3275,7 @@ function useRemoveReaction() {
3189
3275
  ({ threadId, commentId, emoji }) => {
3190
3276
  const userId = getCurrentUserId(room);
3191
3277
  const removedAt = /* @__PURE__ */ new Date();
3192
- const { store, onMutationFailure } = getExtrasForClient2(client);
3278
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
3193
3279
  const optimisticUpdateId = store.addOptimisticUpdate({
3194
3280
  type: "remove-reaction",
3195
3281
  threadId,
@@ -3229,9 +3315,9 @@ function useMarkThreadAsRead() {
3229
3315
  const room = useRoom();
3230
3316
  return React5.useCallback(
3231
3317
  (threadId) => {
3232
- const { store, onMutationFailure } = getExtrasForClient2(client);
3318
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
3233
3319
  const inboxNotification = Object.values(
3234
- store.getFullState().inboxNotificationsById
3320
+ store.getFullState().notificationsById
3235
3321
  ).find(
3236
3322
  (inboxNotification2) => inboxNotification2.kind === "thread" && inboxNotification2.threadId === threadId
3237
3323
  );
@@ -3271,7 +3357,7 @@ function useMarkThreadAsResolved() {
3271
3357
  return React5.useCallback(
3272
3358
  (threadId) => {
3273
3359
  const updatedAt = /* @__PURE__ */ new Date();
3274
- const { store, onMutationFailure } = getExtrasForClient2(client);
3360
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
3275
3361
  const optimisticUpdateId = store.addOptimisticUpdate({
3276
3362
  type: "mark-thread-as-resolved",
3277
3363
  threadId,
@@ -3305,7 +3391,7 @@ function useMarkThreadAsUnresolved() {
3305
3391
  return React5.useCallback(
3306
3392
  (threadId) => {
3307
3393
  const updatedAt = /* @__PURE__ */ new Date();
3308
- const { store, onMutationFailure } = getExtrasForClient2(client);
3394
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
3309
3395
  const optimisticUpdateId = store.addOptimisticUpdate({
3310
3396
  type: "mark-thread-as-unresolved",
3311
3397
  threadId,
@@ -3335,10 +3421,10 @@ function useMarkThreadAsUnresolved() {
3335
3421
  }
3336
3422
  function useThreadSubscription(threadId) {
3337
3423
  const client = useClient();
3338
- const { store } = getExtrasForClient2(client);
3424
+ const { store } = getRoomExtrasForClient(client);
3339
3425
  const selector = React5.useCallback(
3340
3426
  (state) => {
3341
- const inboxNotification = state.inboxNotifications.find(
3427
+ const inboxNotification = state.notifications.find(
3342
3428
  (inboxNotification2) => inboxNotification2.kind === "thread" && inboxNotification2.threadId === threadId
3343
3429
  );
3344
3430
  const thread = state.threadsById[threadId];
@@ -3365,13 +3451,13 @@ function useRoomNotificationSettings() {
3365
3451
  const updateRoomNotificationSettings = useUpdateRoomNotificationSettings();
3366
3452
  const client = useClient();
3367
3453
  const room = useRoom();
3368
- const { store } = getExtrasForClient2(client);
3454
+ const { store } = getRoomExtrasForClient(client);
3369
3455
  const getter = React5.useCallback(
3370
3456
  () => store.getNotificationSettingsAsync(room.id),
3371
3457
  [store, room.id]
3372
3458
  );
3373
3459
  React5.useEffect(() => {
3374
- const { getInboxNotificationSettings } = getExtrasForClient2(client);
3460
+ const { getInboxNotificationSettings } = getRoomExtrasForClient(client);
3375
3461
  void getInboxNotificationSettings(room);
3376
3462
  }, [client, room]);
3377
3463
  const settings = useSyncExternalStoreWithSelector2(
@@ -3389,7 +3475,7 @@ function useRoomNotificationSettingsSuspense() {
3389
3475
  const updateRoomNotificationSettings = useUpdateRoomNotificationSettings();
3390
3476
  const client = useClient();
3391
3477
  const room = useRoom();
3392
- const { store } = getExtrasForClient2(client);
3478
+ const { store } = getRoomExtrasForClient(client);
3393
3479
  const getter = React5.useCallback(
3394
3480
  () => store.getNotificationSettingsAsync(room.id),
3395
3481
  [store, room.id]
@@ -3402,7 +3488,7 @@ function useRoomNotificationSettingsSuspense() {
3402
3488
  shallow4
3403
3489
  );
3404
3490
  if (settings.isLoading) {
3405
- const { getInboxNotificationSettings } = getExtrasForClient2(client);
3491
+ const { getInboxNotificationSettings } = getRoomExtrasForClient(client);
3406
3492
  throw getInboxNotificationSettings(room);
3407
3493
  } else if (settings.error) {
3408
3494
  throw settings.error;
@@ -3420,7 +3506,7 @@ function useHistoryVersionData(versionId) {
3420
3506
  setState({ isLoading: true });
3421
3507
  const load = async () => {
3422
3508
  try {
3423
- const response = await room[kInternal2].getTextVersion(versionId);
3509
+ const response = await room[kInternal3].getTextVersion(versionId);
3424
3510
  const buffer = await response.arrayBuffer();
3425
3511
  const data = new Uint8Array(buffer);
3426
3512
  setState({
@@ -3443,7 +3529,7 @@ function useHistoryVersionData(versionId) {
3443
3529
  function useHistoryVersions() {
3444
3530
  const client = useClient();
3445
3531
  const room = useRoom();
3446
- const { store, getRoomVersions } = getExtrasForClient2(client);
3532
+ const { store, getRoomVersions } = getRoomExtrasForClient(client);
3447
3533
  const getter = React5.useCallback(
3448
3534
  () => store.getVersionsAsync(room.id),
3449
3535
  [store, room.id]
@@ -3463,7 +3549,7 @@ function useHistoryVersions() {
3463
3549
  function useHistoryVersionsSuspense() {
3464
3550
  const client = useClient();
3465
3551
  const room = useRoom();
3466
- const { store } = getExtrasForClient2(client);
3552
+ const { store } = getRoomExtrasForClient(client);
3467
3553
  const getter = React5.useCallback(
3468
3554
  () => store.getVersionsAsync(room.id),
3469
3555
  [store, room.id]
@@ -3476,7 +3562,7 @@ function useHistoryVersionsSuspense() {
3476
3562
  shallow4
3477
3563
  );
3478
3564
  if (state.isLoading) {
3479
- const { getRoomVersions } = getExtrasForClient2(client);
3565
+ const { getRoomVersions } = getRoomExtrasForClient(client);
3480
3566
  throw getRoomVersions(room);
3481
3567
  } else if (state.error) {
3482
3568
  throw state.error;
@@ -3488,7 +3574,7 @@ function useUpdateRoomNotificationSettings() {
3488
3574
  const room = useRoom();
3489
3575
  return React5.useCallback(
3490
3576
  (settings) => {
3491
- const { store, onMutationFailure } = getExtrasForClient2(client);
3577
+ const { store, onMutationFailure } = getRoomExtrasForClient(client);
3492
3578
  const optimisticUpdateId = store.addOptimisticUpdate({
3493
3579
  type: "update-notification-settings",
3494
3580
  roomId: room.id,
@@ -3571,47 +3657,14 @@ function useStorageStatusSuspense(options) {
3571
3657
  function useThreadsSuspense(options = {
3572
3658
  query: { metadata: {} }
3573
3659
  }) {
3574
- const { scrollOnLoad = true } = options;
3575
3660
  const client = useClient();
3576
3661
  const room = useRoom();
3577
- const queryKey = React5.useMemo(
3578
- () => generateQueryKey(room.id, options.query),
3579
- [room, options]
3580
- );
3581
- const { store, getThreadsAndInboxNotifications } = getExtrasForClient2(client);
3582
- const query = store.getFullState().queries2[queryKey];
3583
- if (query === void 0 || query.isLoading) {
3584
- throw getThreadsAndInboxNotifications(room, queryKey, options);
3585
- }
3586
- if (query.error) {
3587
- throw query.error;
3588
- }
3589
- const selector = React5.useCallback(
3590
- (state2) => {
3591
- return {
3592
- threads: selectThreads(state2, {
3593
- roomId: room.id,
3594
- query: options.query,
3595
- orderBy: "age"
3596
- }),
3597
- isLoading: false
3598
- };
3599
- },
3600
- [room, queryKey]
3601
- // eslint-disable-line react-hooks/exhaustive-deps
3602
- );
3603
- React5.useEffect(() => {
3604
- const { incrementQuerySubscribers } = getExtrasForClient2(client);
3605
- return incrementQuerySubscribers(queryKey);
3606
- }, [client, queryKey]);
3607
- const state = useSyncExternalStoreWithSelector2(
3608
- store.subscribeThreads,
3609
- store.getFullState,
3610
- store.getFullState,
3611
- selector
3612
- );
3613
- useScrollToCommentOnLoadEffect(scrollOnLoad, state);
3614
- return state;
3662
+ const { store } = getRoomExtrasForClient(client);
3663
+ use(store.waitUntilRoomThreadsLoaded(room.id, options.query));
3664
+ const result = useThreads(options);
3665
+ assert2(!result.error, "Did not expect error");
3666
+ assert2(!result.isLoading, "Did not expect loading");
3667
+ return result;
3615
3668
  }
3616
3669
  function selectorFor_useAttachmentUrl(state) {
3617
3670
  if (state === void 0 || state?.isLoading) {
@@ -3628,7 +3681,7 @@ function selectorFor_useAttachmentUrl(state) {
3628
3681
  }
3629
3682
  function useAttachmentUrl(attachmentId) {
3630
3683
  const room = useRoom();
3631
- const { attachmentUrlsStore } = room[kInternal2];
3684
+ const { attachmentUrlsStore } = room[kInternal3];
3632
3685
  const getAttachmentUrlState = React5.useCallback(
3633
3686
  () => attachmentUrlsStore.getState(attachmentId),
3634
3687
  [attachmentUrlsStore, attachmentId]
@@ -3646,7 +3699,7 @@ function useAttachmentUrl(attachmentId) {
3646
3699
  }
3647
3700
  function useAttachmentUrlSuspense(attachmentId) {
3648
3701
  const room = useRoom();
3649
- const { attachmentUrlsStore } = room[kInternal2];
3702
+ const { attachmentUrlsStore } = room[kInternal3];
3650
3703
  const getAttachmentUrlState = React5.useCallback(
3651
3704
  () => attachmentUrlsStore.getState(attachmentId),
3652
3705
  [attachmentUrlsStore, attachmentId]
@@ -3675,9 +3728,6 @@ function useAttachmentUrlSuspense(attachmentId) {
3675
3728
  function createRoomContext(client) {
3676
3729
  return getOrCreateRoomContextBundle(client);
3677
3730
  }
3678
- function generateQueryKey(roomId, options) {
3679
- return `${roomId}-${stringify2(options ?? {})}`;
3680
- }
3681
3731
  var _RoomProvider = RoomProvider;
3682
3732
  var _useBroadcastEvent = useBroadcastEvent;
3683
3733
  var _useOthersListener = useOthersListener;
@@ -3721,8 +3771,8 @@ export {
3721
3771
  PKG_FORMAT,
3722
3772
  ClientSideSuspense,
3723
3773
  RoomContext,
3724
- ClientContext,
3725
3774
  selectThreads,
3775
+ ClientContext,
3726
3776
  getUmbrellaStoreForClient,
3727
3777
  useClient,
3728
3778
  LiveblocksProvider,
@@ -3800,4 +3850,4 @@ export {
3800
3850
  _useStorageRoot,
3801
3851
  _useUpdateMyPresence
3802
3852
  };
3803
- //# sourceMappingURL=chunk-464Q66DF.mjs.map
3853
+ //# sourceMappingURL=chunk-BNSPCUSO.mjs.map