@dxos/echo-pipeline 0.4.9 → 0.4.10-main.05b9ab6
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-RTEEJ723.mjs → chunk-KMWJLYEQ.mjs} +77 -57
- package/dist/lib/browser/chunk-KMWJLYEQ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +617 -217
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +10 -2
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node/{chunk-7VZVCCNF.cjs → chunk-YZA42CKA.cjs} +82 -62
- package/dist/lib/node/chunk-YZA42CKA.cjs.map +7 -0
- package/dist/lib/node/index.cjs +635 -237
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +20 -13
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/types/src/automerge/automerge-doc-loader.d.ts +66 -0
- package/dist/types/src/automerge/automerge-doc-loader.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-doc-loader.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-doc-loader.test.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-host.d.ts +21 -18
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-repo.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-repo.test.d.ts.map +1 -0
- package/dist/types/src/automerge/index.d.ts +4 -0
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/level.test.d.ts +2 -0
- package/dist/types/src/automerge/level.test.d.ts.map +1 -0
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +30 -0
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -0
- package/dist/types/src/automerge/local-host-network-adapter.d.ts +8 -1
- package/dist/types/src/automerge/local-host-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/migrations.d.ts +7 -0
- package/dist/types/src/automerge/migrations.d.ts.map +1 -0
- package/dist/types/src/automerge/reference.d.ts +15 -0
- package/dist/types/src/automerge/reference.d.ts.map +1 -0
- package/dist/types/src/automerge/storage-adapter.test.d.ts +2 -0
- package/dist/types/src/automerge/storage-adapter.test.d.ts.map +1 -0
- package/dist/types/src/automerge/types.d.ts +73 -0
- package/dist/types/src/automerge/types.d.ts.map +1 -0
- package/dist/types/src/metadata/metadata-store.d.ts +2 -1
- package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
- package/dist/types/src/space/control-pipeline.d.ts +3 -1
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +3 -1
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +6 -9
- package/dist/types/src/space/space.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/level.d.ts +3 -0
- package/dist/types/src/testing/level.d.ts.map +1 -0
- 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/package.json +33 -30
- package/src/automerge/automerge-doc-loader.test.ts +97 -0
- package/src/automerge/automerge-doc-loader.ts +244 -0
- package/src/automerge/automerge-host.test.ts +22 -8
- package/src/automerge/automerge-host.ts +66 -118
- package/src/automerge/automerge-repo.test.ts +29 -0
- package/src/automerge/index.ts +4 -0
- package/src/automerge/level.test.ts +82 -0
- package/src/automerge/leveldb-storage-adapter.ts +117 -0
- package/src/automerge/local-host-network-adapter.ts +19 -13
- package/src/automerge/migrations.ts +41 -0
- package/src/automerge/reference.ts +31 -0
- package/src/automerge/storage-adapter.test.ts +90 -0
- package/src/automerge/types.ts +86 -0
- package/src/db-host/data-service.ts +1 -1
- package/src/metadata/metadata-store.ts +17 -8
- package/src/space/control-pipeline.ts +11 -1
- package/src/space/space-manager.ts +4 -0
- package/src/space/space.test.ts +7 -7
- package/src/space/space.ts +17 -22
- package/src/testing/index.ts +1 -0
- package/src/testing/level.ts +11 -0
- package/src/testing/test-agent-builder.ts +1 -0
- package/dist/lib/browser/chunk-RTEEJ723.mjs.map +0 -7
- package/dist/lib/node/chunk-7VZVCCNF.cjs.map +0 -7
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
|
|
7
|
+
import { type StorageAdapterInterface } from '@dxos/automerge/automerge-repo';
|
|
8
|
+
import { PublicKey } from '@dxos/keys';
|
|
9
|
+
import { StorageType, createStorage } from '@dxos/random-access-storage';
|
|
10
|
+
import { afterTest, describe, openAndClose, test } from '@dxos/test';
|
|
11
|
+
import { type MaybePromise } from '@dxos/util';
|
|
12
|
+
|
|
13
|
+
import { AutomergeStorageAdapter } from './automerge-storage-adapter';
|
|
14
|
+
import { LevelDBStorageAdapter } from './leveldb-storage-adapter';
|
|
15
|
+
import { createTestLevel } from '../testing';
|
|
16
|
+
|
|
17
|
+
const runTests = (
|
|
18
|
+
testNamespace: string,
|
|
19
|
+
/** Run per test. Expects automatic clean-up with `afterTest`. */ createAdapter: () => MaybePromise<StorageAdapterInterface>,
|
|
20
|
+
) => {
|
|
21
|
+
describe(testNamespace, () => {
|
|
22
|
+
const chunks = [
|
|
23
|
+
{ key: ['a', 'b', 'c', '1'], data: PublicKey.random().asUint8Array() },
|
|
24
|
+
{ key: ['a', 'b', 'c', '2'], data: PublicKey.random().asUint8Array() },
|
|
25
|
+
{ key: ['a', 'b', 'd', '3'], data: PublicKey.random().asUint8Array() },
|
|
26
|
+
{ key: ['a', 'b', 'd', '4'], data: PublicKey.random().asUint8Array() },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
test('should store and retrieve data', async () => {
|
|
30
|
+
const adapter = await createAdapter();
|
|
31
|
+
|
|
32
|
+
await adapter.save(chunks[0].key, chunks[0].data);
|
|
33
|
+
expect(await adapter.load(chunks[0].key)).to.deep.equal(chunks[0].data);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('loadRange return inputs with correct prefixes', async () => {
|
|
37
|
+
const adapter = await createAdapter();
|
|
38
|
+
|
|
39
|
+
for (const chunk of chunks) {
|
|
40
|
+
await adapter.save(chunk.key, chunk.data);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
expect((await adapter.loadRange(['a', 'b'])).length).to.equal(4);
|
|
44
|
+
expect((await adapter.loadRange(['a', 'b', 'c']))[0]).to.deep.equal(chunks[0]);
|
|
45
|
+
expect((await adapter.loadRange(['a', 'b', 'c'])).length).to.equal(2);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('deletion works', async () => {
|
|
49
|
+
const adapter = await createAdapter();
|
|
50
|
+
|
|
51
|
+
for (const chunk of chunks) {
|
|
52
|
+
await adapter.save(chunk.key, chunk.data);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await adapter.remove(['a', 'b', 'c', '1']);
|
|
56
|
+
|
|
57
|
+
expect((await adapter.loadRange(['a', 'b'])).length).to.equal(3);
|
|
58
|
+
expect((await adapter.loadRange(['a', 'b', 'c'])).length).to.equal(1);
|
|
59
|
+
|
|
60
|
+
await adapter.removeRange(['a', 'b', 'd']);
|
|
61
|
+
|
|
62
|
+
expect((await adapter.loadRange(['a', 'b'])).length).to.equal(1);
|
|
63
|
+
expect((await adapter.loadRange(['a', 'b']))[0]).to.deep.equal(chunks[1]);
|
|
64
|
+
expect(await adapter.load(['a', 'b', 'c', '2'])).to.deep.equal(chunks[1].data);
|
|
65
|
+
expect(await adapter.load(['a', 'b', 'd', '3'])).to.be.undefined;
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Run tests for AutomergeStorageAdapter.
|
|
72
|
+
*/
|
|
73
|
+
runTests('AutomergeStorageAdapter', () => {
|
|
74
|
+
const storage = createStorage({ type: StorageType.RAM });
|
|
75
|
+
afterTest(() => storage.close());
|
|
76
|
+
const dir = storage.createDirectory('automerge');
|
|
77
|
+
const adapter = new AutomergeStorageAdapter(dir);
|
|
78
|
+
afterTest(() => adapter.close());
|
|
79
|
+
return adapter;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Run tests for LevelDBStorageAdapter.
|
|
84
|
+
*/
|
|
85
|
+
runTests('LevelDBStorageAdapter', async () => {
|
|
86
|
+
const level = createTestLevel();
|
|
87
|
+
const adapter = new LevelDBStorageAdapter({ db: level.sublevel('automerge') });
|
|
88
|
+
await openAndClose(level, adapter as any);
|
|
89
|
+
return adapter;
|
|
90
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type AbstractChainedBatch, type AbstractSublevel } from 'abstract-level';
|
|
6
|
+
import { type Level } from 'level';
|
|
7
|
+
|
|
8
|
+
import { type EncodedReferenceObject } from './reference';
|
|
9
|
+
|
|
10
|
+
export type SpaceState = {
|
|
11
|
+
// Url of the root automerge document.
|
|
12
|
+
rootUrl?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export interface SpaceDoc {
|
|
16
|
+
access?: {
|
|
17
|
+
spaceKey: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Objects inlined in the current document.
|
|
21
|
+
*/
|
|
22
|
+
objects?: {
|
|
23
|
+
[key: string]: ObjectStructure;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Object id points to an automerge doc url where the object is embedded.
|
|
27
|
+
*/
|
|
28
|
+
links?: {
|
|
29
|
+
[echoId: string]: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Representation of an ECHO object in an AM document.
|
|
35
|
+
*/
|
|
36
|
+
export type ObjectStructure = {
|
|
37
|
+
data: Record<string, any>;
|
|
38
|
+
meta: ObjectMeta;
|
|
39
|
+
system: ObjectSystem;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Echo object metadata.
|
|
44
|
+
*/
|
|
45
|
+
export type ObjectMeta = {
|
|
46
|
+
/**
|
|
47
|
+
* Foreign keys.
|
|
48
|
+
*/
|
|
49
|
+
keys: ForeignKey[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Reference to an object in a foreign database.
|
|
54
|
+
*/
|
|
55
|
+
export type ForeignKey = {
|
|
56
|
+
/**
|
|
57
|
+
* Name of the foreign database/system.
|
|
58
|
+
* E.g. `github.com`.
|
|
59
|
+
*/
|
|
60
|
+
source?: string;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Id within the foreign database.
|
|
64
|
+
*/
|
|
65
|
+
id?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Automerge object system properties.
|
|
70
|
+
* (Is automerge specific.)
|
|
71
|
+
*/
|
|
72
|
+
export type ObjectSystem = {
|
|
73
|
+
/**
|
|
74
|
+
* Deletion marker.
|
|
75
|
+
*/
|
|
76
|
+
deleted?: boolean;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Object reference ('protobuf' protocol) type.
|
|
80
|
+
*/
|
|
81
|
+
type?: EncodedReferenceObject;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type LevelDB = Level<string, string>;
|
|
85
|
+
export type SubLevelDB = AbstractSublevel<any, string | Buffer | Uint8Array, string, string>;
|
|
86
|
+
export type BatchLevel = AbstractChainedBatch<any, string, string>;
|
|
@@ -11,7 +11,7 @@ import { invariant } from '@dxos/invariant';
|
|
|
11
11
|
import { PublicKey } from '@dxos/keys';
|
|
12
12
|
import { log } from '@dxos/log';
|
|
13
13
|
import { DataCorruptionError, STORAGE_VERSION, schema } from '@dxos/protocols';
|
|
14
|
-
import {
|
|
14
|
+
import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
|
|
15
15
|
import {
|
|
16
16
|
type ControlPipelineSnapshot,
|
|
17
17
|
EchoMetadata,
|
|
@@ -174,13 +174,8 @@ export class MetadataStore {
|
|
|
174
174
|
scheduleTaskInterval(
|
|
175
175
|
this._invitationCleanupCtx,
|
|
176
176
|
async () => {
|
|
177
|
-
for (const invitation of this.
|
|
178
|
-
if (
|
|
179
|
-
invitation.created &&
|
|
180
|
-
invitation.lifetime &&
|
|
181
|
-
invitation.lifetime !== 0 &&
|
|
182
|
-
invitation.created.getTime() + invitation.lifetime * 1000 < Date.now()
|
|
183
|
-
) {
|
|
177
|
+
for (const invitation of this._metadata.invitations ?? []) {
|
|
178
|
+
if (hasInvitationExpired(invitation) || isLegacyInvitationFormat(invitation)) {
|
|
184
179
|
await this.removeInvitation(invitation.invitationId);
|
|
185
180
|
}
|
|
186
181
|
}
|
|
@@ -343,3 +338,17 @@ export class MetadataStore {
|
|
|
343
338
|
}
|
|
344
339
|
|
|
345
340
|
const fromBytesInt32 = (buf: Buffer) => buf.readInt32LE(0);
|
|
341
|
+
|
|
342
|
+
export const hasInvitationExpired = (invitation: Invitation): boolean => {
|
|
343
|
+
return Boolean(
|
|
344
|
+
invitation.created &&
|
|
345
|
+
invitation.lifetime &&
|
|
346
|
+
invitation.lifetime !== 0 &&
|
|
347
|
+
invitation.created.getTime() + invitation.lifetime * 1000 < Date.now(),
|
|
348
|
+
);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// TODO: remove once "multiuse" type invitations get removed from local metadata of existing profiles
|
|
352
|
+
const isLegacyInvitationFormat = (invitation: Invitation): boolean => {
|
|
353
|
+
return invitation.type === Invitation.Type.MULTIUSE;
|
|
354
|
+
};
|
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { DeferredTask, sleepWithContext, trackLeaks } from '@dxos/async';
|
|
6
6
|
import { Context } from '@dxos/context';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
SpaceStateMachine,
|
|
9
|
+
type SpaceState,
|
|
10
|
+
type MemberInfo,
|
|
11
|
+
type FeedInfo,
|
|
12
|
+
type DelegateInvitationCredential,
|
|
13
|
+
} from '@dxos/credentials';
|
|
8
14
|
import { type FeedWrapper } from '@dxos/feed-store';
|
|
9
15
|
import { PublicKey } from '@dxos/keys';
|
|
10
16
|
import { log } from '@dxos/log';
|
|
@@ -50,6 +56,8 @@ export class ControlPipeline {
|
|
|
50
56
|
public readonly onFeedAdmitted = new Callback<AsyncCallback<FeedInfo>>();
|
|
51
57
|
public readonly onMemberAdmitted: Callback<AsyncCallback<MemberInfo>>;
|
|
52
58
|
public readonly onCredentialProcessed: Callback<AsyncCallback<Credential>>;
|
|
59
|
+
public readonly onDelegatedInvitation: Callback<AsyncCallback<DelegateInvitationCredential>>;
|
|
60
|
+
public readonly onDelegatedInvitationRemoved: Callback<AsyncCallback<DelegateInvitationCredential>>;
|
|
53
61
|
|
|
54
62
|
@trace.metricsCounter()
|
|
55
63
|
private _usage = new TimeUsageCounter();
|
|
@@ -92,6 +100,8 @@ export class ControlPipeline {
|
|
|
92
100
|
|
|
93
101
|
this.onMemberAdmitted = this._spaceStateMachine.onMemberAdmitted;
|
|
94
102
|
this.onCredentialProcessed = this._spaceStateMachine.onCredentialProcessed;
|
|
103
|
+
this.onDelegatedInvitation = this._spaceStateMachine.onDelegatedInvitation;
|
|
104
|
+
this.onDelegatedInvitationRemoved = this._spaceStateMachine.onDelegatedInvitationRemoved;
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
get spaceState(): SpaceState {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { synchronized, trackLeaks } from '@dxos/async';
|
|
6
|
+
import { type DelegateInvitationCredential } from '@dxos/credentials';
|
|
6
7
|
import { failUndefined } from '@dxos/debug';
|
|
7
8
|
import { type FeedStore } from '@dxos/feed-store';
|
|
8
9
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -42,6 +43,7 @@ export type ConstructSpaceParams = {
|
|
|
42
43
|
*/
|
|
43
44
|
onAuthorizedConnection: (session: Teleport) => void;
|
|
44
45
|
onAuthFailure?: (session: Teleport) => void;
|
|
46
|
+
onDelegatedInvitationStatusChange: (invitation: DelegateInvitationCredential, isActive: boolean) => Promise<void>;
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
/**
|
|
@@ -84,6 +86,7 @@ export class SpaceManager {
|
|
|
84
86
|
swarmIdentity,
|
|
85
87
|
onAuthorizedConnection,
|
|
86
88
|
onAuthFailure,
|
|
89
|
+
onDelegatedInvitationStatusChange,
|
|
87
90
|
memberKey,
|
|
88
91
|
}: ConstructSpaceParams) {
|
|
89
92
|
log.trace('dxos.echo.space-manager.construct-space', trace.begin({ id: this._instanceId }));
|
|
@@ -111,6 +114,7 @@ export class SpaceManager {
|
|
|
111
114
|
metadataStore: this._metadataStore,
|
|
112
115
|
snapshotManager,
|
|
113
116
|
memberKey,
|
|
117
|
+
onDelegatedInvitationStatusChange,
|
|
114
118
|
});
|
|
115
119
|
this._spaces.set(space.key, space);
|
|
116
120
|
|
package/src/space/space.test.ts
CHANGED
|
@@ -20,7 +20,7 @@ describe('space/space', () => {
|
|
|
20
20
|
const agent = await builder.createPeer();
|
|
21
21
|
const space = await agent.createSpace();
|
|
22
22
|
|
|
23
|
-
await space.open(
|
|
23
|
+
await space.open(Context.default());
|
|
24
24
|
expect(space.isOpen).toBeTruthy();
|
|
25
25
|
afterTest(() => space.close());
|
|
26
26
|
|
|
@@ -43,7 +43,7 @@ describe('space/space', () => {
|
|
|
43
43
|
const agent = await builder.createPeer();
|
|
44
44
|
const space = await agent.createSpace(agent.identityKey);
|
|
45
45
|
|
|
46
|
-
await space.open(
|
|
46
|
+
await space.open(Context.default());
|
|
47
47
|
expect(space.isOpen).toBeTruthy();
|
|
48
48
|
afterTest(() => space.close());
|
|
49
49
|
|
|
@@ -62,7 +62,7 @@ describe('space/space', () => {
|
|
|
62
62
|
const agent = await builder.createPeer();
|
|
63
63
|
const space = await agent.createSpace(agent.identityKey, space1.key, space1.genesisFeedKey, undefined, true);
|
|
64
64
|
|
|
65
|
-
await space.open(
|
|
65
|
+
await space.open(Context.default());
|
|
66
66
|
expect(space.isOpen).toBeTruthy();
|
|
67
67
|
afterTest(() => space.close());
|
|
68
68
|
|
|
@@ -114,7 +114,7 @@ describe('space/space', () => {
|
|
|
114
114
|
const agent = await builder.createPeer();
|
|
115
115
|
const space1 = await agent.createSpace();
|
|
116
116
|
|
|
117
|
-
await space1.open(
|
|
117
|
+
await space1.open(Context.default());
|
|
118
118
|
expect(space1.isOpen).toBeTruthy();
|
|
119
119
|
afterTest(() => space1.close());
|
|
120
120
|
|
|
@@ -128,7 +128,7 @@ describe('space/space', () => {
|
|
|
128
128
|
// Re-open.
|
|
129
129
|
const space2 = await agent.createSpace(agent.identityKey, space1.key, space1.genesisFeedKey, space1.dataFeedKey);
|
|
130
130
|
|
|
131
|
-
await space2.open(
|
|
131
|
+
await space2.open(Context.default());
|
|
132
132
|
await space2.controlPipeline.state!.waitUntilTimeframe(space2.controlPipeline.state!.endTimeframe);
|
|
133
133
|
});
|
|
134
134
|
|
|
@@ -139,7 +139,7 @@ describe('space/space', () => {
|
|
|
139
139
|
const space = await agent.createSpace();
|
|
140
140
|
|
|
141
141
|
{
|
|
142
|
-
await space.open(
|
|
142
|
+
await space.open(Context.default());
|
|
143
143
|
afterTest(() => space.close());
|
|
144
144
|
expect(space.isOpen).toBeTruthy();
|
|
145
145
|
|
|
@@ -153,7 +153,7 @@ describe('space/space', () => {
|
|
|
153
153
|
|
|
154
154
|
// Re-open.
|
|
155
155
|
{
|
|
156
|
-
await space.open(
|
|
156
|
+
await space.open(Context.default());
|
|
157
157
|
expect(space.isOpen).toBeTruthy();
|
|
158
158
|
|
|
159
159
|
await space.controlPipeline.state!.waitUntilTimeframe(space.controlPipeline.state!.endTimeframe);
|
package/src/space/space.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Event, Mutex, synchronized, trackLeaks } from '@dxos/async';
|
|
6
|
-
import { type Context } from '@dxos/context';
|
|
7
|
-
import { type FeedInfo } from '@dxos/credentials';
|
|
6
|
+
import { Resource, type Context, LifecycleState } from '@dxos/context';
|
|
7
|
+
import { type FeedInfo, type DelegateInvitationCredential } from '@dxos/credentials';
|
|
8
8
|
import { type FeedOptions, type FeedWrapper } from '@dxos/feed-store';
|
|
9
9
|
import { invariant } from '@dxos/invariant';
|
|
10
10
|
import { type PublicKey } from '@dxos/keys';
|
|
@@ -35,6 +35,8 @@ export type SpaceParams = {
|
|
|
35
35
|
|
|
36
36
|
// TODO(dmaretskyi): Superseded by epochs.
|
|
37
37
|
snapshotId?: string | undefined;
|
|
38
|
+
|
|
39
|
+
onDelegatedInvitationStatusChange: (invitation: DelegateInvitationCredential, isActive: boolean) => Promise<void>;
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
export type CreatePipelineParams = {
|
|
@@ -48,7 +50,7 @@ export type CreatePipelineParams = {
|
|
|
48
50
|
// TODO(dmaretskyi): Extract database stuff.
|
|
49
51
|
@trackLeaks('open', 'close')
|
|
50
52
|
@trace.resource()
|
|
51
|
-
export class Space {
|
|
53
|
+
export class Space extends Resource {
|
|
52
54
|
private readonly _addFeedMutex = new Mutex();
|
|
53
55
|
|
|
54
56
|
public readonly onCredentialProcessed = new Callback<AsyncCallback<Credential>>();
|
|
@@ -64,11 +66,11 @@ export class Space {
|
|
|
64
66
|
|
|
65
67
|
private readonly _snapshotManager: SnapshotManager;
|
|
66
68
|
|
|
67
|
-
private _isOpen = false;
|
|
68
69
|
private _controlFeed?: FeedWrapper<FeedMessage>;
|
|
69
70
|
private _dataFeed?: FeedWrapper<FeedMessage>;
|
|
70
71
|
|
|
71
72
|
constructor(params: SpaceParams) {
|
|
73
|
+
super();
|
|
72
74
|
invariant(params.spaceKey && params.feedProvider);
|
|
73
75
|
this._key = params.spaceKey;
|
|
74
76
|
this._genesisFeedKey = params.genesisFeed.key;
|
|
@@ -99,6 +101,14 @@ export class Space {
|
|
|
99
101
|
log('onCredentialProcessed', { credential });
|
|
100
102
|
this.stateUpdate.emit();
|
|
101
103
|
});
|
|
104
|
+
this._controlPipeline.onDelegatedInvitation.set(async (invitation) => {
|
|
105
|
+
log('onDelegatedInvitation', { invitation });
|
|
106
|
+
await params.onDelegatedInvitationStatusChange(invitation, true);
|
|
107
|
+
});
|
|
108
|
+
this._controlPipeline.onDelegatedInvitationRemoved.set(async (invitation) => {
|
|
109
|
+
log('onDelegatedInvitationRemoved', { invitation });
|
|
110
|
+
await params.onDelegatedInvitationStatusChange(invitation, false);
|
|
111
|
+
});
|
|
102
112
|
|
|
103
113
|
// Start replicating the genesis feed.
|
|
104
114
|
this.protocol = params.protocol;
|
|
@@ -112,7 +122,7 @@ export class Space {
|
|
|
112
122
|
}
|
|
113
123
|
|
|
114
124
|
get isOpen() {
|
|
115
|
-
return this.
|
|
125
|
+
return this._lifecycleState === LifecycleState.OPEN;
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
get genesisFeedKey(): PublicKey {
|
|
@@ -162,40 +172,25 @@ export class Space {
|
|
|
162
172
|
return Array.from(this._controlPipeline.spaceState.feeds.values());
|
|
163
173
|
}
|
|
164
174
|
|
|
165
|
-
/**
|
|
166
|
-
* Use for diagnostics.
|
|
167
|
-
*/
|
|
168
|
-
// getDataFeeds(): FeedInfo[] {
|
|
169
|
-
// return this._dataPipeline?.getFeeds();
|
|
170
|
-
// }
|
|
171
|
-
@synchronized
|
|
172
175
|
@trace.span()
|
|
173
|
-
async
|
|
176
|
+
protected override async _open(ctx: Context) {
|
|
174
177
|
log('opening...');
|
|
175
|
-
if (this._isOpen) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
178
|
|
|
179
179
|
// Order is important.
|
|
180
180
|
await this._controlPipeline.start();
|
|
181
181
|
await this.protocol.start();
|
|
182
182
|
|
|
183
|
-
this._isOpen = true;
|
|
184
183
|
log('opened');
|
|
185
184
|
}
|
|
186
185
|
|
|
187
186
|
@synchronized
|
|
188
|
-
async
|
|
187
|
+
protected override async _close() {
|
|
189
188
|
log('closing...', { key: this._key });
|
|
190
|
-
if (!this._isOpen) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
189
|
|
|
194
190
|
// Closes in reverse order to open.
|
|
195
191
|
await this.protocol.stop();
|
|
196
192
|
await this._controlPipeline.stop();
|
|
197
193
|
|
|
198
|
-
this._isOpen = false;
|
|
199
194
|
log('closed');
|
|
200
195
|
}
|
|
201
196
|
}
|
package/src/testing/index.ts
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Level } from 'level';
|
|
6
|
+
|
|
7
|
+
import { PublicKey } from '@dxos/keys';
|
|
8
|
+
|
|
9
|
+
import { type LevelDB } from '../automerge/types';
|
|
10
|
+
|
|
11
|
+
export const createTestLevel = (): LevelDB => new Level<string, string>(`/tmp/dxos-${PublicKey.random().toHex()}`);
|
|
@@ -190,6 +190,7 @@ export class TestAgent {
|
|
|
190
190
|
this.createGossip().createExtension({ remotePeerId: session.remotePeerId }),
|
|
191
191
|
);
|
|
192
192
|
},
|
|
193
|
+
onDelegatedInvitationStatusChange: async () => {},
|
|
193
194
|
});
|
|
194
195
|
await space.setControlFeed(controlFeed);
|
|
195
196
|
await space.setDataFeed(dataFeed);
|