@liveblocks/server 1.0.15 → 1.1.0

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 CHANGED
@@ -1369,63 +1369,45 @@ var Storage = class {
1369
1369
  };
1370
1370
 
1371
1371
  // src/YjsStorage.ts
1372
-
1373
1372
  var _jsbase64 = require('js-base64');
1374
1373
 
1375
1374
  var _yjs = require('yjs'); var Y = _interopRequireWildcard(_yjs);
1376
- var MAX_Y_UPDATE_SIZE = 1e5;
1375
+ var UPDATE_COUNT_THRESHOLD = 1e3;
1377
1376
  var YjsStorage = class {
1378
- constructor(driver) {
1377
+ constructor(driver, updateCountThreshold = UPDATE_COUNT_THRESHOLD) {
1379
1378
  __publicField(this, "driver");
1379
+ __publicField(this, "updateCountThreshold");
1380
1380
  __publicField(this, "doc", new Y.Doc());
1381
1381
  // the root document
1382
- __publicField(this, "lastUpdatesById", /* @__PURE__ */ new Map());
1383
1382
  __publicField(this, "lastSnapshotById", /* @__PURE__ */ new Map());
1384
- // Keeps track of which keys are loaded, so we can clean them up without calling `.list()`
1385
- __publicField(this, "keysById", new (0, _core.DefaultMap)(
1386
- () => /* @__PURE__ */ new Set()
1387
- ));
1388
1383
  __publicField(this, "initPromisesById", /* @__PURE__ */ new Map());
1389
- /**
1390
- * Given a record of updates, merge them and compress if savings are significant
1391
- */
1392
- __publicField(this, "_loadAndCompressYJSUpdates", async (docUpdates, doc, docId) => {
1393
- const SAVINGS_THRESHOLD = 0.2;
1394
- const updates = Object.values(docUpdates);
1395
- const sizeOnDisk = updates.reduce((acc, update) => {
1396
- return acc + update.length;
1397
- }, 0);
1398
- if (updates.length > 0) {
1399
- const docKeys = Object.keys(docUpdates);
1400
- this.keysById.set(docId, new Set(docKeys));
1401
- const mergedUpdate = Y.mergeUpdates(updates);
1402
- Y.applyUpdate(doc, mergedUpdate);
1403
- const garbageCollectedUpdate = Y.encodeStateAsUpdate(doc);
1404
- if (garbageCollectedUpdate.length < sizeOnDisk * (1 - SAVINGS_THRESHOLD)) {
1405
- const newKey = _nanoid.nanoid.call(void 0, );
1406
- await this.driver.write_y_updates(
1407
- docId,
1408
- newKey,
1409
- garbageCollectedUpdate
1410
- );
1411
- await this.driver.delete_y_updates(docId, docKeys);
1412
- this.keysById.set(docId, /* @__PURE__ */ new Set([newKey]));
1413
- }
1414
- }
1384
+ __publicField(this, "storedKeysById", /* @__PURE__ */ new Map());
1385
+ // compact the updates into a single update and write it to the durable storage
1386
+ __publicField(this, "_compactYJSUpdates", async (doc, docId, storedKeys) => {
1387
+ const compactedUpdate = Y.encodeStateAsUpdate(doc);
1388
+ const newKey = _nanoid.nanoid.call(void 0, );
1389
+ await this.driver.write_y_updates(docId, newKey, compactedUpdate);
1390
+ await this.driver.delete_y_updates(docId, storedKeys);
1391
+ this.storedKeysById.set(docId, [newKey]);
1415
1392
  });
1416
1393
  __publicField(this, "_loadYDocFromDurableStorage", async (doc, docId) => {
1417
1394
  const docUpdates = Object.fromEntries(
1418
1395
  await this.driver.iter_y_updates(docId)
1419
1396
  );
1420
- await this._loadAndCompressYJSUpdates(docUpdates, doc, docId);
1421
- this.lastUpdatesById.set(docId, {
1422
- currentKey: _nanoid.nanoid.call(void 0, ),
1423
- lastVector: Y.encodeStateVector(doc)
1424
- });
1397
+ const updates = Object.values(docUpdates);
1398
+ const newupdate = Y.mergeUpdates(updates);
1399
+ const storedKeys = Object.keys(docUpdates);
1400
+ Y.applyUpdate(doc, newupdate);
1401
+ if (this.shouldCompact(storedKeys)) {
1402
+ await this._compactYJSUpdates(doc, docId, storedKeys);
1403
+ } else {
1404
+ this.storedKeysById.set(docId, storedKeys);
1405
+ }
1425
1406
  doc.emit("load", [doc]);
1426
1407
  return doc;
1427
1408
  });
1428
1409
  this.driver = driver;
1410
+ this.updateCountThreshold = updateCountThreshold;
1429
1411
  this.doc.on("subdocs", ({ removed }) => {
1430
1412
  removed.forEach((subdoc) => {
1431
1413
  subdoc.destroy();
@@ -1490,7 +1472,9 @@ var YjsStorage = class {
1490
1472
  }
1491
1473
  /**
1492
1474
  * @param update base64 encoded uint8array
1493
- * @returns
1475
+ * @returns { isUpdated: boolean; snapshotHash: string }
1476
+ * isUpdated: true if the update had an effect on the YDoc
1477
+ * snapshotHash: the hash of the new snapshot
1494
1478
  */
1495
1479
  async addYDocUpdate(logger, update, guid, isV2) {
1496
1480
  const doc = guid !== void 0 ? await this.getYSubdoc(guid) : this.doc;
@@ -1505,7 +1489,7 @@ var YjsStorage = class {
1505
1489
  const afterSnapshot = this._putLastSnapshot(doc);
1506
1490
  const updated = !Y.equalSnapshots(beforeSnapshot, afterSnapshot);
1507
1491
  if (updated) {
1508
- await this.handleYDocUpdate(doc);
1492
+ await this.handleYDocUpdate(doc, updateAsU8, isV2);
1509
1493
  }
1510
1494
  return {
1511
1495
  isUpdated: updated,
@@ -1583,43 +1567,27 @@ var YjsStorage = class {
1583
1567
  return subdoc;
1584
1568
  }
1585
1569
  // When the YJS doc changes, update it in durable storage
1586
- async handleYDocUpdate(doc) {
1570
+ async handleYDocUpdate(doc, update, isV2) {
1571
+ const v1update = isV2 ? Y.convertUpdateFormatV2ToV1(update) : update;
1587
1572
  const docId = doc.guid === this.doc.guid ? ROOT_YDOC_ID : doc.guid;
1588
- const docUpdateInfo = this.lastUpdatesById.get(docId);
1589
- const updateSinceLastVector = Y.encodeStateAsUpdate(
1590
- doc,
1591
- _optionalChain([docUpdateInfo, 'optionalAccess', _49 => _49.lastVector])
1592
- );
1593
- const storageKey = _nullishCoalesce(_optionalChain([docUpdateInfo, 'optionalAccess', _50 => _50.currentKey]), () => ( _nanoid.nanoid.call(void 0, )));
1594
- if (updateSinceLastVector.length > MAX_Y_UPDATE_SIZE) {
1595
- const newKey = _nanoid.nanoid.call(void 0, );
1596
- await this.driver.write_y_updates(
1597
- docId,
1598
- newKey,
1599
- Y.encodeStateAsUpdate(doc)
1600
- );
1601
- await this.driver.delete_y_updates(
1602
- docId,
1603
- Array.from(this.keysById.getOrCreate(docId))
1604
- );
1605
- this.keysById.set(docId, /* @__PURE__ */ new Set([newKey]));
1606
- this.lastUpdatesById.set(docId, {
1607
- currentKey: _nanoid.nanoid.call(void 0, ),
1608
- // start writing to a new key
1609
- lastVector: Y.encodeStateVector(doc)
1610
- });
1573
+ const storedKeys = this.storedKeysById.get(docId);
1574
+ if (this.shouldCompact(storedKeys)) {
1575
+ await this._compactYJSUpdates(doc, docId, storedKeys || []);
1576
+ return;
1577
+ }
1578
+ const newKey = _nanoid.nanoid.call(void 0, );
1579
+ await this.driver.write_y_updates(docId, newKey, v1update);
1580
+ if (!storedKeys) {
1581
+ this.storedKeysById.set(docId, [newKey]);
1611
1582
  } else {
1612
- await this.driver.write_y_updates(
1613
- docId,
1614
- storageKey,
1615
- updateSinceLastVector
1616
- );
1617
- const keys = [storageKey];
1618
- const currentKeys = this.keysById.getOrCreate(docId);
1619
- for (const key of keys) {
1620
- currentKeys.add(key);
1621
- }
1583
+ storedKeys.push(newKey);
1584
+ }
1585
+ }
1586
+ shouldCompact(storedKeys) {
1587
+ if (!storedKeys) {
1588
+ return false;
1622
1589
  }
1590
+ return storedKeys.length >= this.updateCountThreshold;
1623
1591
  }
1624
1592
  };
1625
1593
 
@@ -1768,7 +1736,7 @@ var BrowserSession = class {
1768
1736
  __privateSet(this, __hasNotifiedClientStorageUpdateError, false);
1769
1737
  }
1770
1738
  get lastActiveAt() {
1771
- const lastPing = _optionalChain([__privateGet, 'call', _51 => _51(this, __socket), 'access', _52 => _52.getLastPongTimestamp, 'optionalCall', _53 => _53()]);
1739
+ const lastPing = _optionalChain([__privateGet, 'call', _49 => _49(this, __socket), 'access', _50 => _50.getLastPongTimestamp, 'optionalCall', _51 => _51()]);
1772
1740
  if (lastPing && lastPing > __privateGet(this, __lastActiveAt)) {
1773
1741
  return lastPing;
1774
1742
  } else {
@@ -1860,28 +1828,28 @@ var Room = class {
1860
1828
  __publicField(this, "hooks");
1861
1829
  __privateAdd(this, __debug2);
1862
1830
  __privateAdd(this, __allowStreaming);
1863
- const driver = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _54 => _54.storage]), () => ( makeNewInMemoryDriver()));
1831
+ const driver = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _52 => _52.storage]), () => ( makeNewInMemoryDriver()));
1864
1832
  this.meta = meta;
1865
1833
  this.driver = driver;
1866
- this.logger = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _55 => _55.logger]), () => ( BLACK_HOLE));
1867
- __privateSet(this, __allowStreaming, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _56 => _56.allowStreaming]), () => ( true)));
1834
+ this.logger = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _53 => _53.logger]), () => ( BLACK_HOLE));
1835
+ __privateSet(this, __allowStreaming, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _54 => _54.allowStreaming]), () => ( true)));
1868
1836
  this.hooks = {
1869
- isClientMsgAllowed: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _57 => _57.hooks, 'optionalAccess', _58 => _58.isClientMsgAllowed]), () => ( (() => {
1837
+ isClientMsgAllowed: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _55 => _55.hooks, 'optionalAccess', _56 => _56.isClientMsgAllowed]), () => ( (() => {
1870
1838
  return {
1871
1839
  allowed: true
1872
1840
  };
1873
1841
  }))),
1874
1842
  // YYY .load() isn't called on the RoomServer yet! As soon as it does, these hooks will get called
1875
- onRoomWillLoad: _optionalChain([options, 'optionalAccess', _59 => _59.hooks, 'optionalAccess', _60 => _60.onRoomWillLoad]),
1876
- onRoomDidLoad: _optionalChain([options, 'optionalAccess', _61 => _61.hooks, 'optionalAccess', _62 => _62.onRoomDidLoad]),
1877
- onRoomWillUnload: _optionalChain([options, 'optionalAccess', _63 => _63.hooks, 'optionalAccess', _64 => _64.onRoomWillUnload]),
1878
- onRoomDidUnload: _optionalChain([options, 'optionalAccess', _65 => _65.hooks, 'optionalAccess', _66 => _66.onRoomDidUnload]),
1879
- onSessionDidStart: _optionalChain([options, 'optionalAccess', _67 => _67.hooks, 'optionalAccess', _68 => _68.onSessionDidStart]),
1880
- onSessionDidEnd: _optionalChain([options, 'optionalAccess', _69 => _69.hooks, 'optionalAccess', _70 => _70.onSessionDidEnd]),
1881
- postClientMsgStorageDidUpdate: _optionalChain([options, 'optionalAccess', _71 => _71.hooks, 'optionalAccess', _72 => _72.postClientMsgStorageDidUpdate]),
1882
- postClientMsgYdocDidUpdate: _optionalChain([options, 'optionalAccess', _73 => _73.hooks, 'optionalAccess', _74 => _74.postClientMsgYdocDidUpdate])
1843
+ onRoomWillLoad: _optionalChain([options, 'optionalAccess', _57 => _57.hooks, 'optionalAccess', _58 => _58.onRoomWillLoad]),
1844
+ onRoomDidLoad: _optionalChain([options, 'optionalAccess', _59 => _59.hooks, 'optionalAccess', _60 => _60.onRoomDidLoad]),
1845
+ onRoomWillUnload: _optionalChain([options, 'optionalAccess', _61 => _61.hooks, 'optionalAccess', _62 => _62.onRoomWillUnload]),
1846
+ onRoomDidUnload: _optionalChain([options, 'optionalAccess', _63 => _63.hooks, 'optionalAccess', _64 => _64.onRoomDidUnload]),
1847
+ onSessionDidStart: _optionalChain([options, 'optionalAccess', _65 => _65.hooks, 'optionalAccess', _66 => _66.onSessionDidStart]),
1848
+ onSessionDidEnd: _optionalChain([options, 'optionalAccess', _67 => _67.hooks, 'optionalAccess', _68 => _68.onSessionDidEnd]),
1849
+ postClientMsgStorageDidUpdate: _optionalChain([options, 'optionalAccess', _69 => _69.hooks, 'optionalAccess', _70 => _70.postClientMsgStorageDidUpdate]),
1850
+ postClientMsgYdocDidUpdate: _optionalChain([options, 'optionalAccess', _71 => _71.hooks, 'optionalAccess', _72 => _72.postClientMsgYdocDidUpdate])
1883
1851
  };
1884
- __privateSet(this, __debug2, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _75 => _75.enableDebugLogging]), () => ( false)));
1852
+ __privateSet(this, __debug2, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _73 => _73.enableDebugLogging]), () => ( false)));
1885
1853
  }
1886
1854
  get loadingState() {
1887
1855
  if (this._loadData$ === null) {
@@ -1937,13 +1905,13 @@ var Room = class {
1937
1905
  * room will be reloaded from storage.
1938
1906
  */
1939
1907
  unload(ctx) {
1940
- _optionalChain([this, 'access', _76 => _76.hooks, 'access', _77 => _77.onRoomWillUnload, 'optionalCall', _78 => _78(ctx)]);
1908
+ _optionalChain([this, 'access', _74 => _74.hooks, 'access', _75 => _75.onRoomWillUnload, 'optionalCall', _76 => _76(ctx)]);
1941
1909
  if (this._data) {
1942
1910
  this.storage.unload();
1943
1911
  this.yjsStorage.unload();
1944
1912
  }
1945
1913
  this._loadData$ = null;
1946
- _optionalChain([this, 'access', _79 => _79.hooks, 'access', _80 => _80.onRoomDidUnload, 'optionalCall', _81 => _81(ctx)]);
1914
+ _optionalChain([this, 'access', _77 => _77.hooks, 'access', _78 => _78.onRoomDidUnload, 'optionalCall', _79 => _79(ctx)]);
1947
1915
  }
1948
1916
  /**
1949
1917
  * Issues a Ticket with a new/unique actor ID
@@ -1957,17 +1925,17 @@ var Room = class {
1957
1925
  * unused Ticket will simply get garbage collected.
1958
1926
  */
1959
1927
  async createTicket(options) {
1960
- const actor$ = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _82 => _82.actor]), () => ( this.getNextActor()));
1928
+ const actor$ = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _80 => _80.actor]), () => ( this.getNextActor()));
1961
1929
  const sessionKey = _nanoid.nanoid.call(void 0, );
1962
- const info = _optionalChain([options, 'optionalAccess', _83 => _83.info]);
1930
+ const info = _optionalChain([options, 'optionalAccess', _81 => _81.info]);
1963
1931
  const ticket = {
1964
- version: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _84 => _84.version]), () => ( HIGHEST_PROTOCOL_VERSION)),
1932
+ version: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _82 => _82.version]), () => ( HIGHEST_PROTOCOL_VERSION)),
1965
1933
  actor: await actor$,
1966
1934
  sessionKey,
1967
- meta: _optionalChain([options, 'optionalAccess', _85 => _85.meta]),
1968
- publicMeta: _optionalChain([options, 'optionalAccess', _86 => _86.publicMeta]),
1969
- user: _optionalChain([options, 'optionalAccess', _87 => _87.id]) ? { id: options.id, info } : { anonymousId: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _88 => _88.anonymousId]), () => ( _nanoid.nanoid.call(void 0, ))), info },
1970
- scopes: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _89 => _89.scopes]), () => ( ["room:write"]))
1935
+ meta: _optionalChain([options, 'optionalAccess', _83 => _83.meta]),
1936
+ publicMeta: _optionalChain([options, 'optionalAccess', _84 => _84.publicMeta]),
1937
+ user: _optionalChain([options, 'optionalAccess', _85 => _85.id]) ? { id: options.id, info } : { anonymousId: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _86 => _86.anonymousId]), () => ( _nanoid.nanoid.call(void 0, ))), info },
1938
+ scopes: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _87 => _87.scopes]), () => ( ["room:write"]))
1971
1939
  };
1972
1940
  if (__privateGet(this, __debug2)) {
1973
1941
  console.log(`new ticket created: ${JSON.stringify(ticket)}`);
@@ -2096,7 +2064,7 @@ var Room = class {
2096
2064
  ctx,
2097
2065
  defer
2098
2066
  );
2099
- const p$ = _optionalChain([this, 'access', _90 => _90.hooks, 'access', _91 => _91.onSessionDidStart, 'optionalCall', _92 => _92(newSession, ctx)]);
2067
+ const p$ = _optionalChain([this, 'access', _88 => _88.hooks, 'access', _89 => _89.onSessionDidStart, 'optionalCall', _90 => _90(newSession, ctx)]);
2100
2068
  if (p$) defer(p$);
2101
2069
  }
2102
2070
  /**
@@ -2120,7 +2088,7 @@ var Room = class {
2120
2088
  for (const other of this.otherSessions(key)) {
2121
2089
  other.send({ type: _core.ServerMsgCode.USER_LEFT, actor: session.actor });
2122
2090
  }
2123
- const p$ = _optionalChain([this, 'access', _93 => _93.hooks, 'access', _94 => _94.onSessionDidEnd, 'optionalCall', _95 => _95(session, ctx)]);
2091
+ const p$ = _optionalChain([this, 'access', _91 => _91.hooks, 'access', _92 => _92.onSessionDidEnd, 'optionalCall', _93 => _93(session, ctx)]);
2124
2092
  if (p$) defer(p$);
2125
2093
  }
2126
2094
  }
@@ -2424,7 +2392,7 @@ var Room = class {
2424
2392
  }
2425
2393
  // Don't ever manually call this!
2426
2394
  async _load(ctx) {
2427
- await _optionalChain([this, 'access', _96 => _96.hooks, 'access', _97 => _97.onRoomWillLoad, 'optionalCall', _98 => _98(ctx)]);
2395
+ await _optionalChain([this, 'access', _94 => _94.hooks, 'access', _95 => _95.onRoomWillLoad, 'optionalCall', _96 => _96(ctx)]);
2428
2396
  const storage = await this._loadStorage();
2429
2397
  const yjsStorage = await this._loadYjsStorage();
2430
2398
  this._data = {
@@ -2432,7 +2400,7 @@ var Room = class {
2432
2400
  storage,
2433
2401
  yjsStorage
2434
2402
  };
2435
- await _optionalChain([this, 'access', _99 => _99.hooks, 'access', _100 => _100.onRoomDidLoad, 'optionalCall', _101 => _101(ctx)]);
2403
+ await _optionalChain([this, 'access', _97 => _97.hooks, 'access', _98 => _98.onRoomDidLoad, 'optionalCall', _99 => _99(ctx)]);
2436
2404
  }
2437
2405
  /**
2438
2406
  * Returns a new, unique, actor ID.
@@ -2473,7 +2441,7 @@ var Room = class {
2473
2441
  }
2474
2442
  const sent = session.sendPong();
2475
2443
  if (sent !== 0) {
2476
- await _optionalChain([this, 'access', _102 => _102.hooks, 'access', _103 => _103.onDidPong, 'optionalCall', _104 => _104(ctx)]);
2444
+ await _optionalChain([this, 'access', _100 => _100.hooks, 'access', _101 => _101.onDidPong, 'optionalCall', _102 => _102(ctx)]);
2477
2445
  }
2478
2446
  }
2479
2447
  async _processClientMsg_withExclusiveAccess(sessionKey, messages, ctx, defer) {
@@ -2612,7 +2580,7 @@ var Room = class {
2612
2580
  break;
2613
2581
  }
2614
2582
  case _core.ClientMsgCode.UPDATE_STORAGE: {
2615
- _optionalChain([this, 'access', _105 => _105.driver, 'access', _106 => _106.bump_storage_version, 'optionalCall', _107 => _107()]);
2583
+ _optionalChain([this, 'access', _103 => _103.driver, 'access', _104 => _104.bump_storage_version, 'optionalCall', _105 => _105()]);
2616
2584
  const result = await this.storage.applyOps(msg.ops);
2617
2585
  const opsToForward = result.flatMap(
2618
2586
  (r) => r.action === "accepted" ? [r.op] : []
@@ -2644,7 +2612,7 @@ var Room = class {
2644
2612
  });
2645
2613
  }
2646
2614
  if (opsToForward.length > 0) {
2647
- const p$ = _optionalChain([this, 'access', _108 => _108.hooks, 'access', _109 => _109.postClientMsgStorageDidUpdate, 'optionalCall', _110 => _110(ctx)]);
2615
+ const p$ = _optionalChain([this, 'access', _106 => _106.hooks, 'access', _107 => _107.postClientMsgStorageDidUpdate, 'optionalCall', _108 => _108(ctx)]);
2648
2616
  if (p$) defer(p$);
2649
2617
  }
2650
2618
  break;
@@ -2695,7 +2663,7 @@ var Room = class {
2695
2663
  defer
2696
2664
  );
2697
2665
  if (result.isUpdated) {
2698
- const p$ = _optionalChain([this, 'access', _111 => _111.hooks, 'access', _112 => _112.postClientMsgYdocDidUpdate, 'optionalCall', _113 => _113(ctx, session)]);
2666
+ const p$ = _optionalChain([this, 'access', _109 => _109.hooks, 'access', _110 => _110.postClientMsgYdocDidUpdate, 'optionalCall', _111 => _111(ctx, session)]);
2699
2667
  if (p$) defer(p$);
2700
2668
  }
2701
2669
  break;