@liveblocks/server 1.0.14 → 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.d.cts CHANGED
@@ -824,12 +824,12 @@ declare class Storage {
824
824
 
825
825
  declare class YjsStorage {
826
826
  private readonly driver;
827
+ private readonly updateCountThreshold;
827
828
  private readonly doc;
828
- private readonly lastUpdatesById;
829
829
  private readonly lastSnapshotById;
830
- private readonly keysById;
831
830
  private readonly initPromisesById;
832
- constructor(driver: IStorageDriver);
831
+ private readonly storedKeysById;
832
+ constructor(driver: IStorageDriver, updateCountThreshold?: number);
833
833
  getYDoc(docId: YDocId): Promise<Y.Doc>;
834
834
  /**
835
835
  * If passed a state vector, an update with diff will be returned, if not the entire doc is returned.
@@ -846,7 +846,9 @@ declare class YjsStorage {
846
846
  }): Promise<string | null>;
847
847
  /**
848
848
  * @param update base64 encoded uint8array
849
- * @returns
849
+ * @returns { isUpdated: boolean; snapshotHash: string }
850
+ * isUpdated: true if the update had an effect on the YDoc
851
+ * snapshotHash: the hash of the new snapshot
850
852
  */
851
853
  addYDocUpdate(logger: Logger, update: string | Uint8Array, guid?: Guid, isV2?: boolean): Promise<{
852
854
  isUpdated: boolean;
@@ -860,15 +862,13 @@ declare class YjsStorage {
860
862
  unload(): void;
861
863
  private _getOrPutLastSnapshot;
862
864
  private _putLastSnapshot;
863
- /**
864
- * Given a record of updates, merge them and compress if savings are significant
865
- */
866
- private _loadAndCompressYJSUpdates;
865
+ private _compactYJSUpdates;
867
866
  private _loadYDocFromDurableStorage;
868
867
  private findYSubdocByGuid;
869
868
  private calculateSnapshotHash;
870
869
  private getYSubdoc;
871
870
  private handleYDocUpdate;
871
+ private shouldCompact;
872
872
  }
873
873
 
874
874
  /**
package/dist/index.d.ts CHANGED
@@ -824,12 +824,12 @@ declare class Storage {
824
824
 
825
825
  declare class YjsStorage {
826
826
  private readonly driver;
827
+ private readonly updateCountThreshold;
827
828
  private readonly doc;
828
- private readonly lastUpdatesById;
829
829
  private readonly lastSnapshotById;
830
- private readonly keysById;
831
830
  private readonly initPromisesById;
832
- constructor(driver: IStorageDriver);
831
+ private readonly storedKeysById;
832
+ constructor(driver: IStorageDriver, updateCountThreshold?: number);
833
833
  getYDoc(docId: YDocId): Promise<Y.Doc>;
834
834
  /**
835
835
  * If passed a state vector, an update with diff will be returned, if not the entire doc is returned.
@@ -846,7 +846,9 @@ declare class YjsStorage {
846
846
  }): Promise<string | null>;
847
847
  /**
848
848
  * @param update base64 encoded uint8array
849
- * @returns
849
+ * @returns { isUpdated: boolean; snapshotHash: string }
850
+ * isUpdated: true if the update had an effect on the YDoc
851
+ * snapshotHash: the hash of the new snapshot
850
852
  */
851
853
  addYDocUpdate(logger: Logger, update: string | Uint8Array, guid?: Guid, isV2?: boolean): Promise<{
852
854
  isUpdated: boolean;
@@ -860,15 +862,13 @@ declare class YjsStorage {
860
862
  unload(): void;
861
863
  private _getOrPutLastSnapshot;
862
864
  private _putLastSnapshot;
863
- /**
864
- * Given a record of updates, merge them and compress if savings are significant
865
- */
866
- private _loadAndCompressYJSUpdates;
865
+ private _compactYJSUpdates;
867
866
  private _loadYDocFromDurableStorage;
868
867
  private findYSubdocByGuid;
869
868
  private calculateSnapshotHash;
870
869
  private getYSubdoc;
871
870
  private handleYDocUpdate;
871
+ private shouldCompact;
872
872
  }
873
873
 
874
874
  /**
package/dist/index.js CHANGED
@@ -1369,63 +1369,45 @@ var Storage = class {
1369
1369
  };
1370
1370
 
1371
1371
  // src/YjsStorage.ts
1372
- import { DefaultMap as DefaultMap2 } from "@liveblocks/core";
1373
1372
  import { Base64 } from "js-base64";
1374
1373
  import { nanoid } from "nanoid";
1375
1374
  import * as Y from "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 DefaultMap2(
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();
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();
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(),
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
- docUpdateInfo?.lastVector
1592
- );
1593
- const storageKey = docUpdateInfo?.currentKey ?? nanoid();
1594
- if (updateSinceLastVector.length > MAX_Y_UPDATE_SIZE) {
1595
- const newKey = nanoid();
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(),
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();
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