@liveblocks/core 3.21.0-exp9 → 3.21.0-private2

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.js 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 = "3.21.0-exp9";
9
+ var PKG_VERSION = "3.21.0-private2";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -1567,6 +1567,15 @@ function isUrl(string) {
1567
1567
  }
1568
1568
 
1569
1569
  // src/api-client.ts
1570
+ function commentsResourceForVisibility(visibility) {
1571
+ if (visibility === "private") {
1572
+ return "comments:private";
1573
+ }
1574
+ if (visibility === "public") {
1575
+ return "comments:public";
1576
+ }
1577
+ return "comments";
1578
+ }
1570
1579
  function createApiClient({
1571
1580
  baseUrl,
1572
1581
  authManager,
@@ -1618,7 +1627,7 @@ function createApiClient({
1618
1627
  url`/v2/c/rooms/${options.roomId}/threads`,
1619
1628
  await authManager.getAuthValue({
1620
1629
  roomId: options.roomId,
1621
- resource: "comments",
1630
+ resource: commentsResourceForVisibility(options.query?.visibility),
1622
1631
  access: "read"
1623
1632
  }),
1624
1633
  {
@@ -1687,11 +1696,12 @@ function createApiClient({
1687
1696
  url`/v2/c/rooms/${options.roomId}/threads`,
1688
1697
  await authManager.getAuthValue({
1689
1698
  roomId: options.roomId,
1690
- resource: "comments",
1699
+ resource: commentsResourceForVisibility(options.visibility ?? "public"),
1691
1700
  access: "write"
1692
1701
  }),
1693
1702
  {
1694
1703
  id: threadId,
1704
+ visibility: options.visibility,
1695
1705
  comment: {
1696
1706
  id: commentId,
1697
1707
  body: options.body,
@@ -1708,7 +1718,7 @@ function createApiClient({
1708
1718
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}`,
1709
1719
  await authManager.getAuthValue({
1710
1720
  roomId: options.roomId,
1711
- resource: "comments",
1721
+ resource: commentsResourceForVisibility(options.visibility),
1712
1722
  access: "write"
1713
1723
  })
1714
1724
  );
@@ -1746,7 +1756,7 @@ function createApiClient({
1746
1756
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/metadata`,
1747
1757
  await authManager.getAuthValue({
1748
1758
  roomId: options.roomId,
1749
- resource: "comments",
1759
+ resource: commentsResourceForVisibility(options.visibility),
1750
1760
  access: "write"
1751
1761
  }),
1752
1762
  options.metadata
@@ -1757,7 +1767,7 @@ function createApiClient({
1757
1767
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}/metadata`,
1758
1768
  await authManager.getAuthValue({
1759
1769
  roomId: options.roomId,
1760
- resource: "comments",
1770
+ resource: commentsResourceForVisibility(options.visibility),
1761
1771
  access: "write"
1762
1772
  }),
1763
1773
  options.metadata
@@ -1769,7 +1779,7 @@ function createApiClient({
1769
1779
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments`,
1770
1780
  await authManager.getAuthValue({
1771
1781
  roomId: options.roomId,
1772
- resource: "comments",
1782
+ resource: commentsResourceForVisibility(options.visibility),
1773
1783
  access: "write"
1774
1784
  }),
1775
1785
  {
@@ -1786,7 +1796,7 @@ function createApiClient({
1786
1796
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}`,
1787
1797
  await authManager.getAuthValue({
1788
1798
  roomId: options.roomId,
1789
- resource: "comments",
1799
+ resource: commentsResourceForVisibility(options.visibility),
1790
1800
  access: "write"
1791
1801
  }),
1792
1802
  {
@@ -1802,7 +1812,7 @@ function createApiClient({
1802
1812
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}`,
1803
1813
  await authManager.getAuthValue({
1804
1814
  roomId: options.roomId,
1805
- resource: "comments",
1815
+ resource: commentsResourceForVisibility(options.visibility),
1806
1816
  access: "write"
1807
1817
  })
1808
1818
  );
@@ -1812,7 +1822,7 @@ function createApiClient({
1812
1822
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}/reactions`,
1813
1823
  await authManager.getAuthValue({
1814
1824
  roomId: options.roomId,
1815
- resource: "comments",
1825
+ resource: commentsResourceForVisibility(options.visibility),
1816
1826
  access: "write"
1817
1827
  }),
1818
1828
  { emoji: options.emoji }
@@ -1824,7 +1834,7 @@ function createApiClient({
1824
1834
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/comments/${options.commentId}/reactions/${options.emoji}`,
1825
1835
  await authManager.getAuthValue({
1826
1836
  roomId: options.roomId,
1827
- resource: "comments",
1837
+ resource: commentsResourceForVisibility(options.visibility),
1828
1838
  access: "write"
1829
1839
  })
1830
1840
  );
@@ -1834,7 +1844,7 @@ function createApiClient({
1834
1844
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/mark-as-resolved`,
1835
1845
  await authManager.getAuthValue({
1836
1846
  roomId: options.roomId,
1837
- resource: "comments",
1847
+ resource: commentsResourceForVisibility(options.visibility),
1838
1848
  access: "write"
1839
1849
  })
1840
1850
  );
@@ -1844,7 +1854,7 @@ function createApiClient({
1844
1854
  url`/v2/c/rooms/${options.roomId}/threads/${options.threadId}/mark-as-unresolved`,
1845
1855
  await authManager.getAuthValue({
1846
1856
  roomId: options.roomId,
1847
- resource: "comments",
1857
+ resource: commentsResourceForVisibility(options.visibility),
1848
1858
  access: "write"
1849
1859
  })
1850
1860
  );
@@ -3847,7 +3857,6 @@ var ManagedSocket = class {
3847
3857
 
3848
3858
  // src/internal.ts
3849
3859
  var kInternal = /* @__PURE__ */ Symbol();
3850
- var kStorageUpdateSource = /* @__PURE__ */ Symbol();
3851
3860
 
3852
3861
  // src/lib/IncrementalJsonParser.ts
3853
3862
  var EMPTY_OBJECT = Object.freeze({});
@@ -5192,6 +5201,12 @@ var Permission = {
5192
5201
  CommentsWrite: "comments:write",
5193
5202
  CommentsRead: "comments:read",
5194
5203
  CommentsNone: "comments:none",
5204
+ CommentsPublicWrite: "comments:public:write",
5205
+ CommentsPublicRead: "comments:public:read",
5206
+ CommentsPublicNone: "comments:public:none",
5207
+ CommentsPrivateWrite: "comments:private:write",
5208
+ CommentsPrivateRead: "comments:private:read",
5209
+ CommentsPrivateNone: "comments:private:none",
5195
5210
  /**
5196
5211
  * Feeds
5197
5212
  */
@@ -5204,12 +5219,6 @@ var Permission = {
5204
5219
  LegacyRoomPresenceWrite: "room:presence:write"
5205
5220
  };
5206
5221
  var ACCESS_LEVELS = ["none", "read", "write"];
5207
- var basePermissionScopes = /* @__PURE__ */ new Set([
5208
- Permission.Read,
5209
- Permission.Write,
5210
- Permission.RoomRead,
5211
- Permission.RoomWrite
5212
- ]);
5213
5222
  var ACCESS_LEVEL_RANKS = {
5214
5223
  none: 0,
5215
5224
  read: 1,
@@ -5220,9 +5229,6 @@ var PERMISSIONS_BY_RESOURCE = {
5220
5229
  read: [Permission.Read, Permission.RoomRead],
5221
5230
  write: [Permission.Write, Permission.RoomWrite]
5222
5231
  },
5223
- personal: {
5224
- write: []
5225
- },
5226
5232
  storage: {
5227
5233
  write: [Permission.StorageWrite],
5228
5234
  read: [Permission.StorageRead],
@@ -5233,6 +5239,16 @@ var PERMISSIONS_BY_RESOURCE = {
5233
5239
  read: [Permission.CommentsRead],
5234
5240
  none: [Permission.CommentsNone]
5235
5241
  },
5242
+ "comments:public": {
5243
+ write: [Permission.CommentsPublicWrite],
5244
+ read: [Permission.CommentsPublicRead],
5245
+ none: [Permission.CommentsPublicNone]
5246
+ },
5247
+ "comments:private": {
5248
+ write: [Permission.CommentsPrivateWrite],
5249
+ read: [Permission.CommentsPrivateRead],
5250
+ none: [Permission.CommentsPrivateNone]
5251
+ },
5236
5252
  feeds: {
5237
5253
  write: [Permission.FeedsWrite],
5238
5254
  read: [Permission.FeedsRead],
@@ -5243,6 +5259,8 @@ var NO_PERMISSION_MATRIX = {
5243
5259
  room: "none",
5244
5260
  storage: "none",
5245
5261
  comments: "none",
5262
+ "comments:public": "none",
5263
+ "comments:private": "none",
5246
5264
  feeds: "none",
5247
5265
  personal: "none"
5248
5266
  };
@@ -5250,8 +5268,20 @@ var BASE_PERMISSION_RESOURCE = "room";
5250
5268
  var ROOM_PERMISSION_RESOURCES = [
5251
5269
  "storage",
5252
5270
  "comments",
5271
+ "comments:public",
5272
+ "comments:private",
5253
5273
  "feeds"
5254
5274
  ];
5275
+ var COMMENT_VISIBILITY_RESOURCES = [
5276
+ "comments:public",
5277
+ "comments:private"
5278
+ ];
5279
+ var basePermissionScopes = /* @__PURE__ */ new Set([
5280
+ Permission.Read,
5281
+ Permission.Write,
5282
+ Permission.RoomRead,
5283
+ Permission.RoomWrite
5284
+ ]);
5255
5285
  var VALID_PERMISSIONS = new Set(Object.values(Permission));
5256
5286
  function isPermission(permission) {
5257
5287
  return VALID_PERMISSIONS.has(permission);
@@ -5267,34 +5297,40 @@ function resolveResourceAccess(scopes, resource) {
5267
5297
  }
5268
5298
  return resourceAccess;
5269
5299
  }
5270
- function permissionMatrixFromResolvedScopes(resolved) {
5271
- if (!resolved.hasDefaultPermission) {
5272
- return { ...NO_PERMISSION_MATRIX };
5273
- }
5274
- const matrix = {
5275
- ...NO_PERMISSION_MATRIX,
5276
- [BASE_PERMISSION_RESOURCE]: resolved.baseAccess,
5277
- personal: "write"
5278
- };
5279
- for (const resource of ROOM_PERMISSION_RESOURCES) {
5280
- matrix[resource] = resolved.matrix[resource] ?? resolved.baseAccess;
5281
- }
5282
- return matrix;
5283
- }
5284
- function permissionMatrixFromScopes(scopes) {
5285
- return permissionMatrixFromResolvedScopes(resolvePermissionScopes(scopes));
5286
- }
5287
- function resolvePermissionScopes(scopes) {
5288
- const hasDefaultPermission = scopes.includes(Permission.Write) || scopes.includes(Permission.Read) || scopes.includes(Permission.RoomWrite) || scopes.includes(Permission.RoomRead);
5289
- const baseAccess = scopes.includes(Permission.Write) || scopes.includes(Permission.RoomWrite) ? "write" : scopes.includes(Permission.Read) || scopes.includes(Permission.RoomRead) ? "read" : "none";
5300
+ function explicitPermissionMatrixFromScopes(scopes) {
5290
5301
  const matrix = {};
5302
+ const baseAccess = resolveResourceAccess(scopes, BASE_PERMISSION_RESOURCE);
5303
+ if (baseAccess !== void 0) {
5304
+ matrix.room = baseAccess;
5305
+ }
5291
5306
  for (const resource of ROOM_PERMISSION_RESOURCES) {
5292
5307
  const access = resolveResourceAccess(scopes, resource);
5293
5308
  if (access !== void 0) {
5294
5309
  matrix[resource] = access;
5295
5310
  }
5296
5311
  }
5297
- return { hasDefaultPermission, baseAccess, matrix };
5312
+ return matrix;
5313
+ }
5314
+ function permissionMatrixFromExplicitPermissions(explicitMatrix) {
5315
+ const baseAccess = explicitMatrix.room;
5316
+ if (baseAccess === void 0) {
5317
+ return { ...NO_PERMISSION_MATRIX };
5318
+ }
5319
+ const commentsAccess = explicitMatrix.comments ?? baseAccess;
5320
+ return {
5321
+ room: baseAccess,
5322
+ storage: explicitMatrix.storage ?? baseAccess,
5323
+ comments: commentsAccess,
5324
+ "comments:public": explicitMatrix["comments:public"] ?? commentsAccess,
5325
+ "comments:private": explicitMatrix["comments:private"] ?? commentsAccess,
5326
+ feeds: explicitMatrix.feeds ?? baseAccess,
5327
+ personal: "write"
5328
+ };
5329
+ }
5330
+ function permissionMatrixFromScopes(scopes) {
5331
+ return permissionMatrixFromExplicitPermissions(
5332
+ explicitPermissionMatrixFromScopes(scopes)
5333
+ );
5298
5334
  }
5299
5335
  function hasPermissionAccess(matrix, resource, requiredAccess) {
5300
5336
  const access = matrix[resource] ?? "none";
@@ -5307,38 +5343,29 @@ function resolveRoomPermissionMatrix(permissions, roomId) {
5307
5343
  if (matchedPermissions.length === 0) {
5308
5344
  return void 0;
5309
5345
  }
5310
- let hasDefaultPermission = false;
5311
- let baseAccess = "none";
5312
- const explicitMatrix = {};
5313
- const explicitSpecificity = {};
5346
+ const matrix = {};
5347
+ const specificityByResource = {};
5314
5348
  for (const entry of matchedPermissions) {
5315
- const resolved = resolvePermissionScopes(entry.scopes);
5349
+ const explicitMatrix = explicitPermissionMatrixFromScopes(entry.scopes);
5316
5350
  const specificity = roomPatternSpecificity(entry.pattern);
5317
- if (resolved.hasDefaultPermission) {
5318
- hasDefaultPermission = true;
5319
- baseAccess = strongestAccess(baseAccess, resolved.baseAccess);
5351
+ if (explicitMatrix.room !== void 0) {
5352
+ matrix.room = strongestAccess(matrix.room ?? "none", explicitMatrix.room);
5320
5353
  }
5321
5354
  for (const resource of ROOM_PERMISSION_RESOURCES) {
5322
- const access = resolved.matrix[resource];
5323
- if (access !== void 0) {
5324
- const currentSpecificity = explicitSpecificity[resource] ?? -1;
5325
- if (specificity > currentSpecificity) {
5326
- explicitMatrix[resource] = access;
5327
- explicitSpecificity[resource] = specificity;
5328
- } else if (specificity === currentSpecificity) {
5329
- explicitMatrix[resource] = strongestAccess(
5330
- explicitMatrix[resource] ?? "none",
5331
- access
5332
- );
5333
- }
5355
+ const access = explicitAccessForResource(explicitMatrix, resource);
5356
+ if (access === void 0) {
5357
+ continue;
5358
+ }
5359
+ const currentSpecificity = specificityByResource[resource] ?? -1;
5360
+ if (specificity > currentSpecificity) {
5361
+ matrix[resource] = access;
5362
+ specificityByResource[resource] = specificity;
5363
+ } else if (specificity === currentSpecificity) {
5364
+ matrix[resource] = strongestAccess(matrix[resource] ?? "none", access);
5334
5365
  }
5335
5366
  }
5336
5367
  }
5337
- return permissionMatrixFromResolvedScopes({
5338
- hasDefaultPermission,
5339
- baseAccess,
5340
- matrix: explicitMatrix
5341
- });
5368
+ return permissionMatrixFromExplicitPermissions(matrix);
5342
5369
  }
5343
5370
  function normalizeRoomPermissions(permissions) {
5344
5371
  if (!Array.isArray(permissions)) {
@@ -5381,12 +5408,21 @@ function permissionMatrixToScopes(matrix) {
5381
5408
  if (baseAccess !== "none") {
5382
5409
  scopes.push(permissionForAccessLevel(BASE_PERMISSION_RESOURCE, baseAccess));
5383
5410
  }
5384
- for (const resource of ROOM_PERMISSION_RESOURCES) {
5385
- const access = matrix[resource];
5386
- if (access !== baseAccess) {
5387
- scopes.push(permissionForAccessLevel(resource, access));
5411
+ if (matrix.storage !== baseAccess) {
5412
+ scopes.push(permissionForAccessLevel("storage", matrix.storage));
5413
+ }
5414
+ const commentsAccess = matrix.comments;
5415
+ if (commentsAccess !== baseAccess) {
5416
+ scopes.push(permissionForAccessLevel("comments", commentsAccess));
5417
+ }
5418
+ for (const resource of COMMENT_VISIBILITY_RESOURCES) {
5419
+ if (matrix[resource] !== commentsAccess) {
5420
+ scopes.push(permissionForAccessLevel(resource, matrix[resource]));
5388
5421
  }
5389
5422
  }
5423
+ if (matrix.feeds !== baseAccess) {
5424
+ scopes.push(permissionForAccessLevel("feeds", matrix.feeds));
5425
+ }
5390
5426
  return scopes;
5391
5427
  }
5392
5428
  function mergeRoomPermissionScopes({
@@ -5395,63 +5431,55 @@ function mergeRoomPermissionScopes({
5395
5431
  userAccesses
5396
5432
  }) {
5397
5433
  const sources = [
5398
- resolvePermissionScopes(defaultAccesses),
5399
- mergeResolvedScopesByHighestAccess(
5400
- groupsAccesses.map(resolvePermissionScopes)
5434
+ explicitPermissionMatrixFromScopes(defaultAccesses),
5435
+ mergeExplicitPermissionMatricesByHighestAccess(
5436
+ groupsAccesses.map(explicitPermissionMatrixFromScopes)
5401
5437
  ),
5402
- resolvePermissionScopes(userAccesses)
5438
+ explicitPermissionMatrixFromScopes(userAccesses)
5403
5439
  ];
5404
- const merged = {
5405
- hasDefaultPermission: false,
5406
- baseAccess: "none",
5407
- matrix: {}
5408
- };
5440
+ const merged = {};
5409
5441
  for (const source of sources) {
5410
- if (source.hasDefaultPermission) {
5411
- merged.hasDefaultPermission = true;
5412
- merged.baseAccess = source.baseAccess;
5442
+ if (source.room !== void 0) {
5443
+ merged.room = source.room;
5413
5444
  }
5414
5445
  for (const resource of ROOM_PERMISSION_RESOURCES) {
5415
- const access = source.matrix[resource];
5446
+ const access = explicitAccessForResource(source, resource);
5416
5447
  if (access !== void 0) {
5417
- merged.matrix[resource] = access;
5448
+ merged[resource] = access;
5418
5449
  }
5419
5450
  }
5420
5451
  }
5421
- return permissionMatrixToScopes(permissionMatrixFromResolvedScopes(merged));
5452
+ return permissionMatrixToScopes(
5453
+ permissionMatrixFromExplicitPermissions(merged)
5454
+ );
5422
5455
  }
5423
- function mergeResolvedScopesByHighestAccess(sources) {
5424
- const merged = {
5425
- hasDefaultPermission: false,
5426
- baseAccess: "none",
5427
- matrix: {}
5428
- };
5456
+ function mergeExplicitPermissionMatricesByHighestAccess(sources) {
5457
+ const merged = {};
5429
5458
  for (const source of sources) {
5430
- if (source.hasDefaultPermission) {
5431
- merged.hasDefaultPermission = true;
5432
- merged.baseAccess = strongestAccess(merged.baseAccess, source.baseAccess);
5459
+ if (source.room !== void 0) {
5460
+ merged.room = strongestAccess(merged.room ?? "none", source.room);
5433
5461
  }
5434
5462
  for (const resource of ROOM_PERMISSION_RESOURCES) {
5435
- const access = source.matrix[resource];
5463
+ const access = explicitAccessForResource(source, resource);
5436
5464
  if (access !== void 0) {
5437
- merged.matrix[resource] = strongestAccess(
5438
- merged.matrix[resource] ?? "none",
5439
- access
5440
- );
5465
+ merged[resource] = strongestAccess(merged[resource] ?? "none", access);
5441
5466
  }
5442
5467
  }
5443
5468
  }
5444
5469
  return merged;
5445
5470
  }
5471
+ function explicitAccessForResource(source, resource) {
5472
+ return source[resource] ?? (isCommentVisibilityResource(resource) ? source.comments : void 0);
5473
+ }
5446
5474
  function permissionForAccessLevel(resource, access, field = resource) {
5447
- const levels = PERMISSIONS_BY_RESOURCE[resource];
5448
- const permissions = levels[access];
5449
- if (permissions === void 0 || permissions.length === 0) {
5450
- throw new Error(
5451
- `Invalid permission level for ${field}: ${JSON.stringify(access) ?? String(access)}`
5452
- );
5475
+ const permissions = PERMISSIONS_BY_RESOURCE[resource][access];
5476
+ const permission = permissions?.[0];
5477
+ if (permission !== void 0) {
5478
+ return permission;
5453
5479
  }
5454
- return permissions[0];
5480
+ throw new Error(
5481
+ `Invalid permission level for ${field}: ${JSON.stringify(access) ?? String(access)}`
5482
+ );
5455
5483
  }
5456
5484
  function strongestAccess(left, right) {
5457
5485
  return ACCESS_LEVEL_RANKS[right] > ACCESS_LEVEL_RANKS[left] ? right : left;
@@ -5479,7 +5507,7 @@ function validatePermissionsSet(scopes) {
5479
5507
  if (basePermissionScopes.has(scope) || scope === Permission.LegacyRoomPresenceWrite) {
5480
5508
  continue;
5481
5509
  }
5482
- const feature = scope.slice(0, scope.indexOf(":"));
5510
+ const feature = permissionFeature(scope);
5483
5511
  if (seenFeatures.has(feature)) {
5484
5512
  return `Permissions can include at most one scope per feature, got multiple "${feature}" scopes`;
5485
5513
  }
@@ -5487,6 +5515,13 @@ function validatePermissionsSet(scopes) {
5487
5515
  }
5488
5516
  return true;
5489
5517
  }
5518
+ function permissionFeature(scope) {
5519
+ const accessSeparatorIndex = scope.lastIndexOf(":");
5520
+ return accessSeparatorIndex === -1 ? scope : scope.slice(0, accessSeparatorIndex);
5521
+ }
5522
+ function isCommentVisibilityResource(resource) {
5523
+ return resource.startsWith("comments:");
5524
+ }
5490
5525
 
5491
5526
  // src/protocol/AuthToken.ts
5492
5527
  function isValidAuthTokenPayload(data) {
@@ -5674,7 +5709,13 @@ function cachedTokenSatisfiesRequest(cachedToken, request) {
5674
5709
  cachedToken.permissions ?? [],
5675
5710
  request.roomId
5676
5711
  );
5677
- return matrix !== void 0 && hasPermissionAccess(matrix, request.resource, request.access);
5712
+ if (matrix === void 0) {
5713
+ return false;
5714
+ }
5715
+ if (request.resource === "comments" && request.access === "read") {
5716
+ return hasPermissionAccess(matrix, "comments", "read") || hasPermissionAccess(matrix, "comments:public", "read") || hasPermissionAccess(matrix, "comments:private", "read");
5717
+ }
5718
+ return hasPermissionAccess(matrix, request.resource, request.access);
5678
5719
  }
5679
5720
  function prepareAuthentication(authOptions) {
5680
5721
  const { publicApiKey, authEndpoint } = authOptions;
@@ -5768,15 +5809,13 @@ var OpCode = Object.freeze({
5768
5809
  DELETE_CRDT: 5,
5769
5810
  DELETE_OBJECT_KEY: 6,
5770
5811
  CREATE_MAP: 7,
5771
- CREATE_REGISTER: 8,
5772
- CREATE_TEXT: 9,
5773
- UPDATE_TEXT: 10
5812
+ CREATE_REGISTER: 8
5774
5813
  });
5775
5814
  function isIgnoredOp(op) {
5776
5815
  return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
5777
5816
  }
5778
5817
  function isCreateOp(op) {
5779
- return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_TEXT;
5818
+ return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST;
5780
5819
  }
5781
5820
 
5782
5821
  // src/protocol/StorageNode.ts
@@ -5784,8 +5823,7 @@ var CrdtType = Object.freeze({
5784
5823
  OBJECT: 0,
5785
5824
  LIST: 1,
5786
5825
  MAP: 2,
5787
- REGISTER: 3,
5788
- TEXT: 4
5826
+ REGISTER: 3
5789
5827
  });
5790
5828
  function isRootStorageNode(node) {
5791
5829
  return node[0] === "root";
@@ -5802,9 +5840,6 @@ function isMapStorageNode(node) {
5802
5840
  function isRegisterStorageNode(node) {
5803
5841
  return node[1].type === CrdtType.REGISTER;
5804
5842
  }
5805
- function isTextStorageNode(node) {
5806
- return node[1].type === CrdtType.TEXT;
5807
- }
5808
5843
  function isCompactRootNode(node) {
5809
5844
  return node[0] === "root";
5810
5845
  }
@@ -5827,9 +5862,6 @@ function* compactNodesToNodeStream(compactNodes) {
5827
5862
  case CrdtType.REGISTER:
5828
5863
  yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
5829
5864
  break;
5830
- case CrdtType.TEXT:
5831
- yield [cnode[0], { type: CrdtType.TEXT, parentId: cnode[2], parentKey: cnode[3], data: cnode[4], version: cnode[5] }];
5832
- break;
5833
5865
  default:
5834
5866
  }
5835
5867
  }
@@ -5858,17 +5890,6 @@ function* nodeStreamToCompactNodes(nodes) {
5858
5890
  const id = node[0];
5859
5891
  const crdt = node[1];
5860
5892
  yield [id, CrdtType.REGISTER, crdt.parentId, crdt.parentKey, crdt.data];
5861
- } else if (isTextStorageNode(node)) {
5862
- const id = node[0];
5863
- const crdt = node[1];
5864
- yield [
5865
- id,
5866
- CrdtType.TEXT,
5867
- crdt.parentId,
5868
- crdt.parentKey,
5869
- crdt.data,
5870
- crdt.version
5871
- ];
5872
5893
  } else {
5873
5894
  }
5874
5895
  }
@@ -6071,10 +6092,6 @@ ${parentKey}`;
6071
6092
  get size() {
6072
6093
  return this.#byOpId.size;
6073
6094
  }
6074
- /** The still-unacknowledged op with the given opId, if any. */
6075
- get(opId) {
6076
- return this.#byOpId.get(opId);
6077
- }
6078
6095
  /**
6079
6096
  * Mark the given Op as still unacknowledged.
6080
6097
  */
@@ -6175,8 +6192,8 @@ function createManagedPool(roomId, options) {
6175
6192
  deleteNode: (id) => void nodes.delete(id),
6176
6193
  generateId: () => `${getCurrentConnectionId()}:${clock++}`,
6177
6194
  generateOpId: () => `${getCurrentConnectionId()}:${opClock++}`,
6178
- dispatch(ops, reverse, storageUpdates, options2) {
6179
- onDispatch?.(ops, reverse, storageUpdates, options2);
6195
+ dispatch(ops, reverse, storageUpdates) {
6196
+ onDispatch?.(ops, reverse, storageUpdates);
6180
6197
  },
6181
6198
  assertStorageIsWritable: () => {
6182
6199
  if (!isStorageWritable()) {
@@ -8556,7 +8573,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8556
8573
  const preciseSize = new TextEncoder().encode(jsonString).length;
8557
8574
  if (preciseSize > MAX_LIVE_OBJECT_SIZE) {
8558
8575
  throw new Error(
8559
- `LiveObject size exceeded limit: ${preciseSize} bytes > ${MAX_LIVE_OBJECT_SIZE} bytes. See https://liveblocks.io/docs/platform/limits#Liveblocks-Storage-limits`
8576
+ `LiveObject size exceeded limit: ${preciseSize} bytes > ${MAX_LIVE_OBJECT_SIZE} bytes. See https://liveblocks.io/docs/pricing/limits#Other-limits`
8560
8577
  );
8561
8578
  }
8562
8579
  }
@@ -8735,1269 +8752,149 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8735
8752
  }
8736
8753
  };
8737
8754
 
8738
- // src/crdts/liveTextOps.ts
8739
- function attributesEqual(left, right) {
8740
- if (left === right) {
8755
+ // src/crdts/liveblocks-helpers.ts
8756
+ function creationOpToLiveNode(op) {
8757
+ return lsonToLiveNode(creationOpToLson(op));
8758
+ }
8759
+ function creationOpToLson(op) {
8760
+ switch (op.type) {
8761
+ case OpCode.CREATE_REGISTER:
8762
+ return op.data;
8763
+ case OpCode.CREATE_OBJECT:
8764
+ return new LiveObject(op.data);
8765
+ case OpCode.CREATE_MAP:
8766
+ return new LiveMap();
8767
+ case OpCode.CREATE_LIST:
8768
+ return new LiveList([]);
8769
+ default:
8770
+ return assertNever(op, "Unknown creation Op");
8771
+ }
8772
+ }
8773
+ function isSameNodeOrChildOf(node, parent) {
8774
+ if (node === parent) {
8741
8775
  return true;
8742
8776
  }
8743
- if (left === void 0 || right === void 0) {
8744
- return false;
8777
+ if (node.parent.type === "HasParent") {
8778
+ return isSameNodeOrChildOf(node.parent.node, parent);
8745
8779
  }
8746
- const leftKeys = Object.keys(left);
8747
- const rightKeys = Object.keys(right);
8748
- if (leftKeys.length !== rightKeys.length) {
8749
- return false;
8780
+ return false;
8781
+ }
8782
+ function deserialize(node, parentToChildren, pool) {
8783
+ if (isObjectStorageNode(node)) {
8784
+ return LiveObject._deserialize(node, parentToChildren, pool);
8785
+ } else if (isListStorageNode(node)) {
8786
+ return LiveList._deserialize(node, parentToChildren, pool);
8787
+ } else if (isMapStorageNode(node)) {
8788
+ return LiveMap._deserialize(node, parentToChildren, pool);
8789
+ } else if (isRegisterStorageNode(node)) {
8790
+ return LiveRegister._deserialize(node, parentToChildren, pool);
8791
+ } else {
8792
+ throw new Error("Unexpected CRDT type");
8750
8793
  }
8751
- for (const key of leftKeys) {
8752
- if (left[key] !== right[key]) {
8753
- return false;
8754
- }
8794
+ }
8795
+ function deserializeToLson(node, parentToChildren, pool) {
8796
+ if (isObjectStorageNode(node)) {
8797
+ return LiveObject._deserialize(node, parentToChildren, pool);
8798
+ } else if (isListStorageNode(node)) {
8799
+ return LiveList._deserialize(node, parentToChildren, pool);
8800
+ } else if (isMapStorageNode(node)) {
8801
+ return LiveMap._deserialize(node, parentToChildren, pool);
8802
+ } else if (isRegisterStorageNode(node)) {
8803
+ return node[1].data;
8804
+ } else {
8805
+ throw new Error("Unexpected CRDT type");
8755
8806
  }
8756
- return true;
8757
8807
  }
8758
- function cloneAttributes(attributes) {
8759
- return attributes === void 0 ? void 0 : freeze({ ...attributes });
8808
+ function isLiveStructure(value) {
8809
+ return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
8760
8810
  }
8761
- function normalizeSegments(segments) {
8762
- const normalized = [];
8763
- for (const segment of segments) {
8764
- if (segment.text.length === 0) {
8765
- continue;
8766
- }
8767
- const last = normalized.at(-1);
8768
- const attributes = cloneAttributes(segment.attributes);
8769
- if (last !== void 0 && attributesEqual(last.attributes, attributes)) {
8770
- last.text += segment.text;
8771
- } else {
8772
- normalized.push({ text: segment.text, attributes });
8773
- }
8774
- }
8775
- return normalized;
8811
+ function isLiveNode(value) {
8812
+ return isLiveStructure(value) || isLiveRegister(value);
8776
8813
  }
8777
- function dataToSegments(data) {
8778
- return normalizeSegments(
8779
- data.map(([text, attributes]) => ({
8780
- text,
8781
- attributes
8782
- }))
8783
- );
8814
+ function isLiveList(value) {
8815
+ return value instanceof LiveList;
8784
8816
  }
8785
- function segmentsToData(segments) {
8786
- return segments.map(
8787
- (segment) => segment.attributes === void 0 ? [segment.text] : [segment.text, { ...segment.attributes }]
8788
- );
8817
+ function isLiveMap(value) {
8818
+ return value instanceof LiveMap;
8789
8819
  }
8790
- function textLength(segments) {
8791
- return segments.reduce((sum, segment) => sum + segment.text.length, 0);
8820
+ function isLiveObject(value) {
8821
+ return value instanceof LiveObject;
8792
8822
  }
8793
- function splitSegmentsAt(segments, index) {
8794
- const result = [];
8795
- let offset = 0;
8796
- for (const segment of segments) {
8797
- const end = offset + segment.text.length;
8798
- if (index > offset && index < end) {
8799
- const before2 = segment.text.slice(0, index - offset);
8800
- const after2 = segment.text.slice(index - offset);
8801
- result.push({ text: before2, attributes: segment.attributes });
8802
- result.push({ text: after2, attributes: segment.attributes });
8803
- } else {
8804
- result.push({ text: segment.text, attributes: segment.attributes });
8805
- }
8806
- offset = end;
8807
- }
8808
- return result;
8823
+ function isLiveRegister(value) {
8824
+ return value instanceof LiveRegister;
8809
8825
  }
8810
- function clipRange(index, length, contentLength) {
8811
- const clippedIndex = Math.max(0, Math.min(index, contentLength));
8812
- const clippedEnd = Math.max(
8813
- clippedIndex,
8814
- Math.min(index + length, contentLength)
8815
- );
8816
- return { index: clippedIndex, length: clippedEnd - clippedIndex };
8826
+ function cloneLson(value) {
8827
+ return value === void 0 ? void 0 : isLiveStructure(value) ? value.clone() : deepClone(value);
8817
8828
  }
8818
- function applyInsert(segments, index, text, attributes) {
8819
- if (text.length === 0) {
8820
- return normalizeSegments(segments);
8821
- }
8822
- const split = splitSegmentsAt(segments, index);
8823
- const result = [];
8824
- let offset = 0;
8825
- let inserted = false;
8826
- for (const segment of split) {
8827
- if (!inserted && offset === index) {
8828
- result.push({ text, attributes });
8829
- inserted = true;
8830
- }
8831
- result.push(segment);
8832
- offset += segment.text.length;
8833
- }
8834
- if (!inserted) {
8835
- result.push({ text, attributes });
8829
+ function liveNodeToLson(obj) {
8830
+ if (obj instanceof LiveRegister) {
8831
+ return obj.data;
8832
+ } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject) {
8833
+ return obj;
8834
+ } else {
8835
+ return assertNever(obj, "Unknown AbstractCrdt");
8836
8836
  }
8837
- return normalizeSegments(result);
8838
8837
  }
8839
- function extractDeletedSegments(segments, index, length) {
8840
- const split = splitSegmentsAt(
8841
- splitSegmentsAt(segments, index),
8842
- index + length
8843
- );
8844
- const deleted = [];
8845
- let offset = 0;
8846
- for (const segment of split) {
8847
- const end = offset + segment.text.length;
8848
- if (offset >= index && end <= index + length) {
8849
- deleted.push({
8850
- text: segment.text,
8851
- attributes: segment.attributes
8852
- });
8853
- }
8854
- offset = end;
8838
+ function lsonToLiveNode(value) {
8839
+ if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList) {
8840
+ return value;
8841
+ } else {
8842
+ return new LiveRegister(value);
8855
8843
  }
8856
- return normalizeSegments(deleted);
8857
8844
  }
8858
- function applyDelete(segments, index, length) {
8859
- const deletedSegments = extractDeletedSegments(segments, index, length);
8860
- const split = splitSegmentsAt(
8861
- splitSegmentsAt(segments, index),
8862
- index + length
8863
- );
8864
- const result = [];
8865
- let offset = 0;
8866
- let deletedText = "";
8867
- for (const segment of split) {
8868
- const end = offset + segment.text.length;
8869
- if (offset >= index && end <= index + length) {
8870
- deletedText += segment.text;
8845
+ function dumpPool(pool) {
8846
+ const rows = Array.from(pool.nodes.values(), (node) => {
8847
+ const parent = node.parent;
8848
+ const parentId = parent.type === "HasParent" ? parent.node._id ?? "?" : parent.type === "Orphaned" ? "<orphaned>" : "-";
8849
+ let value;
8850
+ if (node instanceof LiveRegister) {
8851
+ value = stringifyOrLog(node.data);
8852
+ } else if (node instanceof LiveList) {
8853
+ value = "<LiveList>";
8854
+ } else if (node instanceof LiveMap) {
8855
+ value = "<LiveMap>";
8871
8856
  } else {
8872
- result.push(segment);
8857
+ value = "<LiveObject>";
8873
8858
  }
8874
- offset = end;
8875
- }
8876
- return {
8877
- segments: normalizeSegments(result),
8878
- deletedText,
8879
- deletedSegments
8880
- };
8859
+ return { id: nn(node._id), parentId, key: node._parentKey ?? "", value };
8860
+ });
8861
+ rows.sort((a, b) => {
8862
+ if (a.parentId !== b.parentId) return a.parentId < b.parentId ? -1 : 1;
8863
+ if (a.key !== b.key) return a.key < b.key ? -1 : 1;
8864
+ return 0;
8865
+ });
8866
+ return rows.map(
8867
+ (r) => ` ${r.id} parent=${r.parentId} key=${r.key || "\u2014"} ${r.value}`
8868
+ ).join("\n");
8881
8869
  }
8882
- function applyFormat(segments, index, length, attributes) {
8883
- const split = splitSegmentsAt(
8884
- splitSegmentsAt(segments, index),
8885
- index + length
8886
- );
8887
- const result = [];
8888
- let offset = 0;
8889
- for (const segment of split) {
8890
- const end = offset + segment.text.length;
8891
- if (offset >= index && end <= index + length) {
8892
- const nextAttributes = {
8893
- ...segment.attributes ?? {}
8894
- };
8895
- for (const [key, value] of Object.entries(attributes)) {
8896
- if (value === null) {
8897
- delete nextAttributes[key];
8898
- } else {
8899
- nextAttributes[key] = value;
8900
- }
8901
- }
8902
- result.push({
8903
- text: segment.text,
8904
- attributes: Object.keys(nextAttributes).length === 0 ? void 0 : freeze(nextAttributes)
8905
- });
8906
- } else {
8907
- result.push(segment);
8908
- }
8909
- offset = end;
8870
+ function isJsonEq(a, b) {
8871
+ if (a === b) {
8872
+ return true;
8910
8873
  }
8911
- return normalizeSegments(result);
8912
- }
8913
- function formatReverseOperations(segments, index, length, patch) {
8914
- const split = splitSegmentsAt(
8915
- splitSegmentsAt(segments, index),
8916
- index + length
8917
- );
8918
- const result = [];
8919
- let offset = 0;
8920
- for (const segment of split) {
8921
- const end = offset + segment.text.length;
8922
- if (offset >= index && end <= index + length) {
8923
- const attributes = {};
8924
- for (const key of Object.keys(patch)) {
8925
- attributes[key] = segment.attributes?.[key] ?? null;
8926
- }
8927
- result.push({
8928
- type: "format",
8929
- index: offset,
8930
- length: segment.text.length,
8931
- attributes
8932
- });
8933
- }
8934
- offset = end;
8874
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
8875
+ return false;
8935
8876
  }
8936
- return result;
8937
- }
8938
- function mapIndexThroughOperation(index, op) {
8939
- if (op.type === "insert") {
8940
- return op.index <= index ? index + op.text.length : index;
8941
- } else if (op.type === "delete") {
8942
- if (op.index >= index) {
8943
- return index;
8877
+ if (Array.isArray(a) || Array.isArray(b)) {
8878
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
8879
+ return false;
8944
8880
  }
8945
- return Math.max(op.index, index - op.length);
8946
- } else {
8947
- return index;
8881
+ for (let i = 0; i < a.length; i++) {
8882
+ if (!isJsonEq(a[i], b[i])) {
8883
+ return false;
8884
+ }
8885
+ }
8886
+ return true;
8948
8887
  }
8949
- }
8950
- function mapTextIndexThroughOperations(index, ops) {
8951
- let mapped = index;
8952
- for (const op of ops) {
8953
- mapped = mapIndexThroughOperation(mapped, op);
8888
+ const aKeys = Object.keys(a);
8889
+ if (aKeys.length !== Object.keys(b).length) {
8890
+ return false;
8954
8891
  }
8955
- return mapped;
8956
- }
8957
- function inverseMapIndexThroughOperation(index, op) {
8958
- if (op.type === "insert") {
8959
- if (index <= op.index) {
8960
- return index;
8892
+ for (const key of aKeys) {
8893
+ if (!isJsonEq(a[key], b[key])) {
8894
+ return false;
8961
8895
  }
8962
- return Math.max(op.index, index - op.text.length);
8963
- } else if (op.type === "delete") {
8964
- return op.index <= index ? index + op.length : index;
8965
- } else {
8966
- return index;
8967
- }
8968
- }
8969
- function inverseMapTextIndexThroughOperations(index, ops) {
8970
- let mapped = index;
8971
- for (let i = ops.length - 1; i >= 0; i--) {
8972
- mapped = inverseMapIndexThroughOperation(mapped, ops[i]);
8973
8896
  }
8974
- return mapped;
8975
- }
8976
- function oppositeOrder(order) {
8977
- return order === "before" ? "after" : "before";
8978
- }
8979
- function mapIndexOverDelete(index, deleteIndex, deleteLength) {
8980
- if (deleteIndex >= index) {
8981
- return index;
8982
- }
8983
- return Math.max(deleteIndex, index - deleteLength);
8984
- }
8985
- function transformInsert(op, over, order) {
8986
- if (over.type === "insert") {
8987
- const shifts = over.index < op.index || over.index === op.index && order === "after";
8988
- return [shifts ? { ...op, index: op.index + over.text.length } : { ...op }];
8989
- } else if (over.type === "delete") {
8990
- return [
8991
- { ...op, index: mapIndexOverDelete(op.index, over.index, over.length) }
8992
- ];
8993
- } else {
8994
- return [{ ...op }];
8995
- }
8996
- }
8997
- function transformDelete(op, over) {
8998
- const start = op.index;
8999
- const end = op.index + op.length;
9000
- if (over.type === "insert") {
9001
- const at = over.index;
9002
- const len = over.text.length;
9003
- if (at <= start) {
9004
- return [{ ...op, index: start + len }];
9005
- }
9006
- if (at >= end) {
9007
- return [{ ...op }];
9008
- }
9009
- return [
9010
- { type: "delete", index: start, length: at - start },
9011
- { type: "delete", index: start + len, length: end - at }
9012
- ];
9013
- } else if (over.type === "delete") {
9014
- const newStart = mapIndexOverDelete(start, over.index, over.length);
9015
- const newEnd = mapIndexOverDelete(end, over.index, over.length);
9016
- return newEnd - newStart > 0 ? [{ type: "delete", index: newStart, length: newEnd - newStart }] : [];
9017
- } else {
9018
- return [{ ...op }];
9019
- }
9020
- }
9021
- function transformFormat(op, over, order) {
9022
- const start = op.index;
9023
- const end = op.index + op.length;
9024
- if (over.type === "insert") {
9025
- const at = over.index;
9026
- const len = over.text.length;
9027
- if (at <= start) {
9028
- return [{ ...op, index: start + len }];
9029
- }
9030
- if (at >= end) {
9031
- return [{ ...op }];
9032
- }
9033
- return [
9034
- {
9035
- type: "format",
9036
- index: start,
9037
- length: at - start,
9038
- attributes: op.attributes
9039
- },
9040
- {
9041
- type: "format",
9042
- index: at + len,
9043
- length: end - at,
9044
- attributes: op.attributes
9045
- }
9046
- ];
9047
- } else if (over.type === "delete") {
9048
- const newStart = mapIndexOverDelete(start, over.index, over.length);
9049
- const newEnd = mapIndexOverDelete(end, over.index, over.length);
9050
- return newEnd - newStart > 0 ? [
9051
- {
9052
- type: "format",
9053
- index: newStart,
9054
- length: newEnd - newStart,
9055
- attributes: op.attributes
9056
- }
9057
- ] : [];
9058
- } else {
9059
- if (order === "after") {
9060
- return [{ ...op }];
9061
- }
9062
- const overlapStart = Math.max(start, over.index);
9063
- const overlapEnd = Math.min(end, over.index + over.length);
9064
- if (overlapStart >= overlapEnd) {
9065
- return [{ ...op }];
9066
- }
9067
- const hasConflict = Object.keys(op.attributes).some(
9068
- (key) => key in over.attributes
9069
- );
9070
- if (!hasConflict) {
9071
- return [{ ...op }];
9072
- }
9073
- const reduced = {};
9074
- for (const [key, value] of Object.entries(op.attributes)) {
9075
- if (!(key in over.attributes)) {
9076
- reduced[key] = value;
9077
- }
9078
- }
9079
- const pieces = [];
9080
- if (start < overlapStart) {
9081
- pieces.push({
9082
- type: "format",
9083
- index: start,
9084
- length: overlapStart - start,
9085
- attributes: op.attributes
9086
- });
9087
- }
9088
- if (Object.keys(reduced).length > 0) {
9089
- pieces.push({
9090
- type: "format",
9091
- index: overlapStart,
9092
- length: overlapEnd - overlapStart,
9093
- attributes: reduced
9094
- });
9095
- }
9096
- if (overlapEnd < end) {
9097
- pieces.push({
9098
- type: "format",
9099
- index: overlapEnd,
9100
- length: end - overlapEnd,
9101
- attributes: op.attributes
9102
- });
9103
- }
9104
- return pieces;
9105
- }
9106
- }
9107
- function transformSingle(op, over, order) {
9108
- switch (op.type) {
9109
- case "insert":
9110
- return transformInsert(op, over, order);
9111
- case "delete":
9112
- return transformDelete(op, over);
9113
- case "format":
9114
- return transformFormat(op, over, order);
9115
- }
9116
- }
9117
- function transformTextOperationsX(a, b, order) {
9118
- if (a.length === 0 || b.length === 0) {
9119
- return [[...a], [...b]];
9120
- }
9121
- if (a.length === 1 && b.length === 1) {
9122
- return [
9123
- transformSingle(a[0], b[0], order),
9124
- transformSingle(b[0], a[0], oppositeOrder(order))
9125
- ];
9126
- }
9127
- if (a.length > 1) {
9128
- const [headA1, b1] = transformTextOperationsX([a[0]], b, order);
9129
- const [restA1, b2] = transformTextOperationsX(a.slice(1), b1, order);
9130
- return [[...headA1, ...restA1], b2];
9131
- }
9132
- const [a1, headB1] = transformTextOperationsX(a, [b[0]], order);
9133
- const [a2, restB1] = transformTextOperationsX(a1, b.slice(1), order);
9134
- return [a2, [...headB1, ...restB1]];
9135
- }
9136
- function transformTextOperations(ops, over, order) {
9137
- return transformTextOperationsX(ops, over, order)[0];
9138
- }
9139
- function textOperationsEqual(a, b) {
9140
- return a === b || stableStringify(a) === stableStringify(b);
9141
- }
9142
- function applyTextOperationsToSegments(segments, ops) {
9143
- let next = [...segments];
9144
- for (const op of ops) {
9145
- if (op.type === "insert") {
9146
- const index = Math.max(0, Math.min(op.index, textLength(next)));
9147
- next = applyInsert(next, index, op.text, op.attributes);
9148
- } else if (op.type === "delete") {
9149
- const index = Math.max(0, Math.min(op.index, textLength(next)));
9150
- const clipped = clipRange(index, op.length, textLength(next));
9151
- next = applyDelete(next, clipped.index, clipped.length).segments;
9152
- } else {
9153
- const index = Math.max(0, Math.min(op.index, textLength(next)));
9154
- const clipped = clipRange(index, op.length, textLength(next));
9155
- next = applyFormat(next, clipped.index, clipped.length, op.attributes);
9156
- }
9157
- }
9158
- return next;
9159
- }
9160
- function applyLiveTextOperations(data, ops) {
9161
- return segmentsToData(
9162
- applyTextOperationsToSegments(dataToSegments(data), ops)
9163
- );
9164
- }
9165
- function invertTextOperations(segments, ops) {
9166
- let shadow = [...segments];
9167
- const reverse = [];
9168
- for (const op of ops) {
9169
- if (op.type === "insert") {
9170
- shadow = applyInsert(shadow, op.index, op.text, op.attributes);
9171
- reverse.unshift({
9172
- type: "delete",
9173
- index: op.index,
9174
- length: op.text.length
9175
- });
9176
- } else if (op.type === "delete") {
9177
- const deletedSegments = extractDeletedSegments(
9178
- shadow,
9179
- op.index,
9180
- op.length
9181
- );
9182
- shadow = applyDelete(shadow, op.index, op.length).segments;
9183
- const inserts = [];
9184
- let insertIndex = op.index;
9185
- for (const segment of deletedSegments) {
9186
- inserts.push({
9187
- type: "insert",
9188
- index: insertIndex,
9189
- text: segment.text,
9190
- attributes: segment.attributes
9191
- });
9192
- insertIndex += segment.text.length;
9193
- }
9194
- for (let index = inserts.length - 1; index >= 0; index--) {
9195
- reverse.unshift(inserts[index]);
9196
- }
9197
- } else {
9198
- const inverse = formatReverseOperations(
9199
- shadow,
9200
- op.index,
9201
- op.length,
9202
- op.attributes
9203
- );
9204
- shadow = applyFormat(shadow, op.index, op.length, op.attributes);
9205
- reverse.unshift(...inverse.reverse());
9206
- }
9207
- }
9208
- return reverse;
9209
- }
9210
-
9211
- // src/crdts/LiveText.ts
9212
- var ACCEPTED_OPS_HISTORY_LIMIT = 1e3;
9213
- var LiveText = class _LiveText extends AbstractCrdt {
9214
- /** The local document: #confirmed ⊕ #inFlightOps ⊕ #queuedOps. */
9215
- #segments;
9216
- /** The server-confirmed document (only authoritative ops applied). */
9217
- #confirmed;
9218
- #version;
9219
- /** The op currently awaiting server acknowledgement (at most one). */
9220
- #inFlightOpId;
9221
- /** Its ops, continuously re-expressed against current server state. */
9222
- #inFlightOps = [];
9223
- /** Local edits made while an op is in flight; sent after the ack. */
9224
- #queuedOps = [];
9225
- #acceptedOps = [];
9226
- /**
9227
- * Creates a new LiveText document.
9228
- *
9229
- * @param textOrData Initial plain text, or an array of `[text]` /
9230
- * `[text, attributes]` segments. Defaults to an empty document.
9231
- *
9232
- * @example
9233
- * new LiveText();
9234
- * new LiveText("Hello world");
9235
- * new LiveText([["Hello ", { bold: true }], ["world"]]);
9236
- */
9237
- constructor(textOrData = "", version = 0) {
9238
- super();
9239
- this.#segments = typeof textOrData === "string" ? textOrData.length === 0 ? [] : [{ text: textOrData }] : dataToSegments(textOrData);
9240
- this.#confirmed = [...this.#segments];
9241
- this.#version = version;
9242
- Object.defineProperty(this, kInternal, {
9243
- value: {
9244
- encodeIndex: (localIndex) => this.#encodeIndex(localIndex),
9245
- decodeIndex: (index, fromVersion) => this.#decodeIndex(index, fromVersion)
9246
- },
9247
- enumerable: false
9248
- });
9249
- }
9250
- get version() {
9251
- return this.#version;
9252
- }
9253
- get length() {
9254
- return textLength(this.#segments);
9255
- }
9256
- /** @internal */
9257
- static _deserialize([id, item], _parentToChildren, pool) {
9258
- const text = new _LiveText(item.data, item.version);
9259
- text._attach(id, pool);
9260
- return text;
9261
- }
9262
- /** @internal */
9263
- _toOps(parentId, parentKey) {
9264
- if (this._id === void 0) {
9265
- throw new Error("Cannot serialize LiveText if it is not attached");
9266
- }
9267
- return [
9268
- {
9269
- type: OpCode.CREATE_TEXT,
9270
- id: this._id,
9271
- parentId,
9272
- parentKey,
9273
- data: this.toJSON(),
9274
- version: this.#version
9275
- }
9276
- ];
9277
- }
9278
- /** @internal */
9279
- _serialize() {
9280
- if (this.parent.type !== "HasParent") {
9281
- throw new Error("Cannot serialize LiveText if parent is missing");
9282
- }
9283
- return {
9284
- type: CrdtType.TEXT,
9285
- parentId: nn(this.parent.node._id, "Parent node expected to have ID"),
9286
- parentKey: this.parent.key,
9287
- data: this.toJSON(),
9288
- version: this.#version
9289
- };
9290
- }
9291
- /** @internal */
9292
- _attachChild(_op) {
9293
- throw new Error("LiveText cannot contain child nodes");
9294
- }
9295
- /** @internal */
9296
- _detachChild(_crdt) {
9297
- throw new Error("LiveText cannot contain child nodes");
9298
- }
9299
- /** @internal */
9300
- _apply(op, isLocal) {
9301
- if (op.type !== OpCode.UPDATE_TEXT) {
9302
- return super._apply(op, isLocal);
9303
- }
9304
- if (isLocal) {
9305
- return this.#applyLocal(op);
9306
- }
9307
- if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
9308
- return this.#applyAck(op);
9309
- }
9310
- if (op.opId !== void 0 && this.#acceptedOps.some((entry) => entry.opId === op.opId)) {
9311
- this.#version = Math.max(this.#version, op.version ?? op.baseVersion + 1);
9312
- return { modified: false };
9313
- }
9314
- return this.#applyRemote(op);
9315
- }
9316
- /**
9317
- * Inserts text at the given index.
9318
- *
9319
- * @param index Character index at which to insert. Values outside the
9320
- * document range are clipped.
9321
- * @param text Text to insert.
9322
- * @param attributes Optional inline attributes for the inserted text.
9323
- *
9324
- * @example
9325
- * const text = new LiveText("Hello");
9326
- * text.insert(5, " world");
9327
- * text.insert(0, "Say: ", { italic: true });
9328
- */
9329
- insert(index, text, attributes) {
9330
- const clippedIndex = Math.max(0, Math.min(index, this.length));
9331
- this.#dispatch([{ type: "insert", index: clippedIndex, text, attributes }]);
9332
- }
9333
- /**
9334
- * Deletes `length` characters starting at `index`.
9335
- *
9336
- * @example
9337
- * const text = new LiveText("Hello world");
9338
- * text.delete(5, 6); // "Hello"
9339
- */
9340
- delete(index, length) {
9341
- const clipped = clipRange(index, length, this.length);
9342
- if (clipped.length === 0) {
9343
- return;
9344
- }
9345
- this.#dispatch([
9346
- { type: "delete", index: clipped.index, length: clipped.length }
9347
- ]);
9348
- }
9349
- /**
9350
- * Replaces a range of text with new text.
9351
- *
9352
- * @example
9353
- * const text = new LiveText("Hello world");
9354
- * text.replace(0, 5, "Hi"); // "Hi world"
9355
- */
9356
- replace(index, length, text, attributes) {
9357
- const clipped = clipRange(index, length, this.length);
9358
- const ops = [];
9359
- if (clipped.length > 0) {
9360
- ops.push({
9361
- type: "delete",
9362
- index: clipped.index,
9363
- length: clipped.length
9364
- });
9365
- }
9366
- if (text.length > 0) {
9367
- ops.push({ type: "insert", index: clipped.index, text, attributes });
9368
- }
9369
- this.#dispatch(ops);
9370
- }
9371
- /**
9372
- * Encode a local-document index (an offset into this LiveText's current
9373
- * #segments, which CodeMirror or any consumer mirrors as its document)
9374
- * into server-confirmed coordinates suitable for broadcasting to peers via
9375
- * presence or any other side channel.
9376
- *
9377
- * The returned index is in this LiveText's current #confirmed coordinates
9378
- * — that is, with this client's local pending ops inverse-mapped out.
9379
- * Pair it with the current {@link LiveText.version} when sending so the
9380
- * receiver can call {@link PrivateLiveTextApi.decodeIndex} to land the
9381
- * position in their own local document coordinates regardless of their
9382
- * private pending ops.
9383
- *
9384
- * Index ambiguity at boundaries is resolved by an inverse-of-forward
9385
- * convention: a position at or before a local insertion is reported as
9386
- * the position right before the insertion in #confirmed; a position past
9387
- * the insertion shifts left by the insertion's length. Positions inside
9388
- * an own-pending insertion collapse to the insertion point.
9389
- */
9390
- #encodeIndex(localIndex) {
9391
- let mapped = Math.max(0, Math.min(localIndex, this.length));
9392
- mapped = inverseMapTextIndexThroughOperations(mapped, this.#queuedOps);
9393
- mapped = inverseMapTextIndexThroughOperations(mapped, this.#inFlightOps);
9394
- return mapped;
9395
- }
9396
- /**
9397
- * Decode an `(index, fromVersion)` pair produced by
9398
- * {@link PrivateLiveTextApi.encodeIndex} — typically on a peer — into an
9399
- * offset in this LiveText's current local document (an index suitable for
9400
- * placing a CodeMirror marker, an annotation anchor, or anything else that
9401
- * lives over #segments).
9402
- *
9403
- * Composes the accepted ops applied since `fromVersion` (drawn from
9404
- * #acceptedOps in locally-applied form) with this client's own local
9405
- * pending ops, in that order. The result is in current #segments
9406
- * coordinates.
9407
- *
9408
- * Returns `null` when the position cannot be decoded against the current
9409
- * state:
9410
- * - `fromVersion` is greater than this LiveText's current version: the
9411
- * peer is ahead of us. The caller should park the message and retry
9412
- * after more accepted ops arrive.
9413
- * - `fromVersion` falls outside the retained accepted-ops history. This
9414
- * only happens after very long-lived disconnections; the caller can
9415
- * fall back to using the raw index and letting subsequent local
9416
- * transactions map it (with bounded drift).
9417
- */
9418
- #decodeIndex(index, fromVersion) {
9419
- if (fromVersion > this.#version) {
9420
- return null;
9421
- }
9422
- if (fromVersion < this.#version) {
9423
- const oldest = this.#acceptedOps[0]?.version;
9424
- if (oldest === void 0 || oldest > fromVersion + 1) {
9425
- return null;
9426
- }
9427
- }
9428
- let mapped = index;
9429
- for (const entry of this.#acceptedOps) {
9430
- if (entry.version <= fromVersion) continue;
9431
- if (entry.version > this.#version) break;
9432
- if (entry.ops.length === 0) continue;
9433
- mapped = mapTextIndexThroughOperations(mapped, entry.ops);
9434
- }
9435
- mapped = mapTextIndexThroughOperations(mapped, this.#inFlightOps);
9436
- mapped = mapTextIndexThroughOperations(mapped, this.#queuedOps);
9437
- return Math.max(0, Math.min(mapped, this.length));
9438
- }
9439
- /**
9440
- * Applies or removes inline attributes on a range of text.
9441
- *
9442
- * Set an attribute to `null` to remove it from the range.
9443
- *
9444
- * @example
9445
- * const text = new LiveText("Hello world");
9446
- * text.format(0, 5, { bold: true });
9447
- * text.format(0, 5, { bold: null });
9448
- */
9449
- format(index, length, attributes) {
9450
- const clipped = clipRange(index, length, this.length);
9451
- if (clipped.length === 0) {
9452
- return;
9453
- }
9454
- this.#dispatch([
9455
- {
9456
- type: "format",
9457
- index: clipped.index,
9458
- length: clipped.length,
9459
- attributes
9460
- }
9461
- ]);
9462
- }
9463
- /** Local edits made through the public API. */
9464
- #dispatch(ops) {
9465
- if (ops.length === 0) {
9466
- return;
9467
- }
9468
- this._pool?.assertStorageIsWritable();
9469
- const attached = this._pool !== void 0 && this._id !== void 0;
9470
- const reverse = attached ? this.#invertOperations(ops) : [];
9471
- const changes = this.#applyOperationsLocally(ops);
9472
- if (!attached) {
9473
- return;
9474
- }
9475
- const pool = nn(this._pool);
9476
- const id = nn(this._id);
9477
- const updates = /* @__PURE__ */ new Map([
9478
- [
9479
- id,
9480
- {
9481
- type: "LiveText",
9482
- node: this,
9483
- version: this.#version,
9484
- updates: changes
9485
- }
9486
- ]
9487
- ]);
9488
- if (this.#inFlightOpId === void 0) {
9489
- const opId = pool.generateOpId();
9490
- this.#inFlightOpId = opId;
9491
- this.#inFlightOps = [...ops];
9492
- pool.dispatch(
9493
- [
9494
- {
9495
- type: OpCode.UPDATE_TEXT,
9496
- id,
9497
- opId,
9498
- baseVersion: this.#version,
9499
- ops: [...ops]
9500
- }
9501
- ],
9502
- reverse,
9503
- updates
9504
- );
9505
- } else {
9506
- this.#queuedOps.push(...ops);
9507
- pool.dispatch([], reverse, updates, { clearRedoStack: true });
9508
- }
9509
- }
9510
- /**
9511
- * A local replay of an existing wire op: an undo/redo frame, or an
9512
- * unacknowledged op re-sent after a reconnect.
9513
- */
9514
- #applyLocal(op) {
9515
- const mutableOp = op;
9516
- if (op.opId !== void 0 && op.opId === this.#inFlightOpId) {
9517
- this.#inFlightOps = [...this.#inFlightOps, ...this.#queuedOps];
9518
- this.#queuedOps = [];
9519
- mutableOp.baseVersion = this.#version;
9520
- mutableOp.ops = [...this.#inFlightOps];
9521
- return { modified: false };
9522
- }
9523
- let ops = op.ops;
9524
- for (const entry of this.#acceptedOps) {
9525
- if (entry.version > op.baseVersion && entry.ops.length > 0) {
9526
- ops = transformTextOperations(ops, entry.ops, "after");
9527
- }
9528
- }
9529
- const reverse = this.#invertOperations(ops);
9530
- const changes = this.#applyOperationsLocally(ops);
9531
- if (this.#inFlightOpId === void 0 && ops.length > 0) {
9532
- this.#inFlightOpId = nn(op.opId, "Local ops must have an opId");
9533
- this.#inFlightOps = [...ops];
9534
- mutableOp.baseVersion = this.#version;
9535
- mutableOp.ops = [...ops];
9536
- } else {
9537
- this.#queuedOps.push(...ops);
9538
- mutableOp.baseVersion = this.#version;
9539
- mutableOp.ops = [];
9540
- }
9541
- if (changes.length === 0) {
9542
- return { modified: false };
9543
- }
9544
- return {
9545
- reverse,
9546
- modified: {
9547
- type: "LiveText",
9548
- node: this,
9549
- version: this.#version,
9550
- updates: changes
9551
- }
9552
- };
9553
- }
9554
- /** Server acknowledgement of our in-flight op. */
9555
- #applyAck(op) {
9556
- const ackedVersion = op.version ?? Math.max(this.#version, op.baseVersion + 1);
9557
- const predicted = this.#inFlightOps;
9558
- const opId = this.#inFlightOpId;
9559
- this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
9560
- this.#inFlightOpId = void 0;
9561
- this.#inFlightOps = [];
9562
- let appliedOps = [];
9563
- let result = { modified: false };
9564
- if (!textOperationsEqual(op.ops, predicted)) {
9565
- error2(
9566
- "LiveText: acknowledgement did not match the local prediction; resynchronizing"
9567
- );
9568
- const rebuilt = this.#rebuildLocalFromConfirmed();
9569
- appliedOps = rebuilt.appliedOps;
9570
- if (rebuilt.changes.length > 0) {
9571
- result = {
9572
- reverse: [],
9573
- modified: {
9574
- type: "LiveText",
9575
- node: this,
9576
- version: ackedVersion,
9577
- updates: rebuilt.changes
9578
- }
9579
- };
9580
- }
9581
- }
9582
- this.#version = Math.max(this.#version, ackedVersion);
9583
- this.#recordAccepted(ackedVersion, appliedOps, opId);
9584
- this.#flushQueued();
9585
- return result;
9586
- }
9587
- /** An accepted op from another client (or a server-fabricated fix op). */
9588
- #applyRemote(op) {
9589
- const version = op.version ?? this.#version + 1;
9590
- this.#confirmed = applyTextOperationsToSegments(this.#confirmed, op.ops);
9591
- const [overInFlight, inFlight] = transformTextOperationsX(
9592
- op.ops,
9593
- this.#inFlightOps,
9594
- "before"
9595
- );
9596
- const [applied, queued] = transformTextOperationsX(
9597
- overInFlight,
9598
- this.#queuedOps,
9599
- "before"
9600
- );
9601
- this.#inFlightOps = inFlight;
9602
- this.#queuedOps = queued;
9603
- this.#recordAccepted(version, applied, op.opId);
9604
- if (applied.length === 0) {
9605
- this.#version = Math.max(this.#version, version);
9606
- return { modified: false };
9607
- }
9608
- const reverse = this.#invertOperations(applied);
9609
- const changes = this.#applyOperationsLocally(applied);
9610
- this.#version = Math.max(this.#version, version);
9611
- return {
9612
- reverse,
9613
- modified: {
9614
- type: "LiveText",
9615
- node: this,
9616
- version: this.#version,
9617
- updates: changes
9618
- }
9619
- };
9620
- }
9621
- /** Send the queued ops as the next in-flight op (after an ack). */
9622
- #flushQueued() {
9623
- if (this.#queuedOps.length === 0 || this._pool === void 0 || this._id === void 0) {
9624
- return;
9625
- }
9626
- const opId = this._pool.generateOpId();
9627
- this.#inFlightOpId = opId;
9628
- this.#inFlightOps = this.#queuedOps;
9629
- this.#queuedOps = [];
9630
- this._pool.dispatch(
9631
- [
9632
- {
9633
- type: OpCode.UPDATE_TEXT,
9634
- id: this._id,
9635
- opId,
9636
- baseVersion: this.#version,
9637
- ops: [...this.#inFlightOps]
9638
- }
9639
- ],
9640
- [],
9641
- /* @__PURE__ */ new Map(),
9642
- // The local content was already applied (and made undoable) when the
9643
- // edits happened; this is purely an outbound flush.
9644
- { clearRedoStack: false }
9645
- );
9646
- }
9647
- /**
9648
- * Rebuild the local document as confirmed ⊕ queued ops, returning the
9649
- * coarse delta that was applied. Only used by defensive recovery paths.
9650
- */
9651
- #rebuildLocalFromConfirmed() {
9652
- const before2 = this.#segments;
9653
- const after2 = applyTextOperationsToSegments(this.#confirmed, [
9654
- ...this.#inFlightOps,
9655
- ...this.#queuedOps
9656
- ]);
9657
- if (stableStringify(segmentsToData(before2)) === stableStringify(segmentsToData(after2))) {
9658
- this.#segments = after2;
9659
- return { appliedOps: [], changes: [] };
9660
- }
9661
- const beforeText = before2.map((segment) => segment.text).join("");
9662
- this.#segments = after2;
9663
- this.invalidate();
9664
- const appliedOps = [];
9665
- const changes = [];
9666
- if (beforeText.length > 0) {
9667
- appliedOps.push({ type: "delete", index: 0, length: beforeText.length });
9668
- changes.push({
9669
- type: "delete",
9670
- index: 0,
9671
- length: beforeText.length,
9672
- deletedText: beforeText
9673
- });
9674
- }
9675
- let index = 0;
9676
- for (const segment of after2) {
9677
- appliedOps.push({
9678
- type: "insert",
9679
- index,
9680
- text: segment.text,
9681
- attributes: segment.attributes
9682
- });
9683
- changes.push({
9684
- type: "insert",
9685
- index,
9686
- text: segment.text,
9687
- attributes: segment.attributes
9688
- });
9689
- index += segment.text.length;
9690
- }
9691
- return { appliedOps, changes };
9692
- }
9693
- /**
9694
- * Reconcile this node against an authoritative storage snapshot (e.g.
9695
- * after a reconnect). The confirmed state and version are replaced by the
9696
- * snapshot's; pending (in-flight + queued) ops are preserved on top and
9697
- * will be re-sent by the offline-ops replay.
9698
- *
9699
- * @internal
9700
- */
9701
- _resyncText(data, version) {
9702
- this.#confirmed = dataToSegments(data);
9703
- this.#version = version;
9704
- this.#acceptedOps = [];
9705
- const rebuilt = this.#rebuildLocalFromConfirmed();
9706
- if (rebuilt.changes.length === 0) {
9707
- return void 0;
9708
- }
9709
- return {
9710
- type: "LiveText",
9711
- node: this,
9712
- version: this.#version,
9713
- updates: rebuilt.changes
9714
- };
9715
- }
9716
- /**
9717
- * Called when the server rejected one of our ops. Drops all pending state
9718
- * for this node (edits queued behind a rejected op cannot be trusted
9719
- * either); the room follows up with a storage resync.
9720
- *
9721
- * @internal
9722
- */
9723
- _rejectPendingOp(opId) {
9724
- if (opId !== this.#inFlightOpId) {
9725
- return;
9726
- }
9727
- this.#inFlightOpId = void 0;
9728
- this.#inFlightOps = [];
9729
- this.#queuedOps = [];
9730
- }
9731
- #recordAccepted(version, ops, opId) {
9732
- if (this.#acceptedOps.some((entry) => entry.version === version)) {
9733
- return;
9734
- }
9735
- this.#acceptedOps.push({ version, opId, ops: [...ops] });
9736
- this.#acceptedOps.sort((left, right) => left.version - right.version);
9737
- if (this.#acceptedOps.length > ACCEPTED_OPS_HISTORY_LIMIT) {
9738
- this.#acceptedOps.splice(
9739
- 0,
9740
- this.#acceptedOps.length - ACCEPTED_OPS_HISTORY_LIMIT
9741
- );
9742
- }
9743
- }
9744
- #applyOperationsLocally(ops) {
9745
- const changes = [];
9746
- for (const op of ops) {
9747
- if (op.type === "insert") {
9748
- this.#segments = applyInsert(
9749
- this.#segments,
9750
- op.index,
9751
- op.text,
9752
- op.attributes
9753
- );
9754
- changes.push({
9755
- type: "insert",
9756
- index: op.index,
9757
- text: op.text,
9758
- attributes: op.attributes
9759
- });
9760
- } else if (op.type === "delete") {
9761
- const result = applyDelete(this.#segments, op.index, op.length);
9762
- this.#segments = result.segments;
9763
- changes.push({
9764
- type: "delete",
9765
- index: op.index,
9766
- length: op.length,
9767
- deletedText: result.deletedText
9768
- });
9769
- } else {
9770
- this.#segments = applyFormat(
9771
- this.#segments,
9772
- op.index,
9773
- op.length,
9774
- op.attributes
9775
- );
9776
- changes.push({
9777
- type: "format",
9778
- index: op.index,
9779
- length: op.length,
9780
- attributes: op.attributes
9781
- });
9782
- }
9783
- }
9784
- this.invalidate();
9785
- return changes;
9786
- }
9787
- #invertOperations(ops) {
9788
- return [
9789
- {
9790
- type: OpCode.UPDATE_TEXT,
9791
- id: nn(this._id),
9792
- baseVersion: this.#version,
9793
- ops: invertTextOperations(this.#segments, ops)
9794
- }
9795
- ];
9796
- }
9797
- /** Returns the plain text content without attributes. Equivalent to joining the text from each segment in {@link LiveText.toJSON}. */
9798
- toString() {
9799
- return this.#segments.map((segment) => segment.text).join("");
9800
- }
9801
- /**
9802
- * Returns a JSON-compatible snapshot of the document as a {@link LiveTextData}
9803
- * array.
9804
- *
9805
- * @example
9806
- * new LiveText([["Hello ", { bold: true }], ["world"]]).toJSON();
9807
- * // [["Hello ", { bold: true }], ["world"]]
9808
- */
9809
- toJSON() {
9810
- return super.toJSON();
9811
- }
9812
- /** @internal */
9813
- _toJSON() {
9814
- return segmentsToData(this.#segments);
9815
- }
9816
- /** @internal */
9817
- toTreeNode(key) {
9818
- return super.toTreeNode(key);
9819
- }
9820
- /** @internal */
9821
- _toTreeNode(key) {
9822
- const nodeId = this._id ?? nanoid();
9823
- const payload = this.toJSON().map(
9824
- (segment, index) => ({
9825
- type: "Json",
9826
- id: `${nodeId}:${index}`,
9827
- key: String(index),
9828
- payload: segment
9829
- })
9830
- );
9831
- payload.push({
9832
- type: "Json",
9833
- id: `${nodeId}:version`,
9834
- key: "version",
9835
- payload: this.version
9836
- });
9837
- return {
9838
- type: "LiveText",
9839
- id: nodeId,
9840
- key,
9841
- payload
9842
- };
9843
- }
9844
- clone() {
9845
- return new _LiveText(this.toJSON(), this.#version);
9846
- }
9847
- };
9848
-
9849
- // src/crdts/liveblocks-helpers.ts
9850
- function creationOpToLiveNode(op) {
9851
- return lsonToLiveNode(creationOpToLson(op));
9852
- }
9853
- function creationOpToLson(op) {
9854
- switch (op.type) {
9855
- case OpCode.CREATE_REGISTER:
9856
- return op.data;
9857
- case OpCode.CREATE_OBJECT:
9858
- return new LiveObject(op.data);
9859
- case OpCode.CREATE_MAP:
9860
- return new LiveMap();
9861
- case OpCode.CREATE_LIST:
9862
- return new LiveList([]);
9863
- case OpCode.CREATE_TEXT:
9864
- return new LiveText(op.data, op.version);
9865
- default:
9866
- return assertNever(op, "Unknown creation Op");
9867
- }
9868
- }
9869
- function isSameNodeOrChildOf(node, parent) {
9870
- if (node === parent) {
9871
- return true;
9872
- }
9873
- if (node.parent.type === "HasParent") {
9874
- return isSameNodeOrChildOf(node.parent.node, parent);
9875
- }
9876
- return false;
9877
- }
9878
- function deserialize(node, parentToChildren, pool) {
9879
- if (isObjectStorageNode(node)) {
9880
- return LiveObject._deserialize(node, parentToChildren, pool);
9881
- } else if (isListStorageNode(node)) {
9882
- return LiveList._deserialize(node, parentToChildren, pool);
9883
- } else if (isMapStorageNode(node)) {
9884
- return LiveMap._deserialize(node, parentToChildren, pool);
9885
- } else if (isRegisterStorageNode(node)) {
9886
- return LiveRegister._deserialize(node, parentToChildren, pool);
9887
- } else if (isTextStorageNode(node)) {
9888
- return LiveText._deserialize(node, parentToChildren, pool);
9889
- } else {
9890
- throw new Error("Unexpected CRDT type");
9891
- }
9892
- }
9893
- function deserializeToLson(node, parentToChildren, pool) {
9894
- if (isObjectStorageNode(node)) {
9895
- return LiveObject._deserialize(node, parentToChildren, pool);
9896
- } else if (isListStorageNode(node)) {
9897
- return LiveList._deserialize(node, parentToChildren, pool);
9898
- } else if (isMapStorageNode(node)) {
9899
- return LiveMap._deserialize(node, parentToChildren, pool);
9900
- } else if (isRegisterStorageNode(node)) {
9901
- return node[1].data;
9902
- } else if (isTextStorageNode(node)) {
9903
- return LiveText._deserialize(node, parentToChildren, pool);
9904
- } else {
9905
- throw new Error("Unexpected CRDT type");
9906
- }
9907
- }
9908
- function isLiveStructure(value) {
9909
- return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveText(value);
9910
- }
9911
- function isLiveNode(value) {
9912
- return isLiveStructure(value) || isLiveRegister(value);
9913
- }
9914
- function isLiveList(value) {
9915
- return value instanceof LiveList;
9916
- }
9917
- function isLiveMap(value) {
9918
- return value instanceof LiveMap;
9919
- }
9920
- function isLiveObject(value) {
9921
- return value instanceof LiveObject;
9922
- }
9923
- function isLiveText(value) {
9924
- return value instanceof LiveText;
9925
- }
9926
- function isLiveRegister(value) {
9927
- return value instanceof LiveRegister;
9928
- }
9929
- function cloneLson(value) {
9930
- return value === void 0 ? void 0 : isLiveStructure(value) ? value.clone() : deepClone(value);
9931
- }
9932
- function liveNodeToLson(obj) {
9933
- if (obj instanceof LiveRegister) {
9934
- return obj.data;
9935
- } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveText) {
9936
- return obj;
9937
- } else {
9938
- return assertNever(obj, "Unknown AbstractCrdt");
9939
- }
9940
- }
9941
- function lsonToLiveNode(value) {
9942
- if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveText) {
9943
- return value;
9944
- } else {
9945
- return new LiveRegister(value);
9946
- }
9947
- }
9948
- function dumpPool(pool) {
9949
- const rows = Array.from(pool.nodes.values(), (node) => {
9950
- const parent = node.parent;
9951
- const parentId = parent.type === "HasParent" ? parent.node._id ?? "?" : parent.type === "Orphaned" ? "<orphaned>" : "-";
9952
- let value;
9953
- if (node instanceof LiveRegister) {
9954
- value = stringifyOrLog(node.data);
9955
- } else if (node instanceof LiveList) {
9956
- value = "<LiveList>";
9957
- } else if (node instanceof LiveMap) {
9958
- value = "<LiveMap>";
9959
- } else {
9960
- value = "<LiveObject>";
9961
- }
9962
- return { id: nn(node._id), parentId, key: node._parentKey ?? "", value };
9963
- });
9964
- rows.sort((a, b) => {
9965
- if (a.parentId !== b.parentId) return a.parentId < b.parentId ? -1 : 1;
9966
- if (a.key !== b.key) return a.key < b.key ? -1 : 1;
9967
- return 0;
9968
- });
9969
- return rows.map(
9970
- (r) => ` ${r.id} parent=${r.parentId} key=${r.key || "\u2014"} ${r.value}`
9971
- ).join("\n");
9972
- }
9973
- function isJsonEq(a, b) {
9974
- if (a === b) {
9975
- return true;
9976
- }
9977
- if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
9978
- return false;
9979
- }
9980
- if (Array.isArray(a) || Array.isArray(b)) {
9981
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
9982
- return false;
9983
- }
9984
- for (let i = 0; i < a.length; i++) {
9985
- if (!isJsonEq(a[i], b[i])) {
9986
- return false;
9987
- }
9988
- }
9989
- return true;
9990
- }
9991
- const aKeys = Object.keys(a);
9992
- if (aKeys.length !== Object.keys(b).length) {
9993
- return false;
9994
- }
9995
- for (const key of aKeys) {
9996
- if (!isJsonEq(a[key], b[key])) {
9997
- return false;
9998
- }
9999
- }
10000
- return true;
8897
+ return true;
10001
8898
  }
10002
8899
  function diffNodeMap(prev, next) {
10003
8900
  const ops = [];
@@ -10082,16 +8979,6 @@ function diffNodeMap(prev, next) {
10082
8979
  parentKey: crdt.parentKey
10083
8980
  });
10084
8981
  break;
10085
- case CrdtType.TEXT:
10086
- ops.push({
10087
- type: OpCode.CREATE_TEXT,
10088
- id,
10089
- parentId: crdt.parentId,
10090
- parentKey: crdt.parentKey,
10091
- data: crdt.data,
10092
- version: crdt.version
10093
- });
10094
- break;
10095
8982
  }
10096
8983
  }
10097
8984
  });
@@ -10124,43 +9011,19 @@ function mergeListStorageUpdates(first, second) {
10124
9011
  updates: updates.concat(second.updates)
10125
9012
  };
10126
9013
  }
10127
- function mergeTextStorageUpdates(first, second) {
10128
- return {
10129
- ...second,
10130
- updates: first.updates.concat(second.updates)
10131
- };
10132
- }
10133
9014
  function mergeStorageUpdates(first, second) {
10134
9015
  if (first === void 0) {
10135
9016
  return second;
10136
9017
  }
10137
- let merged;
10138
9018
  if (first.type === "LiveObject" && second.type === "LiveObject") {
10139
- merged = mergeObjectStorageUpdates(first, second);
9019
+ return mergeObjectStorageUpdates(first, second);
10140
9020
  } else if (first.type === "LiveMap" && second.type === "LiveMap") {
10141
- merged = mergeMapStorageUpdates(first, second);
9021
+ return mergeMapStorageUpdates(first, second);
10142
9022
  } else if (first.type === "LiveList" && second.type === "LiveList") {
10143
- merged = mergeListStorageUpdates(first, second);
10144
- } else if (first.type === "LiveText" && second.type === "LiveText") {
10145
- merged = mergeTextStorageUpdates(first, second);
9023
+ return mergeListStorageUpdates(first, second);
10146
9024
  } else {
10147
- merged = second;
10148
- }
10149
- const sa = first[kStorageUpdateSource];
10150
- const sb = second[kStorageUpdateSource];
10151
- if (sa !== void 0 || sb !== void 0) {
10152
- if (sa?.origin === "remote" || sb?.origin === "remote") {
10153
- merged[kStorageUpdateSource] = { origin: "remote" };
10154
- } else if (sa?.via === "history" || sb?.via === "history") {
10155
- const historySource = sb?.via === "history" ? sb : sa?.via === "history" ? sa : void 0;
10156
- if (historySource?.via === "history") {
10157
- merged[kStorageUpdateSource] = historySource;
10158
- }
10159
- } else {
10160
- merged[kStorageUpdateSource] = { origin: "local", via: "mutation" };
10161
- }
10162
9025
  }
10163
- return merged;
9026
+ return second;
10164
9027
  }
10165
9028
 
10166
9029
  // src/devtools/bridge.ts
@@ -11104,8 +9967,6 @@ function createRoom(options, config) {
11104
9967
  activeBatch: null,
11105
9968
  unacknowledgedOps
11106
9969
  };
11107
- let nextHistoryItemId = 0;
11108
- let historyDisabled = 0;
11109
9970
  const nodeMapBuffer = makeNodeMapBuffer();
11110
9971
  const stopwatch = config.enableDebugLogging ? makeStopWatch() : void 0;
11111
9972
  let lastTokenKey;
@@ -11190,10 +10051,7 @@ function createRoom(options, config) {
11190
10051
  }
11191
10052
  }
11192
10053
  });
11193
- function onDispatch(ops, reverse, storageUpdates, options2) {
11194
- for (const value of storageUpdates.values()) {
11195
- value[kStorageUpdateSource] = { origin: "local", via: "mutation" };
11196
- }
10054
+ function onDispatch(ops, reverse, storageUpdates) {
11197
10055
  if (context.activeBatch) {
11198
10056
  for (const op of ops) {
11199
10057
  context.activeBatch.ops.push(op);
@@ -11212,10 +10070,8 @@ function createRoom(options, config) {
11212
10070
  if (reverse.length > 0) {
11213
10071
  addToUndoStack(reverse);
11214
10072
  }
11215
- if (options2?.clearRedoStack ?? ops.length > 0) {
11216
- clearRedoStack();
11217
- }
11218
10073
  if (ops.length > 0) {
10074
+ context.redoStack.length = 0;
11219
10075
  dispatchOps(ops);
11220
10076
  }
11221
10077
  notify({ storageUpdates });
@@ -11235,7 +10091,6 @@ function createRoom(options, config) {
11235
10091
  others: makeEventSource(),
11236
10092
  storageBatch: makeEventSource(),
11237
10093
  history: makeEventSource(),
11238
- privateHistory: makeEventSource(),
11239
10094
  storageDidLoad: makeEventSource(),
11240
10095
  storageStatus: makeEventSource(),
11241
10096
  ydoc: makeEventSource(),
@@ -11328,23 +10183,6 @@ function createRoom(options, config) {
11328
10183
  }
11329
10184
  const ops = diffNodeMap(currentItems, nodes);
11330
10185
  const result = applyRemoteOps(ops);
11331
- for (const [id, crdt] of nodes) {
11332
- if (crdt.type === CrdtType.TEXT) {
11333
- const node = context.pool.nodes.get(id);
11334
- if (node !== void 0 && isLiveText(node)) {
11335
- const update = node._resyncText(crdt.data, crdt.version);
11336
- if (update !== void 0) {
11337
- result.updates.storageUpdates.set(
11338
- id,
11339
- mergeStorageUpdates(
11340
- result.updates.storageUpdates.get(id),
11341
- update
11342
- )
11343
- );
11344
- }
11345
- }
11346
- }
11347
- }
11348
10186
  notify(result.updates);
11349
10187
  } else {
11350
10188
  context.root = LiveObject._fromItems(
@@ -11368,26 +10206,11 @@ function createRoom(options, config) {
11368
10206
  }
11369
10207
  });
11370
10208
  }
11371
- function notifyPrivateHistory(event) {
11372
- if (historyDisabled > 0) return;
11373
- eventHub.privateHistory.notify(event);
11374
- }
11375
- function clearRedoStack() {
11376
- if (context.redoStack.length === 0) return;
11377
- const ids = context.redoStack.map((item) => item.id);
11378
- context.redoStack.length = 0;
11379
- notifyPrivateHistory({ action: "discard", ids });
11380
- }
11381
10209
  function _addToRealUndoStack(frames) {
11382
10210
  if (context.undoStack.length >= 50) {
11383
- const evicted = context.undoStack.shift();
11384
- if (evicted !== void 0) {
11385
- notifyPrivateHistory({ action: "discard", ids: [evicted.id] });
11386
- }
10211
+ context.undoStack.shift();
11387
10212
  }
11388
- const id = nextHistoryItemId++;
11389
- context.undoStack.push({ id, frames });
11390
- notifyPrivateHistory({ action: "push", id });
10213
+ context.undoStack.push(frames);
11391
10214
  onHistoryChange();
11392
10215
  }
11393
10216
  function addToUndoStack(frames) {
@@ -11425,7 +10248,7 @@ function createRoom(options, config) {
11425
10248
  "Internal. Tried to get connection id but connection was never open"
11426
10249
  );
11427
10250
  }
11428
- function applyLocalOps(frames, localStorageUpdateSource = { origin: "local", via: "mutation" }) {
10251
+ function applyLocalOps(frames) {
11429
10252
  const [pframes, ops] = partition(
11430
10253
  frames,
11431
10254
  (f) => f.type === "presence"
@@ -11437,8 +10260,7 @@ function createRoom(options, config) {
11437
10260
  pframes,
11438
10261
  opsWithOpIds,
11439
10262
  /* isLocal */
11440
- true,
11441
- localStorageUpdateSource
10263
+ true
11442
10264
  );
11443
10265
  return { opsToEmit: opsWithOpIds, reverse, updates };
11444
10266
  }
@@ -11450,7 +10272,7 @@ function createRoom(options, config) {
11450
10272
  false
11451
10273
  );
11452
10274
  }
11453
- function applyOps(pframes, ops, isLocal, localStorageUpdateSource = { origin: "local", via: "mutation" }) {
10275
+ function applyOps(pframes, ops, isLocal) {
11454
10276
  const output = {
11455
10277
  reverse: new Deque(),
11456
10278
  storageUpdates: /* @__PURE__ */ new Map(),
@@ -11488,7 +10310,6 @@ function createRoom(options, config) {
11488
10310
  }
11489
10311
  const applyOpResult = applyOp(op, source);
11490
10312
  if (applyOpResult.modified) {
11491
- applyOpResult.modified[kStorageUpdateSource] = source === 1 /* THEIRS */ ? { origin: "remote" } : localStorageUpdateSource;
11492
10313
  const nodeId = applyOpResult.modified.node._id;
11493
10314
  if (!(nodeId && createdNodeIds.has(nodeId))) {
11494
10315
  output.storageUpdates.set(
@@ -11500,7 +10321,7 @@ function createRoom(options, config) {
11500
10321
  );
11501
10322
  output.reverse.pushLeft(applyOpResult.reverse);
11502
10323
  }
11503
- if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_TEXT) {
10324
+ if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT) {
11504
10325
  createdNodeIds.add(op.id);
11505
10326
  }
11506
10327
  }
@@ -11520,7 +10341,6 @@ function createRoom(options, config) {
11520
10341
  switch (op.type) {
11521
10342
  case OpCode.DELETE_OBJECT_KEY:
11522
10343
  case OpCode.UPDATE_OBJECT:
11523
- case OpCode.UPDATE_TEXT:
11524
10344
  case OpCode.DELETE_CRDT: {
11525
10345
  const node = context.pool.nodes.get(op.id);
11526
10346
  if (node === void 0) {
@@ -11545,7 +10365,6 @@ function createRoom(options, config) {
11545
10365
  case OpCode.CREATE_OBJECT:
11546
10366
  case OpCode.CREATE_LIST:
11547
10367
  case OpCode.CREATE_MAP:
11548
- case OpCode.CREATE_TEXT:
11549
10368
  case OpCode.CREATE_REGISTER: {
11550
10369
  if (op.parentId === void 0) {
11551
10370
  return { modified: false };
@@ -11795,37 +10614,16 @@ function createRoom(options, config) {
11795
10614
  }
11796
10615
  break;
11797
10616
  }
11798
- // Receiving a RejectedOps message means the server refused some of
11799
- // our ops, so our optimistic local state is out of sync with the
11800
- // server. For LiveText ops this is a normal (if rare) situation
11801
- // e.g. a client that was offline long enough to fall outside the
11802
- // server's retained history window — and we can recover: drop the
11803
- // rejected pending state and re-fetch the authoritative storage
11804
- // snapshot. For other ops (e.g. permission rejections), rolling back
11805
- // particular Ops is hard/impossible, so we keep the old behavior of
11806
- // accepting the out-of-sync reality and surfacing an error.
10617
+ // Receiving a RejectedOps message in the client means that the server is no
10618
+ // longer in sync with the client. Trying to synchronize the client again by
10619
+ // rolling back particular Ops may be hard/impossible. It's fine to not try and
10620
+ // accept the out-of-sync reality and throw an error.
11807
10621
  case ServerMsgCode.REJECT_STORAGE_OP: {
11808
10622
  errorWithTitle(
11809
10623
  "Storage mutation rejection error",
11810
10624
  message.reason
11811
10625
  );
11812
- let needsStorageResync = false;
11813
- for (const opId of message.opIds) {
11814
- const rejectedOp = context.unacknowledgedOps.get(opId);
11815
- context.unacknowledgedOps.delete(opId);
11816
- context.buffer.storageOperations = context.buffer.storageOperations.filter((op) => op.opId !== opId);
11817
- if (rejectedOp !== void 0 && rejectedOp.type === OpCode.UPDATE_TEXT) {
11818
- const node = context.pool.nodes.get(rejectedOp.id);
11819
- if (node !== void 0 && isLiveText(node)) {
11820
- node._rejectPendingOp(opId);
11821
- needsStorageResync = true;
11822
- }
11823
- }
11824
- }
11825
- if (needsStorageResync) {
11826
- refreshStorage();
11827
- flushNowOrSoon();
11828
- } else if (process.env.NODE_ENV !== "production") {
10626
+ if (process.env.NODE_ENV !== "production") {
11829
10627
  throw new Error(
11830
10628
  `Storage mutations rejected by server: ${message.reason}`
11831
10629
  );
@@ -12367,19 +11165,14 @@ function createRoom(options, config) {
12367
11165
  if (context.activeBatch) {
12368
11166
  throw new Error("undo is not allowed during a batch");
12369
11167
  }
12370
- const item = context.undoStack.pop();
12371
- if (item === void 0) {
11168
+ const frames = context.undoStack.pop();
11169
+ if (frames === void 0) {
12372
11170
  return;
12373
11171
  }
12374
11172
  context.pausedHistory = null;
12375
- const result = applyLocalOps(item.frames, {
12376
- origin: "local",
12377
- via: "history",
12378
- action: "undo"
12379
- });
12380
- context.redoStack.push({ id: item.id, frames: result.reverse });
12381
- notifyPrivateHistory({ action: "undo", id: item.id });
11173
+ const result = applyLocalOps(frames);
12382
11174
  notify(result.updates);
11175
+ context.redoStack.push(result.reverse);
12383
11176
  onHistoryChange();
12384
11177
  for (const op of result.opsToEmit) {
12385
11178
  context.buffer.storageOperations.push(op);
@@ -12390,19 +11183,14 @@ function createRoom(options, config) {
12390
11183
  if (context.activeBatch) {
12391
11184
  throw new Error("redo is not allowed during a batch");
12392
11185
  }
12393
- const item = context.redoStack.pop();
12394
- if (item === void 0) {
11186
+ const frames = context.redoStack.pop();
11187
+ if (frames === void 0) {
12395
11188
  return;
12396
11189
  }
12397
11190
  context.pausedHistory = null;
12398
- const result = applyLocalOps(item.frames, {
12399
- origin: "local",
12400
- via: "history",
12401
- action: "redo"
12402
- });
12403
- context.undoStack.push({ id: item.id, frames: result.reverse });
12404
- notifyPrivateHistory({ action: "redo", id: item.id });
11191
+ const result = applyLocalOps(frames);
12405
11192
  notify(result.updates);
11193
+ context.undoStack.push(result.reverse);
12406
11194
  onHistoryChange();
12407
11195
  for (const op of result.opsToEmit) {
12408
11196
  context.buffer.storageOperations.push(op);
@@ -12412,8 +11200,6 @@ function createRoom(options, config) {
12412
11200
  function clear() {
12413
11201
  context.undoStack.length = 0;
12414
11202
  context.redoStack.length = 0;
12415
- notifyPrivateHistory({ action: "clear" });
12416
- onHistoryChange();
12417
11203
  }
12418
11204
  function batch2(callback) {
12419
11205
  if (context.activeBatch) {
@@ -12442,7 +11228,7 @@ function createRoom(options, config) {
12442
11228
  commitPausedHistoryToUndoStack();
12443
11229
  }
12444
11230
  if (currentBatch.ops.length > 0) {
12445
- clearRedoStack();
11231
+ context.redoStack.length = 0;
12446
11232
  }
12447
11233
  if (currentBatch.ops.length > 0) {
12448
11234
  dispatchOps(currentBatch.ops);
@@ -12471,6 +11257,7 @@ function createRoom(options, config) {
12471
11257
  }
12472
11258
  commitPausedHistoryToUndoStack();
12473
11259
  }
11260
+ let historyDisabled = 0;
12474
11261
  function disableHistory(fn) {
12475
11262
  const origUndo = context.undoStack;
12476
11263
  const origRedo = context.redoStack;
@@ -12572,6 +11359,7 @@ function createRoom(options, config) {
12572
11359
  roomId,
12573
11360
  threadId: options2.threadId,
12574
11361
  commentId: options2.commentId,
11362
+ visibility: options2.visibility,
12575
11363
  metadata: options2.metadata,
12576
11364
  body: options2.body,
12577
11365
  commentMetadata: options2.commentMetadata,
@@ -12709,28 +11497,13 @@ function createRoom(options, config) {
12709
11497
  },
12710
11498
  // prettier-ignore
12711
11499
  get undoStack() {
12712
- return structuredClone(
12713
- context.undoStack.map((item) => ({
12714
- id: item.id,
12715
- frames: item.frames
12716
- }))
12717
- );
12718
- },
12719
- // prettier-ignore
12720
- get redoStack() {
12721
- return structuredClone(
12722
- context.redoStack.map((item) => ({
12723
- id: item.id,
12724
- frames: item.frames
12725
- }))
12726
- );
11500
+ return deepClone(context.undoStack);
12727
11501
  },
12728
11502
  // prettier-ignore
12729
11503
  get nodeCount() {
12730
11504
  return context.pool.nodes.size;
12731
11505
  },
12732
11506
  // prettier-ignore
12733
- history: eventHub.privateHistory.observable,
12734
11507
  getYjsProvider() {
12735
11508
  return context.yjsProvider;
12736
11509
  },
@@ -12743,6 +11516,7 @@ function createRoom(options, config) {
12743
11516
  yjsProviderDidChange: context.yjsProviderDidChange.observable,
12744
11517
  // send metadata when using a text editor
12745
11518
  reportTextEditor,
11519
+ getPermissionMatrix: () => context.dynamicSessionInfoSig.get()?.permissionMatrix,
12746
11520
  // create a text mention when using a text editor
12747
11521
  createTextMention,
12748
11522
  // delete a text mention when using a text editor
@@ -13750,12 +12524,6 @@ function toPlainLson(lson) {
13750
12524
  liveblocksType: "LiveList",
13751
12525
  data: [...lson].map((item) => toPlainLson(item))
13752
12526
  };
13753
- } else if (lson instanceof LiveText) {
13754
- return {
13755
- liveblocksType: "LiveText",
13756
- data: lson.toJSON(),
13757
- version: lson.version
13758
- };
13759
12527
  } else {
13760
12528
  return lson;
13761
12529
  }
@@ -13937,7 +12705,6 @@ export {
13937
12705
  LiveList,
13938
12706
  LiveMap,
13939
12707
  LiveObject,
13940
- LiveText,
13941
12708
  LiveblocksError,
13942
12709
  MENTION_CHARACTER,
13943
12710
  MutableSignal,
@@ -13949,7 +12716,6 @@ export {
13949
12716
  SortedList,
13950
12717
  TextEditorType,
13951
12718
  WebsocketCloseCodes,
13952
- applyLiveTextOperations,
13953
12719
  asPos,
13954
12720
  assert,
13955
12721
  assertNever,
@@ -14007,10 +12773,8 @@ export {
14007
12773
  isRegisterStorageNode,
14008
12774
  isRootStorageNode,
14009
12775
  isStartsWithOperator,
14010
- isTextStorageNode,
14011
12776
  isUrl,
14012
12777
  kInternal,
14013
- kStorageUpdateSource,
14014
12778
  keys,
14015
12779
  makeAbortController,
14016
12780
  makeEventSource,
@@ -14037,7 +12801,6 @@ export {
14037
12801
  stringifyCommentBody,
14038
12802
  throwUsageError,
14039
12803
  toPlainLson,
14040
- transformTextOperations,
14041
12804
  tryParseJson,
14042
12805
  url,
14043
12806
  urljoin,