@dxos/echo-pipeline 0.8.3 → 0.8.4-main.1c7ec43d41

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
@@ -0,0 +1,62 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type DocumentId, type QueryState, type Repo } from '@automerge/automerge-repo';
6
+
7
+ /**
8
+ * Variants reported by {@link QueryState}: `'loading' | 'ready' | 'unavailable' | 'failed'`.
9
+ */
10
+ export type HandleQueryState = QueryState<unknown>['state'];
11
+
12
+ /**
13
+ * Returns the {@link QueryState} variant for a document already known to the
14
+ * repo (i.e. one for which a handle has been registered via `Repo.find` /
15
+ * `Repo.findWithProgress` / `Repo.create`), or `undefined` if no handle/query
16
+ * exists for this document yet.
17
+ *
18
+ * Use this anywhere you need to ask "is this document loaded?" / "did sync
19
+ * give up?" — read the result against the {@link HandleQueryState} variants
20
+ * (e.g. `=== 'ready'`).
21
+ *
22
+ * # Why we can't just call `DocHandle.isReady()` / read `DocHandle.state`
23
+ *
24
+ * The current subduction fork of `@automerge/automerge-repo`
25
+ * (`2.6.0-subduction.10`) **gutted** the per-handle state machine. Inside the
26
+ * fork, every `DocHandle` state predicate is a constant placeholder:
27
+ *
28
+ * ```
29
+ * isReady = () => true
30
+ * isUnloaded = () => false
31
+ * isDeleted = () => false
32
+ * isUnavailable = () => false
33
+ * inState = (states) => states.includes('ready')
34
+ * get state = () => 'ready'
35
+ * whenReady = async () => {} // resolves immediately, ignores argument
36
+ * ```
37
+ *
38
+ * That means `handle.isReady()` always lies "yes", `handle.state` always reads
39
+ * `'ready'`, and `await handle.whenReady()` never blocks — even for handles
40
+ * whose document has never been loaded from storage or synced from a peer.
41
+ * Reading `handle.doc()` on such a "ready" handle returns the empty initial
42
+ * doc (`A.init()` or `A.emptyChange(A.init())`) rather than throwing or
43
+ * returning undefined, which can silently corrupt downstream logic that
44
+ * trusted the handle's `isReady()` signal.
45
+ *
46
+ * Real liveness moved to the {@link DocumentQuery} returned by
47
+ * `Repo.findWithProgress`, whose state goes through
48
+ * `'loading' → 'ready' | 'unavailable' | 'failed'`. Production code that
49
+ * needs to know whether a document is actually loaded must consult the query
50
+ * state through this helper instead of any `DocHandle.*` predicate.
51
+ *
52
+ * TODO(mykola): Remove this helper once the fork either (a) restores the
53
+ * `DocHandle` state machine, or (b) every caller is migrated to query
54
+ * `DocumentProgress` directly with a richer API surface (await on state
55
+ * transitions, etc.).
56
+ */
57
+ export const getHandleState = (repo: Repo, documentId: DocumentId): HandleQueryState | undefined => {
58
+ if (!repo.handles[documentId]) {
59
+ return undefined;
60
+ }
61
+ return repo.findWithProgress(documentId).peek().state;
62
+ };
@@ -4,18 +4,45 @@
4
4
 
5
5
  import type { Heads } from '@automerge/automerge';
6
6
  import type { DocumentId } from '@automerge/automerge-repo';
7
+ import { type MixedEncoding } from 'level-transcoder';
7
8
 
8
- import { headsEncoding } from '@dxos/indexing';
9
+ import type { ProtoCodec } from '@dxos/codec-protobuf';
9
10
  import type { BatchLevel, SublevelDB } from '@dxos/kv-store';
11
+ import { log } from '@dxos/log';
12
+ import { schema } from '@dxos/protocols/proto';
13
+ import type { Heads as HeadsProto } from '@dxos/protocols/proto/dxos/echo/query';
10
14
 
11
- export type HeadsStoreParams = {
15
+ // NOTE: Lazy so that code that doesn't use indexing doesn't need to load the codec (breaks in workerd).
16
+ let headsCodec: ProtoCodec<HeadsProto>;
17
+ const getHeadsCodec = () => (headsCodec ??= schema.getCodecForType('dxos.echo.query.Heads'));
18
+
19
+ const headsEncoding: MixedEncoding<Heads, Uint8Array, Heads> = {
20
+ encode: (value: Heads): Uint8Array => getHeadsCodec().encode({ hashes: value }),
21
+ decode: (encodedValue: Uint8Array): Heads => {
22
+ try {
23
+ return getHeadsCodec().decode(encodedValue).hashes!;
24
+ } catch {
25
+ // Legacy encoding migration path.
26
+ log.warn('Detected legacy encoding of heads in storage.');
27
+ const concatenatedHeads = Buffer.from(encodedValue).toString('utf8').replace(/"/g, '');
28
+ const heads = [];
29
+ for (let i = 0; i < concatenatedHeads.length; i += 64) {
30
+ heads.push(concatenatedHeads.slice(i, i + 64));
31
+ }
32
+ return heads;
33
+ }
34
+ },
35
+ format: 'buffer',
36
+ };
37
+
38
+ export type HeadsStoreProps = {
12
39
  db: SublevelDB;
13
40
  };
14
41
 
15
42
  export class HeadsStore {
16
43
  private readonly _db: SublevelDB;
17
44
 
18
- constructor({ db }: HeadsStoreParams) {
45
+ constructor({ db }: HeadsStoreProps) {
19
46
  this._db = db;
20
47
  }
21
48
 
@@ -34,4 +61,16 @@ export class HeadsStore {
34
61
  valueEncoding: headsEncoding,
35
62
  });
36
63
  }
64
+
65
+ /**
66
+ * Iterate over all document IDs and their heads.
67
+ */
68
+ async *iterateAll(): AsyncGenerator<{ documentId: DocumentId; heads: Heads }> {
69
+ for await (const [documentId, heads] of this._db.iterator<DocumentId, Heads>({
70
+ keyEncoding: 'utf8',
71
+ valueEncoding: headsEncoding,
72
+ })) {
73
+ yield { documentId, heads };
74
+ }
75
+ }
37
76
  }
@@ -3,9 +3,10 @@
3
3
  //
4
4
 
5
5
  export * from './automerge-host';
6
+ export * from './handle-state';
6
7
  export * from './leveldb-storage-adapter';
7
8
  export * from './mesh-echo-replicator';
8
- export * from './echo-replicator';
9
+ export type * from './echo-replicator';
9
10
  export { diffCollectionState } from './collection-synchronizer';
10
11
  export * from './space-collection';
11
12
  export * from './echo-data-monitor';
@@ -2,10 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  // s
4
4
 
5
- import { type StorageAdapterInterface, type Chunk, type StorageKey } from '@automerge/automerge-repo';
5
+ import { type Chunk, type StorageAdapterInterface, type StorageKey } from '@automerge/automerge-repo';
6
6
  import { type MixedEncoding } from 'level-transcoder';
7
7
 
8
- import { LifecycleState, Resource } from '@dxos/context';
8
+ import { Resource } from '@dxos/context';
9
9
  import { type BatchLevel, type SublevelDB } from '@dxos/kv-store';
10
10
  import { type MaybePromise } from '@dxos/util';
11
11
 
@@ -16,27 +16,27 @@ export interface StorageAdapterDataMonitor {
16
16
  recordStoreDuration(durationMs: number): void;
17
17
  }
18
18
 
19
- export type LevelDBStorageAdapterParams = {
19
+ export type LevelDBStorageAdapterProps = {
20
20
  db: SublevelDB;
21
21
  callbacks?: StorageCallbacks;
22
22
  monitor?: StorageAdapterDataMonitor;
23
23
  };
24
24
 
25
- export type BeforeSaveParams = { path: StorageKey; batch: BatchLevel };
25
+ export type BeforeSaveProps = { path: StorageKey; batch: BatchLevel };
26
26
 
27
27
  export interface StorageCallbacks {
28
- beforeSave(params: BeforeSaveParams): MaybePromise<void>;
28
+ beforeSave(params: BeforeSaveProps): MaybePromise<void>;
29
29
  afterSave(path: StorageKey): MaybePromise<void>;
30
30
  }
31
31
 
32
32
  export class LevelDBStorageAdapter extends Resource implements StorageAdapterInterface {
33
- constructor(private readonly _params: LevelDBStorageAdapterParams) {
33
+ constructor(private readonly _params: LevelDBStorageAdapterProps) {
34
34
  super();
35
35
  }
36
36
 
37
37
  async load(keyArray: StorageKey): Promise<Uint8Array | undefined> {
38
38
  try {
39
- if (this._lifecycleState !== LifecycleState.OPEN) {
39
+ if (!this.isOpen) {
40
40
  // TODO(mykola): this should be an error.
41
41
  return undefined;
42
42
  }
@@ -54,7 +54,7 @@ export class LevelDBStorageAdapter extends Resource implements StorageAdapterInt
54
54
  }
55
55
 
56
56
  async save(keyArray: StorageKey, binary: Uint8Array): Promise<void> {
57
- if (this._lifecycleState !== LifecycleState.OPEN) {
57
+ if (!this.isOpen) {
58
58
  return undefined;
59
59
  }
60
60
  const startMs = Date.now();
@@ -71,15 +71,44 @@ export class LevelDBStorageAdapter extends Resource implements StorageAdapterInt
71
71
  this._params.monitor?.recordStoreDuration(Date.now() - startMs);
72
72
  }
73
73
 
74
+ /**
75
+ * Atomically persist multiple key/value entries in a single LevelDB batch write.
76
+ */
77
+ async saveBatch(entries: Array<[StorageKey, Uint8Array]>): Promise<void> {
78
+ if (!this.isOpen || entries.length === 0) {
79
+ return undefined;
80
+ }
81
+ const startMs = Date.now();
82
+ const batch = this._params.db.batch();
83
+
84
+ for (const [keyArray] of entries) {
85
+ await this._params.callbacks?.beforeSave?.({ path: keyArray, batch });
86
+ }
87
+ for (const [keyArray, binary] of entries) {
88
+ batch.put<StorageKey, Uint8Array>(keyArray, Buffer.from(binary), {
89
+ ...encodingOptions,
90
+ });
91
+ }
92
+ await batch.write();
93
+
94
+ let bytesStored = 0;
95
+ for (const [keyArray, binary] of entries) {
96
+ bytesStored += binary.byteLength;
97
+ await this._params.callbacks?.afterSave?.(keyArray);
98
+ }
99
+ this._params.monitor?.recordBytesStored(bytesStored);
100
+ this._params.monitor?.recordStoreDuration(Date.now() - startMs);
101
+ }
102
+
74
103
  async remove(keyArray: StorageKey): Promise<void> {
75
- if (this._lifecycleState !== LifecycleState.OPEN) {
104
+ if (!this.isOpen) {
76
105
  return undefined;
77
106
  }
78
107
  await this._params.db.del<StorageKey>(keyArray, { ...encodingOptions });
79
108
  }
80
109
 
81
110
  async loadRange(keyPrefix: StorageKey): Promise<Chunk[]> {
82
- if (this._lifecycleState !== LifecycleState.OPEN) {
111
+ if (!this.isOpen) {
83
112
  return [];
84
113
  }
85
114
  const startMs = Date.now();
@@ -100,7 +129,7 @@ export class LevelDBStorageAdapter extends Resource implements StorageAdapterInt
100
129
  }
101
130
 
102
131
  async removeRange(keyPrefix: StorageKey): Promise<void> {
103
- if (this._lifecycleState !== LifecycleState.OPEN) {
132
+ if (!this.isOpen) {
104
133
  return undefined;
105
134
  }
106
135
  const batch = this._params.db.batch();
@@ -12,20 +12,20 @@ import { log } from '@dxos/log';
12
12
  import type { AutomergeProtocolMessage } from '@dxos/protocols';
13
13
  import { AutomergeReplicator, type AutomergeReplicatorFactory } from '@dxos/teleport-extension-automerge-replicator';
14
14
 
15
- import type { ReplicatorConnection, ShouldAdvertiseParams, ShouldSyncCollectionParams } from './echo-replicator';
15
+ import type { AutomergeReplicatorConnection, ShouldAdvertiseProps, ShouldSyncCollectionProps } from './echo-replicator';
16
16
 
17
17
  const DEFAULT_FACTORY: AutomergeReplicatorFactory = (params) => new AutomergeReplicator(...params);
18
18
 
19
- export type MeshReplicatorConnectionParams = {
19
+ export type MeshReplicatorConnectionProps = {
20
20
  ownPeerId: string;
21
21
  onRemoteConnected: () => void;
22
22
  onRemoteDisconnected: () => void;
23
- shouldAdvertise: (params: ShouldAdvertiseParams) => Promise<boolean>;
24
- shouldSyncCollection: (params: ShouldSyncCollectionParams) => boolean;
23
+ shouldAdvertise: (params: ShouldAdvertiseProps) => Promise<boolean>;
24
+ shouldSyncCollection: (params: ShouldSyncCollectionProps) => boolean;
25
25
  replicatorFactory?: AutomergeReplicatorFactory;
26
26
  };
27
27
 
28
- export class MeshReplicatorConnection extends Resource implements ReplicatorConnection {
28
+ export class MeshReplicatorConnection extends Resource implements AutomergeReplicatorConnection {
29
29
  public readable: ReadableStream<AutomergeProtocolMessage>;
30
30
  public writable: WritableStream<AutomergeProtocolMessage>;
31
31
  public remoteDeviceKey: PublicKey | null = null;
@@ -35,7 +35,7 @@ export class MeshReplicatorConnection extends Resource implements ReplicatorConn
35
35
  private _remotePeerId: string | null = null;
36
36
  private _isEnabled = false;
37
37
 
38
- constructor(private readonly _params: MeshReplicatorConnectionParams) {
38
+ constructor(private readonly _params: MeshReplicatorConnectionProps) {
39
39
  super();
40
40
 
41
41
  let readableStreamController!: ReadableStreamDefaultController<AutomergeProtocolMessage>;
@@ -111,11 +111,15 @@ export class MeshReplicatorConnection extends Resource implements ReplicatorConn
111
111
  return this._isEnabled;
112
112
  }
113
113
 
114
- async shouldAdvertise(params: ShouldAdvertiseParams): Promise<boolean> {
114
+ get bundleSyncEnabled(): boolean {
115
+ return false;
116
+ }
117
+
118
+ async shouldAdvertise(params: ShouldAdvertiseProps): Promise<boolean> {
115
119
  return this._params.shouldAdvertise(params);
116
120
  }
117
121
 
118
- shouldSyncCollection(params: ShouldSyncCollectionParams): boolean {
122
+ shouldSyncCollection(params: ShouldSyncCollectionProps): boolean {
119
123
  return this._params.shouldSyncCollection(params);
120
124
  }
121
125
 
@@ -2,27 +2,29 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { type Context } from '@dxos/context';
5
6
  import type { CollectionId } from '@dxos/echo-protocol';
6
7
  import { invariant } from '@dxos/invariant';
7
8
  import { PublicKey, type SpaceId } from '@dxos/keys';
8
9
  import { log } from '@dxos/log';
9
- import {
10
- type AutomergeReplicator,
11
- type AutomergeReplicatorFactory,
12
- } from '@dxos/teleport-extension-automerge-replicator';
10
+ import type * as TeleportAutomergeReplicator from '@dxos/teleport-extension-automerge-replicator';
13
11
  import { ComplexSet, defaultMap } from '@dxos/util';
14
12
 
15
- import { type EchoReplicator, type EchoReplicatorContext, type ShouldAdvertiseParams } from './echo-replicator';
13
+ import { createIdFromSpaceKey } from '../common/space-id';
14
+ import {
15
+ type AutomergeReplicator,
16
+ type AutomergeReplicatorContext,
17
+ type ShouldAdvertiseProps,
18
+ } from './echo-replicator';
16
19
  import { MeshReplicatorConnection } from './mesh-echo-replicator-connection';
17
20
  import { getSpaceIdFromCollectionId } from './space-collection';
18
- import { createIdFromSpaceKey } from '../common/space-id';
19
21
 
20
22
  // TODO(dmaretskyi): Move out of @dxos/echo-pipeline.
21
23
 
22
24
  /**
23
25
  * Used to replicate with other peers over the network.
24
26
  */
25
- export class MeshEchoReplicator implements EchoReplicator {
27
+ export class MeshEchoReplicator implements AutomergeReplicator {
26
28
  /**
27
29
  * We might have multiple connections open with a peer (one per space), but there'll be only one enabled
28
30
  * connection at any given moment, because there's a single repo for all the spaces.
@@ -40,9 +42,9 @@ export class MeshEchoReplicator implements EchoReplicator {
40
42
  */
41
43
  private readonly _authorizedDevices = new Map<SpaceId, ComplexSet<PublicKey>>();
42
44
 
43
- private _context: EchoReplicatorContext | null = null;
45
+ private _context: AutomergeReplicatorContext | null = null;
44
46
 
45
- async connect(context: EchoReplicatorContext): Promise<void> {
47
+ async connect(_ctx: Context, context: AutomergeReplicatorContext): Promise<void> {
46
48
  this._context = context;
47
49
  }
48
50
 
@@ -63,7 +65,9 @@ export class MeshEchoReplicator implements EchoReplicator {
63
65
  this._context = null;
64
66
  }
65
67
 
66
- createExtension(extensionFactory?: AutomergeReplicatorFactory): AutomergeReplicator {
68
+ createExtension(
69
+ extensionFactory?: TeleportAutomergeReplicator.AutomergeReplicatorFactory,
70
+ ): TeleportAutomergeReplicator.AutomergeReplicator {
67
71
  invariant(this._context);
68
72
 
69
73
  const connection: MeshReplicatorConnection = new MeshReplicatorConnection({
@@ -110,7 +114,7 @@ export class MeshEchoReplicator implements EchoReplicator {
110
114
  }
111
115
  }
112
116
  },
113
- shouldAdvertise: async (params: ShouldAdvertiseParams) => {
117
+ shouldAdvertise: async (params: ShouldAdvertiseProps) => {
114
118
  log('shouldAdvertise', { peerId: connection.peerId, documentId: params.documentId });
115
119
  invariant(this._context);
116
120
  try {
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { onTestFinished, describe, expect, test } from 'vitest';
5
+ import { describe, expect, onTestFinished, test } from 'vitest';
6
6
 
7
7
  import { randomBytes } from '@dxos/crypto';
8
8
  import { PublicKey } from '@dxos/keys';
@@ -18,7 +18,7 @@ export const createIdFromSpaceKey = async (spaceKey: PublicKey): Promise<SpaceId
18
18
  return cachedValue;
19
19
  }
20
20
 
21
- const digest = await subtleCrypto.digest('SHA-256', spaceKey.asUint8Array());
21
+ const digest = await subtleCrypto.digest('SHA-256', spaceKey.asUint8Array() as Uint8Array<ArrayBuffer>);
22
22
 
23
23
  const bytes = new Uint8Array(digest).slice(0, SpaceId.byteLength);
24
24
  const spaceId = SpaceId.encode(bytes);