@liveblocks/core 1.10.0-beta2 → 1.10.0-beta4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/version.ts
8
8
  var PKG_NAME = "@liveblocks/core";
9
- var PKG_VERSION = "1.10.0-beta2";
9
+ var PKG_VERSION = "1.10.0-beta4";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -362,23 +362,29 @@ var FSM = class {
362
362
  }
363
363
  onEnterAsync(nameOrPattern, promiseFn, onOK, onError) {
364
364
  return this.onEnter(nameOrPattern, () => {
365
- let cancelled = false;
366
- void promiseFn(this.currentContext.current).then(
365
+ const abortController = new AbortController();
366
+ const signal = abortController.signal;
367
+ let done = false;
368
+ void promiseFn(this.currentContext.current, signal).then(
367
369
  // On OK
368
370
  (data) => {
369
- if (!cancelled) {
371
+ if (!signal.aborted) {
372
+ done = true;
370
373
  this.transition({ type: "ASYNC_OK", data }, onOK);
371
374
  }
372
375
  },
373
376
  // On Error
374
377
  (reason) => {
375
- if (!cancelled) {
378
+ if (!signal.aborted) {
379
+ done = true;
376
380
  this.transition({ type: "ASYNC_ERROR", reason }, onError);
377
381
  }
378
382
  }
379
383
  );
380
384
  return () => {
381
- cancelled = true;
385
+ if (!done) {
386
+ abortController.abort();
387
+ }
382
388
  };
383
389
  });
384
390
  }
@@ -652,6 +658,7 @@ var ServerMsgCode = /* @__PURE__ */ ((ServerMsgCode2) => {
652
658
 
653
659
  // src/types/IWebSocket.ts
654
660
  var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
661
+ WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_NORMAL"] = 1e3] = "CLOSE_NORMAL";
655
662
  WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
656
663
  WebsocketCloseCodes2[WebsocketCloseCodes2["UNEXPECTED_CONDITION"] = 1011] = "UNEXPECTED_CONDITION";
657
664
  WebsocketCloseCodes2[WebsocketCloseCodes2["TRY_AGAIN_LATER"] = 1013] = "TRY_AGAIN_LATER";
@@ -661,6 +668,7 @@ var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
661
668
  WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
662
669
  WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
663
670
  WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
671
+ WebsocketCloseCodes2[WebsocketCloseCodes2["KICKED"] = 4100] = "KICKED";
664
672
  WebsocketCloseCodes2[WebsocketCloseCodes2["TOKEN_EXPIRED"] = 4109] = "TOKEN_EXPIRED";
665
673
  WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
666
674
  return WebsocketCloseCodes2;
@@ -728,6 +736,7 @@ var StopRetrying = class extends Error {
728
736
  }
729
737
  };
730
738
  var LiveblocksError = class extends Error {
739
+ /** @internal */
731
740
  constructor(message, code) {
732
741
  super(message);
733
742
  this.code = code;
@@ -942,14 +951,16 @@ function createConnectionStateMachine(delegates, options) {
942
951
  // When the "open" event happens, we're ready to transition to the
943
952
  // OK state. This is done by resolving the Promise.
944
953
  //
945
- async (ctx) => {
954
+ async (ctx, signal) => {
946
955
  let capturedPrematureEvent = null;
956
+ let unconfirmedSocket = null;
947
957
  const connect$ = new Promise(
948
958
  (resolve, rej) => {
949
959
  if (ctx.authValue === null) {
950
960
  throw new Error("No auth authValue");
951
961
  }
952
962
  const socket = delegates.createSocket(ctx.authValue);
963
+ unconfirmedSocket = socket;
953
964
  function reject(event) {
954
965
  capturedPrematureEvent = event;
955
966
  socket.removeEventListener("message", onSocketMessage);
@@ -1005,12 +1016,18 @@ function createConnectionStateMachine(delegates, options) {
1005
1016
  //
1006
1017
  ([socket, unsub]) => {
1007
1018
  unsub();
1019
+ if (signal.aborted) {
1020
+ throw new Error("Aborted");
1021
+ }
1008
1022
  if (capturedPrematureEvent) {
1009
1023
  throw capturedPrematureEvent;
1010
1024
  }
1011
1025
  return socket;
1012
1026
  }
1013
- );
1027
+ ).catch((e) => {
1028
+ teardownSocket(unconfirmedSocket);
1029
+ throw e;
1030
+ });
1014
1031
  },
1015
1032
  // Only transition to OK state after a successfully opened WebSocket connection
1016
1033
  (okEvent) => ({
@@ -1325,7 +1342,7 @@ function createAuthManager(authOptions) {
1325
1342
  }
1326
1343
  return false;
1327
1344
  }
1328
- function getCachedToken(roomOptions) {
1345
+ function getCachedToken(requestOptions) {
1329
1346
  const now = Math.ceil(Date.now() / 1e3);
1330
1347
  for (let i = tokens.length - 1; i >= 0; i--) {
1331
1348
  const token = tokens[i];
@@ -1338,11 +1355,15 @@ function createAuthManager(authOptions) {
1338
1355
  if (token.parsed.k === "id" /* ID_TOKEN */) {
1339
1356
  return token;
1340
1357
  } else if (token.parsed.k === "acc" /* ACCESS_TOKEN */) {
1341
- if (!roomOptions) {
1358
+ if (!requestOptions.roomId && Object.entries(token.parsed.perms).length === 0) {
1342
1359
  return token;
1343
1360
  }
1344
1361
  for (const [resource, scopes] of Object.entries(token.parsed.perms)) {
1345
- if (resource.includes("*") && roomOptions.roomId.startsWith(resource.replace("*", "")) || roomOptions.roomId === resource && hasCorrespondingScopes(roomOptions.requestedScope, scopes)) {
1362
+ if (!requestOptions.roomId) {
1363
+ if (resource.includes("*") && hasCorrespondingScopes(requestOptions.requestedScope, scopes)) {
1364
+ return token;
1365
+ }
1366
+ } else if (resource.includes("*") && requestOptions.roomId.startsWith(resource.replace("*", "")) || requestOptions.roomId === resource && hasCorrespondingScopes(requestOptions.requestedScope, scopes)) {
1346
1367
  return token;
1347
1368
  }
1348
1369
  }
@@ -1350,7 +1371,7 @@ function createAuthManager(authOptions) {
1350
1371
  }
1351
1372
  return void 0;
1352
1373
  }
1353
- async function makeAuthRequest(roomId) {
1374
+ async function makeAuthRequest(options) {
1354
1375
  const fetcher = authOptions.polyfills?.fetch ?? (typeof window === "undefined" ? void 0 : window.fetch);
1355
1376
  if (authentication.type === "private") {
1356
1377
  if (fetcher === void 0) {
@@ -1359,9 +1380,10 @@ function createAuthManager(authOptions) {
1359
1380
  );
1360
1381
  }
1361
1382
  const response = await fetchAuthEndpoint(fetcher, authentication.url, {
1362
- room: roomId
1383
+ room: options.roomId
1363
1384
  });
1364
1385
  const parsed = parseAuthToken(response.token);
1386
+ verifyTokenPermissions(parsed, options);
1365
1387
  if (seenTokens.has(parsed.raw)) {
1366
1388
  throw new StopRetrying(
1367
1389
  "The same Liveblocks auth token was issued from the backend before. Caching Liveblocks tokens is not supported."
@@ -1370,10 +1392,12 @@ function createAuthManager(authOptions) {
1370
1392
  return parsed;
1371
1393
  }
1372
1394
  if (authentication.type === "custom") {
1373
- const response = await authentication.callback(roomId);
1395
+ const response = await authentication.callback(options.roomId);
1374
1396
  if (response && typeof response === "object") {
1375
1397
  if (typeof response.token === "string") {
1376
- return parseAuthToken(response.token);
1398
+ const parsed = parseAuthToken(response.token);
1399
+ verifyTokenPermissions(parsed, options);
1400
+ return parsed;
1377
1401
  } else if (typeof response.error === "string") {
1378
1402
  const reason = `Authentication failed: ${"reason" in response && typeof response.reason === "string" ? response.reason : "Forbidden"}`;
1379
1403
  if (response.error === "forbidden") {
@@ -1391,25 +1415,42 @@ function createAuthManager(authOptions) {
1391
1415
  "Unexpected authentication type. Must be private or custom."
1392
1416
  );
1393
1417
  }
1394
- async function getAuthValue(roomOptions) {
1418
+ function verifyTokenPermissions(parsedToken, options) {
1419
+ if (!options.roomId && parsedToken.parsed.k === "acc" /* ACCESS_TOKEN */) {
1420
+ if (Object.entries(parsedToken.parsed.perms).length === 0) {
1421
+ return;
1422
+ }
1423
+ for (const [resource, scopes] of Object.entries(
1424
+ parsedToken.parsed.perms
1425
+ )) {
1426
+ if (resource.includes("*") && hasCorrespondingScopes(options.requestedScope, scopes)) {
1427
+ return;
1428
+ }
1429
+ }
1430
+ throw new StopRetrying(
1431
+ "The issued access token doesn't grant enough permissions. Please follow the instructions at https://liveblocks.io/docs/errors/liveblocks-client/access-tokens-not-enough-permissions"
1432
+ );
1433
+ }
1434
+ }
1435
+ async function getAuthValue(requestOptions) {
1395
1436
  if (authentication.type === "public") {
1396
1437
  return { type: "public", publicApiKey: authentication.publicApiKey };
1397
1438
  }
1398
- const cachedToken = getCachedToken(roomOptions);
1439
+ const cachedToken = getCachedToken(requestOptions);
1399
1440
  if (cachedToken !== void 0) {
1400
1441
  return { type: "secret", token: cachedToken };
1401
1442
  }
1402
1443
  let currentPromise;
1403
- if (roomOptions) {
1404
- currentPromise = requestPromises.get(roomOptions.roomId);
1444
+ if (requestOptions.roomId) {
1445
+ currentPromise = requestPromises.get(requestOptions.roomId);
1405
1446
  if (currentPromise === void 0) {
1406
- currentPromise = makeAuthRequest(roomOptions.roomId);
1407
- requestPromises.set(roomOptions.roomId, currentPromise);
1447
+ currentPromise = makeAuthRequest(requestOptions);
1448
+ requestPromises.set(requestOptions.roomId, currentPromise);
1408
1449
  }
1409
1450
  } else {
1410
1451
  currentPromise = requestPromises.get("liveblocks-user-token");
1411
1452
  if (currentPromise === void 0) {
1412
- currentPromise = makeAuthRequest();
1453
+ currentPromise = makeAuthRequest(requestOptions);
1413
1454
  requestPromises.set("liveblocks-user-token", currentPromise);
1414
1455
  }
1415
1456
  }
@@ -1424,8 +1465,8 @@ function createAuthManager(authOptions) {
1424
1465
  }
1425
1466
  return { type: "secret", token };
1426
1467
  } finally {
1427
- if (roomOptions) {
1428
- requestPromises.delete(roomOptions.roomId);
1468
+ if (requestOptions.roomId) {
1469
+ requestPromises.delete(requestOptions.roomId);
1429
1470
  } else {
1430
1471
  requestPromises.delete("liveblocks-user-token");
1431
1472
  }
@@ -1996,22 +2037,56 @@ function convertToInboxNotificationData(data) {
1996
2037
  readAt
1997
2038
  };
1998
2039
  }
2040
+ function convertToThreadDeleteInfo(data) {
2041
+ const deletedAt = new Date(data.deletedAt);
2042
+ return {
2043
+ ...data,
2044
+ deletedAt
2045
+ };
2046
+ }
2047
+ function convertToInboxNotificationDeleteInfo(data) {
2048
+ const deletedAt = new Date(data.deletedAt);
2049
+ return {
2050
+ ...data,
2051
+ deletedAt
2052
+ };
2053
+ }
2054
+
2055
+ // src/lib/url.ts
2056
+ function toURLSearchParams(params) {
2057
+ const result = new URLSearchParams();
2058
+ for (const [key, value] of Object.entries(params)) {
2059
+ if (value !== void 0 && value !== null) {
2060
+ result.set(key, value.toString());
2061
+ }
2062
+ }
2063
+ return result;
2064
+ }
2065
+ function urljoin(baseUrl, path, params) {
2066
+ const url = new URL(path, baseUrl);
2067
+ if (params !== void 0) {
2068
+ url.search = (params instanceof URLSearchParams ? params : toURLSearchParams(params)).toString();
2069
+ }
2070
+ return url.toString();
2071
+ }
1999
2072
 
2000
2073
  // src/notifications.ts
2001
2074
  var MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY = 50;
2002
- function createInboxNotificationsApi({
2075
+ function createNotificationsApi({
2003
2076
  baseUrl,
2004
2077
  authManager,
2005
2078
  currentUserIdStore,
2006
2079
  fetcher
2007
2080
  }) {
2008
- async function fetchJson(endpoint, options) {
2009
- const authValue = await authManager.getAuthValue();
2081
+ async function fetchJson(endpoint, options, params) {
2082
+ const authValue = await authManager.getAuthValue({
2083
+ requestedScope: "comments:read"
2084
+ });
2010
2085
  if (authValue.type === "secret" && authValue.token.parsed.k === "acc" /* ACCESS_TOKEN */) {
2011
2086
  const userId = authValue.token.parsed.uid;
2012
2087
  currentUserIdStore.set(() => userId);
2013
2088
  }
2014
- const url = new URL(`/v2/c${endpoint}`, baseUrl);
2089
+ const url = urljoin(baseUrl, `/v2/c${endpoint}`, params);
2015
2090
  const response = await fetcher(url.toString(), {
2016
2091
  ...options,
2017
2092
  headers: {
@@ -2047,13 +2122,24 @@ function createInboxNotificationsApi({
2047
2122
  return body;
2048
2123
  }
2049
2124
  async function getInboxNotifications(options) {
2050
- const queryParams = toURLSearchParams({ limit: options?.limit });
2051
- const json = await fetchJson(`/inbox-notifications?${queryParams.toString()}`);
2125
+ const json = await fetchJson("/inbox-notifications", void 0, {
2126
+ limit: options?.limit,
2127
+ since: options?.since?.toISOString()
2128
+ });
2052
2129
  return {
2053
2130
  threads: json.threads.map((thread) => convertToThreadData(thread)),
2054
2131
  inboxNotifications: json.inboxNotifications.map(
2055
2132
  (notification) => convertToInboxNotificationData(notification)
2056
- )
2133
+ ),
2134
+ deletedThreads: json.deletedThreads.map(
2135
+ (info) => convertToThreadDeleteInfo(info)
2136
+ ),
2137
+ deletedInboxNotifications: json.deletedInboxNotifications.map(
2138
+ (info) => convertToInboxNotificationDeleteInfo(info)
2139
+ ),
2140
+ meta: {
2141
+ requestedAt: new Date(json.meta.requestedAt)
2142
+ }
2057
2143
  };
2058
2144
  }
2059
2145
  async function getUnreadInboxNotificationsCount() {
@@ -2096,15 +2182,6 @@ function createInboxNotificationsApi({
2096
2182
  markInboxNotificationAsRead
2097
2183
  };
2098
2184
  }
2099
- function toURLSearchParams(params) {
2100
- const result = new URLSearchParams();
2101
- for (const [key, value] of Object.entries(params)) {
2102
- if (value !== void 0 && value !== null) {
2103
- result.set(key, value.toString());
2104
- }
2105
- }
2106
- return result;
2107
- }
2108
2185
 
2109
2186
  // src/lib/position.ts
2110
2187
  var MIN_CODE = 32;
@@ -4959,12 +5036,12 @@ var CommentsApiError = class extends Error {
4959
5036
  }
4960
5037
  };
4961
5038
  function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
4962
- async function fetchCommentsApi(endpoint, options) {
5039
+ async function fetchCommentsApi(endpoint, params, options) {
4963
5040
  const authValue = await getAuthValue();
4964
- return fetchClientApi(roomId, endpoint, authValue, options);
5041
+ return fetchClientApi(roomId, endpoint, authValue, options, params);
4965
5042
  }
4966
- async function fetchJson(endpoint, options) {
4967
- const response = await fetchCommentsApi(endpoint, options);
5043
+ async function fetchJson(endpoint, options, params) {
5044
+ const response = await fetchCommentsApi(endpoint, params, options);
4968
5045
  if (!response.ok) {
4969
5046
  if (response.status >= 400 && response.status < 600) {
4970
5047
  let error3;
@@ -4990,25 +5067,48 @@ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
4990
5067
  return body;
4991
5068
  }
4992
5069
  async function getThreads(options) {
4993
- const response = await fetchCommentsApi("/threads/search", {
4994
- body: JSON.stringify({
4995
- ...options?.query?.metadata && { metadata: options.query.metadata }
4996
- }),
4997
- headers: {
4998
- "Content-Type": "application/json"
5070
+ const response = await fetchCommentsApi(
5071
+ "/threads/search",
5072
+ {
5073
+ since: options?.since?.toISOString()
4999
5074
  },
5000
- method: "POST"
5001
- });
5075
+ {
5076
+ body: JSON.stringify({
5077
+ ...options?.query?.metadata && { metadata: options.query.metadata }
5078
+ }),
5079
+ headers: {
5080
+ "Content-Type": "application/json"
5081
+ },
5082
+ method: "POST"
5083
+ }
5084
+ );
5002
5085
  if (response.ok) {
5003
5086
  const json = await response.json();
5004
5087
  return {
5005
5088
  threads: json.data.map((thread) => convertToThreadData(thread)),
5006
5089
  inboxNotifications: json.inboxNotifications.map(
5007
5090
  (notification) => convertToInboxNotificationData(notification)
5008
- )
5091
+ ),
5092
+ deletedThreads: json.deletedThreads.map(
5093
+ (info) => convertToThreadDeleteInfo(info)
5094
+ ),
5095
+ deletedInboxNotifications: json.deletedInboxNotifications.map(
5096
+ (info) => convertToInboxNotificationDeleteInfo(info)
5097
+ ),
5098
+ meta: {
5099
+ requestedAt: new Date(json.meta.requestedAt)
5100
+ }
5009
5101
  };
5010
5102
  } else if (response.status === 404) {
5011
- return { threads: [], inboxNotifications: [] };
5103
+ return {
5104
+ threads: [],
5105
+ inboxNotifications: [],
5106
+ deletedThreads: [],
5107
+ deletedInboxNotifications: [],
5108
+ meta: {
5109
+ requestedAt: /* @__PURE__ */ new Date()
5110
+ }
5111
+ };
5012
5112
  } else {
5013
5113
  throw new Error("There was an error while getting threads.");
5014
5114
  }
@@ -5110,7 +5210,7 @@ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
5110
5210
  );
5111
5211
  return convertToCommentData(comment);
5112
5212
  }
5113
- async function deleteComment({
5213
+ async function deleteComment2({
5114
5214
  threadId,
5115
5215
  commentId
5116
5216
  }) {
@@ -5123,7 +5223,7 @@ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
5123
5223
  }
5124
5224
  );
5125
5225
  }
5126
- async function addReaction({
5226
+ async function addReaction2({
5127
5227
  threadId,
5128
5228
  commentId,
5129
5229
  emoji
@@ -5142,7 +5242,7 @@ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
5142
5242
  );
5143
5243
  return convertToCommentUserReaction(reaction);
5144
5244
  }
5145
- async function removeReaction({
5245
+ async function removeReaction2({
5146
5246
  threadId,
5147
5247
  commentId,
5148
5248
  emoji
@@ -5163,11 +5263,12 @@ function createCommentsApi(roomId, getAuthValue, fetchClientApi) {
5163
5263
  editThreadMetadata,
5164
5264
  createComment,
5165
5265
  editComment,
5166
- deleteComment,
5167
- addReaction,
5168
- removeReaction
5266
+ deleteComment: deleteComment2,
5267
+ addReaction: addReaction2,
5268
+ removeReaction: removeReaction2
5169
5269
  };
5170
5270
  }
5271
+ var MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY2 = 50;
5171
5272
  function createRoom(options, config) {
5172
5273
  const initialPresence = typeof options.initialPresence === "function" ? options.initialPresence(config.roomId) : options.initialPresence;
5173
5274
  const initialStorage = typeof options.initialStorage === "function" ? options.initialStorage(config.roomId) : options.initialStorage;
@@ -5383,14 +5484,15 @@ function createRoom(options, config) {
5383
5484
  ydoc: makeEventSource(),
5384
5485
  comments: makeEventSource()
5385
5486
  };
5386
- async function fetchClientApi(roomId, endpoint, authValue, options2) {
5387
- const url = new URL(
5487
+ async function fetchClientApi(roomId, endpoint, authValue, options2, params) {
5488
+ const url = urljoin(
5489
+ config.baseUrl,
5388
5490
  `/v2/c/rooms/${encodeURIComponent(roomId)}${endpoint}`,
5389
- config.baseUrl
5491
+ params
5390
5492
  );
5391
5493
  const fetcher = config.polyfills?.fetch || /* istanbul ignore next */
5392
5494
  fetch;
5393
- return await fetcher(url.toString(), {
5495
+ return await fetcher(url, {
5394
5496
  ...options2,
5395
5497
  headers: {
5396
5498
  ...options2?.headers,
@@ -6218,7 +6320,7 @@ ${Array.from(traces).join("\n\n")}`
6218
6320
  delegates.authenticate,
6219
6321
  fetchClientApi
6220
6322
  );
6221
- async function fetchJson(endpoint, options2) {
6323
+ async function fetchNotificationsJson(endpoint, options2) {
6222
6324
  const authValue = await delegates.authenticate();
6223
6325
  const response = await fetchClientApi(
6224
6326
  config.roomId,
@@ -6228,16 +6330,21 @@ ${Array.from(traces).join("\n\n")}`
6228
6330
  );
6229
6331
  if (!response.ok) {
6230
6332
  if (response.status >= 400 && response.status < 600) {
6231
- let errorMessage = "";
6333
+ let error3;
6232
6334
  try {
6233
6335
  const errorBody = await response.json();
6234
- errorMessage = errorBody.message;
6235
- } catch (error3) {
6236
- errorMessage = response.statusText;
6336
+ error3 = new NotificationsApiError(
6337
+ errorBody.message,
6338
+ response.status,
6339
+ errorBody
6340
+ );
6341
+ } catch {
6342
+ error3 = new NotificationsApiError(
6343
+ response.statusText,
6344
+ response.status
6345
+ );
6237
6346
  }
6238
- throw new Error(
6239
- `Request failed with status ${response.status}: ${errorMessage}`
6240
- );
6347
+ throw error3;
6241
6348
  }
6242
6349
  }
6243
6350
  let body;
@@ -6249,20 +6356,44 @@ ${Array.from(traces).join("\n\n")}`
6249
6356
  return body;
6250
6357
  }
6251
6358
  function getRoomNotificationSettings() {
6252
- return fetchJson("/notification-settings");
6359
+ return fetchNotificationsJson(
6360
+ "/notification-settings"
6361
+ );
6253
6362
  }
6254
6363
  function updateRoomNotificationSettings(settings) {
6255
- return fetchJson("/notification-settings", {
6364
+ return fetchNotificationsJson(
6365
+ "/notification-settings",
6366
+ {
6367
+ method: "POST",
6368
+ body: JSON.stringify(settings),
6369
+ headers: {
6370
+ "Content-Type": "application/json"
6371
+ }
6372
+ }
6373
+ );
6374
+ }
6375
+ async function markInboxNotificationsAsRead(inboxNotificationIds) {
6376
+ await fetchNotificationsJson("/inbox-notifications/read", {
6256
6377
  method: "POST",
6257
- body: JSON.stringify(settings),
6258
6378
  headers: {
6259
6379
  "Content-Type": "application/json"
6260
- }
6380
+ },
6381
+ body: JSON.stringify({ inboxNotificationIds })
6261
6382
  });
6262
6383
  }
6384
+ const batchedMarkInboxNotificationsAsRead = new Batch(
6385
+ async (batchedInboxNotificationIds) => {
6386
+ const inboxNotificationIds = batchedInboxNotificationIds.flat();
6387
+ await markInboxNotificationsAsRead(inboxNotificationIds);
6388
+ return inboxNotificationIds;
6389
+ },
6390
+ { delay: MARK_INBOX_NOTIFICATIONS_AS_READ_BATCH_DELAY2 }
6391
+ );
6392
+ async function markInboxNotificationAsRead(inboxNotificationId) {
6393
+ await batchedMarkInboxNotificationsAsRead.get(inboxNotificationId);
6394
+ }
6263
6395
  return Object.defineProperty(
6264
6396
  {
6265
- /* NOTE: Exposing internals here only to allow testing implementation details in unit tests */
6266
6397
  [kInternal]: {
6267
6398
  get presenceBuffer() {
6268
6399
  return deepClone(context.buffer.presenceUpdates?.data ?? null);
@@ -6284,6 +6415,14 @@ ${Array.from(traces).join("\n\n")}`
6284
6415
  // These exist only for our E2E testing app
6285
6416
  explicitClose: (event) => managedSocket._privateSendMachineEvent({ type: "EXPLICIT_SOCKET_CLOSE", event }),
6286
6417
  rawSend: (data) => managedSocket.send(data)
6418
+ },
6419
+ comments: {
6420
+ ...commentsApi
6421
+ },
6422
+ notifications: {
6423
+ getRoomNotificationSettings,
6424
+ updateRoomNotificationSettings,
6425
+ markInboxNotificationAsRead
6287
6426
  }
6288
6427
  },
6289
6428
  id: config.roomId,
@@ -6321,12 +6460,7 @@ ${Array.from(traces).join("\n\n")}`
6321
6460
  getSelf: () => self.current,
6322
6461
  // Presence
6323
6462
  getPresence: () => context.myPresence.current,
6324
- getOthers: () => context.others.current,
6325
- // Comments
6326
- ...commentsApi,
6327
- // Notifications
6328
- getRoomNotificationSettings,
6329
- updateRoomNotificationSettings
6463
+ getOthers: () => context.others.current
6330
6464
  },
6331
6465
  // Explictly make the internal field non-enumerable, to avoid aggressive
6332
6466
  // freezing when used with Immer
@@ -6466,28 +6600,6 @@ function createClientStore() {
6466
6600
  inboxNotifications: {},
6467
6601
  notificationSettings: {}
6468
6602
  });
6469
- function mergeThreads(existingThreads, newThreads) {
6470
- const updatedThreads = { ...existingThreads };
6471
- Object.entries(newThreads).forEach(([id, thread]) => {
6472
- const existingThread = updatedThreads[id];
6473
- if (existingThread) {
6474
- const result = compareThreads(existingThread, thread);
6475
- if (result === 1)
6476
- return;
6477
- }
6478
- updatedThreads[id] = thread;
6479
- });
6480
- return updatedThreads;
6481
- }
6482
- function mergeNotifications(existingInboxNotifications, newInboxNotifications) {
6483
- const inboxNotifications = Object.values({
6484
- ...existingInboxNotifications,
6485
- ...newInboxNotifications
6486
- });
6487
- return Object.fromEntries(
6488
- inboxNotifications.map((notification) => [notification.id, notification])
6489
- );
6490
- }
6491
6603
  return {
6492
6604
  ...store,
6493
6605
  deleteThread(threadId) {
@@ -6516,21 +6628,19 @@ function createClientStore() {
6516
6628
  };
6517
6629
  });
6518
6630
  },
6519
- updateThreadsAndNotifications(threads, inboxNotifications, queryKey) {
6631
+ updateThreadsAndNotifications(threads, inboxNotifications, deletedThreads, deletedInboxNotifications, queryKey) {
6520
6632
  store.set((state) => ({
6521
6633
  ...state,
6522
- threads: mergeThreads(
6523
- state.threads,
6524
- Object.fromEntries(threads.map((thread) => [thread.id, thread]))
6525
- ),
6526
- inboxNotifications: mergeNotifications(
6634
+ threads: applyThreadUpdates(state.threads, {
6635
+ newThreads: threads,
6636
+ deletedThreads
6637
+ }),
6638
+ inboxNotifications: applyNotificationsUpdates(
6527
6639
  state.inboxNotifications,
6528
- Object.fromEntries(
6529
- inboxNotifications.map((notification) => [
6530
- notification.id,
6531
- notification
6532
- ])
6533
- )
6640
+ {
6641
+ newInboxNotifications: inboxNotifications,
6642
+ deletedNotifications: deletedInboxNotifications
6643
+ }
6534
6644
  ),
6535
6645
  queries: queryKey !== void 0 ? {
6536
6646
  ...state.queries,
@@ -6540,6 +6650,21 @@ function createClientStore() {
6540
6650
  } : state.queries
6541
6651
  }));
6542
6652
  },
6653
+ updateRoomInboxNotificationSettings(roomId, settings, queryKey) {
6654
+ store.set((state) => ({
6655
+ ...state,
6656
+ notificationSettings: {
6657
+ ...state.notificationSettings,
6658
+ [roomId]: settings
6659
+ },
6660
+ queries: {
6661
+ ...state.queries,
6662
+ [queryKey]: {
6663
+ isLoading: false
6664
+ }
6665
+ }
6666
+ }));
6667
+ },
6543
6668
  pushOptimisticUpdate(optimisticUpdate) {
6544
6669
  store.set((state) => ({
6545
6670
  ...state,
@@ -6600,8 +6725,15 @@ function applyOptimisticUpdates(state) {
6600
6725
  if (thread === void 0) {
6601
6726
  break;
6602
6727
  }
6728
+ if (thread.deletedAt !== void 0) {
6729
+ break;
6730
+ }
6731
+ if (thread.updatedAt !== void 0 && thread.updatedAt > optimisticUpdate.updatedAt) {
6732
+ break;
6733
+ }
6603
6734
  result.threads[thread.id] = {
6604
6735
  ...thread,
6736
+ updatedAt: optimisticUpdate.updatedAt,
6605
6737
  metadata: {
6606
6738
  ...thread.metadata,
6607
6739
  ...optimisticUpdate.metadata
@@ -6614,16 +6746,17 @@ function applyOptimisticUpdates(state) {
6614
6746
  if (thread === void 0) {
6615
6747
  break;
6616
6748
  }
6617
- result.threads[thread.id] = {
6618
- ...thread,
6619
- comments: [...thread.comments, optimisticUpdate.comment]
6620
- // TODO: Handle replace comment
6621
- };
6622
- if (!optimisticUpdate.inboxNotificationId) {
6749
+ result.threads[thread.id] = upsertComment(
6750
+ thread,
6751
+ optimisticUpdate.comment
6752
+ );
6753
+ const inboxNotification = Object.values(result.inboxNotifications).find(
6754
+ (notification) => notification.threadId === thread.id
6755
+ );
6756
+ if (inboxNotification === void 0) {
6623
6757
  break;
6624
6758
  }
6625
- const inboxNotification = result.inboxNotifications[optimisticUpdate.inboxNotificationId];
6626
- result.inboxNotifications[optimisticUpdate.inboxNotificationId] = {
6759
+ result.inboxNotifications[inboxNotification.id] = {
6627
6760
  ...inboxNotification,
6628
6761
  notifiedAt: optimisticUpdate.comment.createdAt,
6629
6762
  readAt: optimisticUpdate.comment.createdAt
@@ -6631,20 +6764,14 @@ function applyOptimisticUpdates(state) {
6631
6764
  break;
6632
6765
  }
6633
6766
  case "edit-comment": {
6634
- const thread = result.threads[optimisticUpdate.threadId];
6767
+ const thread = result.threads[optimisticUpdate.comment.threadId];
6635
6768
  if (thread === void 0) {
6636
6769
  break;
6637
6770
  }
6638
- result.threads[thread.id] = {
6639
- ...thread,
6640
- comments: thread.comments.map(
6641
- (comment) => comment.id === optimisticUpdate.commentId ? {
6642
- ...comment,
6643
- editedAt: optimisticUpdate.editedAt,
6644
- body: optimisticUpdate.body
6645
- } : comment
6646
- )
6647
- };
6771
+ result.threads[thread.id] = upsertComment(
6772
+ thread,
6773
+ optimisticUpdate.comment
6774
+ );
6648
6775
  break;
6649
6776
  }
6650
6777
  case "delete-comment": {
@@ -6652,21 +6779,11 @@ function applyOptimisticUpdates(state) {
6652
6779
  if (thread === void 0) {
6653
6780
  break;
6654
6781
  }
6655
- result.threads[thread.id] = {
6656
- ...thread,
6657
- comments: thread.comments.map(
6658
- (comment) => comment.id === optimisticUpdate.commentId ? {
6659
- ...comment,
6660
- deletedAt: optimisticUpdate.deletedAt,
6661
- body: void 0
6662
- } : comment
6663
- )
6664
- };
6665
- if (!result.threads[thread.id].comments.some(
6666
- (comment) => comment.deletedAt === void 0
6667
- )) {
6668
- delete result.threads[thread.id];
6669
- }
6782
+ result.threads[thread.id] = deleteComment(
6783
+ thread,
6784
+ optimisticUpdate.commentId,
6785
+ optimisticUpdate.deletedAt
6786
+ );
6670
6787
  break;
6671
6788
  }
6672
6789
  case "add-reaction": {
@@ -6674,43 +6791,11 @@ function applyOptimisticUpdates(state) {
6674
6791
  if (thread === void 0) {
6675
6792
  break;
6676
6793
  }
6677
- result.threads[thread.id] = {
6678
- ...thread,
6679
- comments: thread.comments.map((comment) => {
6680
- if (comment.id === optimisticUpdate.commentId) {
6681
- if (comment.reactions.some(
6682
- (reaction) => reaction.emoji === optimisticUpdate.emoji
6683
- )) {
6684
- return {
6685
- ...comment,
6686
- reactions: comment.reactions.map(
6687
- (reaction) => reaction.emoji === optimisticUpdate.emoji ? {
6688
- ...reaction,
6689
- users: [
6690
- ...reaction.users,
6691
- { id: optimisticUpdate.userId }
6692
- ]
6693
- } : reaction
6694
- )
6695
- };
6696
- } else {
6697
- return {
6698
- ...comment,
6699
- reactions: [
6700
- ...comment.reactions,
6701
- {
6702
- emoji: optimisticUpdate.emoji,
6703
- createdAt: optimisticUpdate.createdAt,
6704
- users: [{ id: optimisticUpdate.userId }]
6705
- }
6706
- ]
6707
- };
6708
- }
6709
- } else {
6710
- return comment;
6711
- }
6712
- })
6713
- };
6794
+ result.threads[thread.id] = addReaction(
6795
+ thread,
6796
+ optimisticUpdate.commentId,
6797
+ optimisticUpdate.reaction
6798
+ );
6714
6799
  break;
6715
6800
  }
6716
6801
  case "remove-reaction": {
@@ -6718,37 +6803,13 @@ function applyOptimisticUpdates(state) {
6718
6803
  if (thread === void 0) {
6719
6804
  break;
6720
6805
  }
6721
- result.threads[thread.id] = {
6722
- ...thread,
6723
- comments: thread.comments.map((comment) => {
6724
- if (comment.id !== optimisticUpdate.commentId) {
6725
- return comment;
6726
- }
6727
- const reactionIndex = comment.reactions.findIndex(
6728
- (reaction) => reaction.emoji === optimisticUpdate.emoji
6729
- );
6730
- let reactions = comment.reactions;
6731
- if (reactionIndex >= 0 && comment.reactions[reactionIndex].users.some(
6732
- (user) => user.id === optimisticUpdate.userId
6733
- )) {
6734
- if (comment.reactions[reactionIndex].users.length <= 1) {
6735
- reactions = [...comment.reactions];
6736
- reactions.splice(reactionIndex, 1);
6737
- } else {
6738
- reactions[reactionIndex] = {
6739
- ...reactions[reactionIndex],
6740
- users: reactions[reactionIndex].users.filter(
6741
- (user) => user.id !== optimisticUpdate.userId
6742
- )
6743
- };
6744
- }
6745
- }
6746
- return {
6747
- ...comment,
6748
- reactions
6749
- };
6750
- })
6751
- };
6806
+ result.threads[thread.id] = removeReaction(
6807
+ thread,
6808
+ optimisticUpdate.commentId,
6809
+ optimisticUpdate.emoji,
6810
+ optimisticUpdate.userId,
6811
+ optimisticUpdate.removedAt
6812
+ );
6752
6813
  break;
6753
6814
  }
6754
6815
  case "mark-inbox-notification-as-read": {
@@ -6777,6 +6838,222 @@ function applyOptimisticUpdates(state) {
6777
6838
  }
6778
6839
  return result;
6779
6840
  }
6841
+ function applyThreadUpdates(existingThreads, updates) {
6842
+ const updatedThreads = { ...existingThreads };
6843
+ updates.newThreads.forEach((thread) => {
6844
+ const existingThread = updatedThreads[thread.id];
6845
+ if (existingThread) {
6846
+ const result = compareThreads(existingThread, thread);
6847
+ if (result === 1)
6848
+ return;
6849
+ }
6850
+ updatedThreads[thread.id] = thread;
6851
+ });
6852
+ updates.deletedThreads.forEach(({ id, deletedAt }) => {
6853
+ const existingThread = updatedThreads[id];
6854
+ if (existingThread === void 0)
6855
+ return;
6856
+ existingThread.deletedAt = deletedAt;
6857
+ existingThread.updatedAt = deletedAt;
6858
+ existingThread.comments = [];
6859
+ });
6860
+ return updatedThreads;
6861
+ }
6862
+ function applyNotificationsUpdates(existingInboxNotifications, updates) {
6863
+ const updatedInboxNotifications = { ...existingInboxNotifications };
6864
+ updates.newInboxNotifications.forEach((notification) => {
6865
+ const existingNotification = updatedInboxNotifications[notification.id];
6866
+ if (existingNotification) {
6867
+ const result = compareInboxNotifications(
6868
+ existingNotification,
6869
+ notification
6870
+ );
6871
+ if (result === 1)
6872
+ return;
6873
+ }
6874
+ updatedInboxNotifications[notification.id] = notification;
6875
+ });
6876
+ updates.deletedNotifications.forEach(
6877
+ ({ id }) => delete updatedInboxNotifications[id]
6878
+ );
6879
+ return updatedInboxNotifications;
6880
+ }
6881
+ function compareInboxNotifications(inboxNotificationA, inboxNotificationB) {
6882
+ if (inboxNotificationA.notifiedAt > inboxNotificationB.notifiedAt) {
6883
+ return 1;
6884
+ } else if (inboxNotificationA.notifiedAt < inboxNotificationB.notifiedAt) {
6885
+ return -1;
6886
+ }
6887
+ if (inboxNotificationA.readAt && inboxNotificationB.readAt) {
6888
+ return inboxNotificationA.readAt > inboxNotificationB.readAt ? 1 : inboxNotificationA.readAt < inboxNotificationB.readAt ? -1 : 0;
6889
+ } else if (inboxNotificationA.readAt || inboxNotificationB.readAt) {
6890
+ return inboxNotificationA.readAt ? 1 : -1;
6891
+ }
6892
+ return 0;
6893
+ }
6894
+ function upsertComment(thread, comment) {
6895
+ if (thread.deletedAt !== void 0) {
6896
+ return thread;
6897
+ }
6898
+ if (comment.threadId !== thread.id) {
6899
+ warn(
6900
+ `Comment ${comment.id} does not belong to thread ${thread.id}`
6901
+ );
6902
+ return thread;
6903
+ }
6904
+ const existingComment = thread.comments.find(
6905
+ (existingComment2) => existingComment2.id === comment.id
6906
+ );
6907
+ if (existingComment === void 0) {
6908
+ const updatedAt = new Date(
6909
+ Math.max(thread.updatedAt?.getTime() || 0, comment.createdAt.getTime())
6910
+ );
6911
+ const updatedThread = {
6912
+ ...thread,
6913
+ updatedAt,
6914
+ comments: [...thread.comments, comment]
6915
+ };
6916
+ return updatedThread;
6917
+ }
6918
+ if (existingComment.deletedAt !== void 0) {
6919
+ return thread;
6920
+ }
6921
+ if (existingComment.editedAt === void 0 || comment.editedAt === void 0 || existingComment.editedAt <= comment.editedAt) {
6922
+ const updatedComments = thread.comments.map(
6923
+ (existingComment2) => existingComment2.id === comment.id ? comment : existingComment2
6924
+ );
6925
+ const updatedThread = {
6926
+ ...thread,
6927
+ updatedAt: new Date(
6928
+ Math.max(
6929
+ thread.updatedAt?.getTime() || 0,
6930
+ comment.editedAt?.getTime() || comment.createdAt.getTime()
6931
+ )
6932
+ ),
6933
+ comments: updatedComments
6934
+ };
6935
+ return updatedThread;
6936
+ }
6937
+ return thread;
6938
+ }
6939
+ function deleteComment(thread, commentId, deletedAt) {
6940
+ if (thread.deletedAt !== void 0) {
6941
+ return thread;
6942
+ }
6943
+ const existingComment = thread.comments.find(
6944
+ (comment) => comment.id === commentId
6945
+ );
6946
+ if (existingComment === void 0) {
6947
+ return thread;
6948
+ }
6949
+ if (existingComment.deletedAt !== void 0) {
6950
+ return thread;
6951
+ }
6952
+ const updatedComments = thread.comments.map(
6953
+ (comment) => comment.id === commentId ? {
6954
+ ...comment,
6955
+ deletedAt,
6956
+ body: void 0
6957
+ } : comment
6958
+ );
6959
+ if (!updatedComments.some((comment) => comment.deletedAt === void 0)) {
6960
+ return {
6961
+ ...thread,
6962
+ deletedAt,
6963
+ updatedAt: deletedAt,
6964
+ comments: []
6965
+ };
6966
+ }
6967
+ return {
6968
+ ...thread,
6969
+ updatedAt: deletedAt,
6970
+ comments: updatedComments
6971
+ };
6972
+ }
6973
+ function addReaction(thread, commentId, reaction) {
6974
+ if (thread.deletedAt !== void 0) {
6975
+ return thread;
6976
+ }
6977
+ const existingComment = thread.comments.find(
6978
+ (comment) => comment.id === commentId
6979
+ );
6980
+ if (existingComment === void 0) {
6981
+ return thread;
6982
+ }
6983
+ if (existingComment.deletedAt !== void 0) {
6984
+ return thread;
6985
+ }
6986
+ const updatedComments = thread.comments.map(
6987
+ (comment) => comment.id === commentId ? {
6988
+ ...comment,
6989
+ reactions: upsertReaction(comment.reactions, reaction)
6990
+ } : comment
6991
+ );
6992
+ return {
6993
+ ...thread,
6994
+ updatedAt: new Date(
6995
+ Math.max(reaction.createdAt.getTime(), thread.updatedAt?.getTime() || 0)
6996
+ ),
6997
+ comments: updatedComments
6998
+ };
6999
+ }
7000
+ function removeReaction(thread, commentId, emoji, userId, removedAt) {
7001
+ if (thread.deletedAt !== void 0) {
7002
+ return thread;
7003
+ }
7004
+ const existingComment = thread.comments.find(
7005
+ (comment) => comment.id === commentId
7006
+ );
7007
+ if (existingComment === void 0) {
7008
+ return thread;
7009
+ }
7010
+ if (existingComment.deletedAt !== void 0) {
7011
+ return thread;
7012
+ }
7013
+ const updatedComments = thread.comments.map(
7014
+ (comment) => comment.id === commentId ? {
7015
+ ...comment,
7016
+ reactions: comment.reactions.map(
7017
+ (reaction) => reaction.emoji === emoji ? {
7018
+ ...reaction,
7019
+ users: reaction.users.filter((user) => user.id !== userId)
7020
+ } : reaction
7021
+ ).filter((reaction) => reaction.users.length > 0)
7022
+ // Remove reactions with no users left
7023
+ } : comment
7024
+ );
7025
+ return {
7026
+ ...thread,
7027
+ updatedAt: new Date(
7028
+ Math.max(removedAt.getTime(), thread.updatedAt?.getTime() || 0)
7029
+ ),
7030
+ comments: updatedComments
7031
+ };
7032
+ }
7033
+ function upsertReaction(reactions, reaction) {
7034
+ const existingReaction = reactions.find(
7035
+ (existingReaction2) => existingReaction2.emoji === reaction.emoji
7036
+ );
7037
+ if (existingReaction === void 0) {
7038
+ return [
7039
+ ...reactions,
7040
+ {
7041
+ emoji: reaction.emoji,
7042
+ createdAt: reaction.createdAt,
7043
+ users: [{ id: reaction.userId }]
7044
+ }
7045
+ ];
7046
+ }
7047
+ if (existingReaction.users.some((user) => user.id === reaction.userId) === false) {
7048
+ return reactions.map(
7049
+ (existingReaction2) => existingReaction2.emoji === reaction.emoji ? {
7050
+ ...existingReaction2,
7051
+ users: [...existingReaction2.users, { id: reaction.userId }]
7052
+ } : existingReaction2
7053
+ );
7054
+ }
7055
+ return reactions;
7056
+ }
6780
7057
 
6781
7058
  // src/client.ts
6782
7059
  var MIN_THROTTLE = 16;
@@ -6926,7 +7203,7 @@ function createClient(options) {
6926
7203
  getUnreadInboxNotificationsCount,
6927
7204
  markAllInboxNotificationsAsRead,
6928
7205
  markInboxNotificationAsRead
6929
- } = createInboxNotificationsApi({
7206
+ } = createNotificationsApi({
6930
7207
  baseUrl,
6931
7208
  fetcher: clientOptions.polyfills?.fetch || /* istanbul ignore next */
6932
7209
  fetch,
@@ -6971,18 +7248,22 @@ function createClient(options) {
6971
7248
  leave: forceLeave,
6972
7249
  // New, preferred API
6973
7250
  enterRoom,
6974
- // Notifications API
6975
- getInboxNotifications,
6976
- getUnreadInboxNotificationsCount,
6977
- markAllInboxNotificationsAsRead,
6978
- markInboxNotificationAsRead,
6979
7251
  // Internal
6980
7252
  [kInternal]: {
7253
+ notifications: {
7254
+ getInboxNotifications,
7255
+ getUnreadInboxNotificationsCount,
7256
+ markAllInboxNotificationsAsRead,
7257
+ markInboxNotificationAsRead
7258
+ },
6981
7259
  currentUserIdStore,
6982
7260
  resolveMentionSuggestions: clientOptions.resolveMentionSuggestions,
6983
7261
  cacheStore,
6984
7262
  usersStore,
6985
- roomsInfoStore
7263
+ roomsInfoStore,
7264
+ getRoomIds() {
7265
+ return Array.from(roomsById.keys());
7266
+ }
6986
7267
  }
6987
7268
  },
6988
7269
  kInternal,
@@ -7825,6 +8106,7 @@ export {
7825
8106
  ServerMsgCode,
7826
8107
  WebsocketCloseCodes,
7827
8108
  ackOp,
8109
+ addReaction,
7828
8110
  applyOptimisticUpdates,
7829
8111
  asPos,
7830
8112
  assert,
@@ -7836,6 +8118,7 @@ export {
7836
8118
  convertToCommentUserReaction,
7837
8119
  convertToThreadData,
7838
8120
  createClient,
8121
+ deleteComment,
7839
8122
  deprecate,
7840
8123
  deprecateIf,
7841
8124
  detectDupes,
@@ -7858,12 +8141,14 @@ export {
7858
8141
  nn,
7859
8142
  patchLiveObjectKey,
7860
8143
  raise,
8144
+ removeReaction,
7861
8145
  shallow,
7862
8146
  stringify,
7863
8147
  stringifyCommentBody,
7864
8148
  throwUsageError,
7865
8149
  toPlainLson,
7866
8150
  tryParseJson,
8151
+ upsertComment,
7867
8152
  withTimeout
7868
8153
  };
7869
8154
  //# sourceMappingURL=index.mjs.map