@automerge/automerge-repo 2.0.0-alpha.1 → 2.0.0-alpha.12

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.
Files changed (39) hide show
  1. package/dist/DocHandle.d.ts +71 -2
  2. package/dist/DocHandle.d.ts.map +1 -1
  3. package/dist/DocHandle.js +116 -2
  4. package/dist/Repo.d.ts +24 -0
  5. package/dist/Repo.d.ts.map +1 -1
  6. package/dist/Repo.js +94 -57
  7. package/dist/entrypoints/fullfat.d.ts +1 -0
  8. package/dist/entrypoints/fullfat.d.ts.map +1 -1
  9. package/dist/entrypoints/fullfat.js +1 -2
  10. package/dist/entrypoints/slim.d.ts +1 -0
  11. package/dist/entrypoints/slim.d.ts.map +1 -1
  12. package/dist/entrypoints/slim.js +2 -0
  13. package/dist/helpers/tests/storage-adapter-tests.d.ts +2 -2
  14. package/dist/helpers/tests/storage-adapter-tests.d.ts.map +1 -1
  15. package/dist/helpers/tests/storage-adapter-tests.js +19 -39
  16. package/dist/storage/StorageSubsystem.d.ts +11 -1
  17. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  18. package/dist/storage/StorageSubsystem.js +18 -3
  19. package/dist/synchronizer/CollectionSynchronizer.d.ts +13 -0
  20. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  21. package/dist/synchronizer/CollectionSynchronizer.js +13 -6
  22. package/dist/synchronizer/DocSynchronizer.d.ts +7 -0
  23. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  24. package/dist/synchronizer/DocSynchronizer.js +14 -0
  25. package/dist/synchronizer/Synchronizer.d.ts +8 -0
  26. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  27. package/package.json +3 -3
  28. package/src/DocHandle.ts +137 -4
  29. package/src/Repo.ts +123 -56
  30. package/src/entrypoints/fullfat.ts +1 -2
  31. package/src/entrypoints/slim.ts +2 -0
  32. package/src/helpers/tests/storage-adapter-tests.ts +31 -62
  33. package/src/storage/StorageSubsystem.ts +26 -3
  34. package/src/synchronizer/CollectionSynchronizer.ts +23 -6
  35. package/src/synchronizer/DocSynchronizer.ts +15 -0
  36. package/src/synchronizer/Synchronizer.ts +9 -0
  37. package/test/DocHandle.test.ts +141 -0
  38. package/test/Repo.test.ts +73 -0
  39. package/test/StorageSubsystem.test.ts +17 -0
@@ -6,5 +6,4 @@ export * from "../index.js";
6
6
  // disable
7
7
  //
8
8
  // eslint-disable-next-line automerge-slimport/enforce-automerge-slim-import
9
- import { next as Am } from "@automerge/automerge";
10
- Am.init();
9
+ import "@automerge/automerge";
@@ -1,3 +1,4 @@
1
1
  export * from "../index.js";
2
2
  export { initializeBase64Wasm, initializeWasm } from "@automerge/automerge/slim";
3
+ export * as Automerge from "@automerge/automerge/slim";
3
4
  //# sourceMappingURL=slim.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slim.d.ts","sourceRoot":"","sources":["../../src/entrypoints/slim.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA"}
1
+ {"version":3,"file":"slim.d.ts","sourceRoot":"","sources":["../../src/entrypoints/slim.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAEhF,OAAO,KAAK,SAAS,MAAM,2BAA2B,CAAA"}
@@ -1,2 +1,4 @@
1
1
  export * from "../index.js";
2
2
  export { initializeBase64Wasm, initializeWasm } from "@automerge/automerge/slim";
3
+ // TODO: temporary work-around during alpha.
4
+ export * as Automerge from "@automerge/automerge/slim";
@@ -1,7 +1,7 @@
1
1
  import type { StorageAdapterInterface } from "../../storage/StorageAdapterInterface.js";
2
- export declare function runStorageAdapterTests(_setup: SetupFn, title?: string): void;
2
+ export declare function runStorageAdapterTests(setup: SetupFn, title?: string): void;
3
3
  export type SetupFn = () => Promise<{
4
4
  adapter: StorageAdapterInterface;
5
- teardown?: () => void;
5
+ teardown?: () => void | Promise<void>;
6
6
  }>;
7
7
  //# sourceMappingURL=storage-adapter-tests.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/storage-adapter-tests.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAA;AAQvF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CA+K5E;AAID,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;IAClC,OAAO,EAAE,uBAAuB,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB,CAAC,CAAA"}
1
+ {"version":3,"file":"storage-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/storage-adapter-tests.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAA;AAcvF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CA0I3E;AAID,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;IAClC,OAAO,EAAE,uBAAuB,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC,CAAC,CAAA"}
@@ -1,55 +1,46 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, beforeEach, it as _it } from "vitest";
2
2
  const PAYLOAD_A = () => new Uint8Array([0, 1, 127, 99, 154, 235]);
3
3
  const PAYLOAD_B = () => new Uint8Array([1, 76, 160, 53, 57, 10, 230]);
4
4
  const PAYLOAD_C = () => new Uint8Array([2, 111, 74, 131, 236, 96, 142, 193]);
5
5
  const LARGE_PAYLOAD = new Uint8Array(100000).map(() => Math.random() * 256);
6
- export function runStorageAdapterTests(_setup, title) {
7
- const setup = async () => {
8
- const { adapter, teardown = NO_OP } = await _setup();
9
- return { adapter, teardown };
10
- };
6
+ const it = (_it);
7
+ export function runStorageAdapterTests(setup, title) {
8
+ beforeEach(async (ctx) => {
9
+ const { adapter, teardown = NO_OP } = await setup();
10
+ ctx.adapter = adapter;
11
+ return teardown;
12
+ });
11
13
  describe(`Storage adapter acceptance tests ${title ? `(${title})` : ""}`, () => {
12
14
  describe("load", () => {
13
- it("should return undefined if there is no data", async () => {
14
- const { adapter, teardown } = await setup();
15
+ it("should return undefined if there is no data", async ({ adapter }) => {
15
16
  const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"]);
16
17
  expect(actual).toBeUndefined();
17
- teardown();
18
18
  });
19
19
  });
20
20
  describe("save and load", () => {
21
- it("should return data that was saved", async () => {
22
- const { adapter, teardown } = await setup();
21
+ it("should return data that was saved", async ({ adapter }) => {
23
22
  await adapter.save(["storage-adapter-id"], PAYLOAD_A());
24
23
  const actual = await adapter.load(["storage-adapter-id"]);
25
24
  expect(actual).toStrictEqual(PAYLOAD_A());
26
- teardown();
27
25
  });
28
- it("should work with composite keys", async () => {
29
- const { adapter, teardown } = await setup();
26
+ it("should work with composite keys", async ({ adapter }) => {
30
27
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
31
28
  const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"]);
32
29
  expect(actual).toStrictEqual(PAYLOAD_A());
33
- teardown();
34
30
  });
35
- it("should work with a large payload", async () => {
36
- const { adapter, teardown } = await setup();
31
+ it("should work with a large payload", async ({ adapter }) => {
37
32
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], LARGE_PAYLOAD);
38
33
  const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"]);
39
34
  expect(actual).toStrictEqual(LARGE_PAYLOAD);
40
- teardown();
41
35
  });
42
36
  });
43
37
  describe("loadRange", () => {
44
- it("should return an empty array if there is no data", async () => {
45
- const { adapter, teardown } = await setup();
38
+ it("should return an empty array if there is no data", async ({ adapter, }) => {
46
39
  expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([]);
47
- teardown();
48
40
  });
49
41
  });
50
42
  describe("save and loadRange", () => {
51
- it("should return all the data that matches the key", async () => {
52
- const { adapter, teardown } = await setup();
43
+ it("should return all the data that matches the key", async ({ adapter, }) => {
53
44
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
54
45
  await adapter.save(["AAAAA", "snapshot", "yyyyy"], PAYLOAD_B());
55
46
  await adapter.save(["AAAAA", "sync-state", "zzzzz"], PAYLOAD_C());
@@ -62,10 +53,8 @@ export function runStorageAdapterTests(_setup, title) {
62
53
  { key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_A() },
63
54
  { key: ["AAAAA", "sync-state", "zzzzz"], data: PAYLOAD_C() },
64
55
  ]));
65
- teardown();
66
56
  });
67
- it("should only load values that match they key", async () => {
68
- const { adapter, teardown } = await setup();
57
+ it("should only load values that match they key", async ({ adapter }) => {
69
58
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
70
59
  await adapter.save(["BBBBB", "sync-state", "zzzzz"], PAYLOAD_C());
71
60
  const actual = await adapter.loadRange(["AAAAA"]);
@@ -75,33 +64,27 @@ export function runStorageAdapterTests(_setup, title) {
75
64
  expect(actual).toStrictEqual(expect.not.arrayContaining([
76
65
  { key: ["BBBBB", "sync-state", "zzzzz"], data: PAYLOAD_C() },
77
66
  ]));
78
- teardown();
79
67
  });
80
68
  });
81
69
  describe("save and remove", () => {
82
- it("after removing, should be empty", async () => {
83
- const { adapter, teardown } = await setup();
70
+ it("after removing, should be empty", async ({ adapter }) => {
84
71
  await adapter.save(["AAAAA", "snapshot", "xxxxx"], PAYLOAD_A());
85
72
  await adapter.remove(["AAAAA", "snapshot", "xxxxx"]);
86
73
  expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([]);
87
74
  expect(await adapter.load(["AAAAA", "snapshot", "xxxxx"])).toBeUndefined();
88
- teardown();
89
75
  });
90
76
  });
91
77
  describe("save and save", () => {
92
- it("should overwrite data saved with the same key", async () => {
93
- const { adapter, teardown } = await setup();
78
+ it("should overwrite data saved with the same key", async ({ adapter, }) => {
94
79
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
95
80
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_B());
96
81
  expect(await adapter.loadRange(["AAAAA", "sync-state"])).toStrictEqual([
97
82
  { key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_B() },
98
83
  ]);
99
- teardown();
100
84
  });
101
85
  });
102
86
  describe("removeRange", () => {
103
- it("should remove a range of records", async () => {
104
- const { adapter, teardown } = await setup();
87
+ it("should remove a range of records", async ({ adapter }) => {
105
88
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
106
89
  await adapter.save(["AAAAA", "snapshot", "yyyyy"], PAYLOAD_B());
107
90
  await adapter.save(["AAAAA", "sync-state", "zzzzz"], PAYLOAD_C());
@@ -109,10 +92,8 @@ export function runStorageAdapterTests(_setup, title) {
109
92
  expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([
110
93
  { key: ["AAAAA", "snapshot", "yyyyy"], data: PAYLOAD_B() },
111
94
  ]);
112
- teardown();
113
95
  });
114
- it("should not remove records that don't match", async () => {
115
- const { adapter, teardown } = await setup();
96
+ it("should not remove records that don't match", async ({ adapter }) => {
116
97
  await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
117
98
  await adapter.save(["BBBBB", "sync-state", "zzzzz"], PAYLOAD_B());
118
99
  await adapter.removeRange(["AAAAA"]);
@@ -120,7 +101,6 @@ export function runStorageAdapterTests(_setup, title) {
120
101
  expect(actual).toStrictEqual([
121
102
  { key: ["BBBBB", "sync-state", "zzzzz"], data: PAYLOAD_B() },
122
103
  ]);
123
- teardown();
124
104
  });
125
105
  });
126
106
  });
@@ -2,11 +2,20 @@ import * as A from "@automerge/automerge/slim/next";
2
2
  import { type DocumentId } from "../types.js";
3
3
  import { StorageAdapterInterface } from "./StorageAdapterInterface.js";
4
4
  import { StorageId } from "./types.js";
5
+ import { EventEmitter } from "eventemitter3";
6
+ type StorageSubsystemEvents = {
7
+ "document-loaded": (arg: {
8
+ documentId: DocumentId;
9
+ durationMillis: number;
10
+ numOps: number;
11
+ numChanges: number;
12
+ }) => void;
13
+ };
5
14
  /**
6
15
  * The storage subsystem is responsible for saving and loading Automerge documents to and from
7
16
  * storage adapter. It also provides a generic key/value storage interface for other uses.
8
17
  */
9
- export declare class StorageSubsystem {
18
+ export declare class StorageSubsystem extends EventEmitter<StorageSubsystemEvents> {
10
19
  #private;
11
20
  constructor(storageAdapter: StorageAdapterInterface);
12
21
  id(): Promise<StorageId>;
@@ -49,4 +58,5 @@ export declare class StorageSubsystem {
49
58
  loadSyncState(documentId: DocumentId, storageId: StorageId): Promise<A.SyncState | undefined>;
50
59
  saveSyncState(documentId: DocumentId, storageId: StorageId, syncState: A.SyncState): Promise<void>;
51
60
  }
61
+ export {};
52
62
  //# sourceMappingURL=StorageSubsystem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gCAAgC,CAAA;AAInD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAA;AAK7D;;;GAGG;AACH,qBAAa,gBAAgB;;gBAef,cAAc,EAAE,uBAAuB;IAI7C,EAAE,IAAI,OAAO,CAAC,SAAS,CAAC;IA2B9B,kCAAkC;IAC5B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKlC,gCAAgC;IAC1B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM;IAEX,sCAAsC;IACtC,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC;IAKhB,oCAAoC;IAC9B,MAAM;IACV,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,2FAA2F;IAC3F,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAOhB;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAmClE;;;;;;OAMG;IACG,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAazE;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,UAAU;IAkEhC,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC;IAM7B,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,CAAC,CAAC,SAAS,GACrB,OAAO,CAAC,IAAI,CAAC;CA8CjB"}
1
+ {"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gCAAgC,CAAA;AAInD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAA;AAI7D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C,KAAK,sBAAsB,GAAG;IAC5B,iBAAiB,EAAE,CAAC,GAAG,EAAE;QACvB,UAAU,EAAE,UAAU,CAAA;QACtB,cAAc,EAAE,MAAM,CAAA;QACtB,MAAM,EAAE,MAAM,CAAA;QACd,UAAU,EAAE,MAAM,CAAA;KACnB,KAAK,IAAI,CAAA;CACX,CAAA;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;gBAe5D,cAAc,EAAE,uBAAuB;IAK7C,EAAE,IAAI,OAAO,CAAC,SAAS,CAAC;IA2B9B,kCAAkC;IAC5B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKlC,gCAAgC;IAC1B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM;IAEX,sCAAsC;IACtC,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC;IAKhB,oCAAoC;IAC9B,MAAM;IACV,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,2FAA2F;IAC3F,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAOhB;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IA0ClE;;;;;;OAMG;IACG,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAazE;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,UAAU;IAkEhC,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC;IAW7B,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,CAAC,CAAC,SAAS,GACrB,OAAO,CAAC,IAAI,CAAC;CA8CjB"}
@@ -5,11 +5,12 @@ import { mergeArrays } from "../helpers/mergeArrays.js";
5
5
  import { keyHash, headsHash } from "./keyHash.js";
6
6
  import { chunkTypeFromKey } from "./chunkTypeFromKey.js";
7
7
  import * as Uuid from "uuid";
8
+ import { EventEmitter } from "eventemitter3";
8
9
  /**
9
10
  * The storage subsystem is responsible for saving and loading Automerge documents to and from
10
11
  * storage adapter. It also provides a generic key/value storage interface for other uses.
11
12
  */
12
- export class StorageSubsystem {
13
+ export class StorageSubsystem extends EventEmitter {
13
14
  /** The storage adapter to use for saving and loading documents */
14
15
  #storageAdapter;
15
16
  /** Record of the latest heads we've loaded or saved for each document */
@@ -20,6 +21,7 @@ export class StorageSubsystem {
20
21
  #compacting = false;
21
22
  #log = debug(`automerge-repo:storage-subsystem`);
22
23
  constructor(storageAdapter) {
24
+ super();
23
25
  this.#storageAdapter = storageAdapter;
24
26
  }
25
27
  async id() {
@@ -100,7 +102,14 @@ export class StorageSubsystem {
100
102
  if (binary.length === 0)
101
103
  return null;
102
104
  // Load into an Automerge document
105
+ const start = performance.now();
103
106
  const newDoc = A.loadIncremental(A.init(), binary);
107
+ const end = performance.now();
108
+ this.emit("document-loaded", {
109
+ documentId,
110
+ durationMillis: end - start,
111
+ ...A.stats(newDoc),
112
+ });
104
113
  // Record the latest heads for the document
105
114
  this.#storedHeads.set(documentId, A.getHeads(newDoc));
106
115
  return newDoc;
@@ -178,8 +187,14 @@ export class StorageSubsystem {
178
187
  }
179
188
  async loadSyncState(documentId, storageId) {
180
189
  const key = [documentId, "sync-state", storageId];
181
- const loaded = await this.#storageAdapter.load(key);
182
- return loaded ? A.decodeSyncState(loaded) : undefined;
190
+ try {
191
+ const loaded = await this.#storageAdapter.load(key);
192
+ return loaded ? A.decodeSyncState(loaded) : undefined;
193
+ }
194
+ catch (e) {
195
+ this.#log(`Error loading sync state for ${documentId} from ${storageId}`);
196
+ return undefined;
197
+ }
183
198
  }
184
199
  async saveSyncState(documentId, storageId, syncState) {
185
200
  const key = [documentId, "sync-state", storageId];
@@ -1,11 +1,15 @@
1
1
  import { Repo } from "../Repo.js";
2
2
  import { DocMessage } from "../network/messages.js";
3
3
  import { DocumentId, PeerId } from "../types.js";
4
+ import { DocSynchronizer } from "./DocSynchronizer.js";
4
5
  import { Synchronizer } from "./Synchronizer.js";
5
6
  /** A CollectionSynchronizer is responsible for synchronizing a DocCollection with peers. */
6
7
  export declare class CollectionSynchronizer extends Synchronizer {
7
8
  #private;
8
9
  private repo;
10
+ /** A map of documentIds to their synchronizers */
11
+ /** @hidden */
12
+ docSynchronizers: Record<DocumentId, DocSynchronizer>;
9
13
  constructor(repo: Repo);
10
14
  /**
11
15
  * When we receive a sync message for a document we haven't got in memory, we
@@ -23,5 +27,14 @@ export declare class CollectionSynchronizer extends Synchronizer {
23
27
  removePeer(peerId: PeerId): void;
24
28
  /** Returns a list of all connected peer ids */
25
29
  get peers(): PeerId[];
30
+ metrics(): {
31
+ [key: string]: {
32
+ peers: PeerId[];
33
+ size: {
34
+ numOps: number;
35
+ numChanges: number;
36
+ };
37
+ };
38
+ };
26
39
  }
27
40
  //# sourceMappingURL=CollectionSynchronizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAU1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAqD9B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU;IAyBxC;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,UAAU;IAalC,cAAc,CAAC,UAAU,EAAE,UAAU;IAIrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAgBtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;IASzB,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;CACF"}
1
+ {"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAW1C,OAAO,CAAC,IAAI;IAPxB,kDAAkD;IAClD,cAAc;IACd,gBAAgB,EAAE,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,CAAK;gBAKtC,IAAI,EAAE,IAAI;IAsD9B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU;IAyBxC;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,UAAU;IAalC,cAAc,CAAC,UAAU,EAAE,UAAU;IAIrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAgBtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;IASzB,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;IAED,OAAO,IAAI;QACT,CAAC,GAAG,EAAE,MAAM,GAAG;YACb,KAAK,EAAE,MAAM,EAAE,CAAA;YACf,IAAI,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,UAAU,EAAE,MAAM,CAAA;aAAE,CAAA;SAC7C,CAAA;KACF;CASF"}
@@ -9,7 +9,8 @@ export class CollectionSynchronizer extends Synchronizer {
9
9
  /** The set of peers we are connected with */
10
10
  #peers = new Set();
11
11
  /** A map of documentIds to their synchronizers */
12
- #docSynchronizers = {};
12
+ /** @hidden */
13
+ docSynchronizers = {};
13
14
  /** Used to determine if the document is know to the Collection and a synchronizer exists or is being set up */
14
15
  #docSetUp = {};
15
16
  constructor(repo) {
@@ -18,11 +19,11 @@ export class CollectionSynchronizer extends Synchronizer {
18
19
  }
19
20
  /** Returns a synchronizer for the given document, creating one if it doesn't already exist. */
20
21
  #fetchDocSynchronizer(documentId) {
21
- if (!this.#docSynchronizers[documentId]) {
22
+ if (!this.docSynchronizers[documentId]) {
22
23
  const handle = this.repo.find(stringifyAutomergeUrl({ documentId }));
23
- this.#docSynchronizers[documentId] = this.#initDocSynchronizer(handle);
24
+ this.docSynchronizers[documentId] = this.#initDocSynchronizer(handle);
24
25
  }
25
- return this.#docSynchronizers[documentId];
26
+ return this.docSynchronizers[documentId];
26
27
  }
27
28
  /** Creates a new docSynchronizer and sets it up to propagate messages */
28
29
  #initDocSynchronizer(handle) {
@@ -42,6 +43,7 @@ export class CollectionSynchronizer extends Synchronizer {
42
43
  docSynchronizer.on("message", event => this.emit("message", event));
43
44
  docSynchronizer.on("open-doc", event => this.emit("open-doc", event));
44
45
  docSynchronizer.on("sync-state", event => this.emit("sync-state", event));
46
+ docSynchronizer.on("metrics", event => this.emit("metrics", event));
45
47
  return docSynchronizer;
46
48
  }
47
49
  /** returns an array of peerIds that we share this document generously with */
@@ -98,7 +100,7 @@ export class CollectionSynchronizer extends Synchronizer {
98
100
  return;
99
101
  }
100
102
  this.#peers.add(peerId);
101
- for (const docSynchronizer of Object.values(this.#docSynchronizers)) {
103
+ for (const docSynchronizer of Object.values(this.docSynchronizers)) {
102
104
  const { documentId } = docSynchronizer;
103
105
  void this.repo.sharePolicy(peerId, documentId).then(okToShare => {
104
106
  if (okToShare)
@@ -110,7 +112,7 @@ export class CollectionSynchronizer extends Synchronizer {
110
112
  removePeer(peerId) {
111
113
  log(`removing peer ${peerId}`);
112
114
  this.#peers.delete(peerId);
113
- for (const docSynchronizer of Object.values(this.#docSynchronizers)) {
115
+ for (const docSynchronizer of Object.values(this.docSynchronizers)) {
114
116
  docSynchronizer.endSync(peerId);
115
117
  }
116
118
  }
@@ -118,4 +120,9 @@ export class CollectionSynchronizer extends Synchronizer {
118
120
  get peers() {
119
121
  return Array.from(this.#peers);
120
122
  }
123
+ metrics() {
124
+ return Object.fromEntries(Object.entries(this.docSynchronizers).map(([documentId, synchronizer]) => {
125
+ return [documentId, synchronizer.metrics()];
126
+ }));
127
+ }
121
128
  }
@@ -24,6 +24,13 @@ export declare class DocSynchronizer extends Synchronizer {
24
24
  receiveMessage(message: RepoMessage): void;
25
25
  receiveEphemeralMessage(message: EphemeralMessage): void;
26
26
  receiveSyncMessage(message: SyncMessage | RequestMessage): void;
27
+ metrics(): {
28
+ peers: PeerId[];
29
+ size: {
30
+ numOps: number;
31
+ numChanges: number;
32
+ };
33
+ };
27
34
  }
28
35
  export {};
29
36
  //# sourceMappingURL=DocSynchronizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gCAAgC,CAAA;AAGnD,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAOrE,UAAU,qBAAqB;IAC7B,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IAC1B,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,CAAA;CACvE;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAE/C,gBAAgB,SAAM;gBAsBV,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,qBAAqB;IAyB9D,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IAkID,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IAmD3B,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,cAAc,CAAC,OAAO,EAAE,WAAW;IAkBnC,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;IAuBjD,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CA8EzD"}
1
+ {"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gCAAgC,CAAA;AAGnD,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAOrE,UAAU,qBAAqB;IAC7B,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IAC1B,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,CAAA;CACvE;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAE/C,gBAAgB,SAAM;gBAsBV,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,qBAAqB;IAyB9D,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IAkID,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IAmD3B,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,cAAc,CAAC,OAAO,EAAE,WAAW;IAkBnC,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;IAuBjD,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;IAuFxD,OAAO,IAAI;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;CAM7E"}
@@ -252,7 +252,15 @@ export class DocSynchronizer extends Synchronizer {
252
252
  }
253
253
  this.#withSyncState(message.senderId, syncState => {
254
254
  this.#handle.update(doc => {
255
+ const start = performance.now();
255
256
  const [newDoc, newSyncState] = A.receiveSyncMessage(doc, syncState, message.data);
257
+ const end = performance.now();
258
+ this.emit("metrics", {
259
+ type: "receive-sync-message",
260
+ documentId: this.#handle.documentId,
261
+ durationMillis: end - start,
262
+ ...A.stats(doc),
263
+ });
256
264
  this.#setSyncState(message.senderId, newSyncState);
257
265
  // respond to just this peer (as required)
258
266
  this.#sendSyncMessage(message.senderId, doc);
@@ -286,4 +294,10 @@ export class DocSynchronizer extends Synchronizer {
286
294
  }
287
295
  this.#pendingSyncMessages = [];
288
296
  }
297
+ metrics() {
298
+ return {
299
+ peers: this.#peers,
300
+ size: this.#handle.metrics(),
301
+ };
302
+ }
289
303
  }
@@ -9,6 +9,7 @@ export interface SynchronizerEvents {
9
9
  message: (payload: MessageContents) => void;
10
10
  "sync-state": (payload: SyncStatePayload) => void;
11
11
  "open-doc": (arg: OpenDocMessage) => void;
12
+ metrics: (arg: DocSyncMetrics) => void;
12
13
  }
13
14
  /** Notify the repo that the sync state has changed */
14
15
  export interface SyncStatePayload {
@@ -16,4 +17,11 @@ export interface SyncStatePayload {
16
17
  documentId: DocumentId;
17
18
  syncState: SyncState;
18
19
  }
20
+ export type DocSyncMetrics = {
21
+ type: "receive-sync-message";
22
+ documentId: DocumentId;
23
+ durationMillis: number;
24
+ numOps: number;
25
+ numChanges: number;
26
+ };
19
27
  //# sourceMappingURL=Synchronizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EACL,eAAe,EACf,cAAc,EACd,WAAW,EACZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AACrD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEhD,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAA;IAC3C,YAAY,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;IACjD,UAAU,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CAC1C;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;CACrB"}
1
+ {"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EACL,eAAe,EACf,cAAc,EACd,WAAW,EACZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AACrD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEhD,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAA;IAC3C,YAAY,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;IACjD,UAAU,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;IACzC,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CACvC;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;CACrB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,sBAAsB,CAAA;IAC5B,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo",
3
- "version": "2.0.0-alpha.1",
3
+ "version": "2.0.0-alpha.12",
4
4
  "description": "A repository object to manage a collection of automerge documents",
5
5
  "repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo",
6
6
  "author": "Peter van Hardenberg <pvh@pvh.ca>",
@@ -23,7 +23,7 @@
23
23
  "vite": "^5.0.8"
24
24
  },
25
25
  "dependencies": {
26
- "@automerge/automerge": "^2.2.7",
26
+ "@automerge/automerge": "^2.2.8",
27
27
  "bs58check": "^3.0.1",
28
28
  "cbor-x": "^1.3.0",
29
29
  "debug": "^4.3.4",
@@ -60,5 +60,5 @@
60
60
  "publishConfig": {
61
61
  "access": "public"
62
62
  },
63
- "gitHead": "86421aba21c8037b916ee93f962df1c121fef52b"
63
+ "gitHead": "8b016e42d2518ebb11eb148f52b9fb9a0b4467ff"
64
64
  }