@dxos/echo-pipeline 0.1.41 → 0.1.45-next.45667d4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/lib/browser/{chunk-4YAT2XJW.mjs → chunk-2NGLSBL4.mjs} +1128 -1017
  2. package/dist/lib/browser/chunk-2NGLSBL4.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1 -3
  4. package/dist/lib/browser/index.mjs.map +1 -1
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +72 -48
  7. package/dist/lib/browser/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/index.cjs +1048 -938
  9. package/dist/lib/node/index.cjs.map +4 -4
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/testing/index.cjs +735 -540
  12. package/dist/lib/node/testing/index.cjs.map +4 -4
  13. package/dist/types/src/dbhost/database-host.d.ts +2 -4
  14. package/dist/types/src/dbhost/database-host.d.ts.map +1 -1
  15. package/dist/types/src/dbhost/snapshot-store.d.ts +2 -0
  16. package/dist/types/src/dbhost/snapshot-store.d.ts.map +1 -1
  17. package/dist/types/src/metadata/metadata-store.d.ts +3 -2
  18. package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
  19. package/dist/types/src/pipeline/pipeline.d.ts +27 -11
  20. package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
  21. package/dist/types/src/pipeline/timeframe-clock.d.ts +1 -0
  22. package/dist/types/src/pipeline/timeframe-clock.d.ts.map +1 -1
  23. package/dist/types/src/space/auth.d.ts +4 -4
  24. package/dist/types/src/space/auth.d.ts.map +1 -1
  25. package/dist/types/src/space/control-pipeline.d.ts +2 -4
  26. package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
  27. package/dist/types/src/space/data-pipeline.d.ts +19 -10
  28. package/dist/types/src/space/data-pipeline.d.ts.map +1 -1
  29. package/dist/types/src/space/index.d.ts +0 -1
  30. package/dist/types/src/space/index.d.ts.map +1 -1
  31. package/dist/types/src/space/space-manager.d.ts +14 -14
  32. package/dist/types/src/space/space-manager.d.ts.map +1 -1
  33. package/dist/types/src/space/space-protocol.d.ts +2 -2
  34. package/dist/types/src/space/space-protocol.d.ts.map +1 -1
  35. package/dist/types/src/space/space.d.ts +20 -7
  36. package/dist/types/src/space/space.d.ts.map +1 -1
  37. package/dist/types/src/testing/database-test-rig.d.ts.map +1 -1
  38. package/dist/types/src/testing/test-agent-builder.d.ts +15 -5
  39. package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
  40. package/package.json +30 -30
  41. package/src/common/feeds.ts +2 -2
  42. package/src/dbhost/data-service-host.ts +10 -10
  43. package/src/dbhost/data-service.ts +1 -1
  44. package/src/dbhost/database-host.ts +1 -9
  45. package/src/dbhost/snapshot-store.test.ts +2 -2
  46. package/src/dbhost/snapshot-store.ts +16 -1
  47. package/src/metadata/metadata-store.ts +14 -16
  48. package/src/pipeline/message-selector.ts +1 -1
  49. package/src/pipeline/pipeline.test.ts +68 -44
  50. package/src/pipeline/pipeline.ts +118 -35
  51. package/src/pipeline/timeframe-clock.ts +8 -2
  52. package/src/space/auth.ts +6 -6
  53. package/src/space/control-pipeline.test.ts +19 -20
  54. package/src/space/control-pipeline.ts +3 -5
  55. package/src/space/data-pipeline.ts +131 -69
  56. package/src/space/index.ts +0 -1
  57. package/src/space/replication.browser.test.ts +8 -8
  58. package/src/space/space-manager.browser.test.ts +3 -3
  59. package/src/space/space-manager.ts +25 -20
  60. package/src/space/space-protocol.browser.test.ts +1 -1
  61. package/src/space/space-protocol.test.ts +6 -6
  62. package/src/space/space-protocol.ts +7 -7
  63. package/src/space/space.test.ts +137 -48
  64. package/src/space/space.ts +82 -40
  65. package/src/testing/database-test-rig.ts +25 -24
  66. package/src/testing/test-agent-builder.ts +88 -49
  67. package/src/testing/test-feed-builder.ts +1 -1
  68. package/src/testing/util.ts +15 -15
  69. package/src/tests/database-unit.test.ts +4 -4
  70. package/src/tests/database.test.ts +1 -1
  71. package/dist/lib/browser/chunk-4YAT2XJW.mjs.map +0 -7
  72. package/dist/types/src/space/genesis.d.ts +0 -5
  73. package/dist/types/src/space/genesis.d.ts.map +0 -1
  74. package/src/space/genesis.ts +0 -69
@@ -4,19 +4,21 @@
4
4
 
5
5
  import assert from 'node:assert';
6
6
 
7
- import { scheduleTask, synchronized, trackLeaks } from '@dxos/async';
7
+ import { Event, scheduleTask, synchronized, trackLeaks } from '@dxos/async';
8
8
  import { Context } from '@dxos/context';
9
- import { FeedInfo } from '@dxos/credentials';
10
- import { failUndefined } from '@dxos/debug';
11
- import { ItemManager } from '@dxos/echo-db';
9
+ import { CredentialProcessor, FeedInfo, getCredentialAssertion } from '@dxos/credentials';
10
+ import { getStateMachineFromItem, ItemManager } from '@dxos/echo-db';
11
+ import { FeedWriter } from '@dxos/feed-store';
12
12
  import { PublicKey } from '@dxos/keys';
13
13
  import { log } from '@dxos/log';
14
14
  import { ModelFactory } from '@dxos/model-factory';
15
- import { DataMessage, FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
15
+ import { DataMessage } from '@dxos/protocols/proto/dxos/echo/feed';
16
+ import { SpaceCache } from '@dxos/protocols/proto/dxos/echo/metadata';
17
+ import { ObjectSnapshot } from '@dxos/protocols/proto/dxos/echo/model/document';
16
18
  import { SpaceSnapshot } from '@dxos/protocols/proto/dxos/echo/snapshot';
19
+ import { Credential, Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
17
20
  import { Timeframe } from '@dxos/timeframe';
18
21
 
19
- import { createMappedFeedWriter } from '../common';
20
22
  import { DatabaseHost, SnapshotManager } from '../dbhost';
21
23
  import { MetadataStore } from '../metadata';
22
24
  import { Pipeline } from '../pipeline';
@@ -33,6 +35,11 @@ export type DataPipelineParams = {
33
35
  spaceKey: PublicKey;
34
36
  feedInfoProvider: (feedKey: PublicKey) => FeedInfo | undefined;
35
37
  snapshotId: string | undefined;
38
+
39
+ /**
40
+ * Called once.
41
+ */
42
+ onPipelineCreated: (pipeline: Pipeline) => Promise<void>;
36
43
  };
37
44
 
38
45
  /**
@@ -50,11 +57,6 @@ const AUTOMATIC_SNAPSHOT_DEBOUNCE_INTERVAL = 5000;
50
57
  */
51
58
  const TIMEFRAME_SAVE_DEBOUNCE_INTERVAL = 500;
52
59
 
53
- /**
54
- * Flag to disable automatic local snapshots.
55
- */
56
- const DISABLE_SNAPSHOT_CACHE = true;
57
-
58
60
  /**
59
61
  * Controls data pipeline in the space.
60
62
  * Consumes the pipeline and updates the database.
@@ -63,9 +65,7 @@ const DISABLE_SNAPSHOT_CACHE = true;
63
65
  @trackLeaks('open', 'close')
64
66
  export class DataPipeline {
65
67
  private _ctx = new Context();
66
- private _spaceContext!: PipelineFactory;
67
68
  private _pipeline?: Pipeline;
68
- private _snapshot?: SpaceSnapshot;
69
69
  private _targetTimeframe?: Timeframe;
70
70
 
71
71
  private _lastAutomaticSnapshotTimeframe = new Timeframe();
@@ -76,23 +76,22 @@ export class DataPipeline {
76
76
 
77
77
  constructor(private readonly _params: DataPipelineParams) {}
78
78
 
79
- public _itemManager!: ItemManager;
80
- public databaseBackend?: DatabaseHost;
79
+ public itemManager!: ItemManager;
80
+ public databaseHost?: DatabaseHost;
81
+
82
+ public currentEpoch?: Credential;
83
+ public onNewEpoch = new Event<Credential>();
81
84
 
82
85
  get isOpen() {
83
86
  return this._isOpen;
84
87
  }
85
88
 
86
- get pipelineState() {
87
- return this._pipeline?.state;
88
- }
89
-
90
- get snapshotTimeframe() {
91
- return this._snapshot?.timeframe;
89
+ get pipeline() {
90
+ return this._pipeline;
92
91
  }
93
92
 
94
- getStartTimeframe(): Timeframe {
95
- return snapshotTimeframeToStartingTimeframe(this.snapshotTimeframe ?? new Timeframe());
93
+ get pipelineState() {
94
+ return this._pipeline?.state;
96
95
  }
97
96
 
98
97
  setTargetTimeframe(timeframe: Timeframe) {
@@ -100,34 +99,52 @@ export class DataPipeline {
100
99
  this._pipeline?.state.setTargetTimeframe(timeframe);
101
100
  }
102
101
 
102
+ createCredentialProcessor(): CredentialProcessor {
103
+ return {
104
+ process: async (credential) => {
105
+ const assertion = getCredentialAssertion(credential);
106
+ if (assertion['@type'] !== 'dxos.halo.credentials.Epoch') {
107
+ return;
108
+ }
109
+
110
+ log('new epoch', { credential });
111
+ await this._processEpoch(assertion);
112
+
113
+ this.currentEpoch = credential;
114
+ this.onNewEpoch.emit(credential);
115
+ },
116
+ };
117
+ }
118
+
103
119
  @synchronized
104
- async open(spaceContext: PipelineFactory) {
120
+ async open() {
105
121
  if (this._isOpen) {
106
122
  return;
107
123
  }
108
124
 
109
- this._spaceContext = spaceContext;
110
- if (this._params.snapshotId && !DISABLE_SNAPSHOT_CACHE) {
111
- this._snapshot = await this._params.snapshotManager.load(this._params.snapshotId);
112
- this._lastAutomaticSnapshotTimeframe = this._snapshot?.timeframe ?? new Timeframe();
113
- }
125
+ this._pipeline = new Pipeline();
126
+ await this._params.onPipelineCreated(this._pipeline);
127
+ await this._pipeline.start();
128
+ await this._pipeline.pause(); // Start paused until we have the first epoch.
114
129
 
115
- this._pipeline = await this._spaceContext.openPipeline(this.getStartTimeframe());
116
130
  if (this._targetTimeframe) {
117
131
  this._pipeline.state.setTargetTimeframe(this._targetTimeframe);
118
132
  }
119
133
 
120
134
  // Create database backend.
121
- const feedWriter = createMappedFeedWriter<DataMessage, FeedMessage.Payload>(
122
- (data: DataMessage) => ({ data }),
123
- this._pipeline.writer ?? failUndefined()
124
- );
135
+ const feedWriter: FeedWriter<DataMessage> = {
136
+ write: (data, options) => {
137
+ assert(this._pipeline, 'Pipeline is not initialized.');
138
+ assert(this.currentEpoch, 'Epoch is not initialized.');
139
+ return this._pipeline.writer.write({ data }, options);
140
+ },
141
+ };
125
142
 
126
- this.databaseBackend = new DatabaseHost(feedWriter, this._snapshot?.database);
127
- this._itemManager = new ItemManager(this._params.modelFactory);
143
+ this.databaseHost = new DatabaseHost(feedWriter);
144
+ this.itemManager = new ItemManager(this._params.modelFactory);
128
145
 
129
146
  // Connect pipeline to the database.
130
- await this.databaseBackend.open(this._itemManager, this._params.modelFactory);
147
+ await this.databaseHost.open(this.itemManager, this._params.modelFactory);
131
148
 
132
149
  // Start message processing loop.
133
150
  scheduleTask(this._ctx, async () => {
@@ -150,18 +167,16 @@ export class DataPipeline {
150
167
 
151
168
  // NOTE: Make sure the processing is stopped BEFORE we save the snapshot.
152
169
  try {
170
+ await this._saveCache();
153
171
  if (this._pipeline) {
154
172
  await this._saveTargetTimeframe(this._pipeline.state.timeframe);
155
- if (!DISABLE_SNAPSHOT_CACHE) {
156
- await this._saveSnapshot(this._pipeline.state.timeframe);
157
- }
158
173
  }
159
174
  } catch (err) {
160
175
  log.catch(err);
161
176
  }
162
177
 
163
- await this.databaseBackend?.close();
164
- await this._itemManager?.destroy();
178
+ await this.databaseHost?.close();
179
+ await this.itemManager?.destroy();
165
180
  await this._params.snapshotManager.close();
166
181
  }
167
182
 
@@ -169,7 +184,7 @@ export class DataPipeline {
169
184
  assert(this._pipeline, 'Pipeline is not initialized.');
170
185
  for await (const msg of this._pipeline.consume()) {
171
186
  const { feedKey, seq, data } = msg;
172
- log('processing message', { msg });
187
+ log('processing message', { feedKey, seq });
173
188
 
174
189
  try {
175
190
  if (data.payload.data) {
@@ -179,14 +194,14 @@ export class DataPipeline {
179
194
  continue;
180
195
  }
181
196
 
182
- await this.databaseBackend!.echoProcessor({
197
+ await this.databaseHost!.echoProcessor({
183
198
  batch: data.payload.data.batch,
184
199
  meta: {
185
200
  feedKey,
186
201
  seq,
187
202
  timeframe: data.timeframe,
188
- memberKey: feedInfo.assertion.identityKey
189
- }
203
+ memberKey: feedInfo.assertion.identityKey,
204
+ },
190
205
  });
191
206
 
192
207
  // Timeframe clock is not updated yet
@@ -198,29 +213,45 @@ export class DataPipeline {
198
213
  }
199
214
  }
200
215
 
201
- private _createSnapshot(timeframe: Timeframe): SpaceSnapshot {
202
- assert(this.databaseBackend, 'Database backend is not initialized.');
216
+ private _createSnapshot(): SpaceSnapshot {
217
+ assert(this.databaseHost, 'Database backend is not initialized.');
203
218
  return {
204
219
  spaceKey: this._params.spaceKey.asUint8Array(),
205
- timeframe,
206
- database: this.databaseBackend!.createSnapshot()
220
+ timeframe: this._pipeline!.state.timeframe,
221
+ database: this.databaseHost!.createSnapshot(),
207
222
  };
208
223
  }
209
224
 
210
- private async _saveSnapshot(timeframe: Timeframe) {
211
- const snapshot = await this._createSnapshot(timeframe);
212
- const snapshotKey = await this._params.snapshotManager.store(snapshot);
213
- await this._params.metadataStore.setSpaceSnapshot(this._params.spaceKey, snapshotKey);
214
- return snapshot;
215
- }
216
-
217
225
  private async _saveTargetTimeframe(timeframe: Timeframe) {
218
226
  const newTimeframe = Timeframe.merge(this._targetTimeframe ?? new Timeframe(), timeframe);
219
227
  await this._params.metadataStore.setSpaceLatestTimeframe(this._params.spaceKey, newTimeframe);
220
228
  this._targetTimeframe = newTimeframe;
221
229
  }
222
230
 
231
+ private async _saveCache() {
232
+ const cache: SpaceCache = {};
233
+
234
+ try {
235
+ // Add properties to cache.
236
+ const propertiesItem = this.itemManager.items.find(
237
+ (item) =>
238
+ item.modelMeta?.type === 'dxos:model/document' &&
239
+ (getStateMachineFromItem(item)?.snapshot() as ObjectSnapshot).type === 'dxos.sdk.client.Properties',
240
+ );
241
+ if (propertiesItem) {
242
+ cache.properties = getStateMachineFromItem(propertiesItem)?.snapshot() as ObjectSnapshot;
243
+ }
244
+ } catch (err) {
245
+ log.warn('Failed to cache properties', err);
246
+ }
247
+
248
+ // Save cache.
249
+ await this._params.metadataStore.setCache(this._params.spaceKey, cache);
250
+ }
251
+
223
252
  private async _noteTargetStateIfNeeded(timeframe: Timeframe) {
253
+ // TODO(dmaretskyi): Replace this with a proper debounce/throttle.
254
+
224
255
  if (Date.now() - this._lastTimeframeSaveTime > TIMEFRAME_SAVE_DEBOUNCE_INTERVAL) {
225
256
  this._lastTimeframeSaveTime = Date.now();
226
257
 
@@ -228,27 +259,58 @@ export class DataPipeline {
228
259
  }
229
260
 
230
261
  if (
231
- !DISABLE_SNAPSHOT_CACHE &&
232
262
  Date.now() - this._lastSnapshotSaveTime > AUTOMATIC_SNAPSHOT_DEBOUNCE_INTERVAL &&
233
263
  timeframe.totalMessages() - this._lastAutomaticSnapshotTimeframe.totalMessages() > MESSAGES_PER_SNAPSHOT
234
264
  ) {
235
- this._lastSnapshotSaveTime = Date.now();
265
+ await this._saveCache();
266
+ }
267
+ }
236
268
 
237
- const snapshot = await this._saveSnapshot(timeframe);
238
- this._lastAutomaticSnapshotTimeframe = snapshot.timeframe ?? failUndefined();
239
- log('save', { snapshot });
269
+ @synchronized
270
+ private async _processEpoch(epoch: Epoch) {
271
+ assert(this._isOpen); // TODO: In the future we might process epochs before we are open so that data pipeline starts from the last one.
272
+ assert(this._pipeline);
273
+
274
+ if (epoch.snapshotCid) {
275
+ const snapshot = await this._params.snapshotManager.load(epoch.snapshotCid);
276
+
277
+ // TODO(dmaretskyi): Clearing old items + events.
278
+ this.databaseHost!._itemDemuxer.restoreFromSnapshot(snapshot.database);
240
279
  }
280
+
281
+ await this._pipeline.pause();
282
+ await this._pipeline.setCursor(epoch.timeframe);
283
+ await this._pipeline.unpause();
241
284
  }
242
285
 
243
286
  async waitUntilTimeframe(timeframe: Timeframe) {
244
287
  assert(this._pipeline, 'Pipeline is not initialized.');
245
288
  await this._pipeline.state.waitUntilTimeframe(timeframe);
246
289
  }
247
- }
248
290
 
249
- /**
250
- * Increase all indexes by one so that we start processing the next mutation after the one in the snapshot.
251
- */
252
- const snapshotTimeframeToStartingTimeframe = (snapshotTimeframe: Timeframe) => {
253
- return snapshotTimeframe.map(([key, seq]) => [key, seq + 1]);
254
- };
291
+ @synchronized
292
+ async createEpoch(): Promise<Epoch> {
293
+ assert(this._pipeline);
294
+ assert(this.currentEpoch);
295
+
296
+ await this._pipeline.pause();
297
+
298
+ const snapshot = await this._createSnapshot();
299
+ const snapshotCid = await this._params.snapshotManager.store(snapshot);
300
+
301
+ const epoch: Epoch = {
302
+ previousId: this.currentEpoch.id,
303
+ timeframe: this._pipeline.state.timeframe,
304
+ number: (this.currentEpoch.subject.assertion as Epoch).number + 1,
305
+ snapshotCid,
306
+ };
307
+
308
+ await this._pipeline.unpause();
309
+
310
+ return epoch;
311
+ }
312
+
313
+ async ensureEpochInitialized() {
314
+ await this.onNewEpoch.waitForCondition(() => !!this.currentEpoch);
315
+ }
316
+ }
@@ -6,5 +6,4 @@ export * from './auth';
6
6
  export * from './space';
7
7
  export * from './space-manager';
8
8
  export * from './space-protocol';
9
- export * from './genesis';
10
9
  export * from './data-pipeline';
@@ -28,9 +28,9 @@ describe('replication', () => {
28
28
  root: storage.createDirectory('feeds1'),
29
29
  signer: keyring1,
30
30
  hypercore: {
31
- valueEncoding
32
- }
33
- })
31
+ valueEncoding,
32
+ },
33
+ }),
34
34
  });
35
35
 
36
36
  const keyring2 = new Keyring();
@@ -39,13 +39,13 @@ describe('replication', () => {
39
39
  root: storage.createDirectory('feeds2'),
40
40
  signer: keyring2,
41
41
  hypercore: {
42
- valueEncoding
43
- }
44
- })
42
+ valueEncoding,
43
+ },
44
+ }),
45
45
  });
46
46
 
47
47
  const feed1 = await feedStore1.openFeed(await keyring1.createKey(), {
48
- writable: true
48
+ writable: true,
49
49
  });
50
50
  const feed2 = await feedStore2.openFeed(feed1.key);
51
51
 
@@ -54,7 +54,7 @@ describe('replication', () => {
54
54
  stream1.pipe(stream2).pipe(stream1);
55
55
 
56
56
  await feed1.append({
57
- timeframe: new Timeframe([[feed1.key, 123]])
57
+ timeframe: new Timeframe([[feed1.key, 123]]),
58
58
  });
59
59
 
60
60
  await waitForExpect(() => {
@@ -17,16 +17,16 @@ describe('space-manager', () => {
17
17
  test.skip('invitations', async () => {
18
18
  const builder = new TestAgentBuilder({
19
19
  storage: createStorage(),
20
- networkManagerProvider: WebsocketNetworkManagerProvider(SIGNAL_URL)
20
+ networkManagerProvider: WebsocketNetworkManagerProvider(SIGNAL_URL),
21
21
  });
22
22
  afterTest(async () => await builder.close());
23
23
 
24
24
  const peer1 = await builder.createPeer();
25
- const spaceManager1 = peer1.createSpaceManager();
25
+ const spaceManager1 = peer1.spaceManager;
26
26
  await spaceManager1.open();
27
27
 
28
28
  const peer2 = await builder.createPeer();
29
- const spaceManager2 = peer2.createSpaceManager();
29
+ const spaceManager2 = peer2.spaceManager;
30
30
  await spaceManager2.open();
31
31
 
32
32
  afterTest(() => spaceManager1.close());
@@ -3,39 +3,35 @@
3
3
  //
4
4
 
5
5
  import { synchronized, trackLeaks } from '@dxos/async';
6
- import { CredentialSigner } from '@dxos/credentials';
7
6
  import { failUndefined } from '@dxos/debug';
8
7
  import { FeedStore } from '@dxos/feed-store';
9
8
  import { PublicKey } from '@dxos/keys';
10
9
  import { log } from '@dxos/log';
10
+ import { ModelFactory } from '@dxos/model-factory';
11
11
  import { NetworkManager } from '@dxos/network-manager';
12
12
  import { trace } from '@dxos/protocols';
13
13
  import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
14
14
  import { SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
15
- import { Credential, ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
16
15
  import { Teleport } from '@dxos/teleport';
17
16
  import { ComplexMap } from '@dxos/util';
18
17
 
18
+ import { SnapshotManager, SnapshotStore } from '../dbhost';
19
+ import { MetadataStore } from '../metadata';
19
20
  import { Space } from './space';
20
21
  import { SpaceProtocol, SwarmIdentity } from './space-protocol';
21
22
 
22
- // TODO(burdon): Factor out to DataSpaceManager
23
- export interface SigningContext {
24
- identityKey: PublicKey;
25
- deviceKey: PublicKey;
26
- credentialSigner: CredentialSigner; // TODO(burdon): Already has keyring.
27
- recordCredential: (credential: Credential) => Promise<void>;
28
- profile?: ProfileDocument;
29
- }
30
-
31
23
  export type SpaceManagerParams = {
32
24
  feedStore: FeedStore<FeedMessage>;
33
25
  networkManager: NetworkManager;
26
+ modelFactory: ModelFactory;
27
+ metadataStore: MetadataStore;
28
+ snapshotStore: SnapshotStore;
34
29
  };
35
30
 
36
31
  export type ConstructSpaceParams = {
37
32
  metadata: SpaceMetadata;
38
33
  swarmIdentity: SwarmIdentity;
34
+ memberKey: PublicKey;
39
35
  onNetworkConnection: (session: Teleport) => void;
40
36
  };
41
37
 
@@ -47,13 +43,18 @@ export class SpaceManager {
47
43
  private readonly _spaces = new ComplexMap<PublicKey, Space>(PublicKey.hash);
48
44
  private readonly _feedStore: FeedStore<FeedMessage>;
49
45
  private readonly _networkManager: NetworkManager;
46
+ private readonly _modelFactory: ModelFactory;
47
+ private readonly _metadataStore: MetadataStore;
48
+ private readonly _snapshotStore: SnapshotStore;
50
49
  private readonly _instanceId = PublicKey.random().toHex();
51
- public _traceParent?: string;
52
50
 
53
- constructor({ feedStore, networkManager }: SpaceManagerParams) {
51
+ constructor({ feedStore, networkManager, modelFactory, metadataStore, snapshotStore }: SpaceManagerParams) {
54
52
  // TODO(burdon): Assert.
55
53
  this._feedStore = feedStore;
56
54
  this._networkManager = networkManager;
55
+ this._modelFactory = modelFactory;
56
+ this._metadataStore = metadataStore;
57
+ this._snapshotStore = snapshotStore;
57
58
  }
58
59
 
59
60
  // TODO(burdon): Remove.
@@ -62,17 +63,15 @@ export class SpaceManager {
62
63
  }
63
64
 
64
65
  @synchronized
65
- async open() {
66
- log.trace('dxos.echo.space-manager', trace.begin({ id: this._instanceId, parentId: this._traceParent }));
67
- }
66
+ async open() {}
68
67
 
69
68
  @synchronized
70
69
  async close() {
71
70
  await Promise.all([...this._spaces.values()].map((space) => space.close()));
72
- log.trace('dxos.echo.space-manager', trace.end({ id: this._instanceId }));
73
71
  }
74
72
 
75
- async constructSpace({ metadata, swarmIdentity, onNetworkConnection }: ConstructSpaceParams) {
73
+ async constructSpace({ metadata, swarmIdentity, onNetworkConnection, memberKey }: ConstructSpaceParams) {
74
+ log.trace('dxos.echo.space-manager.construct-space', trace.begin({ id: this._instanceId }));
76
75
  log('constructing space...', { spaceKey: metadata.genesisFeedKey });
77
76
 
78
77
  // The genesis feed will be the same as the control feed if the space was created by the local agent.
@@ -83,16 +82,22 @@ export class SpaceManager {
83
82
  topic: spaceKey,
84
83
  swarmIdentity,
85
84
  networkManager: this._networkManager,
86
- onSessionAuth: onNetworkConnection
85
+ onSessionAuth: onNetworkConnection,
87
86
  });
87
+ const snapshotManager = new SnapshotManager(this._snapshotStore);
88
88
 
89
89
  const space = new Space({
90
90
  spaceKey,
91
91
  protocol,
92
92
  genesisFeed,
93
- feedProvider: (feedKey) => this._feedStore.openFeed(feedKey)
93
+ feedProvider: (feedKey) => this._feedStore.openFeed(feedKey),
94
+ modelFactory: this._modelFactory,
95
+ metadataStore: this._metadataStore,
96
+ snapshotManager,
97
+ memberKey,
94
98
  });
95
99
  this._spaces.set(space.key, space);
100
+ log.trace('dxos.echo.space-manager.construct-space', trace.end({ id: this._instanceId }));
96
101
  return space;
97
102
  }
98
103
  }
@@ -93,7 +93,7 @@ describe('space/space-protocol', () => {
93
93
  test('replicates a feed through a webrtc connection', async () => {
94
94
  const builder = new TestAgentBuilder({
95
95
  storage: createStorage(),
96
- networkManagerProvider: WebsocketNetworkManagerProvider(SIGNAL_URL)
96
+ networkManagerProvider: WebsocketNetworkManagerProvider(SIGNAL_URL),
97
97
  });
98
98
  afterTest(async () => await builder.close());
99
99
 
@@ -51,12 +51,12 @@ describe('space/space-protocol', () => {
51
51
  swarmIdentity: {
52
52
  peerKey: peerId1,
53
53
  credentialProvider: MOCK_AUTH_PROVIDER,
54
- credentialAuthenticator: async () => false // Reject everyone.
54
+ credentialAuthenticator: async () => false, // Reject everyone.
55
55
  },
56
56
  networkManager: new NetworkManager({
57
57
  signalManager: new MemorySignalManager(signalContext),
58
- transportFactory: MemoryTransportFactory
59
- })
58
+ transportFactory: MemoryTransportFactory,
59
+ }),
60
60
  });
61
61
 
62
62
  const protocol2 = new SpaceProtocol({
@@ -64,12 +64,12 @@ describe('space/space-protocol', () => {
64
64
  swarmIdentity: {
65
65
  peerKey: peerId2,
66
66
  credentialProvider: MOCK_AUTH_PROVIDER,
67
- credentialAuthenticator: MOCK_AUTH_VERIFIER
67
+ credentialAuthenticator: MOCK_AUTH_VERIFIER,
68
68
  },
69
69
  networkManager: new NetworkManager({
70
70
  signalManager: new MemorySignalManager(signalContext),
71
- transportFactory: MemoryTransportFactory
72
- })
71
+ transportFactory: MemoryTransportFactory,
72
+ }),
73
73
  });
74
74
 
75
75
  await protocol1.start();
@@ -12,7 +12,7 @@ import {
12
12
  SwarmConnection,
13
13
  WireProtocol,
14
14
  WireProtocolParams,
15
- WireProtocolProvider
15
+ WireProtocolProvider,
16
16
  } from '@dxos/network-manager';
17
17
  import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
18
18
  import { Teleport } from '@dxos/teleport';
@@ -101,7 +101,7 @@ export class SpaceProtocol {
101
101
  const topologyConfig = {
102
102
  originateConnections: 4,
103
103
  maxPeers: 10,
104
- sampleSize: 20
104
+ sampleSize: 20,
105
105
  };
106
106
 
107
107
  log('starting...');
@@ -110,7 +110,7 @@ export class SpaceProtocol {
110
110
  peerId: this._swarmIdentity.peerKey,
111
111
  topic: this._topic,
112
112
  topology: new MMSTTopology(topologyConfig),
113
- label: `Protocol swarm: ${this._topic}`
113
+ label: `Protocol swarm: ${this._topic}`,
114
114
  });
115
115
 
116
116
  log('started');
@@ -129,7 +129,7 @@ export class SpaceProtocol {
129
129
  const session = new SpaceProtocolSession({
130
130
  wireParams,
131
131
  swarmIdentity: this._swarmIdentity,
132
- onSessionAuth: this._onSessionAuth
132
+ onSessionAuth: this._onSessionAuth,
133
133
  });
134
134
  this._sessions.set(wireParams.remotePeerId, session);
135
135
 
@@ -155,7 +155,7 @@ export type SpaceProtocolSessionParams = {
155
155
  export enum AuthStatus {
156
156
  INITIAL = 'INITIAL',
157
157
  SUCCESS = 'SUCCESS',
158
- FAILURE = 'FAILURE'
158
+ FAILURE = 'FAILURE',
159
159
  }
160
160
 
161
161
  // TODO(dmaretskyi): Move to a separate file.
@@ -210,8 +210,8 @@ export class SpaceProtocolSession implements WireProtocol {
210
210
  onAuthFailure: () => {
211
211
  log.warn('Auth failed');
212
212
  this._authStatus = AuthStatus.FAILURE;
213
- }
214
- })
213
+ },
214
+ }),
215
215
  );
216
216
  this._teleport.addExtension('dxos.mesh.teleport.replicator', this.replicator);
217
217
  }