@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.cjs +467 -1704
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -354
- package/dist/index.d.ts +59 -354
- package/dist/index.js +304 -1541
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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-
|
|
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:
|
|
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: "
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
5311
|
-
|
|
5312
|
-
const explicitMatrix = {};
|
|
5313
|
-
const explicitSpecificity = {};
|
|
5346
|
+
const matrix = {};
|
|
5347
|
+
const specificityByResource = {};
|
|
5314
5348
|
for (const entry of matchedPermissions) {
|
|
5315
|
-
const
|
|
5349
|
+
const explicitMatrix = explicitPermissionMatrixFromScopes(entry.scopes);
|
|
5316
5350
|
const specificity = roomPatternSpecificity(entry.pattern);
|
|
5317
|
-
if (
|
|
5318
|
-
|
|
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 =
|
|
5323
|
-
if (access
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
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
|
|
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
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
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
|
-
|
|
5399
|
-
|
|
5400
|
-
groupsAccesses.map(
|
|
5434
|
+
explicitPermissionMatrixFromScopes(defaultAccesses),
|
|
5435
|
+
mergeExplicitPermissionMatricesByHighestAccess(
|
|
5436
|
+
groupsAccesses.map(explicitPermissionMatrixFromScopes)
|
|
5401
5437
|
),
|
|
5402
|
-
|
|
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.
|
|
5411
|
-
merged.
|
|
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
|
|
5446
|
+
const access = explicitAccessForResource(source, resource);
|
|
5416
5447
|
if (access !== void 0) {
|
|
5417
|
-
merged
|
|
5448
|
+
merged[resource] = access;
|
|
5418
5449
|
}
|
|
5419
5450
|
}
|
|
5420
5451
|
}
|
|
5421
|
-
return permissionMatrixToScopes(
|
|
5452
|
+
return permissionMatrixToScopes(
|
|
5453
|
+
permissionMatrixFromExplicitPermissions(merged)
|
|
5454
|
+
);
|
|
5422
5455
|
}
|
|
5423
|
-
function
|
|
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.
|
|
5431
|
-
merged.
|
|
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
|
|
5463
|
+
const access = explicitAccessForResource(source, resource);
|
|
5436
5464
|
if (access !== void 0) {
|
|
5437
|
-
merged
|
|
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
|
|
5448
|
-
const
|
|
5449
|
-
if (
|
|
5450
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
6179
|
-
onDispatch?.(ops, reverse, storageUpdates
|
|
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/
|
|
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/
|
|
8739
|
-
function
|
|
8740
|
-
|
|
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 (
|
|
8744
|
-
return
|
|
8777
|
+
if (node.parent.type === "HasParent") {
|
|
8778
|
+
return isSameNodeOrChildOf(node.parent.node, parent);
|
|
8745
8779
|
}
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
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
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
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
|
|
8759
|
-
return
|
|
8808
|
+
function isLiveStructure(value) {
|
|
8809
|
+
return isLiveList(value) || isLiveMap(value) || isLiveObject(value);
|
|
8760
8810
|
}
|
|
8761
|
-
function
|
|
8762
|
-
|
|
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
|
|
8778
|
-
return
|
|
8779
|
-
data.map(([text, attributes]) => ({
|
|
8780
|
-
text,
|
|
8781
|
-
attributes
|
|
8782
|
-
}))
|
|
8783
|
-
);
|
|
8814
|
+
function isLiveList(value) {
|
|
8815
|
+
return value instanceof LiveList;
|
|
8784
8816
|
}
|
|
8785
|
-
function
|
|
8786
|
-
return
|
|
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
|
|
8791
|
-
return
|
|
8820
|
+
function isLiveObject(value) {
|
|
8821
|
+
return value instanceof LiveObject;
|
|
8792
8822
|
}
|
|
8793
|
-
function
|
|
8794
|
-
|
|
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
|
|
8811
|
-
|
|
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
|
|
8819
|
-
if (
|
|
8820
|
-
return
|
|
8821
|
-
}
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
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
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
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
|
|
8859
|
-
const
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
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
|
-
|
|
8857
|
+
value = "<LiveObject>";
|
|
8873
8858
|
}
|
|
8874
|
-
|
|
8875
|
-
}
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
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
|
|
8883
|
-
|
|
8884
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
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
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
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
|
-
|
|
8951
|
-
|
|
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
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
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
|
|
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
|
-
|
|
9019
|
+
return mergeObjectStorageUpdates(first, second);
|
|
10140
9020
|
} else if (first.type === "LiveMap" && second.type === "LiveMap") {
|
|
10141
|
-
|
|
9021
|
+
return mergeMapStorageUpdates(first, second);
|
|
10142
9022
|
} else if (first.type === "LiveList" && second.type === "LiveList") {
|
|
10143
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
11384
|
-
if (evicted !== void 0) {
|
|
11385
|
-
notifyPrivateHistory({ action: "discard", ids: [evicted.id] });
|
|
11386
|
-
}
|
|
10211
|
+
context.undoStack.shift();
|
|
11387
10212
|
}
|
|
11388
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
11799
|
-
//
|
|
11800
|
-
//
|
|
11801
|
-
//
|
|
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
|
-
|
|
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
|
|
12371
|
-
if (
|
|
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(
|
|
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
|
|
12394
|
-
if (
|
|
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(
|
|
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
|
-
|
|
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
|
|
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,
|