@liveblocks/core 2.7.2 → 2.8.0-beta2

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 = "2.7.2";
9
+ var PKG_VERSION = "2.8.0-beta2";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -1871,8 +1871,7 @@ var Batch = class {
1871
1871
  this.clearDelayTimeout();
1872
1872
  }
1873
1873
  };
1874
- function createBatchStore(callback, options) {
1875
- const batch = new Batch(callback, options);
1874
+ function createBatchStore(batch) {
1876
1875
  const cache = /* @__PURE__ */ new Map();
1877
1876
  const eventSource2 = makeEventSource();
1878
1877
  function getCacheKey(args) {
@@ -4796,9 +4795,41 @@ function findNonSerializableValue(value, path = "") {
4796
4795
  return false;
4797
4796
  }
4798
4797
 
4798
+ // src/lib/autoRetry.ts
4799
+ async function autoRetry(promiseFn, maxTries, backoff, exitCondition) {
4800
+ const fallbackBackoff = backoff.length > 0 ? backoff[backoff.length - 1] : 0;
4801
+ let attempt = 0;
4802
+ while (true) {
4803
+ attempt++;
4804
+ const promise = promiseFn();
4805
+ try {
4806
+ return await promise;
4807
+ } catch (err) {
4808
+ if (exitCondition && exitCondition(err)) {
4809
+ throw err;
4810
+ }
4811
+ if (attempt >= maxTries) {
4812
+ throw new Error(`Failed after ${maxTries} attempts: ${String(err)}`);
4813
+ }
4814
+ }
4815
+ const delay = backoff[attempt - 1] ?? fallbackBackoff;
4816
+ await wait(delay);
4817
+ }
4818
+ }
4819
+
4820
+ // src/lib/chunk.ts
4821
+ function chunk(array, size) {
4822
+ const chunks = [];
4823
+ for (let i = 0, j = array.length; i < j; i += size) {
4824
+ chunks.push(array.slice(i, i + size));
4825
+ }
4826
+ return chunks;
4827
+ }
4828
+
4799
4829
  // src/lib/createIds.ts
4800
4830
  var THREAD_ID_PREFIX = "th";
4801
4831
  var COMMENT_ID_PREFIX = "cm";
4832
+ var COMMENT_ATTACHMENT_ID_PREFIX = "at";
4802
4833
  var INBOX_NOTIFICATION_ID_PREFIX = "in";
4803
4834
  function createOptimisticId(prefix) {
4804
4835
  return `${prefix}_${nanoid()}`;
@@ -4809,6 +4840,9 @@ function createThreadId() {
4809
4840
  function createCommentId() {
4810
4841
  return createOptimisticId(COMMENT_ID_PREFIX);
4811
4842
  }
4843
+ function createCommentAttachmentId() {
4844
+ return createOptimisticId(COMMENT_ATTACHMENT_ID_PREFIX);
4845
+ }
4812
4846
  function createInboxNotificationId() {
4813
4847
  return createOptimisticId(INBOX_NOTIFICATION_ID_PREFIX);
4814
4848
  }
@@ -5211,6 +5245,22 @@ function installBackgroundTabSpy() {
5211
5245
  };
5212
5246
  return [inBackgroundSince, unsub];
5213
5247
  }
5248
+ var GET_ATTACHMENT_URLS_BATCH_DELAY = 50;
5249
+ var ATTACHMENT_PART_SIZE = 5 * 1024 * 1024;
5250
+ var ATTACHMENT_PART_BATCH_SIZE = 5;
5251
+ function splitFileIntoParts(file) {
5252
+ const parts = [];
5253
+ let start = 0;
5254
+ while (start < file.size) {
5255
+ const end = Math.min(start + ATTACHMENT_PART_SIZE, file.size);
5256
+ parts.push({
5257
+ partNumber: parts.length + 1,
5258
+ part: file.slice(start, end)
5259
+ });
5260
+ start = end;
5261
+ }
5262
+ return parts;
5263
+ }
5214
5264
  var CommentsApiError = class extends Error {
5215
5265
  constructor(message, status, details) {
5216
5266
  super(message);
@@ -6504,7 +6554,8 @@ ${Array.from(traces).join("\n\n")}`
6504
6554
  metadata,
6505
6555
  body,
6506
6556
  commentId = createCommentId(),
6507
- threadId = createThreadId()
6557
+ threadId = createThreadId(),
6558
+ attachmentIds
6508
6559
  }) {
6509
6560
  const thread = await fetchCommentsJson("/threads", {
6510
6561
  method: "POST",
@@ -6515,7 +6566,8 @@ ${Array.from(traces).join("\n\n")}`
6515
6566
  id: threadId,
6516
6567
  comment: {
6517
6568
  id: commentId,
6518
- body
6569
+ body,
6570
+ attachmentIds
6519
6571
  },
6520
6572
  metadata
6521
6573
  })
@@ -6561,7 +6613,8 @@ ${Array.from(traces).join("\n\n")}`
6561
6613
  async function createComment({
6562
6614
  threadId,
6563
6615
  commentId = createCommentId(),
6564
- body
6616
+ body,
6617
+ attachmentIds
6565
6618
  }) {
6566
6619
  const comment = await fetchCommentsJson(
6567
6620
  `/threads/${encodeURIComponent(threadId)}/comments`,
@@ -6572,7 +6625,8 @@ ${Array.from(traces).join("\n\n")}`
6572
6625
  },
6573
6626
  body: JSON.stringify({
6574
6627
  id: commentId,
6575
- body
6628
+ body,
6629
+ attachmentIds
6576
6630
  })
6577
6631
  }
6578
6632
  );
@@ -6581,7 +6635,8 @@ ${Array.from(traces).join("\n\n")}`
6581
6635
  async function editComment({
6582
6636
  threadId,
6583
6637
  commentId,
6584
- body
6638
+ body,
6639
+ attachmentIds
6585
6640
  }) {
6586
6641
  const comment = await fetchCommentsJson(
6587
6642
  `/threads/${encodeURIComponent(threadId)}/comments/${encodeURIComponent(
@@ -6593,7 +6648,8 @@ ${Array.from(traces).join("\n\n")}`
6593
6648
  "Content-Type": "application/json"
6594
6649
  },
6595
6650
  body: JSON.stringify({
6596
- body
6651
+ body,
6652
+ attachmentIds
6597
6653
  })
6598
6654
  }
6599
6655
  );
@@ -6645,6 +6701,165 @@ ${Array.from(traces).join("\n\n")}`
6645
6701
  }
6646
6702
  );
6647
6703
  }
6704
+ function prepareAttachment(file) {
6705
+ return {
6706
+ type: "localAttachment",
6707
+ status: "idle",
6708
+ id: createCommentAttachmentId(),
6709
+ name: file.name,
6710
+ size: file.size,
6711
+ mimeType: file.type,
6712
+ file
6713
+ };
6714
+ }
6715
+ async function uploadAttachment(attachment, options2 = {}) {
6716
+ const abortSignal = options2.signal;
6717
+ const abortError = abortSignal ? new DOMException(
6718
+ `Upload of attachment ${attachment.id} was aborted.`,
6719
+ "AbortError"
6720
+ ) : void 0;
6721
+ if (abortSignal?.aborted) {
6722
+ throw abortError;
6723
+ }
6724
+ if (attachment.size <= ATTACHMENT_PART_SIZE) {
6725
+ return autoRetry(
6726
+ () => fetchCommentsJson(
6727
+ `/attachments/${encodeURIComponent(attachment.id)}/upload/${encodeURIComponent(attachment.name)}`,
6728
+ {
6729
+ method: "PUT",
6730
+ body: attachment.file,
6731
+ signal: abortSignal
6732
+ },
6733
+ {
6734
+ fileSize: attachment.size
6735
+ }
6736
+ ),
6737
+ 10,
6738
+ [2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3],
6739
+ () => {
6740
+ if (abortSignal?.aborted) {
6741
+ throw abortError;
6742
+ }
6743
+ return false;
6744
+ }
6745
+ );
6746
+ } else {
6747
+ let uploadId;
6748
+ const uploadedParts = [];
6749
+ const createMultiPartUpload = await autoRetry(
6750
+ () => fetchCommentsJson(
6751
+ `/attachments/${encodeURIComponent(attachment.id)}/multipart/${encodeURIComponent(attachment.name)}`,
6752
+ {
6753
+ method: "POST",
6754
+ signal: abortSignal
6755
+ },
6756
+ {
6757
+ fileSize: attachment.size
6758
+ }
6759
+ ),
6760
+ 10,
6761
+ [2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3],
6762
+ () => {
6763
+ if (abortSignal?.aborted) {
6764
+ throw abortError;
6765
+ }
6766
+ return false;
6767
+ }
6768
+ );
6769
+ try {
6770
+ uploadId = createMultiPartUpload.uploadId;
6771
+ const parts = splitFileIntoParts(attachment.file);
6772
+ if (abortSignal?.aborted) {
6773
+ throw abortError;
6774
+ }
6775
+ const batches = chunk(parts, ATTACHMENT_PART_BATCH_SIZE);
6776
+ for (const parts2 of batches) {
6777
+ const uploadedPartsPromises = [];
6778
+ for (const { part, partNumber } of parts2) {
6779
+ uploadedPartsPromises.push(
6780
+ autoRetry(
6781
+ () => fetchCommentsJson(
6782
+ `/attachments/${encodeURIComponent(attachment.id)}/multipart/${encodeURIComponent(createMultiPartUpload.uploadId)}/${encodeURIComponent(partNumber)}`,
6783
+ {
6784
+ method: "PUT",
6785
+ body: part,
6786
+ signal: abortSignal
6787
+ }
6788
+ ),
6789
+ 10,
6790
+ [2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3, 2e3],
6791
+ () => {
6792
+ if (abortSignal?.aborted) {
6793
+ throw abortError;
6794
+ }
6795
+ return false;
6796
+ }
6797
+ )
6798
+ );
6799
+ }
6800
+ uploadedParts.push(...await Promise.all(uploadedPartsPromises));
6801
+ }
6802
+ if (abortSignal?.aborted) {
6803
+ throw abortError;
6804
+ }
6805
+ const sortedUploadedParts = uploadedParts.sort(
6806
+ (a, b) => a.partNumber - b.partNumber
6807
+ );
6808
+ return fetchCommentsJson(
6809
+ `/attachments/${encodeURIComponent(attachment.id)}/multipart/${encodeURIComponent(uploadId)}/complete`,
6810
+ {
6811
+ method: "POST",
6812
+ headers: {
6813
+ "Content-Type": "application/json"
6814
+ },
6815
+ body: JSON.stringify({ parts: sortedUploadedParts }),
6816
+ signal: abortSignal
6817
+ }
6818
+ );
6819
+ } catch (error3) {
6820
+ if (uploadId && error3?.name && (error3.name === "AbortError" || error3.name === "TimeoutError")) {
6821
+ try {
6822
+ await fetchCommentsApi(
6823
+ `/attachments/${encodeURIComponent(attachment.id)}/multipart/${encodeURIComponent(uploadId)}`,
6824
+ void 0,
6825
+ {
6826
+ method: "DELETE"
6827
+ }
6828
+ );
6829
+ } catch (error4) {
6830
+ }
6831
+ }
6832
+ throw error3;
6833
+ }
6834
+ }
6835
+ }
6836
+ async function getAttachmentUrls(attachmentIds) {
6837
+ const { urls } = await fetchCommentsJson(
6838
+ "/attachments/presigned-urls",
6839
+ {
6840
+ method: "POST",
6841
+ headers: {
6842
+ "Content-Type": "application/json"
6843
+ },
6844
+ body: JSON.stringify({ attachmentIds })
6845
+ }
6846
+ );
6847
+ return urls;
6848
+ }
6849
+ const batchedGetAttachmentUrls = new Batch(
6850
+ async (batchedAttachmentIds) => {
6851
+ const attachmentIds = batchedAttachmentIds.flat();
6852
+ const attachmentUrls = await getAttachmentUrls(attachmentIds);
6853
+ return attachmentUrls.map(
6854
+ (url) => url ?? new Error("There was an error while getting this attachment's URL")
6855
+ );
6856
+ },
6857
+ { delay: GET_ATTACHMENT_URLS_BATCH_DELAY }
6858
+ );
6859
+ const attachmentUrlsStore = createBatchStore(batchedGetAttachmentUrls);
6860
+ function getAttachmentUrl(attachmentId) {
6861
+ return batchedGetAttachmentUrls.get(attachmentId);
6862
+ }
6648
6863
  async function fetchNotificationsJson(endpoint, options2) {
6649
6864
  const authValue = await delegates.authenticate();
6650
6865
  const response = await fetchClientApi(
@@ -6760,7 +6975,8 @@ ${Array.from(traces).join("\n\n")}`
6760
6975
  // These exist only for our E2E testing app
6761
6976
  explicitClose: (event) => managedSocket._privateSendMachineEvent({ type: "EXPLICIT_SOCKET_CLOSE", event }),
6762
6977
  rawSend: (data) => managedSocket.send(data)
6763
- }
6978
+ },
6979
+ attachmentUrlsStore
6764
6980
  },
6765
6981
  id: config.roomId,
6766
6982
  subscribe: makeClassicSubscribeFn(events),
@@ -6815,6 +7031,9 @@ ${Array.from(traces).join("\n\n")}`
6815
7031
  deleteComment,
6816
7032
  addReaction,
6817
7033
  removeReaction,
7034
+ prepareAttachment,
7035
+ uploadAttachment,
7036
+ getAttachmentUrl,
6818
7037
  // Notifications
6819
7038
  getNotificationSettings,
6820
7039
  updateNotificationSettings,
@@ -7095,7 +7314,7 @@ function createClient(options) {
7095
7314
  () => !resolveUsers,
7096
7315
  "Set the resolveUsers option in createClient to specify user info."
7097
7316
  );
7098
- const usersStore = createBatchStore(
7317
+ const batchedResolveUsers = new Batch(
7099
7318
  async (batchedUserIds) => {
7100
7319
  const userIds = batchedUserIds.flat();
7101
7320
  const users = await resolveUsers?.({ userIds });
@@ -7104,12 +7323,13 @@ function createClient(options) {
7104
7323
  },
7105
7324
  { delay: RESOLVE_USERS_BATCH_DELAY }
7106
7325
  );
7326
+ const usersStore = createBatchStore(batchedResolveUsers);
7107
7327
  const resolveRoomsInfo = clientOptions.resolveRoomsInfo;
7108
7328
  const warnIfNoResolveRoomsInfo = createDevelopmentWarning(
7109
7329
  () => !resolveRoomsInfo,
7110
7330
  "Set the resolveRoomsInfo option in createClient to specify room info."
7111
7331
  );
7112
- const roomsInfoStore = createBatchStore(
7332
+ const batchedResolveRoomsInfo = new Batch(
7113
7333
  async (batchedRoomIds) => {
7114
7334
  const roomIds = batchedRoomIds.flat();
7115
7335
  const roomsInfo = await resolveRoomsInfo?.({ roomIds });
@@ -7118,6 +7338,7 @@ function createClient(options) {
7118
7338
  },
7119
7339
  { delay: RESOLVE_ROOMS_INFO_BATCH_DELAY }
7120
7340
  );
7341
+ const roomsInfoStore = createBatchStore(batchedResolveRoomsInfo);
7121
7342
  return Object.defineProperty(
7122
7343
  {
7123
7344
  enterRoom,
@@ -7203,7 +7424,7 @@ function createDevelopmentWarning(condition, ...args) {
7203
7424
 
7204
7425
  // src/comments/comment-body.ts
7205
7426
  function isCommentBodyParagraph(element) {
7206
- return "type" in element && element.type === "paragraph";
7427
+ return "type" in element && element.type === "mention";
7207
7428
  }
7208
7429
  function isCommentBodyText(element) {
7209
7430
  return !("type" in element) && "text" in element && typeof element.text === "string";
@@ -8019,7 +8240,9 @@ export {
8019
8240
  asPos,
8020
8241
  assert,
8021
8242
  assertNever,
8243
+ autoRetry,
8022
8244
  b64decode,
8245
+ chunk,
8023
8246
  cloneLson,
8024
8247
  compactObject,
8025
8248
  fancy_console_exports as console,