@liveblocks/core 3.21.0-exp2 → 3.21.0-private1

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-exp2";
9
+ var PKG_VERSION = "3.21.0-private1";
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,
@@ -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,15 @@ 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",
5210
+ CommentsPersonalWrite: "comments:personal:write",
5211
+ CommentsPersonalRead: "comments:personal:read",
5212
+ CommentsPersonalNone: "comments:personal:none",
5195
5213
  /**
5196
5214
  * Feeds
5197
5215
  */
@@ -5204,152 +5222,125 @@ var Permission = {
5204
5222
  LegacyRoomPresenceWrite: "room:presence:write"
5205
5223
  };
5206
5224
  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
5225
  var ACCESS_LEVEL_RANKS = {
5214
5226
  none: 0,
5215
5227
  read: 1,
5216
5228
  write: 2
5217
5229
  };
5218
- var PERMISSIONS_BY_RESOURCE = {
5219
- room: {
5220
- read: [Permission.Read, Permission.RoomRead],
5221
- write: [Permission.Write, Permission.RoomWrite]
5222
- },
5223
- personal: {
5224
- write: []
5225
- },
5226
- storage: {
5227
- write: [Permission.StorageWrite],
5228
- read: [Permission.StorageRead],
5229
- none: [Permission.StorageNone]
5230
- },
5231
- comments: {
5232
- write: [Permission.CommentsWrite],
5233
- read: [Permission.CommentsRead],
5234
- none: [Permission.CommentsNone]
5235
- },
5236
- feeds: {
5237
- write: [Permission.FeedsWrite],
5238
- read: [Permission.FeedsRead],
5239
- none: [Permission.FeedsNone]
5240
- }
5230
+ var BASE_PERMISSIONS_BY_ACCESS = {
5231
+ read: [Permission.Read, Permission.RoomRead],
5232
+ write: [Permission.Write, Permission.RoomWrite]
5241
5233
  };
5242
5234
  var NO_PERMISSION_MATRIX = {
5243
5235
  room: "none",
5244
5236
  storage: "none",
5245
5237
  comments: "none",
5238
+ "comments:public": "none",
5239
+ "comments:private": "none",
5240
+ "comments:personal": "none",
5246
5241
  feeds: "none",
5247
5242
  personal: "none"
5248
5243
  };
5249
5244
  var BASE_PERMISSION_RESOURCE = "room";
5250
- var ROOM_PERMISSION_RESOURCES = [
5245
+ var basePermissionScopes = /* @__PURE__ */ new Set([
5246
+ Permission.Read,
5247
+ Permission.Write,
5248
+ Permission.RoomRead,
5249
+ Permission.RoomWrite
5250
+ ]);
5251
+ var ROOM_PERMISSION_RESOURCES = Object.freeze([
5251
5252
  "storage",
5252
5253
  "comments",
5254
+ "comments:public",
5255
+ "comments:private",
5256
+ "comments:personal",
5253
5257
  "feeds"
5254
- ];
5255
- var VALID_PERMISSIONS = new Set(Object.values(Permission));
5256
- function isPermission(permission) {
5257
- return VALID_PERMISSIONS.has(permission);
5258
- }
5259
- function resolveResourceAccess(scopes, resource) {
5260
- const permissions = PERMISSIONS_BY_RESOURCE[resource];
5261
- let resourceAccess;
5262
- for (const access of ACCESS_LEVELS) {
5263
- const scopedPermissions = permissions[access];
5264
- if (scopedPermissions !== void 0 && scopedPermissions.some((permission) => scopes.includes(permission))) {
5265
- resourceAccess = access;
5266
- }
5267
- }
5268
- return resourceAccess;
5269
- }
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
- };
5258
+ ]);
5259
+ var CHILD_ROOM_PERMISSION_RESOURCES = (() => {
5260
+ const result = /* @__PURE__ */ new Map();
5279
5261
  for (const resource of ROOM_PERMISSION_RESOURCES) {
5280
- matrix[resource] = resolved.matrix[resource] ?? resolved.baseAccess;
5262
+ const childResourcePrefix = `${resource}:`;
5263
+ result.set(
5264
+ resource,
5265
+ Object.freeze(
5266
+ ROOM_PERMISSION_RESOURCES.filter((candidate) => {
5267
+ if (!candidate.startsWith(childResourcePrefix)) {
5268
+ return false;
5269
+ }
5270
+ return !candidate.slice(childResourcePrefix.length).includes(":");
5271
+ })
5272
+ )
5273
+ );
5281
5274
  }
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";
5290
- const matrix = {};
5275
+ return result;
5276
+ })();
5277
+ var LEAF_ROOM_PERMISSION_RESOURCES_BY_PARENT = (() => {
5278
+ const result = /* @__PURE__ */ new Map();
5291
5279
  for (const resource of ROOM_PERMISSION_RESOURCES) {
5292
- const access = resolveResourceAccess(scopes, resource);
5293
- if (access !== void 0) {
5294
- matrix[resource] = access;
5295
- }
5296
- }
5297
- return { hasDefaultPermission, baseAccess, matrix };
5298
- }
5299
- function hasPermissionAccess(matrix, resource, requiredAccess) {
5300
- const access = matrix[resource] ?? "none";
5301
- return ACCESS_LEVEL_RANKS[access] >= ACCESS_LEVEL_RANKS[requiredAccess];
5302
- }
5303
- function resolveRoomPermissionMatrix(permissions, roomId) {
5304
- const matchedPermissions = permissions.filter(
5305
- (entry) => roomPatternMatches(entry.pattern, roomId)
5306
- );
5307
- if (matchedPermissions.length === 0) {
5308
- return void 0;
5280
+ result.set(resource, Object.freeze(getLeafResources(resource)));
5309
5281
  }
5310
- let hasDefaultPermission = false;
5311
- let baseAccess = "none";
5312
- const explicitMatrix = {};
5313
- const explicitSpecificity = {};
5314
- for (const entry of matchedPermissions) {
5315
- const resolved = resolvePermissionScopes(entry.scopes);
5316
- const specificity = roomPatternSpecificity(entry.pattern);
5317
- if (resolved.hasDefaultPermission) {
5318
- hasDefaultPermission = true;
5319
- baseAccess = strongestAccess(baseAccess, resolved.baseAccess);
5320
- }
5321
- 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
- }
5334
- }
5282
+ return result;
5283
+ })();
5284
+ var PARENT_ROOM_PERMISSION_RESOURCES = (() => {
5285
+ const result = /* @__PURE__ */ new Map();
5286
+ for (const parentResource of ROOM_PERMISSION_RESOURCES) {
5287
+ for (const childResource of childResourcesOf(parentResource)) {
5288
+ result.set(childResource, parentResource);
5335
5289
  }
5336
5290
  }
5337
- return permissionMatrixFromResolvedScopes({
5338
- hasDefaultPermission,
5339
- baseAccess,
5340
- matrix: explicitMatrix
5341
- });
5342
- }
5291
+ return result;
5292
+ })();
5293
+ var LEAF_ROOM_PERMISSION_RESOURCES = Object.freeze(
5294
+ ROOM_PERMISSION_RESOURCES.filter(
5295
+ (resource) => childResourcesOf(resource).length === 0
5296
+ )
5297
+ );
5298
+ var PERMISSIONS_BY_ROOM_RESOURCE = {
5299
+ storage: {
5300
+ write: [Permission.StorageWrite],
5301
+ read: [Permission.StorageRead],
5302
+ none: [Permission.StorageNone]
5303
+ },
5304
+ comments: {
5305
+ write: [Permission.CommentsWrite],
5306
+ read: [Permission.CommentsRead],
5307
+ none: [Permission.CommentsNone]
5308
+ },
5309
+ "comments:public": {
5310
+ write: [Permission.CommentsPublicWrite],
5311
+ read: [Permission.CommentsPublicRead],
5312
+ none: [Permission.CommentsPublicNone]
5313
+ },
5314
+ "comments:private": {
5315
+ write: [Permission.CommentsPrivateWrite],
5316
+ read: [Permission.CommentsPrivateRead],
5317
+ none: [Permission.CommentsPrivateNone]
5318
+ },
5319
+ "comments:personal": {
5320
+ write: [Permission.CommentsPersonalWrite],
5321
+ read: [Permission.CommentsPersonalRead],
5322
+ none: [Permission.CommentsPersonalNone]
5323
+ },
5324
+ feeds: {
5325
+ write: [Permission.FeedsWrite],
5326
+ read: [Permission.FeedsRead],
5327
+ none: [Permission.FeedsNone]
5328
+ }
5329
+ };
5330
+ var VALID_PERMISSIONS = new Set(Object.values(Permission));
5343
5331
  function normalizeRoomPermissions(permissions) {
5344
5332
  if (!Array.isArray(permissions)) {
5345
5333
  throw new Error("Permission list must be an array");
5346
5334
  }
5347
5335
  const result = [];
5348
5336
  for (const permission of permissions) {
5349
- if (!isPermission(permission)) {
5337
+ const knownPermission = Object.values(Permission).find(
5338
+ (value) => value === permission
5339
+ );
5340
+ if (knownPermission === void 0) {
5350
5341
  throw new Error(`Not a valid permission: ${permission}`);
5351
5342
  }
5352
- result.push(permission);
5343
+ result.push(knownPermission);
5353
5344
  }
5354
5345
  return result;
5355
5346
  }
@@ -5375,6 +5366,31 @@ function normalizeUpdateRoomAccesses(accesses) {
5375
5366
  ])
5376
5367
  );
5377
5368
  }
5369
+ function validatePermissionsSet(scopes) {
5370
+ const unknownScopes = scopes.filter((scope) => !VALID_PERMISSIONS.has(scope));
5371
+ if (unknownScopes.length > 0) {
5372
+ return `Unknown permission scope(s): ${unknownScopes.join(", ")}`;
5373
+ }
5374
+ const baseScopes = scopes.filter((scope) => basePermissionScopes.has(scope));
5375
+ if (baseScopes.length !== 1) {
5376
+ return `Permissions must include exactly one of ${Permission.Read}, ${Permission.Write} (or the legacy aliases ${Permission.RoomRead}, ${Permission.RoomWrite}), got ${baseScopes.length === 0 ? "none" : baseScopes.join(", ")}`;
5377
+ }
5378
+ const seenFeatures = /* @__PURE__ */ new Set();
5379
+ for (const scope of scopes) {
5380
+ if (basePermissionScopes.has(scope) || scope === Permission.LegacyRoomPresenceWrite) {
5381
+ continue;
5382
+ }
5383
+ const feature = scope.split(":").slice(0, -1).join(":");
5384
+ if (seenFeatures.has(feature)) {
5385
+ return `Permissions can include at most one scope per feature, got multiple "${feature}" scopes`;
5386
+ }
5387
+ seenFeatures.add(feature);
5388
+ }
5389
+ return true;
5390
+ }
5391
+ function permissionMatrixFromScopes(scopes) {
5392
+ return permissionMatrixFromResolvedScopes(resolvePermissionScopes(scopes));
5393
+ }
5378
5394
  function permissionMatrixToScopes(matrix) {
5379
5395
  const scopes = [];
5380
5396
  const baseAccess = matrix.room;
@@ -5382,13 +5398,59 @@ function permissionMatrixToScopes(matrix) {
5382
5398
  scopes.push(permissionForAccessLevel(BASE_PERMISSION_RESOURCE, baseAccess));
5383
5399
  }
5384
5400
  for (const resource of ROOM_PERMISSION_RESOURCES) {
5385
- const access = matrix[resource];
5386
- if (access !== baseAccess) {
5387
- scopes.push(permissionForAccessLevel(resource, access));
5401
+ if (parentResourceOf(resource) === void 0) {
5402
+ pushResourcePermissions(scopes, matrix, resource, baseAccess);
5388
5403
  }
5389
5404
  }
5390
5405
  return scopes;
5391
5406
  }
5407
+ function hasPermissionAccess(matrix, resource, requiredAccess) {
5408
+ const access = matrix[resource] ?? "none";
5409
+ return ACCESS_LEVEL_RANKS[access] >= ACCESS_LEVEL_RANKS[requiredAccess];
5410
+ }
5411
+ function resolveRoomPermissionMatrix(permissions, roomId) {
5412
+ const matchedPermissions = permissions.filter((entry) => {
5413
+ if (entry.pattern.includes("*")) {
5414
+ return roomId.startsWith(entry.pattern.replace("*", ""));
5415
+ }
5416
+ return entry.pattern === roomId;
5417
+ });
5418
+ if (matchedPermissions.length === 0) {
5419
+ return void 0;
5420
+ }
5421
+ let hasDefaultPermission = false;
5422
+ let baseAccess = "none";
5423
+ const leafAccesses = {};
5424
+ const leafSpecificity = {};
5425
+ for (const entry of matchedPermissions) {
5426
+ const resolved = resolvePermissionScopes(entry.scopes);
5427
+ const specificity = entry.pattern.replace("*", "").length;
5428
+ if (resolved.baseAccess !== void 0) {
5429
+ hasDefaultPermission = true;
5430
+ baseAccess = strongestAccess(baseAccess, resolved.baseAccess);
5431
+ }
5432
+ for (const resource of LEAF_ROOM_PERMISSION_RESOURCES) {
5433
+ if (resolved.leafAccesses[resource] === void 0) {
5434
+ continue;
5435
+ }
5436
+ const access = resolveLeafAccessFromSource(resolved, resource);
5437
+ const currentSpecificity = leafSpecificity[resource] ?? -1;
5438
+ if (specificity > currentSpecificity) {
5439
+ leafAccesses[resource] = access;
5440
+ leafSpecificity[resource] = specificity;
5441
+ } else if (specificity === currentSpecificity) {
5442
+ leafAccesses[resource] = strongestAccess(
5443
+ leafAccesses[resource] ?? "none",
5444
+ access
5445
+ );
5446
+ }
5447
+ }
5448
+ }
5449
+ return permissionMatrixFromResolvedScopes({
5450
+ baseAccess: hasDefaultPermission ? baseAccess : void 0,
5451
+ leafAccesses
5452
+ });
5453
+ }
5392
5454
  function mergeRoomPermissionScopes({
5393
5455
  defaultAccesses,
5394
5456
  groupsAccesses,
@@ -5402,90 +5464,158 @@ function mergeRoomPermissionScopes({
5402
5464
  resolvePermissionScopes(userAccesses)
5403
5465
  ];
5404
5466
  const merged = {
5405
- hasDefaultPermission: false,
5406
- baseAccess: "none",
5407
- matrix: {}
5467
+ leafAccesses: {}
5408
5468
  };
5409
5469
  for (const source of sources) {
5410
- if (source.hasDefaultPermission) {
5411
- merged.hasDefaultPermission = true;
5470
+ if (source.baseAccess !== void 0) {
5412
5471
  merged.baseAccess = source.baseAccess;
5413
5472
  }
5414
- for (const resource of ROOM_PERMISSION_RESOURCES) {
5415
- const access = source.matrix[resource];
5416
- if (access !== void 0) {
5417
- merged.matrix[resource] = access;
5473
+ for (const resource of LEAF_ROOM_PERMISSION_RESOURCES) {
5474
+ const sourceAccess = source.leafAccesses[resource];
5475
+ if (sourceAccess === void 0) {
5476
+ continue;
5418
5477
  }
5478
+ merged.leafAccesses[resource] = sourceAccess;
5419
5479
  }
5420
5480
  }
5421
5481
  return permissionMatrixToScopes(permissionMatrixFromResolvedScopes(merged));
5422
5482
  }
5483
+ function resolvePermissionScopes(scopes) {
5484
+ const baseAccess = resolveAccess(scopes, BASE_PERMISSIONS_BY_ACCESS);
5485
+ const leafAccesses = {};
5486
+ for (const resource of ROOM_PERMISSION_RESOURCES) {
5487
+ const access = resolveResourceAccess(scopes, resource);
5488
+ if (access !== void 0) {
5489
+ for (const leafResource of leafResourcesOf(resource)) {
5490
+ leafAccesses[leafResource] = access;
5491
+ }
5492
+ }
5493
+ }
5494
+ return { baseAccess, leafAccesses };
5495
+ }
5496
+ function resolveResourceAccess(scopes, resource) {
5497
+ return resolveAccess(scopes, PERMISSIONS_BY_ROOM_RESOURCE[resource]);
5498
+ }
5499
+ function resolveAccess(scopes, permissions) {
5500
+ let resourceAccess;
5501
+ for (const access of ACCESS_LEVELS) {
5502
+ const scopedPermissions = permissions[access];
5503
+ if (scopedPermissions !== void 0 && scopedPermissions.some((permission) => scopes.includes(permission))) {
5504
+ resourceAccess = access;
5505
+ }
5506
+ }
5507
+ return resourceAccess;
5508
+ }
5509
+ function permissionMatrixFromResolvedScopes(resolved) {
5510
+ if (resolved.baseAccess === void 0) {
5511
+ return { ...NO_PERMISSION_MATRIX };
5512
+ }
5513
+ const matrix = {
5514
+ ...NO_PERMISSION_MATRIX,
5515
+ [BASE_PERMISSION_RESOURCE]: resolved.baseAccess,
5516
+ personal: "write"
5517
+ };
5518
+ for (const resource of LEAF_ROOM_PERMISSION_RESOURCES) {
5519
+ matrix[resource] = resolveLeafAccessFromSource(resolved, resource);
5520
+ }
5521
+ for (const resource of ROOM_PERMISSION_RESOURCES) {
5522
+ if (childResourcesOf(resource).length > 0) {
5523
+ let strongest = "none";
5524
+ for (const leafResource of leafResourcesOf(resource)) {
5525
+ strongest = strongestAccess(strongest, matrix[leafResource]);
5526
+ }
5527
+ matrix[resource] = strongest;
5528
+ }
5529
+ }
5530
+ return matrix;
5531
+ }
5532
+ function resolveLeafAccessFromSource(source, resource) {
5533
+ const access = source.leafAccesses[resource];
5534
+ if (access !== void 0) {
5535
+ return access;
5536
+ }
5537
+ return source.baseAccess ?? "none";
5538
+ }
5539
+ function pushResourcePermissions(scopes, matrix, resource, baseAccess) {
5540
+ const childResources = childResourcesOf(resource);
5541
+ if (childResources.length === 0) {
5542
+ const access = matrix[resource];
5543
+ if (access !== baseAccess) {
5544
+ scopes.push(permissionForAccessLevel(resource, access));
5545
+ }
5546
+ return;
5547
+ }
5548
+ const leafResources = leafResourcesOf(resource);
5549
+ let sharedAccess;
5550
+ let allLeavesShareAccess = true;
5551
+ for (const leafResource of leafResources) {
5552
+ const access = matrix[leafResource];
5553
+ sharedAccess ??= access;
5554
+ if (access !== sharedAccess) {
5555
+ allLeavesShareAccess = false;
5556
+ break;
5557
+ }
5558
+ }
5559
+ if (sharedAccess !== void 0 && sharedAccess !== baseAccess && allLeavesShareAccess) {
5560
+ scopes.push(permissionForAccessLevel(resource, sharedAccess));
5561
+ return;
5562
+ }
5563
+ for (const childResource of childResources) {
5564
+ pushResourcePermissions(scopes, matrix, childResource, baseAccess);
5565
+ }
5566
+ }
5423
5567
  function mergeResolvedScopesByHighestAccess(sources) {
5424
5568
  const merged = {
5425
- hasDefaultPermission: false,
5426
- baseAccess: "none",
5427
- matrix: {}
5569
+ leafAccesses: {}
5428
5570
  };
5429
5571
  for (const source of sources) {
5430
- if (source.hasDefaultPermission) {
5431
- merged.hasDefaultPermission = true;
5432
- merged.baseAccess = strongestAccess(merged.baseAccess, source.baseAccess);
5433
- }
5434
- for (const resource of ROOM_PERMISSION_RESOURCES) {
5435
- const access = source.matrix[resource];
5436
- if (access !== void 0) {
5437
- merged.matrix[resource] = strongestAccess(
5438
- merged.matrix[resource] ?? "none",
5439
- access
5440
- );
5572
+ if (source.baseAccess !== void 0) {
5573
+ merged.baseAccess = strongestAccess(
5574
+ merged.baseAccess ?? "none",
5575
+ source.baseAccess
5576
+ );
5577
+ }
5578
+ for (const resource of LEAF_ROOM_PERMISSION_RESOURCES) {
5579
+ const sourceAccess = source.leafAccesses[resource];
5580
+ if (sourceAccess === void 0) {
5581
+ continue;
5441
5582
  }
5583
+ merged.leafAccesses[resource] = strongestAccess(
5584
+ merged.leafAccesses[resource] ?? "none",
5585
+ sourceAccess
5586
+ );
5442
5587
  }
5443
5588
  }
5444
5589
  return merged;
5445
5590
  }
5446
5591
  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
- );
5592
+ const permissions = resource === "room" ? BASE_PERMISSIONS_BY_ACCESS[access] : resource === "personal" ? void 0 : PERMISSIONS_BY_ROOM_RESOURCE[resource][access];
5593
+ const permission = permissions?.[0];
5594
+ if (permission !== void 0) {
5595
+ return permission;
5453
5596
  }
5454
- return permissions[0];
5597
+ throw new Error(
5598
+ `Invalid permission level for ${field}: ${JSON.stringify(access) ?? String(access)}`
5599
+ );
5455
5600
  }
5456
5601
  function strongestAccess(left, right) {
5457
5602
  return ACCESS_LEVEL_RANKS[right] > ACCESS_LEVEL_RANKS[left] ? right : left;
5458
5603
  }
5459
- function roomPatternMatches(pattern, roomId) {
5460
- if (pattern.includes("*")) {
5461
- return roomId.startsWith(pattern.replace("*", ""));
5462
- }
5463
- return pattern === roomId;
5604
+ function leafResourcesOf(resource) {
5605
+ return LEAF_ROOM_PERMISSION_RESOURCES_BY_PARENT.get(resource) ?? [];
5464
5606
  }
5465
- function roomPatternSpecificity(pattern) {
5466
- return pattern.replace("*", "").length;
5607
+ function childResourcesOf(resource) {
5608
+ return CHILD_ROOM_PERMISSION_RESOURCES.get(resource) ?? [];
5467
5609
  }
5468
- function validatePermissionsSet(scopes) {
5469
- const unknownScopes = scopes.filter((scope) => !VALID_PERMISSIONS.has(scope));
5470
- if (unknownScopes.length > 0) {
5471
- return `Unknown permission scope(s): ${unknownScopes.join(", ")}`;
5472
- }
5473
- const baseScopes = scopes.filter((scope) => basePermissionScopes.has(scope));
5474
- if (baseScopes.length !== 1) {
5475
- return `Permissions must include exactly one of ${Permission.Read}, ${Permission.Write} (or the legacy aliases ${Permission.RoomRead}, ${Permission.RoomWrite}), got ${baseScopes.length === 0 ? "none" : baseScopes.join(", ")}`;
5476
- }
5477
- const seenFeatures = /* @__PURE__ */ new Set();
5478
- for (const scope of scopes) {
5479
- if (basePermissionScopes.has(scope) || scope === Permission.LegacyRoomPresenceWrite) {
5480
- continue;
5481
- }
5482
- const feature = scope.slice(0, scope.indexOf(":"));
5483
- if (seenFeatures.has(feature)) {
5484
- return `Permissions can include at most one scope per feature, got multiple "${feature}" scopes`;
5485
- }
5486
- seenFeatures.add(feature);
5610
+ function parentResourceOf(resource) {
5611
+ return PARENT_ROOM_PERMISSION_RESOURCES.get(resource);
5612
+ }
5613
+ function getLeafResources(resource) {
5614
+ const childResources = childResourcesOf(resource);
5615
+ if (childResources.length === 0) {
5616
+ return [resource];
5487
5617
  }
5488
- return true;
5618
+ return childResources.flatMap(getLeafResources);
5489
5619
  }
5490
5620
 
5491
5621
  // src/protocol/AuthToken.ts
@@ -5768,15 +5898,13 @@ var OpCode = Object.freeze({
5768
5898
  DELETE_CRDT: 5,
5769
5899
  DELETE_OBJECT_KEY: 6,
5770
5900
  CREATE_MAP: 7,
5771
- CREATE_REGISTER: 8,
5772
- CREATE_TEXT: 9,
5773
- UPDATE_TEXT: 10
5901
+ CREATE_REGISTER: 8
5774
5902
  });
5775
5903
  function isIgnoredOp(op) {
5776
5904
  return op.type === OpCode.DELETE_CRDT && op.id === "ACK";
5777
5905
  }
5778
5906
  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;
5907
+ return op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_REGISTER || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_LIST;
5780
5908
  }
5781
5909
 
5782
5910
  // src/protocol/StorageNode.ts
@@ -5784,8 +5912,7 @@ var CrdtType = Object.freeze({
5784
5912
  OBJECT: 0,
5785
5913
  LIST: 1,
5786
5914
  MAP: 2,
5787
- REGISTER: 3,
5788
- TEXT: 4
5915
+ REGISTER: 3
5789
5916
  });
5790
5917
  function isRootStorageNode(node) {
5791
5918
  return node[0] === "root";
@@ -5802,9 +5929,6 @@ function isMapStorageNode(node) {
5802
5929
  function isRegisterStorageNode(node) {
5803
5930
  return node[1].type === CrdtType.REGISTER;
5804
5931
  }
5805
- function isTextStorageNode(node) {
5806
- return node[1].type === CrdtType.TEXT;
5807
- }
5808
5932
  function isCompactRootNode(node) {
5809
5933
  return node[0] === "root";
5810
5934
  }
@@ -5827,9 +5951,6 @@ function* compactNodesToNodeStream(compactNodes) {
5827
5951
  case CrdtType.REGISTER:
5828
5952
  yield [cnode[0], { type: CrdtType.REGISTER, parentId: cnode[2], parentKey: cnode[3], data: cnode[4] }];
5829
5953
  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
5954
  default:
5834
5955
  }
5835
5956
  }
@@ -5858,17 +5979,6 @@ function* nodeStreamToCompactNodes(nodes) {
5858
5979
  const id = node[0];
5859
5980
  const crdt = node[1];
5860
5981
  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
5982
  } else {
5873
5983
  }
5874
5984
  }
@@ -6071,10 +6181,6 @@ ${parentKey}`;
6071
6181
  get size() {
6072
6182
  return this.#byOpId.size;
6073
6183
  }
6074
- /** The still-unacknowledged op with the given opId, if any. */
6075
- get(opId) {
6076
- return this.#byOpId.get(opId);
6077
- }
6078
6184
  /**
6079
6185
  * Mark the given Op as still unacknowledged.
6080
6186
  */
@@ -6175,8 +6281,8 @@ function createManagedPool(roomId, options) {
6175
6281
  deleteNode: (id) => void nodes.delete(id),
6176
6282
  generateId: () => `${getCurrentConnectionId()}:${clock++}`,
6177
6283
  generateOpId: () => `${getCurrentConnectionId()}:${opClock++}`,
6178
- dispatch(ops, reverse, storageUpdates, options2) {
6179
- onDispatch?.(ops, reverse, storageUpdates, options2);
6284
+ dispatch(ops, reverse, storageUpdates) {
6285
+ onDispatch?.(ops, reverse, storageUpdates);
6180
6286
  },
6181
6287
  assertStorageIsWritable: () => {
6182
6288
  if (!isStorageWritable()) {
@@ -8735,1105 +8841,6 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
8735
8841
  }
8736
8842
  };
8737
8843
 
8738
- // src/crdts/liveTextOps.ts
8739
- function attributesEqual(left, right) {
8740
- if (left === right) {
8741
- return true;
8742
- }
8743
- if (left === void 0 || right === void 0) {
8744
- return false;
8745
- }
8746
- const leftKeys = Object.keys(left);
8747
- const rightKeys = Object.keys(right);
8748
- if (leftKeys.length !== rightKeys.length) {
8749
- return false;
8750
- }
8751
- for (const key of leftKeys) {
8752
- if (left[key] !== right[key]) {
8753
- return false;
8754
- }
8755
- }
8756
- return true;
8757
- }
8758
- function cloneAttributes(attributes) {
8759
- return attributes === void 0 ? void 0 : freeze({ ...attributes });
8760
- }
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;
8776
- }
8777
- function dataToSegments(data) {
8778
- return normalizeSegments(
8779
- data.map(([text, attributes]) => ({
8780
- text,
8781
- attributes
8782
- }))
8783
- );
8784
- }
8785
- function segmentsToData(segments) {
8786
- return segments.map(
8787
- (segment) => segment.attributes === void 0 ? [segment.text] : [segment.text, { ...segment.attributes }]
8788
- );
8789
- }
8790
- function textLength(segments) {
8791
- return segments.reduce((sum, segment) => sum + segment.text.length, 0);
8792
- }
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;
8809
- }
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 };
8817
- }
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 });
8836
- }
8837
- return normalizeSegments(result);
8838
- }
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;
8855
- }
8856
- return normalizeSegments(deleted);
8857
- }
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;
8871
- } else {
8872
- result.push(segment);
8873
- }
8874
- offset = end;
8875
- }
8876
- return {
8877
- segments: normalizeSegments(result),
8878
- deletedText,
8879
- deletedSegments
8880
- };
8881
- }
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;
8910
- }
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;
8935
- }
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;
8944
- }
8945
- return Math.max(op.index, index - op.length);
8946
- } else {
8947
- return index;
8948
- }
8949
- }
8950
- function mapTextIndexThroughOperations(index, ops) {
8951
- let mapped = index;
8952
- for (const op of ops) {
8953
- mapped = mapIndexThroughOperation(mapped, op);
8954
- }
8955
- return mapped;
8956
- }
8957
- function inverseMapIndexThroughOperation(index, op) {
8958
- if (op.type === "insert") {
8959
- if (index <= op.index) {
8960
- return index;
8961
- }
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
- }
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 {
9819
- type: "LiveText",
9820
- id: this._id ?? nanoid(),
9821
- key,
9822
- payload: [
9823
- {
9824
- type: "Json",
9825
- id: `${this._id ?? nanoid()}:text`,
9826
- key: "text",
9827
- payload: this.toString()
9828
- }
9829
- ]
9830
- };
9831
- }
9832
- clone() {
9833
- return new _LiveText(this.toJSON(), this.#version);
9834
- }
9835
- };
9836
-
9837
8844
  // src/crdts/liveblocks-helpers.ts
9838
8845
  function creationOpToLiveNode(op) {
9839
8846
  return lsonToLiveNode(creationOpToLson(op));
@@ -9848,8 +8855,6 @@ function creationOpToLson(op) {
9848
8855
  return new LiveMap();
9849
8856
  case OpCode.CREATE_LIST:
9850
8857
  return new LiveList([]);
9851
- case OpCode.CREATE_TEXT:
9852
- return new LiveText(op.data, op.version);
9853
8858
  default:
9854
8859
  return assertNever(op, "Unknown creation Op");
9855
8860
  }
@@ -9872,8 +8877,6 @@ function deserialize(node, parentToChildren, pool) {
9872
8877
  return LiveMap._deserialize(node, parentToChildren, pool);
9873
8878
  } else if (isRegisterStorageNode(node)) {
9874
8879
  return LiveRegister._deserialize(node, parentToChildren, pool);
9875
- } else if (isTextStorageNode(node)) {
9876
- return LiveText._deserialize(node, parentToChildren, pool);
9877
8880
  } else {
9878
8881
  throw new Error("Unexpected CRDT type");
9879
8882
  }
@@ -9887,14 +8890,12 @@ function deserializeToLson(node, parentToChildren, pool) {
9887
8890
  return LiveMap._deserialize(node, parentToChildren, pool);
9888
8891
  } else if (isRegisterStorageNode(node)) {
9889
8892
  return node[1].data;
9890
- } else if (isTextStorageNode(node)) {
9891
- return LiveText._deserialize(node, parentToChildren, pool);
9892
8893
  } else {
9893
8894
  throw new Error("Unexpected CRDT type");
9894
8895
  }
9895
8896
  }
9896
8897
  function isLiveStructure(value) {
9897
- return isLiveList(value) || isLiveMap(value) || isLiveObject(value) || isLiveText(value);
8898
+ return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
9898
8899
  }
9899
8900
  function isLiveNode(value) {
9900
8901
  return isLiveStructure(value) || isLiveRegister(value);
@@ -9908,9 +8909,6 @@ function isLiveMap(value) {
9908
8909
  function isLiveObject(value) {
9909
8910
  return value instanceof LiveObject;
9910
8911
  }
9911
- function isLiveText(value) {
9912
- return value instanceof LiveText;
9913
- }
9914
8912
  function isLiveRegister(value) {
9915
8913
  return value instanceof LiveRegister;
9916
8914
  }
@@ -9920,14 +8918,14 @@ function cloneLson(value) {
9920
8918
  function liveNodeToLson(obj) {
9921
8919
  if (obj instanceof LiveRegister) {
9922
8920
  return obj.data;
9923
- } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject || obj instanceof LiveText) {
8921
+ } else if (obj instanceof LiveList || obj instanceof LiveMap || obj instanceof LiveObject) {
9924
8922
  return obj;
9925
8923
  } else {
9926
8924
  return assertNever(obj, "Unknown AbstractCrdt");
9927
8925
  }
9928
8926
  }
9929
8927
  function lsonToLiveNode(value) {
9930
- if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList || value instanceof LiveText) {
8928
+ if (value instanceof LiveObject || value instanceof LiveMap || value instanceof LiveList) {
9931
8929
  return value;
9932
8930
  } else {
9933
8931
  return new LiveRegister(value);
@@ -10070,16 +9068,6 @@ function diffNodeMap(prev, next) {
10070
9068
  parentKey: crdt.parentKey
10071
9069
  });
10072
9070
  break;
10073
- case CrdtType.TEXT:
10074
- ops.push({
10075
- type: OpCode.CREATE_TEXT,
10076
- id,
10077
- parentId: crdt.parentId,
10078
- parentKey: crdt.parentKey,
10079
- data: crdt.data,
10080
- version: crdt.version
10081
- });
10082
- break;
10083
9071
  }
10084
9072
  }
10085
9073
  });
@@ -10112,43 +9100,19 @@ function mergeListStorageUpdates(first, second) {
10112
9100
  updates: updates.concat(second.updates)
10113
9101
  };
10114
9102
  }
10115
- function mergeTextStorageUpdates(first, second) {
10116
- return {
10117
- ...second,
10118
- updates: first.updates.concat(second.updates)
10119
- };
10120
- }
10121
9103
  function mergeStorageUpdates(first, second) {
10122
9104
  if (first === void 0) {
10123
9105
  return second;
10124
9106
  }
10125
- let merged;
10126
9107
  if (first.type === "LiveObject" && second.type === "LiveObject") {
10127
- merged = mergeObjectStorageUpdates(first, second);
9108
+ return mergeObjectStorageUpdates(first, second);
10128
9109
  } else if (first.type === "LiveMap" && second.type === "LiveMap") {
10129
- merged = mergeMapStorageUpdates(first, second);
9110
+ return mergeMapStorageUpdates(first, second);
10130
9111
  } else if (first.type === "LiveList" && second.type === "LiveList") {
10131
- merged = mergeListStorageUpdates(first, second);
10132
- } else if (first.type === "LiveText" && second.type === "LiveText") {
10133
- merged = mergeTextStorageUpdates(first, second);
9112
+ return mergeListStorageUpdates(first, second);
10134
9113
  } else {
10135
- merged = second;
10136
- }
10137
- const sa = first[kStorageUpdateSource];
10138
- const sb = second[kStorageUpdateSource];
10139
- if (sa !== void 0 || sb !== void 0) {
10140
- if (sa?.origin === "remote" || sb?.origin === "remote") {
10141
- merged[kStorageUpdateSource] = { origin: "remote" };
10142
- } else if (sa?.via === "history" || sb?.via === "history") {
10143
- const historySource = sb?.via === "history" ? sb : sa?.via === "history" ? sa : void 0;
10144
- if (historySource?.via === "history") {
10145
- merged[kStorageUpdateSource] = historySource;
10146
- }
10147
- } else {
10148
- merged[kStorageUpdateSource] = { origin: "local", via: "mutation" };
10149
- }
10150
9114
  }
10151
- return merged;
9115
+ return second;
10152
9116
  }
10153
9117
 
10154
9118
  // src/devtools/bridge.ts
@@ -11092,8 +10056,6 @@ function createRoom(options, config) {
11092
10056
  activeBatch: null,
11093
10057
  unacknowledgedOps
11094
10058
  };
11095
- let nextHistoryItemId = 0;
11096
- let historyDisabled = 0;
11097
10059
  const nodeMapBuffer = makeNodeMapBuffer();
11098
10060
  const stopwatch = config.enableDebugLogging ? makeStopWatch() : void 0;
11099
10061
  let lastTokenKey;
@@ -11178,10 +10140,7 @@ function createRoom(options, config) {
11178
10140
  }
11179
10141
  }
11180
10142
  });
11181
- function onDispatch(ops, reverse, storageUpdates, options2) {
11182
- for (const value of storageUpdates.values()) {
11183
- value[kStorageUpdateSource] = { origin: "local", via: "mutation" };
11184
- }
10143
+ function onDispatch(ops, reverse, storageUpdates) {
11185
10144
  if (context.activeBatch) {
11186
10145
  for (const op of ops) {
11187
10146
  context.activeBatch.ops.push(op);
@@ -11200,10 +10159,8 @@ function createRoom(options, config) {
11200
10159
  if (reverse.length > 0) {
11201
10160
  addToUndoStack(reverse);
11202
10161
  }
11203
- if (options2?.clearRedoStack ?? ops.length > 0) {
11204
- clearRedoStack();
11205
- }
11206
10162
  if (ops.length > 0) {
10163
+ context.redoStack.length = 0;
11207
10164
  dispatchOps(ops);
11208
10165
  }
11209
10166
  notify({ storageUpdates });
@@ -11223,7 +10180,6 @@ function createRoom(options, config) {
11223
10180
  others: makeEventSource(),
11224
10181
  storageBatch: makeEventSource(),
11225
10182
  history: makeEventSource(),
11226
- privateHistory: makeEventSource(),
11227
10183
  storageDidLoad: makeEventSource(),
11228
10184
  storageStatus: makeEventSource(),
11229
10185
  ydoc: makeEventSource(),
@@ -11316,23 +10272,6 @@ function createRoom(options, config) {
11316
10272
  }
11317
10273
  const ops = diffNodeMap(currentItems, nodes);
11318
10274
  const result = applyRemoteOps(ops);
11319
- for (const [id, crdt] of nodes) {
11320
- if (crdt.type === CrdtType.TEXT) {
11321
- const node = context.pool.nodes.get(id);
11322
- if (node !== void 0 && isLiveText(node)) {
11323
- const update = node._resyncText(crdt.data, crdt.version);
11324
- if (update !== void 0) {
11325
- result.updates.storageUpdates.set(
11326
- id,
11327
- mergeStorageUpdates(
11328
- result.updates.storageUpdates.get(id),
11329
- update
11330
- )
11331
- );
11332
- }
11333
- }
11334
- }
11335
- }
11336
10275
  notify(result.updates);
11337
10276
  } else {
11338
10277
  context.root = LiveObject._fromItems(
@@ -11356,26 +10295,11 @@ function createRoom(options, config) {
11356
10295
  }
11357
10296
  });
11358
10297
  }
11359
- function notifyPrivateHistory(event) {
11360
- if (historyDisabled > 0) return;
11361
- eventHub.privateHistory.notify(event);
11362
- }
11363
- function clearRedoStack() {
11364
- if (context.redoStack.length === 0) return;
11365
- const ids = context.redoStack.map((item) => item.id);
11366
- context.redoStack.length = 0;
11367
- notifyPrivateHistory({ action: "discard", ids });
11368
- }
11369
10298
  function _addToRealUndoStack(frames) {
11370
10299
  if (context.undoStack.length >= 50) {
11371
- const evicted = context.undoStack.shift();
11372
- if (evicted !== void 0) {
11373
- notifyPrivateHistory({ action: "discard", ids: [evicted.id] });
11374
- }
10300
+ context.undoStack.shift();
11375
10301
  }
11376
- const id = nextHistoryItemId++;
11377
- context.undoStack.push({ id, frames });
11378
- notifyPrivateHistory({ action: "push", id });
10302
+ context.undoStack.push(frames);
11379
10303
  onHistoryChange();
11380
10304
  }
11381
10305
  function addToUndoStack(frames) {
@@ -11413,7 +10337,7 @@ function createRoom(options, config) {
11413
10337
  "Internal. Tried to get connection id but connection was never open"
11414
10338
  );
11415
10339
  }
11416
- function applyLocalOps(frames, localStorageUpdateSource = { origin: "local", via: "mutation" }) {
10340
+ function applyLocalOps(frames) {
11417
10341
  const [pframes, ops] = partition(
11418
10342
  frames,
11419
10343
  (f) => f.type === "presence"
@@ -11425,8 +10349,7 @@ function createRoom(options, config) {
11425
10349
  pframes,
11426
10350
  opsWithOpIds,
11427
10351
  /* isLocal */
11428
- true,
11429
- localStorageUpdateSource
10352
+ true
11430
10353
  );
11431
10354
  return { opsToEmit: opsWithOpIds, reverse, updates };
11432
10355
  }
@@ -11438,7 +10361,7 @@ function createRoom(options, config) {
11438
10361
  false
11439
10362
  );
11440
10363
  }
11441
- function applyOps(pframes, ops, isLocal, localStorageUpdateSource = { origin: "local", via: "mutation" }) {
10364
+ function applyOps(pframes, ops, isLocal) {
11442
10365
  const output = {
11443
10366
  reverse: new Deque(),
11444
10367
  storageUpdates: /* @__PURE__ */ new Map(),
@@ -11476,7 +10399,6 @@ function createRoom(options, config) {
11476
10399
  }
11477
10400
  const applyOpResult = applyOp(op, source);
11478
10401
  if (applyOpResult.modified) {
11479
- applyOpResult.modified[kStorageUpdateSource] = source === 1 /* THEIRS */ ? { origin: "remote" } : localStorageUpdateSource;
11480
10402
  const nodeId = applyOpResult.modified.node._id;
11481
10403
  if (!(nodeId && createdNodeIds.has(nodeId))) {
11482
10404
  output.storageUpdates.set(
@@ -11488,7 +10410,7 @@ function createRoom(options, config) {
11488
10410
  );
11489
10411
  output.reverse.pushLeft(applyOpResult.reverse);
11490
10412
  }
11491
- if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT || op.type === OpCode.CREATE_TEXT) {
10413
+ if (op.type === OpCode.CREATE_LIST || op.type === OpCode.CREATE_MAP || op.type === OpCode.CREATE_OBJECT) {
11492
10414
  createdNodeIds.add(op.id);
11493
10415
  }
11494
10416
  }
@@ -11508,7 +10430,6 @@ function createRoom(options, config) {
11508
10430
  switch (op.type) {
11509
10431
  case OpCode.DELETE_OBJECT_KEY:
11510
10432
  case OpCode.UPDATE_OBJECT:
11511
- case OpCode.UPDATE_TEXT:
11512
10433
  case OpCode.DELETE_CRDT: {
11513
10434
  const node = context.pool.nodes.get(op.id);
11514
10435
  if (node === void 0) {
@@ -11533,7 +10454,6 @@ function createRoom(options, config) {
11533
10454
  case OpCode.CREATE_OBJECT:
11534
10455
  case OpCode.CREATE_LIST:
11535
10456
  case OpCode.CREATE_MAP:
11536
- case OpCode.CREATE_TEXT:
11537
10457
  case OpCode.CREATE_REGISTER: {
11538
10458
  if (op.parentId === void 0) {
11539
10459
  return { modified: false };
@@ -11783,37 +10703,16 @@ function createRoom(options, config) {
11783
10703
  }
11784
10704
  break;
11785
10705
  }
11786
- // Receiving a RejectedOps message means the server refused some of
11787
- // our ops, so our optimistic local state is out of sync with the
11788
- // server. For LiveText ops this is a normal (if rare) situation
11789
- // e.g. a client that was offline long enough to fall outside the
11790
- // server's retained history window — and we can recover: drop the
11791
- // rejected pending state and re-fetch the authoritative storage
11792
- // snapshot. For other ops (e.g. permission rejections), rolling back
11793
- // particular Ops is hard/impossible, so we keep the old behavior of
11794
- // accepting the out-of-sync reality and surfacing an error.
10706
+ // Receiving a RejectedOps message in the client means that the server is no
10707
+ // longer in sync with the client. Trying to synchronize the client again by
10708
+ // rolling back particular Ops may be hard/impossible. It's fine to not try and
10709
+ // accept the out-of-sync reality and throw an error.
11795
10710
  case ServerMsgCode.REJECT_STORAGE_OP: {
11796
10711
  errorWithTitle(
11797
10712
  "Storage mutation rejection error",
11798
10713
  message.reason
11799
10714
  );
11800
- let needsStorageResync = false;
11801
- for (const opId of message.opIds) {
11802
- const rejectedOp = context.unacknowledgedOps.get(opId);
11803
- context.unacknowledgedOps.delete(opId);
11804
- context.buffer.storageOperations = context.buffer.storageOperations.filter((op) => op.opId !== opId);
11805
- if (rejectedOp !== void 0 && rejectedOp.type === OpCode.UPDATE_TEXT) {
11806
- const node = context.pool.nodes.get(rejectedOp.id);
11807
- if (node !== void 0 && isLiveText(node)) {
11808
- node._rejectPendingOp(opId);
11809
- needsStorageResync = true;
11810
- }
11811
- }
11812
- }
11813
- if (needsStorageResync) {
11814
- refreshStorage();
11815
- flushNowOrSoon();
11816
- } else if (process.env.NODE_ENV !== "production") {
10715
+ if (process.env.NODE_ENV !== "production") {
11817
10716
  throw new Error(
11818
10717
  `Storage mutations rejected by server: ${message.reason}`
11819
10718
  );
@@ -12355,19 +11254,14 @@ function createRoom(options, config) {
12355
11254
  if (context.activeBatch) {
12356
11255
  throw new Error("undo is not allowed during a batch");
12357
11256
  }
12358
- const item = context.undoStack.pop();
12359
- if (item === void 0) {
11257
+ const frames = context.undoStack.pop();
11258
+ if (frames === void 0) {
12360
11259
  return;
12361
11260
  }
12362
11261
  context.pausedHistory = null;
12363
- const result = applyLocalOps(item.frames, {
12364
- origin: "local",
12365
- via: "history",
12366
- action: "undo"
12367
- });
12368
- context.redoStack.push({ id: item.id, frames: result.reverse });
12369
- notifyPrivateHistory({ action: "undo", id: item.id });
11262
+ const result = applyLocalOps(frames);
12370
11263
  notify(result.updates);
11264
+ context.redoStack.push(result.reverse);
12371
11265
  onHistoryChange();
12372
11266
  for (const op of result.opsToEmit) {
12373
11267
  context.buffer.storageOperations.push(op);
@@ -12378,19 +11272,14 @@ function createRoom(options, config) {
12378
11272
  if (context.activeBatch) {
12379
11273
  throw new Error("redo is not allowed during a batch");
12380
11274
  }
12381
- const item = context.redoStack.pop();
12382
- if (item === void 0) {
11275
+ const frames = context.redoStack.pop();
11276
+ if (frames === void 0) {
12383
11277
  return;
12384
11278
  }
12385
11279
  context.pausedHistory = null;
12386
- const result = applyLocalOps(item.frames, {
12387
- origin: "local",
12388
- via: "history",
12389
- action: "redo"
12390
- });
12391
- context.undoStack.push({ id: item.id, frames: result.reverse });
12392
- notifyPrivateHistory({ action: "redo", id: item.id });
11280
+ const result = applyLocalOps(frames);
12393
11281
  notify(result.updates);
11282
+ context.undoStack.push(result.reverse);
12394
11283
  onHistoryChange();
12395
11284
  for (const op of result.opsToEmit) {
12396
11285
  context.buffer.storageOperations.push(op);
@@ -12400,8 +11289,6 @@ function createRoom(options, config) {
12400
11289
  function clear() {
12401
11290
  context.undoStack.length = 0;
12402
11291
  context.redoStack.length = 0;
12403
- notifyPrivateHistory({ action: "clear" });
12404
- onHistoryChange();
12405
11292
  }
12406
11293
  function batch2(callback) {
12407
11294
  if (context.activeBatch) {
@@ -12430,7 +11317,7 @@ function createRoom(options, config) {
12430
11317
  commitPausedHistoryToUndoStack();
12431
11318
  }
12432
11319
  if (currentBatch.ops.length > 0) {
12433
- clearRedoStack();
11320
+ context.redoStack.length = 0;
12434
11321
  }
12435
11322
  if (currentBatch.ops.length > 0) {
12436
11323
  dispatchOps(currentBatch.ops);
@@ -12459,6 +11346,7 @@ function createRoom(options, config) {
12459
11346
  }
12460
11347
  commitPausedHistoryToUndoStack();
12461
11348
  }
11349
+ let historyDisabled = 0;
12462
11350
  function disableHistory(fn) {
12463
11351
  const origUndo = context.undoStack;
12464
11352
  const origRedo = context.redoStack;
@@ -12560,6 +11448,7 @@ function createRoom(options, config) {
12560
11448
  roomId,
12561
11449
  threadId: options2.threadId,
12562
11450
  commentId: options2.commentId,
11451
+ visibility: options2.visibility,
12563
11452
  metadata: options2.metadata,
12564
11453
  body: options2.body,
12565
11454
  commentMetadata: options2.commentMetadata,
@@ -12697,28 +11586,13 @@ function createRoom(options, config) {
12697
11586
  },
12698
11587
  // prettier-ignore
12699
11588
  get undoStack() {
12700
- return structuredClone(
12701
- context.undoStack.map((item) => ({
12702
- id: item.id,
12703
- frames: item.frames
12704
- }))
12705
- );
12706
- },
12707
- // prettier-ignore
12708
- get redoStack() {
12709
- return structuredClone(
12710
- context.redoStack.map((item) => ({
12711
- id: item.id,
12712
- frames: item.frames
12713
- }))
12714
- );
11589
+ return deepClone(context.undoStack);
12715
11590
  },
12716
11591
  // prettier-ignore
12717
11592
  get nodeCount() {
12718
11593
  return context.pool.nodes.size;
12719
11594
  },
12720
11595
  // prettier-ignore
12721
- history: eventHub.privateHistory.observable,
12722
11596
  getYjsProvider() {
12723
11597
  return context.yjsProvider;
12724
11598
  },
@@ -13738,12 +12612,6 @@ function toPlainLson(lson) {
13738
12612
  liveblocksType: "LiveList",
13739
12613
  data: [...lson].map((item) => toPlainLson(item))
13740
12614
  };
13741
- } else if (lson instanceof LiveText) {
13742
- return {
13743
- liveblocksType: "LiveText",
13744
- data: lson.toJSON(),
13745
- version: lson.version
13746
- };
13747
12615
  } else {
13748
12616
  return lson;
13749
12617
  }
@@ -13925,7 +12793,6 @@ export {
13925
12793
  LiveList,
13926
12794
  LiveMap,
13927
12795
  LiveObject,
13928
- LiveText,
13929
12796
  LiveblocksError,
13930
12797
  MENTION_CHARACTER,
13931
12798
  MutableSignal,
@@ -13937,7 +12804,6 @@ export {
13937
12804
  SortedList,
13938
12805
  TextEditorType,
13939
12806
  WebsocketCloseCodes,
13940
- applyLiveTextOperations,
13941
12807
  asPos,
13942
12808
  assert,
13943
12809
  assertNever,
@@ -13995,10 +12861,8 @@ export {
13995
12861
  isRegisterStorageNode,
13996
12862
  isRootStorageNode,
13997
12863
  isStartsWithOperator,
13998
- isTextStorageNode,
13999
12864
  isUrl,
14000
12865
  kInternal,
14001
- kStorageUpdateSource,
14002
12866
  keys,
14003
12867
  makeAbortController,
14004
12868
  makeEventSource,
@@ -14025,7 +12889,6 @@ export {
14025
12889
  stringifyCommentBody,
14026
12890
  throwUsageError,
14027
12891
  toPlainLson,
14028
- transformTextOperations,
14029
12892
  tryParseJson,
14030
12893
  url,
14031
12894
  urljoin,