@dxos/echo-pipeline 0.8.4-main.ae835ea → 0.8.4-main.bc674ce

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 (154) hide show
  1. package/dist/lib/browser/{chunk-WJJXJTNS.mjs → chunk-FJPXA75J.mjs} +7 -12
  2. package/dist/lib/browser/chunk-FJPXA75J.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-FR6GJ4S4.mjs → chunk-WIQUYO7H.mjs} +39 -29
  4. package/dist/lib/browser/chunk-WIQUYO7H.mjs.map +7 -0
  5. package/dist/lib/browser/filter/index.mjs +1 -2
  6. package/dist/lib/browser/index.mjs +1933 -620
  7. package/dist/lib/browser/index.mjs.map +4 -4
  8. package/dist/lib/browser/meta.json +1 -1
  9. package/dist/lib/browser/testing/index.mjs +28 -201
  10. package/dist/lib/browser/testing/index.mjs.map +4 -4
  11. package/dist/lib/node-esm/{chunk-JQCCVDLT.mjs → chunk-7PL6UK4B.mjs} +39 -29
  12. package/dist/lib/node-esm/chunk-7PL6UK4B.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-LEQ77KAT.mjs → chunk-S5KGVQFZ.mjs} +7 -12
  14. package/dist/lib/node-esm/chunk-S5KGVQFZ.mjs.map +7 -0
  15. package/dist/lib/node-esm/filter/index.mjs +1 -2
  16. package/dist/lib/node-esm/index.mjs +1933 -620
  17. package/dist/lib/node-esm/index.mjs.map +4 -4
  18. package/dist/lib/node-esm/meta.json +1 -1
  19. package/dist/lib/node-esm/testing/index.mjs +28 -201
  20. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  21. package/dist/types/src/automerge/automerge-host.d.ts +36 -9
  22. package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
  23. package/dist/types/src/automerge/automerge-repo.test.d.ts +1 -1
  24. package/dist/types/src/automerge/automerge-repo.test.d.ts.map +1 -1
  25. package/dist/types/src/automerge/collection-synchronizer.d.ts +3 -2
  26. package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
  27. package/dist/types/src/automerge/echo-network-adapter.d.ts +11 -6
  28. package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
  29. package/dist/types/src/automerge/echo-replicator.d.ts +9 -6
  30. package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
  31. package/dist/types/src/automerge/heads-store.d.ts +9 -2
  32. package/dist/types/src/automerge/heads-store.d.ts.map +1 -1
  33. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +4 -4
  34. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
  35. package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +7 -7
  36. package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
  37. package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
  38. package/dist/types/src/db-host/automerge-data-source.d.ts +34 -0
  39. package/dist/types/src/db-host/automerge-data-source.d.ts.map +1 -0
  40. package/dist/types/src/db-host/automerge-data-source.test.d.ts +2 -0
  41. package/dist/types/src/db-host/automerge-data-source.test.d.ts.map +1 -0
  42. package/dist/types/src/db-host/data-service.d.ts +4 -3
  43. package/dist/types/src/db-host/data-service.d.ts.map +1 -1
  44. package/dist/types/src/db-host/documents-iterator.d.ts.map +1 -1
  45. package/dist/types/src/db-host/documents-synchronizer.d.ts +6 -5
  46. package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
  47. package/dist/types/src/db-host/echo-host.d.ts +43 -11
  48. package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
  49. package/dist/types/src/db-host/index.d.ts +1 -0
  50. package/dist/types/src/db-host/index.d.ts.map +1 -1
  51. package/dist/types/src/db-host/local-queue-service.d.ts +15 -0
  52. package/dist/types/src/db-host/local-queue-service.d.ts.map +1 -0
  53. package/dist/types/src/db-host/query-service.d.ts +7 -2
  54. package/dist/types/src/db-host/query-service.d.ts.map +1 -1
  55. package/dist/types/src/db-host/queue-data-source.d.ts +28 -0
  56. package/dist/types/src/db-host/queue-data-source.d.ts.map +1 -0
  57. package/dist/types/src/db-host/queue-service.test.d.ts +2 -0
  58. package/dist/types/src/db-host/queue-service.test.d.ts.map +1 -0
  59. package/dist/types/src/db-host/space-state-manager.d.ts +1 -0
  60. package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
  61. package/dist/types/src/db-host/stub.d.ts +10 -0
  62. package/dist/types/src/db-host/stub.d.ts.map +1 -0
  63. package/dist/types/src/edge/echo-edge-replicator.d.ts +2 -2
  64. package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
  65. package/dist/types/src/filter/filter-match.d.ts.map +1 -1
  66. package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
  67. package/dist/types/src/pipeline/pipeline.d.ts +2 -2
  68. package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
  69. package/dist/types/src/query/errors.d.ts +11 -15
  70. package/dist/types/src/query/errors.d.ts.map +1 -1
  71. package/dist/types/src/query/plan.d.ts +37 -3
  72. package/dist/types/src/query/plan.d.ts.map +1 -1
  73. package/dist/types/src/query/query-executor.d.ts +39 -3
  74. package/dist/types/src/query/query-executor.d.ts.map +1 -1
  75. package/dist/types/src/query/query-planner.d.ts +6 -0
  76. package/dist/types/src/query/query-planner.d.ts.map +1 -1
  77. package/dist/types/src/space/auth.d.ts +3 -3
  78. package/dist/types/src/space/auth.d.ts.map +1 -1
  79. package/dist/types/src/space/control-pipeline.d.ts +2 -2
  80. package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
  81. package/dist/types/src/space/space-manager.d.ts +6 -6
  82. package/dist/types/src/space/space-manager.d.ts.map +1 -1
  83. package/dist/types/src/space/space-protocol.d.ts +5 -5
  84. package/dist/types/src/space/space-protocol.d.ts.map +1 -1
  85. package/dist/types/src/space/space.d.ts +3 -3
  86. package/dist/types/src/space/space.d.ts.map +1 -1
  87. package/dist/types/src/testing/index.d.ts +0 -2
  88. package/dist/types/src/testing/index.d.ts.map +1 -1
  89. package/dist/types/src/testing/test-network-adapter.d.ts +1 -1
  90. package/dist/types/src/testing/test-network-adapter.d.ts.map +1 -1
  91. package/dist/types/src/testing/test-replicator.d.ts +6 -6
  92. package/dist/types/src/testing/test-replicator.d.ts.map +1 -1
  93. package/dist/types/src/util.d.ts.map +1 -1
  94. package/dist/types/tsconfig.tsbuildinfo +1 -1
  95. package/package.json +48 -38
  96. package/src/automerge/automerge-host.test.ts +37 -14
  97. package/src/automerge/automerge-host.ts +130 -36
  98. package/src/automerge/automerge-repo.test.ts +147 -30
  99. package/src/automerge/collection-synchronizer.test.ts +16 -10
  100. package/src/automerge/collection-synchronizer.ts +41 -11
  101. package/src/automerge/echo-network-adapter.ts +23 -11
  102. package/src/automerge/echo-replicator.ts +10 -6
  103. package/src/automerge/heads-store.ts +14 -2
  104. package/src/automerge/leveldb-storage-adapter.ts +4 -4
  105. package/src/automerge/mesh-echo-replicator-connection.ts +7 -7
  106. package/src/automerge/mesh-echo-replicator.ts +2 -2
  107. package/src/db-host/automerge-data-source.test.ts +294 -0
  108. package/src/db-host/automerge-data-source.ts +144 -0
  109. package/src/db-host/data-service.ts +10 -3
  110. package/src/db-host/documents-iterator.ts +3 -1
  111. package/src/db-host/documents-synchronizer.test.ts +77 -13
  112. package/src/db-host/documents-synchronizer.ts +51 -50
  113. package/src/db-host/echo-host.ts +191 -28
  114. package/src/db-host/index.ts +1 -0
  115. package/src/db-host/local-queue-service.ts +127 -0
  116. package/src/db-host/query-service.ts +17 -4
  117. package/src/db-host/queue-data-source.ts +132 -0
  118. package/src/db-host/queue-service.test.ts +141 -0
  119. package/src/db-host/space-state-manager.ts +4 -0
  120. package/src/db-host/stub.ts +28 -0
  121. package/src/edge/echo-edge-replicator.ts +12 -8
  122. package/src/filter/filter-match.test.ts +31 -25
  123. package/src/filter/filter-match.ts +8 -14
  124. package/src/metadata/metadata-store.ts +5 -2
  125. package/src/pipeline/pipeline.ts +2 -2
  126. package/src/query/errors.ts +2 -2
  127. package/src/query/plan.ts +48 -3
  128. package/src/query/query-executor.ts +612 -131
  129. package/src/query/query-planner.test.ts +509 -35
  130. package/src/query/query-planner.ts +187 -13
  131. package/src/space/admission-discovery-extension.ts +1 -1
  132. package/src/space/auth.ts +6 -6
  133. package/src/space/control-pipeline.ts +2 -2
  134. package/src/space/space-manager.ts +6 -6
  135. package/src/space/space-protocol.ts +11 -11
  136. package/src/space/space.ts +3 -3
  137. package/src/testing/index.ts +0 -2
  138. package/src/testing/test-network-adapter.ts +16 -3
  139. package/src/testing/test-replicator.ts +8 -7
  140. package/src/util.ts +7 -3
  141. package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
  142. package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
  143. package/dist/lib/browser/chunk-FR6GJ4S4.mjs.map +0 -7
  144. package/dist/lib/browser/chunk-WJJXJTNS.mjs.map +0 -7
  145. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  146. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
  147. package/dist/lib/node-esm/chunk-JQCCVDLT.mjs.map +0 -7
  148. package/dist/lib/node-esm/chunk-LEQ77KAT.mjs.map +0 -7
  149. package/dist/types/src/testing/test-data.d.ts +0 -18
  150. package/dist/types/src/testing/test-data.d.ts.map +0 -1
  151. package/dist/types/src/testing/test-schema.d.ts +0 -37
  152. package/dist/types/src/testing/test-schema.d.ts.map +0 -1
  153. package/src/testing/test-data.ts +0 -127
  154. package/src/testing/test-schema.ts +0 -53
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@dxos/echo-pipeline",
3
- "version": "0.8.4-main.ae835ea",
3
+ "version": "0.8.4-main.bc674ce",
4
4
  "description": "ECHO database.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "info@dxos.org",
9
- "sideEffects": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
11
15
  "exports": {
12
16
  ".": {
@@ -43,50 +47,56 @@
43
47
  "src"
44
48
  ],
45
49
  "dependencies": {
46
- "@automerge/automerge": "3.1.2",
47
- "@automerge/automerge-repo": "2.4.0",
50
+ "@automerge/automerge": "3.2.3",
51
+ "@automerge/automerge-repo": "2.5.1",
52
+ "@effect/sql": "0.48.6",
53
+ "@effect/sql-sqlite-node": "0.49.1",
48
54
  "crc-32": "^1.2.2",
49
- "effect": "3.18.3",
55
+ "effect": "3.19.11",
50
56
  "level-transcoder": "^1.0.1",
51
57
  "lodash.isequal": "^4.5.0",
52
- "@dxos/codec-protobuf": "0.8.4-main.ae835ea",
53
- "@dxos/async": "0.8.4-main.ae835ea",
54
- "@dxos/context": "0.8.4-main.ae835ea",
55
- "@dxos/credentials": "0.8.4-main.ae835ea",
56
- "@dxos/crypto": "0.8.4-main.ae835ea",
57
- "@dxos/debug": "0.8.4-main.ae835ea",
58
- "@dxos/echo-protocol": "0.8.4-main.ae835ea",
59
- "@dxos/echo": "0.8.4-main.ae835ea",
60
- "@dxos/edge-client": "0.8.4-main.ae835ea",
61
- "@dxos/errors": "0.8.4-main.ae835ea",
62
- "@dxos/feed-store": "0.8.4-main.ae835ea",
63
- "@dxos/hypercore": "0.8.4-main.ae835ea",
64
- "@dxos/invariant": "0.8.4-main.ae835ea",
65
- "@dxos/keyring": "0.8.4-main.ae835ea",
66
- "@dxos/keys": "0.8.4-main.ae835ea",
67
- "@dxos/kv-store": "0.8.4-main.ae835ea",
68
- "@dxos/indexing": "0.8.4-main.ae835ea",
69
- "@dxos/log": "0.8.4-main.ae835ea",
70
- "@dxos/messaging": "0.8.4-main.ae835ea",
71
- "@dxos/network-manager": "0.8.4-main.ae835ea",
72
- "@dxos/protocols": "0.8.4-main.ae835ea",
73
- "@dxos/node-std": "0.8.4-main.ae835ea",
74
- "@dxos/random-access-storage": "0.8.4-main.ae835ea",
75
- "@dxos/teleport-extension-automerge-replicator": "0.8.4-main.ae835ea",
76
- "@dxos/teleport-extension-gossip": "0.8.4-main.ae835ea",
77
- "@dxos/teleport-extension-object-sync": "0.8.4-main.ae835ea",
78
- "@dxos/timeframe": "0.8.4-main.ae835ea",
79
- "@dxos/teleport": "0.8.4-main.ae835ea",
80
- "@dxos/tracing": "0.8.4-main.ae835ea",
81
- "@dxos/teleport-extension-replicator": "0.8.4-main.ae835ea",
82
- "@dxos/typings": "0.8.4-main.ae835ea",
83
- "@dxos/util": "0.8.4-main.ae835ea"
58
+ "@dxos/async": "0.8.4-main.bc674ce",
59
+ "@dxos/context": "0.8.4-main.bc674ce",
60
+ "@dxos/codec-protobuf": "0.8.4-main.bc674ce",
61
+ "@dxos/credentials": "0.8.4-main.bc674ce",
62
+ "@dxos/debug": "0.8.4-main.bc674ce",
63
+ "@dxos/crypto": "0.8.4-main.bc674ce",
64
+ "@dxos/echo": "0.8.4-main.bc674ce",
65
+ "@dxos/echo-protocol": "0.8.4-main.bc674ce",
66
+ "@dxos/edge-client": "0.8.4-main.bc674ce",
67
+ "@dxos/errors": "0.8.4-main.bc674ce",
68
+ "@dxos/effect": "0.8.4-main.bc674ce",
69
+ "@dxos/feed": "0.8.4-main.bc674ce",
70
+ "@dxos/feed-store": "0.8.4-main.bc674ce",
71
+ "@dxos/index-core": "0.8.4-main.bc674ce",
72
+ "@dxos/hypercore": "0.8.4-main.bc674ce",
73
+ "@dxos/invariant": "0.8.4-main.bc674ce",
74
+ "@dxos/indexing": "0.8.4-main.bc674ce",
75
+ "@dxos/keyring": "0.8.4-main.bc674ce",
76
+ "@dxos/keys": "0.8.4-main.bc674ce",
77
+ "@dxos/kv-store": "0.8.4-main.bc674ce",
78
+ "@dxos/log": "0.8.4-main.bc674ce",
79
+ "@dxos/messaging": "0.8.4-main.bc674ce",
80
+ "@dxos/network-manager": "0.8.4-main.bc674ce",
81
+ "@dxos/node-std": "0.8.4-main.bc674ce",
82
+ "@dxos/protocols": "0.8.4-main.bc674ce",
83
+ "@dxos/random-access-storage": "0.8.4-main.bc674ce",
84
+ "@dxos/sql-sqlite": "0.8.4-main.bc674ce",
85
+ "@dxos/teleport": "0.8.4-main.bc674ce",
86
+ "@dxos/teleport-extension-automerge-replicator": "0.8.4-main.bc674ce",
87
+ "@dxos/teleport-extension-object-sync": "0.8.4-main.bc674ce",
88
+ "@dxos/tracing": "0.8.4-main.bc674ce",
89
+ "@dxos/teleport-extension-gossip": "0.8.4-main.bc674ce",
90
+ "@dxos/teleport-extension-replicator": "0.8.4-main.bc674ce",
91
+ "@dxos/timeframe": "0.8.4-main.bc674ce",
92
+ "@dxos/util": "0.8.4-main.bc674ce",
93
+ "@dxos/typings": "0.8.4-main.bc674ce"
84
94
  },
85
95
  "devDependencies": {
86
96
  "@types/lodash.isequal": "^4.5.0",
87
97
  "fast-check": "^3.19.0",
88
98
  "get-port-please": "^3.1.1",
89
- "@dxos/test-utils": "0.8.4-main.ae835ea"
99
+ "@dxos/test-utils": "0.8.4-main.bc674ce"
90
100
  },
91
101
  "publishConfig": {
92
102
  "access": "public"
@@ -3,9 +3,11 @@
3
3
  //
4
4
 
5
5
  import { getHeads } from '@automerge/automerge';
6
- import type { DocumentId, Heads } from '@automerge/automerge-repo';
6
+ import * as Automerge from '@automerge/automerge';
7
+ import { type DocumentId, type Heads, generateAutomergeUrl, parseAutomergeUrl } from '@automerge/automerge-repo';
7
8
  import { describe, expect, onTestFinished, test } from 'vitest';
8
9
 
10
+ import { Context } from '@dxos/context';
9
11
  import { IndexMetadataStore } from '@dxos/indexing';
10
12
  import { PublicKey } from '@dxos/keys';
11
13
  import type { LevelDB } from '@dxos/kv-store';
@@ -21,31 +23,51 @@ describe('AutomergeHost', () => {
21
23
  test('can create documents', async () => {
22
24
  const level = await createLevel();
23
25
  const host = await setupAutomergeHost({ level });
24
- const handle = host.repo.create<any>();
26
+ const handle = await host.createDoc<any>();
25
27
  handle.change((doc: any) => {
26
28
  doc.text = 'Hello world';
27
29
  });
28
- await host.repo.flush();
30
+ await host.flush();
29
31
  expect(handle.doc()!.text).toEqual('Hello world');
30
32
  });
31
33
 
32
34
  test('changes are preserved in storage', async () => {
33
35
  const level = await createLevel();
34
36
  const host = await setupAutomergeHost({ level });
35
- const handle = host.repo.create();
37
+ const handle = await host.createDoc<any>();
36
38
  handle.change((doc: any) => {
37
39
  doc.text = 'Hello world';
38
40
  });
39
41
  const url = handle.url;
40
42
 
41
- await host.repo.flush();
43
+ await host.flush();
42
44
  await host.close();
43
45
 
44
46
  const host2 = await setupAutomergeHost({ level });
45
- const handle2 = await host2.repo.find<any>(url);
47
+ const handle2 = await host2.loadDoc<any>(Context.default(), url);
46
48
  await handle2.whenReady();
47
49
  expect(handle2.doc()!.text).toEqual('Hello world');
48
- await host2.repo.flush();
50
+ await host2.flush();
51
+ });
52
+
53
+ test('load resolves when document is created from binary', async () => {
54
+ const level = await createLevel();
55
+ const host = await setupAutomergeHost({ level });
56
+
57
+ // Create a document to get its binary representation
58
+ const document = Automerge.from({ text: 'Hello world' });
59
+ const binary = Automerge.save(document);
60
+ const { documentId } = parseAutomergeUrl(generateAutomergeUrl());
61
+
62
+ // Start loading a non-existent document (should hang until created)
63
+ const loadPromise = host.loadDoc(Context.default(), documentId);
64
+
65
+ // Create the document from binary - this should resolve the load
66
+ const createdHandle = await host.createDoc(binary, { preserveHistory: true, documentId });
67
+
68
+ // The load should now resolve
69
+ const loadedHandle = await loadPromise;
70
+ expect(loadedHandle.doc()).toEqual(createdHandle.doc());
49
71
  });
50
72
 
51
73
  test('query single document heads', async () => {
@@ -53,7 +75,7 @@ describe('AutomergeHost', () => {
53
75
 
54
76
  const level = await createLevel(tmpPath);
55
77
  const host = await setupAutomergeHost({ level });
56
- const handle = host.createDoc({ text: 'Hello world' });
78
+ const handle = await host.createDoc({ text: 'Hello world' });
57
79
  const expectedHeads = getHeads(handle.doc()!);
58
80
  await host.flush();
59
81
 
@@ -73,7 +95,7 @@ describe('AutomergeHost', () => {
73
95
 
74
96
  const level = await createLevel(tmpPath);
75
97
  const host = await setupAutomergeHost({ level });
76
- const handles = range(2, () => host.createDoc({ text: 'Hello world' }));
98
+ const handles = await Promise.all(range(2, () => host.createDoc({ text: 'Hello world' })));
77
99
  const expectedHeads: (Heads | undefined)[] = handles.map((handle) => getHeads(handle.doc()!));
78
100
  await host.flush();
79
101
 
@@ -106,7 +128,7 @@ describe('AutomergeHost', () => {
106
128
  {
107
129
  const host2 = await setupAutomergeHost({ level: level2 });
108
130
  for (const i of range(NUM_DOCUMENTS)) {
109
- const handle = host2.createDoc({ docIndex: i });
131
+ const handle = await host2.createDoc({ docIndex: i });
110
132
  documentIds.push(handle.documentId);
111
133
  }
112
134
  await host2.flush();
@@ -115,20 +137,21 @@ describe('AutomergeHost', () => {
115
137
 
116
138
  const host2 = await setupAutomergeHost({ level: level2 });
117
139
 
140
+ const network = await new TestReplicationNetwork().open();
141
+ await host1.addReplicator(await network.createReplicator());
142
+ await host2.addReplicator(await network.createReplicator());
143
+
118
144
  const collectionId = 'test-collection';
119
145
  await host1.updateLocalCollectionState(collectionId, documentIds);
120
146
  await host2.updateLocalCollectionState(collectionId, documentIds);
121
147
 
122
- await using network = await new TestReplicationNetwork().open();
123
- await host1.addReplicator(await network.createReplicator());
124
- await host2.addReplicator(await network.createReplicator());
125
-
126
148
  for (const documentId of documentIds) {
127
149
  await expect.poll(() => host1.getHeads([documentId])).toEqual(await host2.getHeads([documentId]));
128
150
  }
129
151
 
130
152
  await host1.close();
131
153
  await host2.close();
154
+ await network.close();
132
155
  });
133
156
 
134
157
  const createLevel = async (tmpPath?: string) => {
@@ -25,6 +25,7 @@ import {
25
25
  type StorageKey,
26
26
  interpretAsDocumentId,
27
27
  } from '@automerge/automerge-repo';
28
+ import type * as Record from 'effect/Record';
28
29
 
29
30
  import { DeferredTask, Event, asyncTimeout } from '@dxos/async';
30
31
  import { Context, type Lifecycle, Resource, cancelWithContext } from '@dxos/context';
@@ -38,20 +39,20 @@ import { objectPointerCodec } from '@dxos/protocols';
38
39
  import { type SpaceSyncState } from '@dxos/protocols/proto/dxos/echo/service';
39
40
  import { type DocHeadsList, type FlushRequest } from '@dxos/protocols/proto/dxos/echo/service';
40
41
  import { trace } from '@dxos/tracing';
41
- import { ComplexSet, bufferToArray, isNonNullable, range } from '@dxos/util';
42
+ import { ComplexSet, bufferToArray, defaultMap, isNonNullable, range } from '@dxos/util';
42
43
 
43
44
  import { type CollectionState, CollectionSynchronizer, diffCollectionState } from './collection-synchronizer';
44
45
  import { type EchoDataMonitor } from './echo-data-monitor';
45
46
  import { EchoNetworkAdapter, isEchoPeerMetadata } from './echo-network-adapter';
46
- import { type EchoReplicator, type RemoteDocumentExistenceCheckParams } from './echo-replicator';
47
+ import { type EchoReplicator, type RemoteDocumentExistenceCheckProps } from './echo-replicator';
47
48
  import { HeadsStore } from './heads-store';
48
- import { type BeforeSaveParams, LevelDBStorageAdapter } from './leveldb-storage-adapter';
49
+ import { type BeforeSaveProps, LevelDBStorageAdapter } from './leveldb-storage-adapter';
49
50
 
50
51
  export type PeerIdProvider = () => string | undefined;
51
52
 
52
53
  export type RootDocumentSpaceKeyProvider = (documentId: string) => PublicKey | undefined;
53
54
 
54
- export type AutomergeHostParams = {
55
+ export type AutomergeHostProps = {
55
56
  db: LevelDB;
56
57
  indexMetadataStore: IndexMetadataStore;
57
58
  dataMonitor?: EchoDataMonitor;
@@ -65,6 +66,12 @@ export type AutomergeHostParams = {
65
66
 
66
67
  export type LoadDocOptions = {
67
68
  timeout?: number;
69
+
70
+ /**
71
+ * If true, the document will be fetched from the network if it is not found in the local storage.
72
+ * Setting this to false does not guarantee that the document will not be fetched from the network.
73
+ */
74
+ fetchFromNetwork?: boolean;
68
75
  };
69
76
 
70
77
  export type CreateDocOptions = {
@@ -72,6 +79,8 @@ export type CreateDocOptions = {
72
79
  * Import the document together with its history.
73
80
  */
74
81
  preserveHistory?: boolean;
82
+
83
+ documentId?: DocumentId;
75
84
  };
76
85
 
77
86
  export const FIND_PARAMS = {
@@ -91,6 +100,11 @@ const BUNDLE_SYNC_CONCURRENCY = 2;
91
100
  */
92
101
  const BUNDLE_SYNC_THRESHOLD = 50;
93
102
 
103
+ /**
104
+ * Only announce documents that are known to require sync.
105
+ */
106
+ const OPTIMIZED_SHARE_POLICY = true;
107
+
94
108
  /**
95
109
  * Abstracts over the AutomergeRepo.
96
110
  */
@@ -132,7 +146,29 @@ export class AutomergeHost extends Resource {
132
146
  public readonly documentsSaved = new Event();
133
147
 
134
148
  private readonly _headsUpdates = new Map<DocumentId, Heads>();
135
- private _onHeadsChangedTask?: DeferredTask | undefined;
149
+ private _onHeadsChangedTask?: DeferredTask;
150
+
151
+ /**
152
+ * Documents created in this session.
153
+ */
154
+ private _createdDocuments = new Set<DocumentId>();
155
+
156
+ /**
157
+ * Documents that need to be synced based on the result of collection-sync.
158
+ */
159
+ private _documentsToSync = new Set<DocumentId>();
160
+
161
+ /**
162
+ * Documents that are not avaiale locally that should be requested.
163
+ */
164
+ private _documentsToRequest = new Set<DocumentId>();
165
+
166
+ /**
167
+ * Documents requested by remote peers.
168
+ */
169
+ private _documentsRequested = new Map<PeerId, Set<DocumentId>>();
170
+
171
+ private _sharePolicyChangedTask?: DeferredTask;
136
172
 
137
173
  constructor({
138
174
  db,
@@ -140,7 +176,7 @@ export class AutomergeHost extends Resource {
140
176
  dataMonitor,
141
177
  peerIdProvider,
142
178
  getSpaceKeyByRootDocumentId,
143
- }: AutomergeHostParams) {
179
+ }: AutomergeHostProps) {
144
180
  super();
145
181
  this._db = db;
146
182
  this._storage = new LevelDBStorageAdapter({
@@ -158,6 +194,10 @@ export class AutomergeHost extends Resource {
158
194
  onCollectionStateReceived: this._onCollectionStateReceived.bind(this),
159
195
  monitor: dataMonitor,
160
196
  });
197
+ this._echoNetworkAdapter.documentRequested.on(({ peerId, documentId }) => {
198
+ defaultMap(this._documentsRequested, peerId, () => new Set()).add(documentId);
199
+ this._sharePolicyChangedTask!.schedule();
200
+ });
161
201
  this._headsStore = new HeadsStore({ db: db.sublevel('heads') });
162
202
  this._indexMetadataStore = indexMetadataStore;
163
203
  this._peerIdProvider = peerIdProvider;
@@ -178,7 +218,7 @@ export class AutomergeHost extends Resource {
178
218
  // Construct the automerge repo.
179
219
  this._repo = new Repo({
180
220
  peerId: this._peerId as PeerId,
181
- sharePolicy: this._sharePolicy.bind(this),
221
+ shareConfig: this._shareConfig,
182
222
  storage: this._storage,
183
223
  network: [
184
224
  // Upstream swarm.
@@ -226,6 +266,11 @@ export class AutomergeHost extends Resource {
226
266
  );
227
267
  });
228
268
 
269
+ this._sharePolicyChangedTask = new DeferredTask(this._ctx, async () => {
270
+ log('share policy changed');
271
+ this._repo.shareConfigChanged();
272
+ });
273
+
229
274
  await this._echoNetworkAdapter.open();
230
275
  await this._collectionSynchronizer.open();
231
276
  await this._echoNetworkAdapter.open();
@@ -238,13 +283,7 @@ export class AutomergeHost extends Resource {
238
283
  await this._echoNetworkAdapter.close();
239
284
  this._syncTask = undefined;
240
285
  this._onHeadsChangedTask = undefined;
241
- }
242
-
243
- /**
244
- * @deprecated To be abstracted away.
245
- */
246
- get repo(): Repo {
247
- return this._repo;
286
+ this._sharePolicyChangedTask = undefined;
248
287
  }
249
288
 
250
289
  get peerId(): PeerId {
@@ -255,6 +294,10 @@ export class AutomergeHost extends Resource {
255
294
  return Object.keys(this._repo.handles).length;
256
295
  }
257
296
 
297
+ get handles(): Readonly<Record<DocumentId, DocHandle<any>>> {
298
+ return this._repo.handles;
299
+ }
300
+
258
301
  async addReplicator(replicator: EchoReplicator): Promise<void> {
259
302
  invariant(this.isOpen, 'AutomergeHost is not open');
260
303
  await this._echoNetworkAdapter.addReplicator(replicator);
@@ -276,7 +319,22 @@ export class AutomergeHost extends Resource {
276
319
  handle = this._repo.handles[documentId as DocumentId];
277
320
  }
278
321
  if (!handle) {
279
- handle = await this._repo.find(documentId as DocumentId, FIND_PARAMS);
322
+ if (!opts?.fetchFromNetwork) {
323
+ handle = await this._repo.find(documentId as DocumentId, FIND_PARAMS);
324
+ } else {
325
+ try {
326
+ handle = await this._repo.find(documentId as DocumentId, {
327
+ allowableStates: ['ready', 'requesting', 'unavailable'],
328
+ });
329
+ if (handle.state === 'requesting' || handle.state === 'unavailable') {
330
+ this._documentsToRequest.add(handle.documentId);
331
+ this._sharePolicyChangedTask!.schedule();
332
+ }
333
+ } catch (err) {
334
+ log.error('failed to load document', { documentId, err });
335
+ throw err;
336
+ }
337
+ }
280
338
  }
281
339
 
282
340
  // `whenReady` creates a timeout so we guard it with an if to skip it if the handle is already ready.
@@ -302,11 +360,11 @@ export class AutomergeHost extends Resource {
302
360
  /**
303
361
  * Create new persisted document.
304
362
  */
305
- createDoc<T>(initialValue?: T | Doc<T> | Uint8Array, opts?: CreateDocOptions): DocHandle<T> {
363
+ async createDoc<T>(initialValue?: T | Doc<T> | Uint8Array, opts?: CreateDocOptions): Promise<DocHandle<T>> {
306
364
  invariant(this.isOpen, 'AutomergeHost is not open');
307
365
  if (opts?.preserveHistory) {
308
366
  if (initialValue instanceof Uint8Array) {
309
- return this._repo.import(initialValue);
367
+ return this._repo.import(initialValue, { docId: opts?.documentId });
310
368
  }
311
369
 
312
370
  if (!isAutomerge(initialValue)) {
@@ -314,13 +372,22 @@ export class AutomergeHost extends Resource {
314
372
  }
315
373
 
316
374
  // TODO(dmaretskyi): There's a more efficient way.
317
- return this._repo.import(save(initialValue as Doc<T>));
375
+ const handle = this._repo.import(save(initialValue as Doc<T>), { docId: opts?.documentId });
376
+ this._createdDocuments.add(handle.documentId);
377
+ this._sharePolicyChangedTask!.schedule();
378
+ return handle as DocHandle<T>;
318
379
  } else {
319
380
  if (initialValue instanceof Uint8Array) {
320
381
  throw new Error('Cannot create document from Uint8Array without preserving history');
321
382
  }
322
383
 
323
- return this._repo.create(initialValue);
384
+ if (opts?.documentId) {
385
+ throw new Error('Cannot prefil document id when not importing an existing doc');
386
+ }
387
+ const handle = await this._repo.create2<T>(initialValue);
388
+ this._createdDocuments.add(handle.documentId);
389
+ this._sharePolicyChangedTask!.schedule();
390
+ return handle;
324
391
  }
325
392
  }
326
393
 
@@ -373,24 +440,42 @@ export class AutomergeHost extends Resource {
373
440
  log('done re-indexing heads');
374
441
  }
375
442
 
376
- // TODO(dmaretskyi): Share based on HALO permissions and space affinity.
377
- // Hosts, running in the worker, don't share documents unless requested by other peers.
378
- // NOTE: If both peers return sharePolicy=false the replication will not happen
379
- // https://github.com/automerge/automerge-repo/pull/292
380
- private async _sharePolicy(peerId: PeerId, documentId?: DocumentId): Promise<boolean> {
381
- if (!documentId) {
382
- return false;
383
- }
443
+ private readonly _shareConfig = {
444
+ access: async (peerId: PeerId, documentId: DocumentId): Promise<boolean> => {
445
+ return true;
446
+ },
384
447
 
385
- const peerMetadata = this.repo.peerMetadataByPeerId[peerId];
386
- if (isEchoPeerMetadata(peerMetadata)) {
387
- return this._echoNetworkAdapter.shouldAdvertise(peerId, { documentId });
388
- }
448
+ // TODO(dmaretskyi): Share based on HALO permissions and space affinity.
449
+ // Hosts, running in the worker, don't share documents unless requested by other peers.
450
+ // NOTE: If both peers return sharePolicy=false the replication will not happen
451
+ // https://github.com/automerge/automerge-repo/pull/292
452
+ // Called for all loaded documents so they could be advertised to the sync server.
453
+ announce: async (peerId: PeerId, documentId?: DocumentId): Promise<boolean> => {
454
+ if (!documentId) {
455
+ return false;
456
+ }
389
457
 
390
- return false;
391
- }
458
+ if (OPTIMIZED_SHARE_POLICY) {
459
+ if (
460
+ !this._createdDocuments.has(documentId) &&
461
+ !this._documentsToSync.has(documentId) &&
462
+ !this._documentsToRequest.has(documentId)
463
+ ) {
464
+ // Skip advertising documents that don't need to be synced.
465
+ return false;
466
+ }
467
+ }
468
+
469
+ const peerMetadata = this._repo.peerMetadataByPeerId[peerId];
470
+ if (isEchoPeerMetadata(peerMetadata)) {
471
+ return this._echoNetworkAdapter.shouldAdvertise(peerId, { documentId });
472
+ }
392
473
 
393
- private async _beforeSave({ path, batch }: BeforeSaveParams): Promise<void> {
474
+ return false;
475
+ },
476
+ };
477
+
478
+ private async _beforeSave({ path, batch }: BeforeSaveProps): Promise<void> {
394
479
  const handle = this._repo.handles[path[0] as DocumentId];
395
480
  if (!handle || !handle.isReady()) {
396
481
  return;
@@ -452,7 +537,7 @@ export class AutomergeHost extends Resource {
452
537
  return this._repo.peers;
453
538
  }
454
539
 
455
- private async _isDocumentInRemoteCollection(params: RemoteDocumentExistenceCheckParams): Promise<boolean> {
540
+ private async _isDocumentInRemoteCollection(params: RemoteDocumentExistenceCheckProps): Promise<boolean> {
456
541
  for (const collectionId of this._collectionSynchronizer.getRegisteredCollectionIds()) {
457
542
  const remoteCollections = this._collectionSynchronizer.getRemoteCollectionStates(collectionId);
458
543
  const remotePeerDocs = remoteCollections.get(params.peerId as PeerId)?.documents;
@@ -530,6 +615,13 @@ export class AutomergeHost extends Resource {
530
615
  return result;
531
616
  }
532
617
 
618
+ /**
619
+ * Iterate over all document heads stored on disk.
620
+ */
621
+ listDocumentHeads(): AsyncGenerator<{ documentId: DocumentId; heads: Heads }> {
622
+ return this._headsStore.iterateAll();
623
+ }
624
+
533
625
  //
534
626
  // Collection sync.
535
627
  //
@@ -684,12 +776,14 @@ export class AutomergeHost extends Resource {
684
776
 
685
777
  // Load the documents so they will start syncing.
686
778
  for (const documentId of toReplicateWithoutBatching) {
779
+ // Unless we track the document in "to sync" list, it will not be advertised.
780
+ this._documentsToSync!.add(documentId);
687
781
  this._repo.findWithProgress(documentId);
688
782
  }
783
+ this._sharePolicyChangedTask!.schedule();
689
784
  }
690
785
 
691
786
  // TODO(mykola): Add retries of batches https://gist.github.com/mykola-vrmchk/fde270259e9209fcbf1331e5abbf12cf
692
- // TODO(mykola): Use effect to retry batches.
693
787
  private async _pushInBundles(
694
788
  peerId: PeerId,
695
789
  documentIds: DocumentId[],