@dxos/echo-pipeline 0.8.3 → 0.8.4-main.03d5cd7b56

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 (239) hide show
  1. package/dist/lib/{browser/chunk-35I6ERLG.mjs → neutral/chunk-VIHYDCCC.mjs} +313 -709
  2. package/dist/lib/neutral/chunk-VIHYDCCC.mjs.map +7 -0
  3. package/dist/lib/neutral/chunk-YBC6ONBZ.mjs +247 -0
  4. package/dist/lib/neutral/chunk-YBC6ONBZ.mjs.map +7 -0
  5. package/dist/lib/{browser → neutral}/filter/index.mjs +3 -2
  6. package/dist/lib/neutral/index.mjs +7065 -0
  7. package/dist/lib/neutral/index.mjs.map +7 -0
  8. package/dist/lib/neutral/meta.json +1 -0
  9. package/dist/lib/{browser → neutral}/testing/index.mjs +90 -311
  10. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  11. package/dist/types/src/automerge/automerge-host-subduction.test.d.ts +2 -0
  12. package/dist/types/src/automerge/automerge-host-subduction.test.d.ts.map +1 -0
  13. package/dist/types/src/automerge/automerge-host.d.ts +99 -54
  14. package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
  15. package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts +2 -0
  16. package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts.map +1 -0
  17. package/dist/types/src/automerge/automerge-repo.test.d.ts +1 -1
  18. package/dist/types/src/automerge/automerge-repo.test.d.ts.map +1 -1
  19. package/dist/types/src/automerge/automerge-subduction.test.d.ts +2 -0
  20. package/dist/types/src/automerge/automerge-subduction.test.d.ts.map +1 -0
  21. package/dist/types/src/automerge/collection-synchronizer.d.ts +25 -4
  22. package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
  23. package/dist/types/src/automerge/echo-data-monitor.d.ts.map +1 -1
  24. package/dist/types/src/automerge/echo-network-adapter.d.ts +23 -10
  25. package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
  26. package/dist/types/src/automerge/echo-replicator.d.ts +38 -15
  27. package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
  28. package/dist/types/src/automerge/handle-state.d.ts +52 -0
  29. package/dist/types/src/automerge/handle-state.d.ts.map +1 -0
  30. package/dist/types/src/automerge/heads-store.d.ts +9 -2
  31. package/dist/types/src/automerge/heads-store.d.ts.map +1 -1
  32. package/dist/types/src/automerge/index.d.ts +2 -1
  33. package/dist/types/src/automerge/index.d.ts.map +1 -1
  34. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +9 -5
  35. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
  36. package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +9 -8
  37. package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
  38. package/dist/types/src/automerge/mesh-echo-replicator.d.ts +6 -5
  39. package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
  40. package/dist/types/src/automerge/network-protocol.d.ts.map +1 -1
  41. package/dist/types/src/automerge/space-collection.d.ts.map +1 -1
  42. package/dist/types/src/common/codec.d.ts +1 -1
  43. package/dist/types/src/common/codec.d.ts.map +1 -1
  44. package/dist/types/src/common/feeds.d.ts.map +1 -1
  45. package/dist/types/src/common/space-id.d.ts.map +1 -1
  46. package/dist/types/src/db-host/automerge-data-source.d.ts +35 -0
  47. package/dist/types/src/db-host/automerge-data-source.d.ts.map +1 -0
  48. package/dist/types/src/db-host/automerge-data-source.test.d.ts +2 -0
  49. package/dist/types/src/db-host/automerge-data-source.test.d.ts.map +1 -0
  50. package/dist/types/src/db-host/automerge-metrics.d.ts.map +1 -1
  51. package/dist/types/src/db-host/data-service.d.ts +8 -7
  52. package/dist/types/src/db-host/data-service.d.ts.map +1 -1
  53. package/dist/types/src/db-host/database-root.d.ts +9 -3
  54. package/dist/types/src/db-host/database-root.d.ts.map +1 -1
  55. package/dist/types/src/db-host/documents-synchronizer.d.ts +9 -8
  56. package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
  57. package/dist/types/src/db-host/echo-host.d.ts +62 -27
  58. package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
  59. package/dist/types/src/db-host/index.d.ts +2 -0
  60. package/dist/types/src/db-host/index.d.ts.map +1 -1
  61. package/dist/types/src/db-host/invalidation-hint.d.ts +19 -0
  62. package/dist/types/src/db-host/invalidation-hint.d.ts.map +1 -0
  63. package/dist/types/src/db-host/local-queue-service.d.ts +19 -0
  64. package/dist/types/src/db-host/local-queue-service.d.ts.map +1 -0
  65. package/dist/types/src/db-host/query-invalidation.test.d.ts +2 -0
  66. package/dist/types/src/db-host/query-invalidation.test.d.ts.map +1 -0
  67. package/dist/types/src/db-host/query-service.d.ts +19 -11
  68. package/dist/types/src/db-host/query-service.d.ts.map +1 -1
  69. package/dist/types/src/db-host/queue-data-source.d.ts +38 -0
  70. package/dist/types/src/db-host/queue-data-source.d.ts.map +1 -0
  71. package/dist/types/src/db-host/queue-service.test.d.ts +2 -0
  72. package/dist/types/src/db-host/queue-service.test.d.ts.map +1 -0
  73. package/dist/types/src/db-host/space-state-manager.d.ts +4 -3
  74. package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
  75. package/dist/types/src/db-host/stub.d.ts +11 -0
  76. package/dist/types/src/db-host/stub.d.ts.map +1 -0
  77. package/dist/types/src/edge/echo-edge-replicator.d.ts +10 -7
  78. package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
  79. package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts +39 -0
  80. package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts.map +1 -0
  81. package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts +2 -0
  82. package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts.map +1 -0
  83. package/dist/types/src/edge/index.d.ts +1 -0
  84. package/dist/types/src/edge/index.d.ts.map +1 -1
  85. package/dist/types/src/edge/inflight-request-limiter.d.ts.map +1 -1
  86. package/dist/types/src/filter/filter-match.d.ts +4 -1
  87. package/dist/types/src/filter/filter-match.d.ts.map +1 -1
  88. package/dist/types/src/metadata/metadata-store.d.ts +1 -1
  89. package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
  90. package/dist/types/src/pipeline/message-selector.d.ts.map +1 -1
  91. package/dist/types/src/pipeline/pipeline.d.ts +3 -3
  92. package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
  93. package/dist/types/src/pipeline/timeframe-clock.d.ts.map +1 -1
  94. package/dist/types/src/query/errors.d.ts +22 -10
  95. package/dist/types/src/query/errors.d.ts.map +1 -1
  96. package/dist/types/src/query/plan.d.ts +66 -8
  97. package/dist/types/src/query/plan.d.ts.map +1 -1
  98. package/dist/types/src/query/query-executor.d.ts +66 -11
  99. package/dist/types/src/query/query-executor.d.ts.map +1 -1
  100. package/dist/types/src/query/query-planner.d.ts +12 -1
  101. package/dist/types/src/query/query-planner.d.ts.map +1 -1
  102. package/dist/types/src/space/admission-discovery-extension.d.ts.map +1 -1
  103. package/dist/types/src/space/auth.d.ts +3 -3
  104. package/dist/types/src/space/auth.d.ts.map +1 -1
  105. package/dist/types/src/space/control-pipeline.d.ts +5 -4
  106. package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
  107. package/dist/types/src/space/space-manager.d.ts +8 -8
  108. package/dist/types/src/space/space-manager.d.ts.map +1 -1
  109. package/dist/types/src/space/space-protocol.d.ts +10 -9
  110. package/dist/types/src/space/space-protocol.d.ts.map +1 -1
  111. package/dist/types/src/space/space.d.ts +6 -6
  112. package/dist/types/src/space/space.d.ts.map +1 -1
  113. package/dist/types/src/testing/change-metadata.d.ts.map +1 -1
  114. package/dist/types/src/testing/index.d.ts +0 -2
  115. package/dist/types/src/testing/index.d.ts.map +1 -1
  116. package/dist/types/src/testing/test-agent-builder.d.ts +2 -2
  117. package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
  118. package/dist/types/src/testing/test-feed-builder.d.ts.map +1 -1
  119. package/dist/types/src/testing/test-network-adapter.d.ts +1 -1
  120. package/dist/types/src/testing/test-network-adapter.d.ts.map +1 -1
  121. package/dist/types/src/testing/test-replicator.d.ts +14 -13
  122. package/dist/types/src/testing/test-replicator.d.ts.map +1 -1
  123. package/dist/types/src/util.d.ts +1 -1
  124. package/dist/types/src/util.d.ts.map +1 -1
  125. package/dist/types/tsconfig.tsbuildinfo +1 -1
  126. package/package.json +56 -54
  127. package/src/automerge/automerge-host-subduction.test.ts +247 -0
  128. package/src/automerge/automerge-host.test.ts +65 -39
  129. package/src/automerge/automerge-host.ts +589 -206
  130. package/src/automerge/automerge-repo-subduction.test.ts +1049 -0
  131. package/src/automerge/automerge-repo.test.ts +349 -91
  132. package/src/automerge/automerge-subduction.test.ts +330 -0
  133. package/src/automerge/collection-synchronizer.test.ts +53 -17
  134. package/src/automerge/collection-synchronizer.ts +119 -36
  135. package/src/automerge/echo-data-monitor.ts +1 -1
  136. package/src/automerge/echo-network-adapter.test.ts +5 -4
  137. package/src/automerge/echo-network-adapter.ts +74 -30
  138. package/src/automerge/echo-replicator.ts +41 -15
  139. package/src/automerge/handle-state.ts +62 -0
  140. package/src/automerge/heads-store.ts +42 -3
  141. package/src/automerge/index.ts +2 -1
  142. package/src/automerge/leveldb-storage-adapter.ts +40 -11
  143. package/src/automerge/mesh-echo-replicator-connection.ts +12 -8
  144. package/src/automerge/mesh-echo-replicator.ts +15 -11
  145. package/src/automerge/storage-adapter.test.ts +1 -1
  146. package/src/common/space-id.ts +1 -1
  147. package/src/db-host/automerge-data-source.test.ts +299 -0
  148. package/src/db-host/automerge-data-source.ts +187 -0
  149. package/src/db-host/data-service.ts +25 -26
  150. package/src/db-host/database-root.ts +20 -8
  151. package/src/db-host/documents-synchronizer.test.ts +143 -14
  152. package/src/db-host/documents-synchronizer.ts +72 -55
  153. package/src/db-host/echo-host.ts +348 -96
  154. package/src/db-host/index.ts +2 -0
  155. package/src/db-host/invalidation-hint.ts +50 -0
  156. package/src/db-host/local-queue-service.ts +127 -0
  157. package/src/db-host/query-invalidation.test.ts +270 -0
  158. package/src/db-host/query-service.ts +111 -106
  159. package/src/db-host/queue-data-source.ts +160 -0
  160. package/src/db-host/queue-service.test.ts +145 -0
  161. package/src/db-host/space-state-manager.ts +12 -10
  162. package/src/db-host/stub.ts +33 -0
  163. package/src/edge/echo-edge-replicator.test.ts +12 -10
  164. package/src/edge/echo-edge-replicator.ts +144 -58
  165. package/src/edge/echo-edge-subduction-replicator.test.ts +158 -0
  166. package/src/edge/echo-edge-subduction-replicator.ts +377 -0
  167. package/src/edge/index.ts +1 -0
  168. package/src/filter/filter-match.test.ts +68 -32
  169. package/src/filter/filter-match.ts +184 -20
  170. package/src/metadata/metadata-store.ts +11 -8
  171. package/src/pipeline/pipeline-stress.test.ts +1 -1
  172. package/src/pipeline/pipeline.test.ts +2 -2
  173. package/src/pipeline/pipeline.ts +9 -7
  174. package/src/query/errors.ts +3 -1
  175. package/src/query/plan.ts +82 -8
  176. package/src/query/query-executor.ts +1199 -164
  177. package/src/query/query-planner.test.ts +993 -81
  178. package/src/query/query-planner.ts +467 -27
  179. package/src/space/admission-discovery-extension.ts +3 -3
  180. package/src/space/auth.ts +6 -6
  181. package/src/space/control-pipeline.test.ts +5 -4
  182. package/src/space/control-pipeline.ts +16 -13
  183. package/src/space/space-manager.browser.test.ts +1 -1
  184. package/src/space/space-manager.ts +16 -20
  185. package/src/space/space-protocol.browser.test.ts +15 -14
  186. package/src/space/space-protocol.test.ts +15 -14
  187. package/src/space/space-protocol.ts +22 -18
  188. package/src/space/space.test.ts +3 -3
  189. package/src/space/space.ts +11 -11
  190. package/src/testing/index.ts +0 -2
  191. package/src/testing/test-agent-builder.ts +3 -3
  192. package/src/testing/test-network-adapter.ts +24 -3
  193. package/src/testing/test-replicator.ts +19 -20
  194. package/src/util.ts +7 -3
  195. package/dist/lib/browser/chunk-35I6ERLG.mjs.map +0 -7
  196. package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
  197. package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
  198. package/dist/lib/browser/chunk-TQJTKNMS.mjs +0 -126
  199. package/dist/lib/browser/chunk-TQJTKNMS.mjs.map +0 -7
  200. package/dist/lib/browser/index.mjs +0 -4743
  201. package/dist/lib/browser/index.mjs.map +0 -7
  202. package/dist/lib/browser/meta.json +0 -1
  203. package/dist/lib/browser/testing/index.mjs.map +0 -7
  204. package/dist/lib/node/chunk-HOPOFWAL.cjs +0 -147
  205. package/dist/lib/node/chunk-HOPOFWAL.cjs.map +0 -7
  206. package/dist/lib/node/chunk-JXX6LF5U.cjs +0 -2084
  207. package/dist/lib/node/chunk-JXX6LF5U.cjs.map +0 -7
  208. package/dist/lib/node/chunk-Q7SFCCGT.cjs +0 -33
  209. package/dist/lib/node/chunk-Q7SFCCGT.cjs.map +0 -7
  210. package/dist/lib/node/filter/index.cjs +0 -32
  211. package/dist/lib/node/filter/index.cjs.map +0 -7
  212. package/dist/lib/node/index.cjs +0 -4699
  213. package/dist/lib/node/index.cjs.map +0 -7
  214. package/dist/lib/node/meta.json +0 -1
  215. package/dist/lib/node/testing/index.cjs +0 -753
  216. package/dist/lib/node/testing/index.cjs.map +0 -7
  217. package/dist/lib/node-esm/chunk-5BHLPT24.mjs +0 -2067
  218. package/dist/lib/node-esm/chunk-5BHLPT24.mjs.map +0 -7
  219. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  220. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
  221. package/dist/lib/node-esm/chunk-RVK35BS7.mjs +0 -126
  222. package/dist/lib/node-esm/chunk-RVK35BS7.mjs.map +0 -7
  223. package/dist/lib/node-esm/filter/index.mjs +0 -11
  224. package/dist/lib/node-esm/filter/index.mjs.map +0 -7
  225. package/dist/lib/node-esm/index.mjs +0 -4743
  226. package/dist/lib/node-esm/index.mjs.map +0 -7
  227. package/dist/lib/node-esm/meta.json +0 -1
  228. package/dist/lib/node-esm/testing/index.mjs +0 -742
  229. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  230. package/dist/types/src/db-host/documents-iterator.d.ts +0 -7
  231. package/dist/types/src/db-host/documents-iterator.d.ts.map +0 -1
  232. package/dist/types/src/testing/test-data.d.ts +0 -18
  233. package/dist/types/src/testing/test-data.d.ts.map +0 -1
  234. package/dist/types/src/testing/test-schema.d.ts +0 -37
  235. package/dist/types/src/testing/test-schema.d.ts.map +0 -1
  236. package/src/db-host/documents-iterator.ts +0 -74
  237. package/src/testing/test-data.ts +0 -127
  238. package/src/testing/test-schema.ts +0 -53
  239. /package/dist/lib/{browser → neutral}/filter/index.mjs.map +0 -0
@@ -2,20 +2,22 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { next as am } from '@automerge/automerge';
5
+ import { next as A, type Heads } from '@automerge/automerge';
6
6
  import type { DocumentId, PeerId } from '@automerge/automerge-repo';
7
+ import * as Array from 'effect/Array';
8
+ import * as Record from 'effect/Record';
7
9
 
8
- import { asyncReturn, Event, scheduleTask, scheduleTaskInterval } from '@dxos/async';
9
- import { Resource, type Context } from '@dxos/context';
10
+ import { Event, asyncReturn, scheduleTask, scheduleTaskInterval } from '@dxos/async';
11
+ import { type Context, Resource } from '@dxos/context';
10
12
  import { log } from '@dxos/log';
11
13
  import { trace } from '@dxos/tracing';
12
14
  import { defaultMap } from '@dxos/util';
13
15
 
14
16
  const MIN_QUERY_INTERVAL = 5_000;
15
17
 
16
- const POLL_INTERVAL = 30_000;
18
+ const POLL_INTERVAL = 10_000;
17
19
 
18
- export type CollectionSynchronizerParams = {
20
+ export type CollectionSynchronizerProps = {
19
21
  sendCollectionState: (collectionId: string, peerId: PeerId, state: CollectionState) => void;
20
22
  queryCollectionState: (collectionId: string, peerId: PeerId) => void;
21
23
  shouldSyncCollection: (collectionId: string, peerId: PeerId) => boolean;
@@ -24,11 +26,11 @@ export type CollectionSynchronizerParams = {
24
26
  /**
25
27
  * Implements collection sync protocol.
26
28
  */
27
- @trace.resource()
29
+ @trace.resource({ lifecycle: true })
28
30
  export class CollectionSynchronizer extends Resource {
29
- private readonly _sendCollectionState: CollectionSynchronizerParams['sendCollectionState'];
30
- private readonly _queryCollectionState: CollectionSynchronizerParams['queryCollectionState'];
31
- private readonly _shouldSyncCollection: CollectionSynchronizerParams['shouldSyncCollection'];
31
+ private readonly _sendCollectionState: CollectionSynchronizerProps['sendCollectionState'];
32
+ private readonly _queryCollectionState: CollectionSynchronizerProps['queryCollectionState'];
33
+ private readonly _shouldSyncCollection: CollectionSynchronizerProps['shouldSyncCollection'];
32
34
 
33
35
  /**
34
36
  * CollectionId -> State.
@@ -40,7 +42,7 @@ export class CollectionSynchronizer extends Resource {
40
42
 
41
43
  public readonly remoteStateUpdated = new Event<{ collectionId: string; peerId: PeerId; newDocsAppeared: boolean }>();
42
44
 
43
- constructor(params: CollectionSynchronizerParams) {
45
+ constructor(params: CollectionSynchronizerProps) {
44
46
  super();
45
47
  this._sendCollectionState = params.sendCollectionState;
46
48
  this._queryCollectionState = params.queryCollectionState;
@@ -74,13 +76,29 @@ export class CollectionSynchronizer extends Resource {
74
76
  this._activeCollections.add(collectionId);
75
77
 
76
78
  log('setLocalCollectionState', { collectionId, state });
77
- this._getOrCreatePerCollectionState(collectionId).localState = state;
79
+ const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
80
+ perCollectionState.localState = state;
78
81
 
79
- queueMicrotask(async () => {
80
- if (!this._ctx.disposed && this._activeCollections.has(collectionId)) {
81
- this._refreshInterestedPeers(collectionId);
82
- this.refreshCollection(collectionId);
83
- }
82
+ for (const peerId of this._connectedPeers) {
83
+ this._diffCollectionState(collectionId, peerId);
84
+ }
85
+
86
+ this._scheduleBroadcast(collectionId);
87
+ }
88
+
89
+ /**
90
+ * Coalesce bursts of `setLocalCollectionState` (e.g. from `_onHeadsChanged` during an
91
+ * import) into one broadcast microtask per collection. If another call arrives while
92
+ * the microtask is pending, we just flag the collection dirty — the in-flight run
93
+ * already reads the latest `localState`. If it arrives during the run itself, we
94
+ * schedule exactly one follow-up.
95
+ */
96
+ private _scheduleBroadcast(collectionId: string): void {
97
+ const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
98
+ queueMicrotask(() => {
99
+ this._refreshInterestedPeers(collectionId);
100
+ this._broadcastLocalState(collectionId);
101
+ this.refreshCollection(collectionId);
84
102
  });
85
103
  }
86
104
 
@@ -117,10 +135,14 @@ export class CollectionSynchronizer extends Resource {
117
135
  * Callback when a connection to a peer is established.
118
136
  */
119
137
  onConnectionOpen(peerId: PeerId): void {
120
- const spanId = getSpanName(peerId);
121
- trace.spanStart({
138
+ log('onConnectionOpen', { peerId });
139
+ const spanId = getSpanId(peerId);
140
+ // Browser-timeline-only span; the derived ctx is intentionally discarded because
141
+ // the downstream `_queryCollectionState` hop is user-supplied callback land with
142
+ // no ctx plumbing.
143
+ void trace.spanStart({
122
144
  id: spanId,
123
- methodName: spanId,
145
+ methodName: SYNC_SPAN_METHOD,
124
146
  instance: this,
125
147
  parentCtx: this._ctx,
126
148
  showInBrowserTimeline: true,
@@ -146,6 +168,8 @@ export class CollectionSynchronizer extends Resource {
146
168
  * Callback when a connection to a peer is closed.
147
169
  */
148
170
  onConnectionClosed(peerId: PeerId): void {
171
+ log('onConnectionClosed', { peerId });
172
+
149
173
  this._connectedPeers.delete(peerId);
150
174
 
151
175
  for (const perCollectionState of this._perCollectionStates.values()) {
@@ -155,6 +179,9 @@ export class CollectionSynchronizer extends Resource {
155
179
 
156
180
  /**
157
181
  * Callback when a peer queries the state of a collection.
182
+ *
183
+ * If we have no local state yet we silently drop the query; the peer will receive our
184
+ * state via {@link _broadcastLocalState} once `setLocalCollectionState` is called.
158
185
  */
159
186
  onCollectionStateQueried(collectionId: string, peerId: PeerId): void {
160
187
  const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
@@ -171,24 +198,48 @@ export class CollectionSynchronizer extends Resource {
171
198
  log('onRemoteStateReceived', { collectionId, peerId, state });
172
199
  validateCollectionState(state);
173
200
  const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
174
- const existingState = perCollectionState.remoteStates.get(peerId) ?? { documents: {} };
175
- const diff = diffCollectionState(existingState, state);
176
- const spanId = getSpanName(peerId);
201
+ perCollectionState.remoteStates.set(peerId, state);
202
+ this._diffCollectionState(collectionId, peerId);
203
+ }
204
+
205
+ private _diffCollectionState(collectionId: string, peerId: PeerId) {
206
+ const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
207
+ const remoteState = perCollectionState.remoteStates.get(peerId);
208
+ if (!remoteState) {
209
+ return;
210
+ }
211
+
212
+ log('diffCollectionState', { collectionId, peerId });
213
+ const localState = perCollectionState.localState ?? { documents: {} };
214
+ const diff = diffCollectionState(localState, remoteState);
215
+ const spanId = getSpanId(peerId);
177
216
  if (diff.different.length === 0) {
178
217
  trace.spanEnd(spanId);
179
218
  } else {
180
- trace.spanStart({
219
+ // Browser-timeline-only span; see note in onConnectionOpen.
220
+ void trace.spanStart({
181
221
  id: spanId,
182
- methodName: spanId,
222
+ methodName: SYNC_SPAN_METHOD,
183
223
  instance: this,
184
224
  parentCtx: this._ctx,
185
225
  showInBrowserTimeline: true,
186
226
  attributes: { peerId },
187
227
  });
188
228
  }
189
- if (diff.missingOnLocal.length > 0 || diff.different.length > 0) {
190
- perCollectionState.remoteStates.set(peerId, state);
191
- this.remoteStateUpdated.emit({ peerId, collectionId, newDocsAppeared: diff.missingOnLocal.length > 0 });
229
+ log('diff', {
230
+ localState: localState.documents,
231
+ remoteState: remoteState.documents,
232
+ missingOnLocal: diff.missingOnLocal,
233
+ missingOnRemote: diff.missingOnRemote,
234
+ different: diff.different,
235
+ });
236
+ if (diff.missingOnLocal.length > 0 || diff.different.length > 0 || diff.missingOnRemote.length > 0) {
237
+ log('emit remote state update');
238
+ this.remoteStateUpdated.emit({
239
+ peerId,
240
+ collectionId,
241
+ newDocsAppeared: diff.missingOnLocal.length > 0,
242
+ });
192
243
  }
193
244
  }
194
245
 
@@ -198,9 +249,35 @@ export class CollectionSynchronizer extends Resource {
198
249
  remoteStates: new Map(),
199
250
  interestedPeers: new Set(),
200
251
  lastQueried: new Map(),
252
+ lastBroadcast: new Map(),
201
253
  }));
202
254
  }
203
255
 
256
+ /**
257
+ * Push local state to interested peers whose last-known remote state differs from local
258
+ * (or is unknown), then pull from peers via {@link refreshCollection}.
259
+ *
260
+ * Diff-gating avoids spamming peers that are already in sync; this matters because
261
+ * {@link setLocalCollectionState} is called on every local heads change.
262
+ */
263
+ private _broadcastLocalState(collectionId: string): void {
264
+ if (this._ctx.disposed || !this._activeCollections.has(collectionId)) {
265
+ return;
266
+ }
267
+ const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
268
+ const localState = perCollectionState.localState;
269
+ if (!localState) {
270
+ return;
271
+ }
272
+ for (const peerId of perCollectionState.interestedPeers) {
273
+ const lastBroadcast = perCollectionState.lastBroadcast.get(peerId) ?? 0;
274
+ if (Date.now() - lastBroadcast > MIN_QUERY_INTERVAL) {
275
+ perCollectionState.lastBroadcast.set(peerId, Date.now());
276
+ this._sendCollectionState(collectionId, peerId, localState);
277
+ }
278
+ }
279
+ }
280
+
204
281
  private _refreshInterestedPeers(collectionId: string): void {
205
282
  for (const peerId of this._connectedPeers) {
206
283
  if (this._shouldSyncCollection(collectionId, peerId)) {
@@ -217,13 +294,14 @@ type PerCollectionState = {
217
294
  remoteStates: Map<PeerId, CollectionState>;
218
295
  interestedPeers: Set<PeerId>;
219
296
  lastQueried: Map<PeerId, number>;
297
+ lastBroadcast: Map<PeerId, number>;
220
298
  };
221
299
 
222
300
  export type CollectionState = {
223
301
  /**
224
302
  * DocumentId -> Heads.
225
303
  */
226
- documents: Record<string, string[]>;
304
+ documents: Record<DocumentId, Heads>;
227
305
  };
228
306
 
229
307
  export type CollectionStateDiff = {
@@ -233,18 +311,21 @@ export type CollectionStateDiff = {
233
311
  };
234
312
 
235
313
  export const diffCollectionState = (local: CollectionState, remote: CollectionState): CollectionStateDiff => {
236
- const allDocuments = new Set<DocumentId>([...Object.keys(local.documents), ...Object.keys(remote.documents)] as any);
314
+ const localDocuments = Record.filter(local.documents, (heads) => heads.length > 0);
315
+ const remoteDocuments = Record.filter(remote.documents, (heads) => heads.length > 0);
316
+ // NOTE: Using `Array.union` is slow.
317
+ const allDocuments = [...new Set([...Record.keys(localDocuments), ...Record.keys(remoteDocuments)])] as DocumentId[];
237
318
 
238
319
  const missingOnRemote: DocumentId[] = [];
239
320
  const missingOnLocal: DocumentId[] = [];
240
321
  const different: DocumentId[] = [];
241
322
  for (const documentId of allDocuments) {
242
- if (!local.documents[documentId]) {
243
- missingOnLocal.push(documentId as DocumentId);
244
- } else if (!remote.documents[documentId]) {
245
- missingOnRemote.push(documentId as DocumentId);
246
- } else if (!am.equals(local.documents[documentId], remote.documents[documentId])) {
247
- different.push(documentId as DocumentId);
323
+ if (!localDocuments[documentId]) {
324
+ missingOnLocal.push(documentId);
325
+ } else if (!remoteDocuments[documentId]) {
326
+ missingOnRemote.push(documentId);
327
+ } else if (!A.equals(local.documents[documentId], remote.documents[documentId])) {
328
+ different.push(documentId);
248
329
  }
249
330
  }
250
331
 
@@ -270,6 +351,8 @@ const isValidDocumentId = (documentId: DocumentId) => {
270
351
  return typeof documentId === 'string' && !documentId.includes(':');
271
352
  };
272
353
 
273
- const getSpanName = (peerId: PeerId) => {
354
+ const SYNC_SPAN_METHOD = 'syncPeer';
355
+
356
+ const getSpanId = (peerId: PeerId) => {
274
357
  return `collection-sync-${peerId}`;
275
358
  };
@@ -5,7 +5,7 @@
5
5
  import { type Message } from '@automerge/automerge-repo';
6
6
 
7
7
  import { type TimeAware, trace } from '@dxos/tracing';
8
- import { CircularBuffer, mapValues, SlidingWindowSummary, type SlidingWindowSummaryConfig } from '@dxos/util';
8
+ import { CircularBuffer, SlidingWindowSummary, type SlidingWindowSummaryConfig, mapValues } from '@dxos/util';
9
9
 
10
10
  import { type NetworkDataMonitor } from './echo-network-adapter';
11
11
  import { type StorageAdapterDataMonitor } from './leveldb-storage-adapter';
@@ -2,10 +2,11 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { cbor, type PeerId } from '@automerge/automerge-repo';
6
- import { onTestFinished, describe, expect, test } from 'vitest';
5
+ import { type PeerId, cbor } from '@automerge/automerge-repo';
6
+ import { describe, expect, onTestFinished, test } from 'vitest';
7
7
 
8
- import { sleep, Trigger, waitForCondition } from '@dxos/async';
8
+ import { Trigger, sleep, waitForCondition } from '@dxos/async';
9
+ import { Context } from '@dxos/context';
9
10
  import { invariant } from '@dxos/invariant';
10
11
  import { PublicKey } from '@dxos/keys';
11
12
  import { type SyncMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/automerge';
@@ -113,7 +114,7 @@ describe('EchoNetworkAdapter', () => {
113
114
  onTestFinished(async () => {
114
115
  await adapter.close();
115
116
  });
116
- await adapter.addReplicator(replicator);
117
+ await adapter.addReplicator(Context.default(), replicator);
117
118
  return adapter;
118
119
  };
119
120
 
@@ -2,30 +2,37 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { NetworkAdapter, type Message, type PeerId, type PeerMetadata } from '@automerge/automerge-repo';
6
-
7
- import { synchronized, Trigger } from '@dxos/async';
8
- import { LifecycleState } from '@dxos/context';
5
+ import {
6
+ type DocumentId,
7
+ type Heads,
8
+ type Message,
9
+ NetworkAdapter,
10
+ type PeerId,
11
+ type PeerMetadata,
12
+ } from '@automerge/automerge-repo';
13
+
14
+ import { Event, Trigger, synchronized } from '@dxos/async';
15
+ import { type Context, LifecycleState } from '@dxos/context';
9
16
  import { invariant } from '@dxos/invariant';
10
17
  import { type PublicKey } from '@dxos/keys';
11
18
  import { log } from '@dxos/log';
12
19
  import type { AutomergeProtocolMessage } from '@dxos/protocols';
13
20
  import { isNonNullable } from '@dxos/util';
14
21
 
22
+ import { createIdFromSpaceKey } from '../common/space-id';
15
23
  import {
16
- type EchoReplicator,
17
- type RemoteDocumentExistenceCheckParams,
18
- type ReplicatorConnection,
19
- type ShouldAdvertiseParams,
20
- type ShouldSyncCollectionParams,
24
+ type AutomergeReplicator,
25
+ type AutomergeReplicatorConnection,
26
+ type RemoteDocumentExistenceCheckProps,
27
+ type ShouldAdvertiseProps,
28
+ type ShouldSyncCollectionProps,
21
29
  } from './echo-replicator';
22
30
  import {
23
- isCollectionQueryMessage,
24
- isCollectionStateMessage,
25
31
  type CollectionQueryMessage,
26
32
  type CollectionStateMessage,
33
+ isCollectionQueryMessage,
34
+ isCollectionStateMessage,
27
35
  } from './network-protocol';
28
- import { createIdFromSpaceKey } from '../common/space-id';
29
36
 
30
37
  export interface NetworkDataMonitor {
31
38
  recordPeerConnected(peerId: string): void;
@@ -35,9 +42,9 @@ export interface NetworkDataMonitor {
35
42
  recordMessageSendingFailed(message: Message): void;
36
43
  }
37
44
 
38
- export type EchoNetworkAdapterParams = {
45
+ export type EchoNetworkAdapterProps = {
39
46
  getContainingSpaceForDocument: (documentId: string) => Promise<PublicKey | null>;
40
- isDocumentInRemoteCollection: (params: RemoteDocumentExistenceCheckParams) => Promise<boolean>;
47
+ isDocumentInRemoteCollection: (params: RemoteDocumentExistenceCheckProps) => Promise<boolean>;
41
48
  onCollectionStateQueried: (collectionId: string, peerId: PeerId) => void;
42
49
  onCollectionStateReceived: (collectionId: string, peerId: PeerId, state: unknown) => void;
43
50
  monitor?: NetworkDataMonitor;
@@ -45,16 +52,17 @@ export type EchoNetworkAdapterParams = {
45
52
 
46
53
  type ConnectionEntry = {
47
54
  isOpen: boolean;
48
- connection: ReplicatorConnection;
55
+ connection: AutomergeReplicatorConnection;
49
56
  reader: ReadableStreamDefaultReader<AutomergeProtocolMessage>;
50
57
  writer: WritableStreamDefaultWriter<AutomergeProtocolMessage>;
58
+ requestedDocuments: Set<DocumentId>;
51
59
  };
52
60
 
53
61
  /**
54
- * Manages a set of {@link EchoReplicator} instances.
62
+ * Manages a set of {@link AutomergeReplicator} instances.
55
63
  */
56
64
  export class EchoNetworkAdapter extends NetworkAdapter {
57
- private readonly _replicators = new Set<EchoReplicator>();
65
+ private readonly _replicators = new Set<AutomergeReplicator>();
58
66
  /**
59
67
  * Remote peer id -> connection.
60
68
  */
@@ -63,7 +71,9 @@ export class EchoNetworkAdapter extends NetworkAdapter {
63
71
  private readonly _connected = new Trigger();
64
72
  private readonly _ready = new Trigger();
65
73
 
66
- constructor(private readonly _params: EchoNetworkAdapterParams) {
74
+ public readonly documentRequested = new Event<{ documentId: DocumentId; peerId: PeerId }>();
75
+
76
+ constructor(private readonly _params: EchoNetworkAdapterProps) {
67
77
  super();
68
78
  }
69
79
 
@@ -125,13 +135,13 @@ export class EchoNetworkAdapter extends NetworkAdapter {
125
135
  }
126
136
 
127
137
  @synchronized
128
- async addReplicator(replicator: EchoReplicator): Promise<void> {
138
+ async addReplicator(ctx: Context, replicator: AutomergeReplicator): Promise<void> {
129
139
  invariant(this._lifecycleState === LifecycleState.OPEN);
130
140
  invariant(this.peerId);
131
141
  invariant(!this._replicators.has(replicator));
132
142
 
133
143
  this._replicators.add(replicator);
134
- await replicator.connect({
144
+ await replicator.connect(ctx, {
135
145
  peerId: this.peerId,
136
146
  onConnectionOpen: this._onConnectionOpen.bind(this),
137
147
  onConnectionClosed: this._onConnectionClosed.bind(this),
@@ -146,14 +156,14 @@ export class EchoNetworkAdapter extends NetworkAdapter {
146
156
  }
147
157
 
148
158
  @synchronized
149
- async removeReplicator(replicator: EchoReplicator): Promise<void> {
159
+ async removeReplicator(replicator: AutomergeReplicator): Promise<void> {
150
160
  invariant(this._lifecycleState === LifecycleState.OPEN);
151
161
  invariant(this._replicators.has(replicator));
152
162
  await replicator.disconnect();
153
163
  this._replicators.delete(replicator);
154
164
  }
155
165
 
156
- async shouldAdvertise(peerId: PeerId, params: ShouldAdvertiseParams): Promise<boolean> {
166
+ async shouldAdvertise(peerId: PeerId, params: ShouldAdvertiseProps): Promise<boolean> {
157
167
  const connection = this._connections.get(peerId);
158
168
  if (!connection) {
159
169
  return false;
@@ -162,7 +172,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
162
172
  return connection.connection.shouldAdvertise(params);
163
173
  }
164
174
 
165
- shouldSyncCollection(peerId: PeerId, params: ShouldSyncCollectionParams): boolean {
175
+ shouldSyncCollection(peerId: PeerId, params: ShouldSyncCollectionProps): boolean {
166
176
  const connection = this._connections.get(peerId);
167
177
  if (!connection) {
168
178
  return false;
@@ -203,6 +213,30 @@ export class EchoNetworkAdapter extends NetworkAdapter {
203
213
  .filter(isNonNullable);
204
214
  }
205
215
 
216
+ bundleSyncEnabledForPeer(peerId: PeerId): boolean {
217
+ const connection = this._connections.get(peerId);
218
+ if (!connection) {
219
+ return false;
220
+ }
221
+ return connection.connection.bundleSyncEnabled;
222
+ }
223
+
224
+ async pushBundle(ctx: Context, peerId: PeerId, bundle: { documentId: DocumentId; data: Uint8Array; heads: Heads }[]) {
225
+ const connection = this._connections.get(peerId);
226
+ if (!connection) {
227
+ throw new Error('Connection not found.');
228
+ }
229
+ return connection.connection.pushBundle!(ctx, bundle);
230
+ }
231
+
232
+ async pullBundle(ctx: Context, peerId: PeerId, docHeads: Record<DocumentId, Heads>) {
233
+ const connection = this._connections.get(peerId);
234
+ if (!connection) {
235
+ throw new Error('Connection not found.');
236
+ }
237
+ return connection.connection.pullBundle!(ctx, docHeads);
238
+ }
239
+
206
240
  private _send(message: Message): void {
207
241
  const connectionEntry = this._connections.get(message.targetId);
208
242
  if (!connectionEntry) {
@@ -225,7 +259,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
225
259
  });
226
260
  }
227
261
 
228
- private _onConnectionOpen(connection: ReplicatorConnection): void {
262
+ private _onConnectionOpen(connection: AutomergeReplicatorConnection): void {
229
263
  log('connection opened', { peerId: connection.peerId });
230
264
  invariant(!this._connections.has(connection.peerId as PeerId));
231
265
  const connectionEntry: ConnectionEntry = {
@@ -233,6 +267,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
233
267
  connection,
234
268
  reader: connection.readable.getReader(),
235
269
  writer: connection.writable.getWriter(),
270
+ requestedDocuments: new Set(),
236
271
  };
237
272
 
238
273
  this._connections.set(connection.peerId as PeerId, connectionEntry);
@@ -247,7 +282,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
247
282
  break;
248
283
  }
249
284
 
250
- this._onMessage(value as Message);
285
+ this._onMessage(connectionEntry, value as Message);
251
286
  }
252
287
  } catch (err) {
253
288
  if (connectionEntry.isOpen) {
@@ -261,7 +296,15 @@ export class EchoNetworkAdapter extends NetworkAdapter {
261
296
  this._params.monitor?.recordPeerConnected(connection.peerId);
262
297
  }
263
298
 
264
- private _onMessage(message: Message): void {
299
+ private _onMessage(connectionEntry: ConnectionEntry, message: Message): void {
300
+ const amMessage = message as AutomergeProtocolMessage;
301
+ if (amMessage.type === 'request') {
302
+ this.documentRequested.emit({
303
+ documentId: amMessage.documentId as DocumentId,
304
+ peerId: connectionEntry.connection.peerId as PeerId,
305
+ });
306
+ }
307
+
265
308
  if (isCollectionQueryMessage(message)) {
266
309
  this._params.onCollectionStateQueried(message.collectionId, message.senderId);
267
310
  } else if (isCollectionStateMessage(message)) {
@@ -272,7 +315,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
272
315
  this._params.monitor?.recordMessageReceived(message);
273
316
  }
274
317
 
275
- private _onConnectionClosed(connection: ReplicatorConnection): void {
318
+ private _onConnectionClosed(connection: AutomergeReplicatorConnection): void {
276
319
  log('connection closed', { peerId: connection.peerId });
277
320
  const entry = this._connections.get(connection.peerId as PeerId);
278
321
  invariant(entry);
@@ -281,8 +324,9 @@ export class EchoNetworkAdapter extends NetworkAdapter {
281
324
  this.emit('peer-disconnected', { peerId: connection.peerId as PeerId });
282
325
  this._params.monitor?.recordPeerDisconnected(connection.peerId);
283
326
 
284
- void entry.reader.cancel().catch((err) => log.catch(err));
285
327
  void entry.writer.abort().catch((err) => log.catch(err));
328
+ void entry.reader.cancel().catch((err) => log.catch(err));
329
+
286
330
  this._connections.delete(connection.peerId as PeerId);
287
331
  }
288
332
 
@@ -290,7 +334,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
290
334
  * Trigger doc-synchronizer shared documents set recalculation. Happens on peer-candidate.
291
335
  * TODO(y): replace with a proper API call when sharePolicy update becomes supported by automerge-repo
292
336
  */
293
- private _onConnectionAuthScopeChanged(connection: ReplicatorConnection): void {
337
+ private _onConnectionAuthScopeChanged(connection: AutomergeReplicatorConnection): void {
294
338
  log('Connection auth scope changed', { peerId: connection.peerId });
295
339
  const entry = this._connections.get(connection.peerId as PeerId);
296
340
  invariant(entry);
@@ -298,7 +342,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
298
342
  this._emitPeerCandidate(connection);
299
343
  }
300
344
 
301
- private _emitPeerCandidate(connection: ReplicatorConnection): void {
345
+ private _emitPeerCandidate(connection: AutomergeReplicatorConnection): void {
302
346
  this.emit('peer-candidate', {
303
347
  peerId: connection.peerId as PeerId,
304
348
  peerMetadata: createEchoPeerMetadata(),
@@ -2,14 +2,19 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { type Heads } from '@automerge/automerge';
6
+ import { type DocumentId } from '@automerge/automerge-repo';
7
+
8
+ import { type Context } from '@dxos/context';
5
9
  import { type PublicKey, type SpaceId } from '@dxos/keys';
6
- import type { AutomergeProtocolMessage } from '@dxos/protocols';
10
+ import { type AutomergeProtocolMessage } from '@dxos/protocols';
7
11
 
8
- export interface EchoReplicator {
12
+ // TODO(burdon): Rename AutomergeReplicator?
13
+ export interface AutomergeReplicator {
9
14
  /**
10
15
  * Called on when replicator is added to EchoHost.
11
16
  */
12
- connect(context: EchoReplicatorContext): Promise<void>;
17
+ connect(ctx: Context, context: AutomergeReplicatorContext): Promise<void>;
13
18
 
14
19
  /**
15
20
  * Called on when replicator is removed from EchoHost.
@@ -17,7 +22,7 @@ export interface EchoReplicator {
17
22
  disconnect(): Promise<void>;
18
23
  }
19
24
 
20
- export interface EchoReplicatorContext {
25
+ export interface AutomergeReplicatorContext {
21
26
  /**
22
27
  * Our own peer id.
23
28
  */
@@ -28,16 +33,20 @@ export interface EchoReplicatorContext {
28
33
  */
29
34
  getContainingSpaceForDocument(documentId: string): Promise<PublicKey | null>;
30
35
  getContainingSpaceIdForDocument(documentId: string): Promise<SpaceId | null>;
31
- isDocumentInRemoteCollection(params: RemoteDocumentExistenceCheckParams): Promise<boolean>;
32
36
 
33
- onConnectionOpen(connection: ReplicatorConnection): void;
34
- onConnectionClosed(connection: ReplicatorConnection): void;
35
- onConnectionAuthScopeChanged(connection: ReplicatorConnection): void;
37
+ /**
38
+ * Returns false if collection sync hasn't happened yet.
39
+ */
40
+ isDocumentInRemoteCollection(params: RemoteDocumentExistenceCheckProps): Promise<boolean>;
41
+
42
+ onConnectionOpen(connection: AutomergeReplicatorConnection): void;
43
+ onConnectionClosed(connection: AutomergeReplicatorConnection): void;
44
+ onConnectionAuthScopeChanged(connection: AutomergeReplicatorConnection): void;
36
45
  }
37
46
 
38
- export interface ReplicatorConnection {
47
+ export interface AutomergeReplicatorConnection {
39
48
  /**
40
- * Remove peer id.
49
+ * Remote peer id.
41
50
  */
42
51
  get peerId(): string;
43
52
 
@@ -55,23 +64,40 @@ export interface ReplicatorConnection {
55
64
  * @returns true if the document should be advertised to this peer.
56
65
  * The remote peer can still request the document by its id bypassing this check.
57
66
  */
58
- shouldAdvertise(params: ShouldAdvertiseParams): Promise<boolean>;
67
+ shouldAdvertise(params: ShouldAdvertiseProps): Promise<boolean>;
59
68
 
60
69
  /**
61
70
  * @returns true if the collection should be synced to this peer.
62
71
  */
63
- shouldSyncCollection(params: ShouldSyncCollectionParams): boolean;
72
+ shouldSyncCollection(params: ShouldSyncCollectionProps): boolean;
73
+
74
+ /**
75
+ * Batch syncing considered enabled if AutomergeReplicatorConnection implements `pushBatch` and `pullBatch` methods.
76
+ * @returns true if the batch syncing is enabled.
77
+ */
78
+ get bundleSyncEnabled(): boolean;
79
+
80
+ /**
81
+ * Pushes the batch of documents to the remote peer.
82
+ */
83
+ pushBundle?(ctx: Context, bundle: { documentId: DocumentId; data: Uint8Array; heads: Heads }[]): Promise<void>;
84
+
85
+ /**
86
+ * Pulls the batch of documents from the remote peer.
87
+ */
88
+ // TODO(mykola): Use automerge-repo-bundles Bundle type here.
89
+ pullBundle?(ctx: Context, docHeads: Record<DocumentId, Heads>): Promise<Record<DocumentId, Uint8Array>>;
64
90
  }
65
91
 
66
- export type ShouldAdvertiseParams = {
92
+ export type ShouldAdvertiseProps = {
67
93
  documentId: string;
68
94
  };
69
95
 
70
- export type ShouldSyncCollectionParams = {
96
+ export type ShouldSyncCollectionProps = {
71
97
  collectionId: string;
72
98
  };
73
99
 
74
- export type RemoteDocumentExistenceCheckParams = {
100
+ export type RemoteDocumentExistenceCheckProps = {
75
101
  peerId: string;
76
102
  documentId: string;
77
103
  };