@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.
- package/dist/lib/{browser/chunk-35I6ERLG.mjs → neutral/chunk-VIHYDCCC.mjs} +313 -709
- package/dist/lib/neutral/chunk-VIHYDCCC.mjs.map +7 -0
- package/dist/lib/neutral/chunk-YBC6ONBZ.mjs +247 -0
- package/dist/lib/neutral/chunk-YBC6ONBZ.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/filter/index.mjs +3 -2
- package/dist/lib/neutral/index.mjs +7065 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/{browser → neutral}/testing/index.mjs +90 -311
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/automerge/automerge-host-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-host-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-host.d.ts +99 -54
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-repo.test.d.ts +1 -1
- package/dist/types/src/automerge/automerge-repo.test.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/collection-synchronizer.d.ts +25 -4
- package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-data-monitor.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +23 -10
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +38 -15
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/handle-state.d.ts +52 -0
- package/dist/types/src/automerge/handle-state.d.ts.map +1 -0
- package/dist/types/src/automerge/heads-store.d.ts +9 -2
- package/dist/types/src/automerge/heads-store.d.ts.map +1 -1
- package/dist/types/src/automerge/index.d.ts +2 -1
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +9 -5
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +9 -8
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts +6 -5
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/network-protocol.d.ts.map +1 -1
- package/dist/types/src/automerge/space-collection.d.ts.map +1 -1
- package/dist/types/src/common/codec.d.ts +1 -1
- package/dist/types/src/common/codec.d.ts.map +1 -1
- package/dist/types/src/common/feeds.d.ts.map +1 -1
- package/dist/types/src/common/space-id.d.ts.map +1 -1
- package/dist/types/src/db-host/automerge-data-source.d.ts +35 -0
- package/dist/types/src/db-host/automerge-data-source.d.ts.map +1 -0
- package/dist/types/src/db-host/automerge-data-source.test.d.ts +2 -0
- package/dist/types/src/db-host/automerge-data-source.test.d.ts.map +1 -0
- package/dist/types/src/db-host/automerge-metrics.d.ts.map +1 -1
- package/dist/types/src/db-host/data-service.d.ts +8 -7
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/dist/types/src/db-host/database-root.d.ts +9 -3
- package/dist/types/src/db-host/database-root.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +9 -8
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +62 -27
- package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
- package/dist/types/src/db-host/index.d.ts +2 -0
- package/dist/types/src/db-host/index.d.ts.map +1 -1
- package/dist/types/src/db-host/invalidation-hint.d.ts +19 -0
- package/dist/types/src/db-host/invalidation-hint.d.ts.map +1 -0
- package/dist/types/src/db-host/local-queue-service.d.ts +19 -0
- package/dist/types/src/db-host/local-queue-service.d.ts.map +1 -0
- package/dist/types/src/db-host/query-invalidation.test.d.ts +2 -0
- package/dist/types/src/db-host/query-invalidation.test.d.ts.map +1 -0
- package/dist/types/src/db-host/query-service.d.ts +19 -11
- package/dist/types/src/db-host/query-service.d.ts.map +1 -1
- package/dist/types/src/db-host/queue-data-source.d.ts +38 -0
- package/dist/types/src/db-host/queue-data-source.d.ts.map +1 -0
- package/dist/types/src/db-host/queue-service.test.d.ts +2 -0
- package/dist/types/src/db-host/queue-service.test.d.ts.map +1 -0
- package/dist/types/src/db-host/space-state-manager.d.ts +4 -3
- package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
- package/dist/types/src/db-host/stub.d.ts +11 -0
- package/dist/types/src/db-host/stub.d.ts.map +1 -0
- package/dist/types/src/edge/echo-edge-replicator.d.ts +10 -7
- package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
- package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts +39 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts.map +1 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts +2 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts.map +1 -0
- package/dist/types/src/edge/index.d.ts +1 -0
- package/dist/types/src/edge/index.d.ts.map +1 -1
- package/dist/types/src/edge/inflight-request-limiter.d.ts.map +1 -1
- package/dist/types/src/filter/filter-match.d.ts +4 -1
- package/dist/types/src/filter/filter-match.d.ts.map +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
- package/dist/types/src/pipeline/message-selector.d.ts.map +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts +3 -3
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/pipeline/timeframe-clock.d.ts.map +1 -1
- package/dist/types/src/query/errors.d.ts +22 -10
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +66 -8
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +66 -11
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +12 -1
- package/dist/types/src/query/query-planner.d.ts.map +1 -1
- package/dist/types/src/space/admission-discovery-extension.d.ts.map +1 -1
- package/dist/types/src/space/auth.d.ts +3 -3
- package/dist/types/src/space/auth.d.ts.map +1 -1
- package/dist/types/src/space/control-pipeline.d.ts +5 -4
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +8 -8
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +10 -9
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +6 -6
- package/dist/types/src/space/space.d.ts.map +1 -1
- package/dist/types/src/testing/change-metadata.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +0 -2
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-agent-builder.d.ts +2 -2
- package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-feed-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-network-adapter.d.ts +1 -1
- package/dist/types/src/testing/test-network-adapter.d.ts.map +1 -1
- package/dist/types/src/testing/test-replicator.d.ts +14 -13
- package/dist/types/src/testing/test-replicator.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +1 -1
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +56 -54
- package/src/automerge/automerge-host-subduction.test.ts +247 -0
- package/src/automerge/automerge-host.test.ts +65 -39
- package/src/automerge/automerge-host.ts +589 -206
- package/src/automerge/automerge-repo-subduction.test.ts +1049 -0
- package/src/automerge/automerge-repo.test.ts +349 -91
- package/src/automerge/automerge-subduction.test.ts +330 -0
- package/src/automerge/collection-synchronizer.test.ts +53 -17
- package/src/automerge/collection-synchronizer.ts +119 -36
- package/src/automerge/echo-data-monitor.ts +1 -1
- package/src/automerge/echo-network-adapter.test.ts +5 -4
- package/src/automerge/echo-network-adapter.ts +74 -30
- package/src/automerge/echo-replicator.ts +41 -15
- package/src/automerge/handle-state.ts +62 -0
- package/src/automerge/heads-store.ts +42 -3
- package/src/automerge/index.ts +2 -1
- package/src/automerge/leveldb-storage-adapter.ts +40 -11
- package/src/automerge/mesh-echo-replicator-connection.ts +12 -8
- package/src/automerge/mesh-echo-replicator.ts +15 -11
- package/src/automerge/storage-adapter.test.ts +1 -1
- package/src/common/space-id.ts +1 -1
- package/src/db-host/automerge-data-source.test.ts +299 -0
- package/src/db-host/automerge-data-source.ts +187 -0
- package/src/db-host/data-service.ts +25 -26
- package/src/db-host/database-root.ts +20 -8
- package/src/db-host/documents-synchronizer.test.ts +143 -14
- package/src/db-host/documents-synchronizer.ts +72 -55
- package/src/db-host/echo-host.ts +348 -96
- package/src/db-host/index.ts +2 -0
- package/src/db-host/invalidation-hint.ts +50 -0
- package/src/db-host/local-queue-service.ts +127 -0
- package/src/db-host/query-invalidation.test.ts +270 -0
- package/src/db-host/query-service.ts +111 -106
- package/src/db-host/queue-data-source.ts +160 -0
- package/src/db-host/queue-service.test.ts +145 -0
- package/src/db-host/space-state-manager.ts +12 -10
- package/src/db-host/stub.ts +33 -0
- package/src/edge/echo-edge-replicator.test.ts +12 -10
- package/src/edge/echo-edge-replicator.ts +144 -58
- package/src/edge/echo-edge-subduction-replicator.test.ts +158 -0
- package/src/edge/echo-edge-subduction-replicator.ts +377 -0
- package/src/edge/index.ts +1 -0
- package/src/filter/filter-match.test.ts +68 -32
- package/src/filter/filter-match.ts +184 -20
- package/src/metadata/metadata-store.ts +11 -8
- package/src/pipeline/pipeline-stress.test.ts +1 -1
- package/src/pipeline/pipeline.test.ts +2 -2
- package/src/pipeline/pipeline.ts +9 -7
- package/src/query/errors.ts +3 -1
- package/src/query/plan.ts +82 -8
- package/src/query/query-executor.ts +1199 -164
- package/src/query/query-planner.test.ts +993 -81
- package/src/query/query-planner.ts +467 -27
- package/src/space/admission-discovery-extension.ts +3 -3
- package/src/space/auth.ts +6 -6
- package/src/space/control-pipeline.test.ts +5 -4
- package/src/space/control-pipeline.ts +16 -13
- package/src/space/space-manager.browser.test.ts +1 -1
- package/src/space/space-manager.ts +16 -20
- package/src/space/space-protocol.browser.test.ts +15 -14
- package/src/space/space-protocol.test.ts +15 -14
- package/src/space/space-protocol.ts +22 -18
- package/src/space/space.test.ts +3 -3
- package/src/space/space.ts +11 -11
- package/src/testing/index.ts +0 -2
- package/src/testing/test-agent-builder.ts +3 -3
- package/src/testing/test-network-adapter.ts +24 -3
- package/src/testing/test-replicator.ts +19 -20
- package/src/util.ts +7 -3
- package/dist/lib/browser/chunk-35I6ERLG.mjs.map +0 -7
- package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
- package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
- package/dist/lib/browser/chunk-TQJTKNMS.mjs +0 -126
- package/dist/lib/browser/chunk-TQJTKNMS.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -4743
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node/chunk-HOPOFWAL.cjs +0 -147
- package/dist/lib/node/chunk-HOPOFWAL.cjs.map +0 -7
- package/dist/lib/node/chunk-JXX6LF5U.cjs +0 -2084
- package/dist/lib/node/chunk-JXX6LF5U.cjs.map +0 -7
- package/dist/lib/node/chunk-Q7SFCCGT.cjs +0 -33
- package/dist/lib/node/chunk-Q7SFCCGT.cjs.map +0 -7
- package/dist/lib/node/filter/index.cjs +0 -32
- package/dist/lib/node/filter/index.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -4699
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/testing/index.cjs +0 -753
- package/dist/lib/node/testing/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-5BHLPT24.mjs +0 -2067
- package/dist/lib/node-esm/chunk-5BHLPT24.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RVK35BS7.mjs +0 -126
- package/dist/lib/node-esm/chunk-RVK35BS7.mjs.map +0 -7
- package/dist/lib/node-esm/filter/index.mjs +0 -11
- package/dist/lib/node-esm/filter/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -4743
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -742
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/db-host/documents-iterator.d.ts +0 -7
- package/dist/types/src/db-host/documents-iterator.d.ts.map +0 -1
- package/dist/types/src/testing/test-data.d.ts +0 -18
- package/dist/types/src/testing/test-data.d.ts.map +0 -1
- package/dist/types/src/testing/test-schema.d.ts +0 -37
- package/dist/types/src/testing/test-schema.d.ts.map +0 -1
- package/src/db-host/documents-iterator.ts +0 -74
- package/src/testing/test-data.ts +0 -127
- package/src/testing/test-schema.ts +0 -53
- /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 {
|
|
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
|
-
|
|
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 }:
|
|
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
|
}
|
package/src/automerge/index.ts
CHANGED
|
@@ -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
|
|
5
|
+
import { type Chunk, type StorageAdapterInterface, type StorageKey } from '@automerge/automerge-repo';
|
|
6
6
|
import { type MixedEncoding } from 'level-transcoder';
|
|
7
7
|
|
|
8
|
-
import {
|
|
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
|
|
19
|
+
export type LevelDBStorageAdapterProps = {
|
|
20
20
|
db: SublevelDB;
|
|
21
21
|
callbacks?: StorageCallbacks;
|
|
22
22
|
monitor?: StorageAdapterDataMonitor;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
export type
|
|
25
|
+
export type BeforeSaveProps = { path: StorageKey; batch: BatchLevel };
|
|
26
26
|
|
|
27
27
|
export interface StorageCallbacks {
|
|
28
|
-
beforeSave(params:
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
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
|
|
19
|
+
export type MeshReplicatorConnectionProps = {
|
|
20
20
|
ownPeerId: string;
|
|
21
21
|
onRemoteConnected: () => void;
|
|
22
22
|
onRemoteDisconnected: () => void;
|
|
23
|
-
shouldAdvertise: (params:
|
|
24
|
-
shouldSyncCollection: (params:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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 {
|
|
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
|
|
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:
|
|
45
|
+
private _context: AutomergeReplicatorContext | null = null;
|
|
44
46
|
|
|
45
|
-
async connect(context:
|
|
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(
|
|
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:
|
|
117
|
+
shouldAdvertise: async (params: ShouldAdvertiseProps) => {
|
|
114
118
|
log('shouldAdvertise', { peerId: connection.peerId, documentId: params.documentId });
|
|
115
119
|
invariant(this._context);
|
|
116
120
|
try {
|
package/src/common/space-id.ts
CHANGED
|
@@ -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);
|