@dxos/echo-pipeline 0.8.3 → 0.8.4-main.1068cf700f
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/neutral/chunk-FJPXA75J.mjs +235 -0
- package/dist/lib/neutral/chunk-FJPXA75J.mjs.map +7 -0
- package/dist/lib/{browser/chunk-35I6ERLG.mjs → neutral/chunk-WIQUYO7H.mjs} +300 -213
- package/dist/lib/neutral/chunk-WIQUYO7H.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/filter/index.mjs +3 -2
- package/dist/lib/{browser → neutral}/index.mjs +2832 -946
- 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 +73 -224
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/automerge/automerge-host.d.ts +53 -42
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- 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/collection-synchronizer.d.ts +6 -4
- package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +22 -10
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +37 -15
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- 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 +1 -1
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +5 -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 +5 -5
- package/dist/types/src/automerge/mesh-echo-replicator.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/db-host/automerge-data-source.d.ts +34 -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/data-service.d.ts +5 -4
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/dist/types/src/db-host/database-root.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +6 -5
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +44 -23
- package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
- package/dist/types/src/db-host/index.d.ts +1 -0
- package/dist/types/src/db-host/index.d.ts.map +1 -1
- package/dist/types/src/db-host/local-queue-service.d.ts +16 -0
- package/dist/types/src/db-host/local-queue-service.d.ts.map +1 -0
- package/dist/types/src/db-host/query-service.d.ts +14 -9
- package/dist/types/src/db-host/query-service.d.ts.map +1 -1
- package/dist/types/src/db-host/queue-data-source.d.ts +28 -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 +2 -1
- package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
- package/dist/types/src/db-host/stub.d.ts +10 -0
- package/dist/types/src/db-host/stub.d.ts.map +1 -0
- package/dist/types/src/edge/echo-edge-replicator.d.ts +8 -6
- package/dist/types/src/edge/echo-edge-replicator.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/pipeline.d.ts +3 -3
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/query/errors.d.ts +21 -9
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +57 -5
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +49 -10
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +9 -0
- 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 +3 -3
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +7 -7
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +6 -6
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +4 -4
- package/dist/types/src/space/space.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-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 +53 -45
- package/src/automerge/automerge-host.test.ts +55 -24
- package/src/automerge/automerge-host.ts +399 -126
- package/src/automerge/automerge-repo.test.ts +214 -46
- package/src/automerge/collection-synchronizer.test.ts +22 -15
- package/src/automerge/collection-synchronizer.ts +57 -22
- package/src/automerge/echo-data-monitor.ts +1 -1
- package/src/automerge/echo-network-adapter.test.ts +3 -3
- package/src/automerge/echo-network-adapter.ts +73 -28
- package/src/automerge/echo-replicator.ts +40 -15
- package/src/automerge/heads-store.ts +42 -3
- package/src/automerge/index.ts +1 -1
- package/src/automerge/leveldb-storage-adapter.ts +11 -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 +292 -0
- package/src/db-host/automerge-data-source.ts +144 -0
- package/src/db-host/data-service.ts +19 -20
- package/src/db-host/database-root.ts +2 -2
- package/src/db-host/documents-synchronizer.test.ts +73 -13
- package/src/db-host/documents-synchronizer.ts +58 -44
- package/src/db-host/echo-host.ts +194 -84
- package/src/db-host/index.ts +1 -0
- package/src/db-host/local-queue-service.ts +147 -0
- package/src/db-host/query-service.ts +28 -95
- package/src/db-host/queue-data-source.ts +132 -0
- package/src/db-host/queue-service.test.ts +145 -0
- package/src/db-host/space-state-manager.ts +6 -2
- package/src/db-host/stub.ts +28 -0
- package/src/edge/echo-edge-replicator.test.ts +8 -6
- package/src/edge/echo-edge-replicator.ts +124 -42
- package/src/filter/filter-match.test.ts +58 -32
- package/src/filter/filter-match.ts +163 -15
- package/src/metadata/metadata-store.ts +8 -5
- package/src/pipeline/pipeline-stress.test.ts +4 -2
- package/src/pipeline/pipeline.test.ts +3 -2
- package/src/pipeline/pipeline.ts +10 -7
- package/src/query/errors.ts +3 -1
- package/src/query/plan.ts +75 -5
- package/src/query/query-executor.ts +812 -117
- package/src/query/query-planner.test.ts +652 -34
- package/src/query/query-planner.ts +276 -13
- package/src/space/admission-discovery-extension.ts +3 -3
- package/src/space/auth.ts +6 -6
- package/src/space/control-pipeline.test.ts +4 -3
- package/src/space/control-pipeline.ts +11 -8
- package/src/space/space-manager.browser.test.ts +1 -1
- package/src/space/space-manager.ts +11 -10
- package/src/space/space-protocol.browser.test.ts +2 -2
- package/src/space/space-protocol.test.ts +3 -2
- package/src/space/space-protocol.ts +17 -14
- package/src/space/space.test.ts +1 -1
- package/src/space/space.ts +6 -5
- package/src/testing/index.ts +0 -2
- package/src/testing/test-agent-builder.ts +4 -3
- package/src/testing/test-network-adapter.ts +16 -3
- package/src/testing/test-replicator.ts +23 -18
- 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.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
|
@@ -7,7 +7,7 @@ import { getRandomPort } from 'get-port-please';
|
|
|
7
7
|
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
8
8
|
|
|
9
9
|
import { Event } from '@dxos/async';
|
|
10
|
-
import {
|
|
10
|
+
import { EdgeClient, type EdgeHttpClient, MessageSchema, createEphemeralEdgeIdentity } from '@dxos/edge-client';
|
|
11
11
|
import { createTestEdgeWsServer } from '@dxos/edge-client/testing';
|
|
12
12
|
import { PublicKey, SpaceId } from '@dxos/keys';
|
|
13
13
|
import { EdgeService } from '@dxos/protocols';
|
|
@@ -16,8 +16,9 @@ import { createBuf } from '@dxos/protocols/buf';
|
|
|
16
16
|
import type { Peer } from '@dxos/protocols/proto/dxos/edge/messenger';
|
|
17
17
|
import { openAndClose } from '@dxos/test-utils';
|
|
18
18
|
|
|
19
|
+
import type { AutomergeReplicatorConnection, AutomergeReplicatorContext } from '../automerge';
|
|
20
|
+
|
|
19
21
|
import { EchoEdgeReplicator } from './echo-edge-replicator';
|
|
20
|
-
import type { EchoReplicatorContext, ReplicatorConnection } from '../automerge';
|
|
21
22
|
|
|
22
23
|
describe('EchoEdgeReplicator', () => {
|
|
23
24
|
test('reconnects', async () => {
|
|
@@ -78,8 +79,9 @@ describe('EchoEdgeReplicator', () => {
|
|
|
78
79
|
});
|
|
79
80
|
});
|
|
80
81
|
|
|
81
|
-
const connectReplicator = async (client: EdgeClient, context:
|
|
82
|
-
|
|
82
|
+
const connectReplicator = async (client: EdgeClient, context: AutomergeReplicatorContext) => {
|
|
83
|
+
// EdgeHttpClient functionality is not tested here.
|
|
84
|
+
const replicator = new EchoEdgeReplicator({ edgeConnection: client, edgeHttpClient: {} as EdgeHttpClient });
|
|
83
85
|
await replicator.connect(context);
|
|
84
86
|
onTestFinished(() => replicator.disconnect());
|
|
85
87
|
return replicator;
|
|
@@ -99,7 +101,7 @@ const createMockContext = (args?: {
|
|
|
99
101
|
documentSpaceId?: { [documentId: string]: SpaceId };
|
|
100
102
|
}) => {
|
|
101
103
|
const connectionOpen = new Event();
|
|
102
|
-
const openConnections:
|
|
104
|
+
const openConnections: AutomergeReplicatorConnection[] = [];
|
|
103
105
|
return {
|
|
104
106
|
context: {
|
|
105
107
|
getContainingSpaceIdForDocument: async (documentId) => args?.documentSpaceId?.[documentId] ?? null,
|
|
@@ -119,7 +121,7 @@ const createMockContext = (args?: {
|
|
|
119
121
|
},
|
|
120
122
|
|
|
121
123
|
peerId: PublicKey.random().toHex(),
|
|
122
|
-
} satisfies
|
|
124
|
+
} satisfies AutomergeReplicatorContext,
|
|
123
125
|
|
|
124
126
|
openConnections,
|
|
125
127
|
connectionOpen,
|
|
@@ -2,34 +2,44 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Automerge from '@automerge/automerge';
|
|
6
|
+
import { type DocumentId, type Heads, cbor } from '@automerge/automerge-repo';
|
|
6
7
|
|
|
7
|
-
import { Mutex,
|
|
8
|
+
import { Mutex, scheduleMicroTask, scheduleTask } from '@dxos/async';
|
|
8
9
|
import { Context, Resource } from '@dxos/context';
|
|
9
10
|
import { randomUUID } from '@dxos/crypto';
|
|
10
11
|
import type { CollectionId } from '@dxos/echo-protocol';
|
|
11
|
-
import { type EdgeConnection } from '@dxos/edge-client';
|
|
12
|
+
import { type EdgeConnection, type EdgeHttpClient } from '@dxos/edge-client';
|
|
12
13
|
import { invariant } from '@dxos/invariant';
|
|
13
14
|
import type { SpaceId } from '@dxos/keys';
|
|
14
15
|
import { log } from '@dxos/log';
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
type AutomergeProtocolMessage,
|
|
18
|
+
DocumentCodec,
|
|
19
|
+
EdgeService,
|
|
20
|
+
type ErrorProtocolMessage,
|
|
21
|
+
type ExportBundleRequest,
|
|
22
|
+
type ImportBundleRequest,
|
|
23
|
+
type PeerId,
|
|
24
|
+
} from '@dxos/protocols';
|
|
16
25
|
import { buf } from '@dxos/protocols/buf';
|
|
17
26
|
import {
|
|
18
27
|
type Message as RouterMessage,
|
|
19
28
|
MessageSchema as RouterMessageSchema,
|
|
20
29
|
} from '@dxos/protocols/buf/dxos/edge/messenger_pb';
|
|
21
|
-
import { bufferToArray } from '@dxos/util';
|
|
30
|
+
import { bufferToArray, setDeep } from '@dxos/util';
|
|
22
31
|
|
|
23
|
-
import { InflightRequestLimiter } from './inflight-request-limiter';
|
|
24
32
|
import {
|
|
33
|
+
type AutomergeReplicator,
|
|
34
|
+
type AutomergeReplicatorConnection,
|
|
35
|
+
type AutomergeReplicatorContext,
|
|
36
|
+
type ShouldAdvertiseProps,
|
|
37
|
+
type ShouldSyncCollectionProps,
|
|
25
38
|
getSpaceIdFromCollectionId,
|
|
26
|
-
type EchoReplicator,
|
|
27
|
-
type EchoReplicatorContext,
|
|
28
|
-
type ReplicatorConnection,
|
|
29
|
-
type ShouldAdvertiseParams,
|
|
30
|
-
type ShouldSyncCollectionParams,
|
|
31
39
|
} from '../automerge';
|
|
32
40
|
|
|
41
|
+
import { InflightRequestLimiter } from './inflight-request-limiter';
|
|
42
|
+
|
|
33
43
|
/**
|
|
34
44
|
* Delay before restarting the connection after receiving a forbidden error.
|
|
35
45
|
*/
|
|
@@ -37,27 +47,30 @@ const INITIAL_RESTART_DELAY = 500;
|
|
|
37
47
|
const RESTART_DELAY_JITTER = 250;
|
|
38
48
|
const MAX_RESTART_DELAY = 5000;
|
|
39
49
|
|
|
40
|
-
export type
|
|
50
|
+
export type EchoEdgeReplicatorProps = {
|
|
41
51
|
edgeConnection: EdgeConnection;
|
|
52
|
+
edgeHttpClient: EdgeHttpClient;
|
|
42
53
|
disableSharePolicy?: boolean;
|
|
43
54
|
};
|
|
44
55
|
|
|
45
|
-
export class EchoEdgeReplicator implements
|
|
56
|
+
export class EchoEdgeReplicator implements AutomergeReplicator {
|
|
46
57
|
private readonly _edgeConnection: EdgeConnection;
|
|
58
|
+
private readonly _edgeHttpClient: EdgeHttpClient;
|
|
47
59
|
private readonly _mutex = new Mutex();
|
|
48
60
|
|
|
49
61
|
private _ctx?: Context = undefined;
|
|
50
|
-
private _context:
|
|
62
|
+
private _context: AutomergeReplicatorContext | null = null;
|
|
51
63
|
private _connectedSpaces = new Set<SpaceId>();
|
|
52
64
|
private _connections = new Map<SpaceId, EdgeReplicatorConnection>();
|
|
53
65
|
private _sharePolicyEnabled = true;
|
|
54
66
|
|
|
55
|
-
constructor({ edgeConnection, disableSharePolicy }:
|
|
67
|
+
constructor({ edgeConnection, edgeHttpClient, disableSharePolicy }: EchoEdgeReplicatorProps) {
|
|
56
68
|
this._edgeConnection = edgeConnection;
|
|
69
|
+
this._edgeHttpClient = edgeHttpClient;
|
|
57
70
|
this._sharePolicyEnabled = !disableSharePolicy;
|
|
58
71
|
}
|
|
59
72
|
|
|
60
|
-
async connect(context:
|
|
73
|
+
async connect(context: AutomergeReplicatorContext): Promise<void> {
|
|
61
74
|
log('connecting...', { peerId: context.peerId, connectedSpaces: this._connectedSpaces.size });
|
|
62
75
|
this._context = context;
|
|
63
76
|
this._ctx = Context.default();
|
|
@@ -95,6 +108,7 @@ export class EchoEdgeReplicator implements EchoReplicator {
|
|
|
95
108
|
}
|
|
96
109
|
|
|
97
110
|
async connectToSpace(spaceId: SpaceId): Promise<void> {
|
|
111
|
+
log('connectToSpace', { spaceId });
|
|
98
112
|
using _guard = await this._mutex.acquire();
|
|
99
113
|
|
|
100
114
|
if (this._connectedSpaces.has(spaceId)) {
|
|
@@ -127,14 +141,17 @@ export class EchoEdgeReplicator implements EchoReplicator {
|
|
|
127
141
|
let restartScheduled = false;
|
|
128
142
|
|
|
129
143
|
const connection = new EdgeReplicatorConnection({
|
|
144
|
+
edgeHttpClient: this._edgeHttpClient,
|
|
130
145
|
edgeConnection: this._edgeConnection,
|
|
131
146
|
spaceId,
|
|
132
147
|
context: this._context,
|
|
133
148
|
sharedPolicyEnabled: this._sharePolicyEnabled,
|
|
134
149
|
onRemoteConnected: async () => {
|
|
150
|
+
log.trace('dxos.echo.edge.replicator.onRemoteConnected', { spaceId });
|
|
135
151
|
this._context?.onConnectionOpen(connection);
|
|
136
152
|
},
|
|
137
153
|
onRemoteDisconnected: async () => {
|
|
154
|
+
log.trace('dxos.echo.edge.replicator.onRemoteDisconnected', { spaceId });
|
|
138
155
|
this._context?.onConnectionClosed(connection);
|
|
139
156
|
},
|
|
140
157
|
onRestartRequested: async () => {
|
|
@@ -162,6 +179,7 @@ export class EchoEdgeReplicator implements EchoReplicator {
|
|
|
162
179
|
if (ctx?.disposed) {
|
|
163
180
|
return;
|
|
164
181
|
}
|
|
182
|
+
log.trace('dxos.echo.edge.replicator.restart', { spaceId, reconnects, restartDelay });
|
|
165
183
|
await this._openConnection(spaceId, reconnects + 1);
|
|
166
184
|
},
|
|
167
185
|
restartDelay,
|
|
@@ -174,10 +192,11 @@ export class EchoEdgeReplicator implements EchoReplicator {
|
|
|
174
192
|
}
|
|
175
193
|
}
|
|
176
194
|
|
|
177
|
-
type
|
|
195
|
+
type EdgeReplicatorConnectionsProps = {
|
|
178
196
|
edgeConnection: EdgeConnection;
|
|
197
|
+
edgeHttpClient: EdgeHttpClient;
|
|
179
198
|
spaceId: SpaceId;
|
|
180
|
-
context:
|
|
199
|
+
context: AutomergeReplicatorContext;
|
|
181
200
|
sharedPolicyEnabled: boolean;
|
|
182
201
|
onRemoteConnected: () => Promise<void>;
|
|
183
202
|
onRemoteDisconnected: () => Promise<void>;
|
|
@@ -187,16 +206,19 @@ type EdgeReplicatorConnectionsParams = {
|
|
|
187
206
|
const MAX_INFLIGHT_REQUESTS = 5;
|
|
188
207
|
const MAX_RATE_LIMIT_WAIT_TIME_MS = 3000;
|
|
189
208
|
|
|
190
|
-
class EdgeReplicatorConnection extends Resource implements
|
|
209
|
+
class EdgeReplicatorConnection extends Resource implements AutomergeReplicatorConnection {
|
|
210
|
+
private readonly _connectionId = randomUUID();
|
|
191
211
|
private readonly _edgeConnection: EdgeConnection;
|
|
212
|
+
private readonly _edgeHttpClient: EdgeHttpClient;
|
|
192
213
|
private readonly _remotePeerId: string | null = null;
|
|
193
214
|
private readonly _targetServiceId: string;
|
|
194
215
|
private readonly _spaceId: SpaceId;
|
|
195
|
-
private readonly _context:
|
|
216
|
+
private readonly _context: AutomergeReplicatorContext;
|
|
196
217
|
private readonly _sharedPolicyEnabled: boolean;
|
|
197
218
|
private readonly _onRemoteConnected: () => Promise<void>;
|
|
198
219
|
private readonly _onRemoteDisconnected: () => Promise<void>;
|
|
199
220
|
private readonly _onRestartRequested: () => void;
|
|
221
|
+
private _sequence = 0;
|
|
200
222
|
|
|
201
223
|
private _requestLimiter = new InflightRequestLimiter({
|
|
202
224
|
maxInflightRequests: MAX_INFLIGHT_REQUESTS,
|
|
@@ -210,22 +232,24 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
210
232
|
|
|
211
233
|
constructor({
|
|
212
234
|
edgeConnection,
|
|
235
|
+
edgeHttpClient,
|
|
213
236
|
spaceId,
|
|
214
237
|
context,
|
|
215
238
|
sharedPolicyEnabled,
|
|
216
239
|
onRemoteConnected,
|
|
217
240
|
onRemoteDisconnected,
|
|
218
241
|
onRestartRequested,
|
|
219
|
-
}:
|
|
242
|
+
}: EdgeReplicatorConnectionsProps) {
|
|
220
243
|
super();
|
|
221
244
|
this._edgeConnection = edgeConnection;
|
|
245
|
+
this._edgeHttpClient = edgeHttpClient;
|
|
222
246
|
this._spaceId = spaceId;
|
|
223
247
|
this._context = context;
|
|
224
248
|
// Generate a unique peer id for every connection.
|
|
225
249
|
// This way automerge-repo will have separate sync states for every connection.
|
|
226
250
|
// This is important because the previous connection might have had some messages that failed to deliver
|
|
227
251
|
// abd if we don't clear the sync-state, automerge will not attempt to deliver them again.
|
|
228
|
-
this._remotePeerId = `${EdgeService.AUTOMERGE_REPLICATOR}:${spaceId}-${
|
|
252
|
+
this._remotePeerId = `${EdgeService.AUTOMERGE_REPLICATOR}:${spaceId}-${this._connectionId}`;
|
|
229
253
|
this._targetServiceId = `${EdgeService.AUTOMERGE_REPLICATOR}:${spaceId}`;
|
|
230
254
|
this._sharedPolicyEnabled = sharedPolicyEnabled;
|
|
231
255
|
this._onRemoteConnected = onRemoteConnected;
|
|
@@ -252,13 +276,26 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
252
276
|
|
|
253
277
|
await this._requestLimiter.open();
|
|
254
278
|
|
|
255
|
-
// TODO: handle reconnects
|
|
256
279
|
this._ctx.onDispose(
|
|
257
280
|
this._edgeConnection.onMessage((msg: RouterMessage) => {
|
|
258
281
|
this._onMessage(msg);
|
|
259
282
|
}),
|
|
260
283
|
);
|
|
261
284
|
|
|
285
|
+
let firstReconnect = true;
|
|
286
|
+
this._ctx.onDispose(
|
|
287
|
+
// NOTE: This will fire immediately if the connection is already open.
|
|
288
|
+
this._edgeConnection.onReconnected(async () => {
|
|
289
|
+
if (firstReconnect) {
|
|
290
|
+
log.verbose('first reconnect skipped');
|
|
291
|
+
firstReconnect = false;
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this._onRestartRequested();
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
298
|
+
|
|
262
299
|
await this._onRemoteConnected();
|
|
263
300
|
}
|
|
264
301
|
|
|
@@ -276,7 +313,7 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
276
313
|
return this._remotePeerId;
|
|
277
314
|
}
|
|
278
315
|
|
|
279
|
-
async shouldAdvertise(params:
|
|
316
|
+
async shouldAdvertise(params: ShouldAdvertiseProps): Promise<boolean> {
|
|
280
317
|
if (!this._sharedPolicyEnabled) {
|
|
281
318
|
return true;
|
|
282
319
|
}
|
|
@@ -301,7 +338,7 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
301
338
|
return spaceId === this._spaceId;
|
|
302
339
|
}
|
|
303
340
|
|
|
304
|
-
shouldSyncCollection(params:
|
|
341
|
+
shouldSyncCollection(params: ShouldSyncCollectionProps): boolean {
|
|
305
342
|
if (!this._sharedPolicyEnabled) {
|
|
306
343
|
return true;
|
|
307
344
|
}
|
|
@@ -317,8 +354,7 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
317
354
|
|
|
318
355
|
const payload = cbor.decode(message.payload!.value) as AutomergeProtocolMessage;
|
|
319
356
|
log.verbose('received', {
|
|
320
|
-
|
|
321
|
-
documentId: payload.type === 'sync' && payload.documentId,
|
|
357
|
+
...getMessageInfo(payload),
|
|
322
358
|
remoteId: this._remotePeerId,
|
|
323
359
|
});
|
|
324
360
|
|
|
@@ -327,11 +363,33 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
327
363
|
this._processMessage(payload);
|
|
328
364
|
}
|
|
329
365
|
|
|
366
|
+
get bundleSyncEnabled(): boolean {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async pushBundle(bundle: { documentId: DocumentId; data: Uint8Array; heads: Heads }[]) {
|
|
371
|
+
const request: ImportBundleRequest = {
|
|
372
|
+
bundle: bundle.map(({ documentId, data, heads }) => ({
|
|
373
|
+
documentId,
|
|
374
|
+
mutation: DocumentCodec.encode(data),
|
|
375
|
+
heads,
|
|
376
|
+
})),
|
|
377
|
+
};
|
|
378
|
+
await this._edgeHttpClient.importBundle(this._spaceId, request);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async pullBundle(docHeads: Record<DocumentId, Heads>): Promise<Record<DocumentId, Uint8Array>> {
|
|
382
|
+
const request: ExportBundleRequest = { docHeads };
|
|
383
|
+
const response = await this._edgeHttpClient.exportBundle(this._spaceId, request);
|
|
384
|
+
return Object.fromEntries(response.bundle.map((doc) => [doc.documentId, DocumentCodec.decode(doc.mutation)]));
|
|
385
|
+
}
|
|
386
|
+
|
|
330
387
|
private _processMessage(message: AutomergeProtocolMessage): void {
|
|
331
388
|
// There's a race between the credentials being replicated that are needed for access control and the data replication.
|
|
332
389
|
// AutomergeReplicator might return a Forbidden error if the credentials are not yet replicated.
|
|
333
390
|
// We restart the connection with some delay to account for that.
|
|
334
|
-
if (
|
|
391
|
+
if (isErrorMessage(message)) {
|
|
392
|
+
log.verbose('stream error', { error: (message as ErrorProtocolMessage).message });
|
|
335
393
|
this._onRestartRequested();
|
|
336
394
|
return;
|
|
337
395
|
}
|
|
@@ -345,29 +403,53 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
|
|
|
345
403
|
// Fix the peer id.
|
|
346
404
|
(message as any).targetId = this._targetServiceId as PeerId;
|
|
347
405
|
|
|
406
|
+
// Note: This is used on EDGE to detect out-of-order messages per connection.
|
|
407
|
+
setDeep(message, ['metadata', 'dxos_sequence'], this._getSequence());
|
|
408
|
+
setDeep(message, ['metadata', 'dxos_connectionId'], this._connectionId);
|
|
409
|
+
|
|
348
410
|
log.verbose('sending...', {
|
|
349
|
-
|
|
350
|
-
documentId: message.type === 'sync' && message.documentId,
|
|
411
|
+
...getMessageInfo(message),
|
|
351
412
|
remoteId: this._remotePeerId,
|
|
352
413
|
});
|
|
353
414
|
|
|
354
415
|
const encoded = cbor.encode(message);
|
|
355
416
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
417
|
+
try {
|
|
418
|
+
await this._edgeConnection.send(
|
|
419
|
+
buf.create(RouterMessageSchema, {
|
|
420
|
+
serviceId: this._targetServiceId,
|
|
421
|
+
source: {
|
|
422
|
+
identityKey: this._edgeConnection.identityKey,
|
|
423
|
+
peerKey: this._edgeConnection.peerKey,
|
|
424
|
+
},
|
|
425
|
+
payload: { value: bufferToArray(encoded) },
|
|
426
|
+
}),
|
|
427
|
+
);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
log.error('failed to send message', { err });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private _getSequence(): number {
|
|
434
|
+
return this._sequence++;
|
|
366
435
|
}
|
|
367
436
|
}
|
|
368
437
|
|
|
369
438
|
/**
|
|
370
439
|
* This message is sent by EDGE AutomergeReplicator when the authorization is denied.
|
|
371
440
|
*/
|
|
372
|
-
const
|
|
373
|
-
|
|
441
|
+
const isErrorMessage = (message: AutomergeProtocolMessage) => message.type === 'error';
|
|
442
|
+
|
|
443
|
+
const getMessageInfo = (msg: AutomergeProtocolMessage) => {
|
|
444
|
+
const { have, heads, need, changes } = msg.type === 'sync' ? Automerge.decodeSyncMessage(msg.data) : {};
|
|
445
|
+
return {
|
|
446
|
+
type: msg.type,
|
|
447
|
+
documentId: 'documentId' in msg ? msg.documentId : undefined,
|
|
448
|
+
collectionId: 'collectionId' in msg ? msg.collectionId : undefined,
|
|
449
|
+
have,
|
|
450
|
+
heads,
|
|
451
|
+
need,
|
|
452
|
+
changes: changes?.length,
|
|
453
|
+
sequence: msg.metadata?.dxos_sequence,
|
|
454
|
+
};
|
|
455
|
+
};
|
|
@@ -4,69 +4,88 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import { Filter } from '@dxos/echo';
|
|
7
|
+
import { Filter, Ref } from '@dxos/echo';
|
|
8
|
+
import { TestSchema } from '@dxos/echo/testing';
|
|
8
9
|
import { ObjectStructure } from '@dxos/echo-protocol';
|
|
9
|
-
import { Expando, EXPANDO_TYPENAME, Ref } from '@dxos/echo-schema';
|
|
10
10
|
import { DXN, ObjectId, SpaceId } from '@dxos/keys';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { type MatchedObject, filterMatchObject } from './filter-match';
|
|
13
13
|
|
|
14
14
|
describe('filterMatch', () => {
|
|
15
|
+
test('everything', () => {
|
|
16
|
+
expect(filterMatchObject(Filter.everything().ast, OBJECT_1)).to.be.true;
|
|
17
|
+
expect(filterMatchObject(Filter.everything().ast, OBJECT_2)).to.be.true;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('nothing', () => {
|
|
21
|
+
expect(filterMatchObject(Filter.nothing().ast, OBJECT_1)).to.be.false;
|
|
22
|
+
expect(filterMatchObject(Filter.nothing().ast, OBJECT_2)).to.be.false;
|
|
23
|
+
});
|
|
24
|
+
|
|
15
25
|
test('properties', () => {
|
|
16
|
-
expect(filterMatchObject(Filter.type(Expando, { title: 'test' }).ast, OBJECT_1)).to.be.true;
|
|
17
|
-
expect(filterMatchObject(Filter.type(Expando, { value: 100 }).ast, OBJECT_1)).to.be.true;
|
|
18
|
-
expect(filterMatchObject(Filter.type(Expando, { complete: false }).ast, OBJECT_1)).to.be.false;
|
|
19
|
-
expect(filterMatchObject(Filter.type(Expando, { missing: undefined }).ast, OBJECT_1)).to.be.true;
|
|
26
|
+
expect(filterMatchObject(Filter.type(TestSchema.Expando, { title: 'test' }).ast, OBJECT_1)).to.be.true;
|
|
27
|
+
expect(filterMatchObject(Filter.type(TestSchema.Expando, { value: 100 }).ast, OBJECT_1)).to.be.true;
|
|
28
|
+
expect(filterMatchObject(Filter.type(TestSchema.Expando, { complete: false }).ast, OBJECT_1)).to.be.false;
|
|
29
|
+
expect(filterMatchObject(Filter.type(TestSchema.Expando, { missing: undefined }).ast, OBJECT_1)).to.be.true;
|
|
30
|
+
expect(filterMatchObject(Filter.type(TestSchema.Expando, { properties: { subject: 'test' } }).ast, OBJECT_1)).to.be
|
|
31
|
+
.true;
|
|
32
|
+
expect(filterMatchObject(Filter.type(TestSchema.Expando, { array: Filter.contains('two') }).ast, OBJECT_1)).to.be
|
|
33
|
+
.true;
|
|
20
34
|
});
|
|
21
35
|
|
|
22
36
|
test('and', () => {
|
|
23
|
-
const filter1 = Filter.type(Expando, { title: 'test' });
|
|
24
|
-
const filter2 = Filter.type(Expando, { value: 100 });
|
|
25
|
-
const filter3 = Filter.type(Expando, { complete: true });
|
|
37
|
+
const filter1 = Filter.type(TestSchema.Expando, { title: 'test' });
|
|
38
|
+
const filter2 = Filter.type(TestSchema.Expando, { value: 100 });
|
|
39
|
+
const filter3 = Filter.type(TestSchema.Expando, { complete: true });
|
|
26
40
|
|
|
27
41
|
expect(filterMatchObject(Filter.and(filter1, filter2, filter3).ast, OBJECT_1)).to.be.true;
|
|
28
42
|
});
|
|
29
43
|
|
|
30
44
|
test('or', () => {
|
|
31
|
-
const filter1 = Filter.type(Expando, { value: 200 });
|
|
32
|
-
const filter2 = Filter.type(Expando, { title: 'test' });
|
|
33
|
-
const filter3 = Filter.type(Expando, { complete: false });
|
|
45
|
+
const filter1 = Filter.type(TestSchema.Expando, { value: 200 });
|
|
46
|
+
const filter2 = Filter.type(TestSchema.Expando, { title: 'test' });
|
|
47
|
+
const filter3 = Filter.type(TestSchema.Expando, { complete: false });
|
|
34
48
|
|
|
35
49
|
expect(filterMatchObject(Filter.or(filter1, filter2, filter3).ast, OBJECT_1)).to.be.true;
|
|
36
50
|
});
|
|
37
51
|
|
|
38
52
|
test('not', () => {
|
|
39
|
-
const filter1 = Filter.type(Expando, { title: 'test' });
|
|
53
|
+
const filter1 = Filter.type(TestSchema.Expando, { title: 'test' });
|
|
40
54
|
const filter2 = Filter.not(filter1);
|
|
41
55
|
|
|
42
56
|
expect(filterMatchObject(filter1.ast, OBJECT_1)).to.be.true;
|
|
43
57
|
expect(filterMatchObject(filter2.ast, OBJECT_1)).to.be.false;
|
|
44
58
|
});
|
|
45
59
|
|
|
60
|
+
test('contains', () => {
|
|
61
|
+
expect(
|
|
62
|
+
filterMatchObject(
|
|
63
|
+
Filter.type(TestSchema.Expando, { properties: { label: Filter.contains('test') } }).ast,
|
|
64
|
+
OBJECT_1,
|
|
65
|
+
),
|
|
66
|
+
).to.be.true;
|
|
67
|
+
expect(
|
|
68
|
+
filterMatchObject(
|
|
69
|
+
Filter.type(TestSchema.Expando, { fields: Filter.contains({ label: 'label', value: 'test' }) }).ast,
|
|
70
|
+
OBJECT_1,
|
|
71
|
+
),
|
|
72
|
+
).to.be.true;
|
|
73
|
+
});
|
|
74
|
+
|
|
46
75
|
test('complex', () => {
|
|
47
|
-
const filter1 = Filter.type(Expando, { title: 'bad' });
|
|
48
|
-
const filter2 = Filter.type(Expando, { value: 0 });
|
|
76
|
+
const filter1 = Filter.type(TestSchema.Expando, { title: 'bad' });
|
|
77
|
+
const filter2 = Filter.type(TestSchema.Expando, { value: 0 });
|
|
49
78
|
expect(filterMatchObject(Filter.not(Filter.or(filter1, filter2)).ast, OBJECT_1)).to.be.true;
|
|
50
79
|
});
|
|
51
80
|
|
|
52
81
|
test('ids', () => {
|
|
53
|
-
const filter = Filter.
|
|
82
|
+
const filter = Filter.id(OBJECT_1.id);
|
|
54
83
|
expect(filterMatchObject(filter.ast, OBJECT_1)).to.be.true;
|
|
55
84
|
expect(filterMatchObject(filter.ast, OBJECT_2)).to.be.false;
|
|
56
85
|
});
|
|
57
86
|
|
|
58
|
-
test('everything', () => {
|
|
59
|
-
expect(filterMatchObject(Filter.everything().ast, OBJECT_1)).to.be.true;
|
|
60
|
-
expect(filterMatchObject(Filter.everything().ast, OBJECT_2)).to.be.true;
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('nothing', () => {
|
|
64
|
-
expect(filterMatchObject(Filter.nothing().ast, OBJECT_1)).to.be.false;
|
|
65
|
-
expect(filterMatchObject(Filter.nothing().ast, OBJECT_2)).to.be.false;
|
|
66
|
-
});
|
|
67
|
-
|
|
68
87
|
test('refs', () => {
|
|
69
|
-
const filter = Filter.type(Expando, { parent: Ref.fromDXN(DXN.fromLocalObjectId(OBJECT_1.id)) });
|
|
88
|
+
const filter = Filter.type(TestSchema.Expando, { parent: Ref.fromDXN(DXN.fromLocalObjectId(OBJECT_1.id)) });
|
|
70
89
|
expect(filterMatchObject(filter.ast, OBJECT_1)).to.be.false;
|
|
71
90
|
expect(filterMatchObject(filter.ast, OBJECT_2)).to.be.false;
|
|
72
91
|
expect(filterMatchObject(filter.ast, OBJECT_3)).to.be.true;
|
|
@@ -77,8 +96,15 @@ const OBJECT_1: MatchedObject = {
|
|
|
77
96
|
id: ObjectId.make('01JVS9YYT5VMVJW0GGTM1YHCCH'),
|
|
78
97
|
spaceId: SpaceId.make('B2NJDFNVZIW77OQSXUBNAD7BUMBD3G5PO'),
|
|
79
98
|
doc: ObjectStructure.makeObject({
|
|
80
|
-
type: DXN.fromTypenameAndVersion(
|
|
81
|
-
data: {
|
|
99
|
+
type: DXN.fromTypenameAndVersion(TestSchema.Expando.typename, '0.1.0').toString(),
|
|
100
|
+
data: {
|
|
101
|
+
title: 'test',
|
|
102
|
+
value: 100,
|
|
103
|
+
complete: true,
|
|
104
|
+
array: ['one', 'two', 'three'],
|
|
105
|
+
properties: { label: ['test'], subject: 'test' },
|
|
106
|
+
fields: [{ label: 'label', value: 'test' }],
|
|
107
|
+
},
|
|
82
108
|
}),
|
|
83
109
|
};
|
|
84
110
|
|
|
@@ -86,7 +112,7 @@ const OBJECT_2: MatchedObject = {
|
|
|
86
112
|
id: ObjectId.make('01JT5TD6K9FFJ3VNM5FGMS5C0Q'),
|
|
87
113
|
spaceId: SpaceId.make('B2NJDFNVZIW77OQSXUBNAD7BUMBD3G5PO'),
|
|
88
114
|
doc: ObjectStructure.makeObject({
|
|
89
|
-
type: DXN.fromTypenameAndVersion(
|
|
115
|
+
type: DXN.fromTypenameAndVersion(TestSchema.Expando.typename, '0.1.0').toString(),
|
|
90
116
|
data: { title: 'test', value: 100, complete: true },
|
|
91
117
|
deleted: true,
|
|
92
118
|
}),
|
|
@@ -96,7 +122,7 @@ const OBJECT_3: MatchedObject = {
|
|
|
96
122
|
id: ObjectId.make('01JT5TD6K9FFJ3VNM5FGMS5C0Q'),
|
|
97
123
|
spaceId: SpaceId.make('B2NJDFNVZIW77OQSXUBNAD7BUMBD3G5PO'),
|
|
98
124
|
doc: ObjectStructure.makeObject({
|
|
99
|
-
type: DXN.fromTypenameAndVersion(
|
|
125
|
+
type: DXN.fromTypenameAndVersion(TestSchema.Expando.typename, '0.1.0').toString(),
|
|
100
126
|
data: { title: 'test', value: 100, complete: true, parent: { '/': DXN.fromLocalObjectId(OBJECT_1.id).toString() } },
|
|
101
127
|
}),
|
|
102
128
|
};
|