@dxos/echo-pipeline 0.1.52 → 0.1.53-main.01cbc6b

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 (47) hide show
  1. package/dist/lib/browser/{chunk-AXGWKSM3.mjs → chunk-RFRZMQ5T.mjs} +444 -392
  2. package/dist/lib/browser/chunk-RFRZMQ5T.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +2 -2
  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 +3 -3
  7. package/dist/lib/browser/testing/index.mjs.map +2 -2
  8. package/dist/lib/node/index.cjs +512 -460
  9. package/dist/lib/node/index.cjs.map +3 -3
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/testing/index.cjs +502 -450
  12. package/dist/lib/node/testing/index.cjs.map +3 -3
  13. package/dist/types/src/dbhost/data-service-host.d.ts +3 -0
  14. package/dist/types/src/dbhost/data-service-host.d.ts.map +1 -1
  15. package/dist/types/src/dbhost/data-service.d.ts +2 -2
  16. package/dist/types/src/dbhost/data-service.d.ts.map +1 -1
  17. package/dist/types/src/dbhost/snapshot-store.d.ts +3 -0
  18. package/dist/types/src/dbhost/snapshot-store.d.ts.map +1 -1
  19. package/dist/types/src/metadata/metadata-store.d.ts +4 -0
  20. package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
  21. package/dist/types/src/pipeline/message-selector.d.ts.map +1 -1
  22. package/dist/types/src/pipeline/pipeline.d.ts +0 -1
  23. package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
  24. package/dist/types/src/space/data-pipeline.d.ts +3 -3
  25. package/dist/types/src/space/data-pipeline.d.ts.map +1 -1
  26. package/dist/types/src/space/space-protocol.d.ts +3 -0
  27. package/dist/types/src/space/space-protocol.d.ts.map +1 -1
  28. package/dist/types/src/space/space.d.ts +0 -1
  29. package/dist/types/src/space/space.d.ts.map +1 -1
  30. package/package.json +31 -32
  31. package/src/common/feeds.ts +3 -3
  32. package/src/dbhost/data-service-host.ts +17 -4
  33. package/src/dbhost/data-service.ts +10 -6
  34. package/src/dbhost/snapshot-store.ts +3 -0
  35. package/src/metadata/metadata-store.ts +20 -6
  36. package/src/pipeline/message-selector.ts +3 -5
  37. package/src/pipeline/pipeline.ts +24 -12
  38. package/src/space/auth.ts +3 -3
  39. package/src/space/data-pipeline.ts +36 -29
  40. package/src/space/space-protocol.browser.test.ts +2 -2
  41. package/src/space/space-protocol.test.ts +2 -2
  42. package/src/space/space-protocol.ts +10 -4
  43. package/src/space/space.test.ts +46 -5
  44. package/src/space/space.ts +8 -17
  45. package/src/testing/util.ts +1 -1
  46. package/src/tests/database.test.ts +1 -1
  47. package/dist/lib/browser/chunk-AXGWKSM3.mjs.map +0 -7
@@ -2,7 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import assert from 'node:assert';
5
+ import invariant from 'tiny-invariant';
6
6
 
7
7
  import { Event, sleep, synchronized, Trigger } from '@dxos/async';
8
8
  import { Context, rejectOnDispose } from '@dxos/context';
@@ -36,6 +36,11 @@ export type WaitUntilReachedTargetParams = {
36
36
  * External state accessor.
37
37
  */
38
38
  export class PipelineState {
39
+ /**
40
+ * @internal
41
+ */
42
+ _ctx = new Context();
43
+
39
44
  // TODO(dmaretskyi): Remove?.
40
45
  public readonly timeframeUpdate = this._timeframeClock.update;
41
46
 
@@ -51,7 +56,10 @@ export class PipelineState {
51
56
  */
52
57
  private _targetTimeframe: Timeframe | undefined;
53
58
 
54
- private _reachedTargetPromise: Promise<void> | undefined;
59
+ /**
60
+ * @internal
61
+ */
62
+ _reachedTargetPromise: Promise<void> | undefined;
55
63
 
56
64
  // prettier-ignore
57
65
  constructor(
@@ -133,6 +141,7 @@ export class PipelineState {
133
141
  if (timeout) {
134
142
  return Promise.race([
135
143
  rejectOnDispose(ctx),
144
+ rejectOnDispose(this._ctx),
136
145
  this._reachedTargetPromise.then(() => {
137
146
  done = true;
138
147
  }),
@@ -224,7 +233,7 @@ export class Pipeline implements PipelineAccessor {
224
233
  }
225
234
 
226
235
  get writer(): FeedWriter<FeedMessage.Payload> {
227
- assert(this._writer, 'Writer not set.');
236
+ invariant(this._writer, 'Writer not set.');
228
237
  return this._writer;
229
238
  }
230
239
 
@@ -247,8 +256,8 @@ export class Pipeline implements PipelineAccessor {
247
256
  }
248
257
 
249
258
  setWriteFeed(feed: FeedWrapper<FeedMessage>) {
250
- assert(!this._writer, 'Writer already set.');
251
- assert(feed.properties.writable, 'Feed must be writable.');
259
+ invariant(!this._writer, 'Writer already set.');
260
+ invariant(feed.properties.writable, 'Feed must be writable.');
252
261
 
253
262
  this._writer = createMappedFeedWriter<FeedMessage.Payload, FeedMessage>(
254
263
  (payload: FeedMessage.Payload) => ({
@@ -274,6 +283,9 @@ export class Pipeline implements PipelineAccessor {
274
283
  this._isStopping = true;
275
284
  await this._feedSetIterator?.close();
276
285
  await this._processingTrigger.wait(); // Wait for the in-flight message to be processed.
286
+ await this._state._ctx.dispose();
287
+ this._state._ctx = new Context();
288
+ this._state._reachedTargetPromise = undefined;
277
289
  log('stopped');
278
290
  this._isStarted = false;
279
291
  }
@@ -283,7 +295,7 @@ export class Pipeline implements PipelineAccessor {
283
295
  */
284
296
  @synchronized
285
297
  async setCursor(timeframe: Timeframe) {
286
- assert(!this._isStarted || this._isPaused, 'Invalid state.');
298
+ invariant(!this._isStarted || this._isPaused, 'Invalid state.');
287
299
 
288
300
  this._state._startTimeframe = timeframe;
289
301
  this._timeframeClock.setTimeframe(timeframe);
@@ -305,7 +317,7 @@ export class Pipeline implements PipelineAccessor {
305
317
  */
306
318
  @synchronized
307
319
  async pause() {
308
- assert(this._isStarted, 'Pipeline is not open.');
320
+ invariant(this._isStarted, 'Pipeline is not open.');
309
321
  if (this._isPaused) {
310
322
  return;
311
323
  }
@@ -317,8 +329,8 @@ export class Pipeline implements PipelineAccessor {
317
329
 
318
330
  @synchronized
319
331
  async unpause() {
320
- assert(this._isStarted, 'Pipeline is not open.');
321
- assert(this._isPaused, 'Pipeline is not paused.');
332
+ invariant(this._isStarted, 'Pipeline is not open.');
333
+ invariant(this._isPaused, 'Pipeline is not paused.');
322
334
 
323
335
  this._pauseTrigger.wake();
324
336
  this._isPaused = false;
@@ -329,10 +341,10 @@ export class Pipeline implements PipelineAccessor {
329
341
  * Updates the timeframe clock after the message has bee processed.
330
342
  */
331
343
  async *consume(): AsyncIterable<FeedMessageBlock> {
332
- assert(!this._isOpen, 'Pipeline is already being consumed.');
344
+ invariant(!this._isOpen, 'Pipeline is already being consumed.');
333
345
  this._isOpen = true;
334
346
 
335
- assert(this._feedSetIterator, 'Iterator not initialized.');
347
+ invariant(this._feedSetIterator, 'Iterator not initialized.');
336
348
  let lastFeedSetIterator = this._feedSetIterator;
337
349
  let iterable = lastFeedSetIterator[Symbol.asyncIterator]();
338
350
 
@@ -341,7 +353,7 @@ export class Pipeline implements PipelineAccessor {
341
353
 
342
354
  // Iterator might have been changed while we were waiting for the processing to complete.
343
355
  if (lastFeedSetIterator !== this._feedSetIterator) {
344
- assert(this._feedSetIterator, 'Iterator not initialized.');
356
+ invariant(this._feedSetIterator, 'Iterator not initialized.');
345
357
  lastFeedSetIterator = this._feedSetIterator;
346
358
  iterable = lastFeedSetIterator[Symbol.asyncIterator]();
347
359
  }
package/src/space/auth.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright 2019 DXOS.org
3
3
  //
4
4
 
5
- import assert from 'node:assert';
5
+ import invariant from 'tiny-invariant';
6
6
 
7
7
  import { runInContext, scheduleTask } from '@dxos/async';
8
8
  import { Context } from '@dxos/context';
@@ -67,9 +67,9 @@ export class AuthExtension extends RpcExtension<Services, Services> {
67
67
  try {
68
68
  const challenge = randomBytes(32);
69
69
  const { credential } = await this.rpc.AuthService.authenticate({ challenge });
70
- assert(credential?.length > 0, 'invalid credential');
70
+ invariant(credential?.length > 0, 'invalid credential');
71
71
  const success = await this._authParams.verifier(challenge, credential);
72
- assert(success, 'credential not verified');
72
+ invariant(success, 'credential not verified');
73
73
  runInContext(this._ctx, () => this._authParams.onAuthSuccess());
74
74
  } catch (err) {
75
75
  log('auth failed', err);
@@ -2,7 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import assert from 'node:assert';
5
+ import invariant from 'tiny-invariant';
6
6
 
7
7
  import { Event, scheduleTask, synchronized, trackLeaks } from '@dxos/async';
8
8
  import { Context } from '@dxos/context';
@@ -65,10 +65,10 @@ const TIMEFRAME_SAVE_DEBOUNCE_INTERVAL = 500;
65
65
  * Reacts to new epochs to restart the pipeline.
66
66
  */
67
67
  @trackLeaks('open', 'close')
68
- export class DataPipeline {
68
+ export class DataPipeline implements CredentialProcessor {
69
69
  private _ctx = new Context();
70
- private _pipeline?: Pipeline;
71
- private _targetTimeframe?: Timeframe;
70
+ private _pipeline?: Pipeline = undefined;
71
+ private _targetTimeframe?: Timeframe = undefined;
72
72
 
73
73
  private _lastAutomaticSnapshotTimeframe = new Timeframe();
74
74
  private _isOpen = false;
@@ -84,17 +84,18 @@ export class DataPipeline {
84
84
  /**
85
85
  * Current epoch. Might be still processing.
86
86
  */
87
- public currentEpoch?: SpecificCredential<Epoch>;
87
+ public currentEpoch?: SpecificCredential<Epoch> = undefined;
88
88
 
89
89
  /**
90
90
  * Epoch currently applied.
91
91
  */
92
- public appliedEpoch?: SpecificCredential<Epoch>;
92
+ public appliedEpoch?: SpecificCredential<Epoch> = undefined;
93
93
 
94
94
  private _lastProcessedEpoch = -1;
95
- public onNewEpoch = new Event<Credential>();
96
95
  private _epochCtx?: Context;
97
96
 
97
+ public onNewEpoch = new Event<Credential>();
98
+
98
99
  get isOpen() {
99
100
  return this._isOpen;
100
101
  }
@@ -112,20 +113,16 @@ export class DataPipeline {
112
113
  this._pipeline?.state.setTargetTimeframe(timeframe);
113
114
  }
114
115
 
115
- createCredentialProcessor(): CredentialProcessor {
116
- return {
117
- process: async (credential) => {
118
- if (!checkCredentialType(credential, 'dxos.halo.credentials.Epoch')) {
119
- return;
120
- }
116
+ async processCredential(credential: Credential) {
117
+ if (!checkCredentialType(credential, 'dxos.halo.credentials.Epoch')) {
118
+ return;
119
+ }
121
120
 
122
- this.currentEpoch = credential;
123
- if (this._isOpen) {
124
- // process epoch
125
- await this._processEpochInSeparateTask(credential);
126
- }
127
- },
128
- };
121
+ this.currentEpoch = credential;
122
+ if (this._isOpen) {
123
+ // process epoch
124
+ await this._processEpochInSeparateTask(credential);
125
+ }
129
126
  }
130
127
 
131
128
  @synchronized
@@ -146,8 +143,8 @@ export class DataPipeline {
146
143
  // Create database backend.
147
144
  const feedWriter: FeedWriter<DataMessage> = {
148
145
  write: (data, options) => {
149
- assert(this._pipeline, 'Pipeline is not initialized.');
150
- assert(this.currentEpoch, 'Epoch is not initialized.');
146
+ invariant(this._pipeline, 'Pipeline is not initialized.');
147
+ invariant(this.currentEpoch, 'Epoch is not initialized.');
151
148
  return this._pipeline.writer.write({ data }, options);
152
149
  },
153
150
  };
@@ -189,6 +186,15 @@ export class DataPipeline {
189
186
 
190
187
  await this.databaseHost?.close();
191
188
  await this.itemManager?.destroy();
189
+
190
+ this._ctx = new Context();
191
+ this._pipeline = undefined;
192
+ this._targetTimeframe = undefined;
193
+ this._lastAutomaticSnapshotTimeframe = new Timeframe();
194
+ this.currentEpoch = undefined;
195
+ this.appliedEpoch = undefined;
196
+ this._lastProcessedEpoch = -1;
197
+ this._epochCtx = undefined;
192
198
  }
193
199
 
194
200
  private async _consumePipeline() {
@@ -198,7 +204,7 @@ export class DataPipeline {
198
204
  await waitForOneEpoch;
199
205
  }
200
206
 
201
- assert(this._pipeline, 'Pipeline is not initialized.');
207
+ invariant(this._pipeline, 'Pipeline is not initialized.');
202
208
  for await (const msg of this._pipeline.consume()) {
203
209
  const { feedKey, seq, data } = msg;
204
210
  log('processing message', { feedKey, seq });
@@ -237,7 +243,7 @@ export class DataPipeline {
237
243
  }
238
244
 
239
245
  private _createSnapshot(): SpaceSnapshot {
240
- assert(this.databaseHost, 'Database backend is not initialized.');
246
+ invariant(this.databaseHost, 'Database backend is not initialized.');
241
247
  return {
242
248
  spaceKey: this._params.spaceKey.asUint8Array(),
243
249
  timeframe: this._pipeline!.state.timeframe,
@@ -259,6 +265,7 @@ export class DataPipeline {
259
265
  const propertiesItem = this.itemManager.items.find(
260
266
  (item) =>
261
267
  item.modelMeta?.type === 'dxos:model/document' &&
268
+ // TODO(burdon): Document?
262
269
  (getStateMachineFromItem(item)?.snapshot() as ObjectSnapshot).type === 'dxos.sdk.client.Properties',
263
270
  );
264
271
  if (propertiesItem) {
@@ -319,8 +326,8 @@ export class DataPipeline {
319
326
 
320
327
  @synchronized
321
328
  private async _processEpoch(ctx: Context, epoch: Epoch) {
322
- assert(this._isOpen, 'Space is closed.');
323
- assert(this._pipeline);
329
+ invariant(this._isOpen, 'Space is closed.');
330
+ invariant(this._pipeline);
324
331
  this._lastProcessedEpoch = epoch.number;
325
332
 
326
333
  log('Processing epoch', { epoch });
@@ -339,14 +346,14 @@ export class DataPipeline {
339
346
  }
340
347
 
341
348
  async waitUntilTimeframe(timeframe: Timeframe) {
342
- assert(this._pipeline, 'Pipeline is not initialized.');
349
+ invariant(this._pipeline, 'Pipeline is not initialized.');
343
350
  await this._pipeline.state.waitUntilTimeframe(timeframe);
344
351
  }
345
352
 
346
353
  @synchronized
347
354
  async createEpoch(): Promise<Epoch> {
348
- assert(this._pipeline);
349
- assert(this.currentEpoch);
355
+ invariant(this._pipeline);
356
+ invariant(this.currentEpoch);
350
357
 
351
358
  await this._pipeline.pause();
352
359
 
@@ -43,8 +43,8 @@ describe('space/space-protocol', () => {
43
43
  afterTest(() => protocol2.stop());
44
44
 
45
45
  await waitForExpect(() => {
46
- expect(presence1.getPeersOnline().map(({ identityKey }) => identityKey)).toContainEqual(peer2.identityKey);
47
- expect(presence2.getPeersOnline().map(({ identityKey }) => identityKey)).toContainEqual(peer1.identityKey);
46
+ expect(presence1.getPeersOnline().some(({ identityKey }) => identityKey.equals(peer2.identityKey))).toBeTruthy();
47
+ expect(presence2.getPeersOnline().some(({ identityKey }) => identityKey.equals(peer1.identityKey))).toBeTruthy();
48
48
  });
49
49
  });
50
50
 
@@ -39,8 +39,8 @@ describe('space/space-protocol', () => {
39
39
  afterTest(() => protocol2.stop());
40
40
 
41
41
  await waitForExpect(() => {
42
- expect(presence1.getPeersOnline().map(({ identityKey }) => identityKey)).toContainEqual(peer2.identityKey);
43
- expect(presence2.getPeersOnline().map(({ identityKey }) => identityKey)).toContainEqual(peer1.identityKey);
42
+ expect(presence1.getPeersOnline().some(({ identityKey }) => identityKey.equals(peer2.identityKey))).toBeTruthy();
43
+ expect(presence2.getPeersOnline().some(({ identityKey }) => identityKey.equals(peer1.identityKey))).toBeTruthy();
44
44
  }, 1_000);
45
45
  });
46
46
 
@@ -2,7 +2,8 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { discoveryKey, sha256 } from '@dxos/crypto';
5
+ import { Event } from '@dxos/async';
6
+ import { discoveryKey, subtleCrypto } from '@dxos/crypto';
6
7
  import { FeedWrapper } from '@dxos/feed-store';
7
8
  import { PublicKey } from '@dxos/keys';
8
9
  import { log, logInfo } from '@dxos/log';
@@ -14,6 +15,7 @@ import {
14
15
  WireProtocolParams,
15
16
  WireProtocolProvider,
16
17
  } from '@dxos/network-manager';
18
+ import { ConnectionInfo } from '@dxos/protocols/proto/dxos/devtools/swarm';
17
19
  import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
18
20
  import { Teleport } from '@dxos/teleport';
19
21
  import { BlobStore, BlobSync } from '@dxos/teleport-extension-object-sync';
@@ -59,7 +61,7 @@ export class SpaceProtocol {
59
61
  public readonly blobSync: BlobSync;
60
62
 
61
63
  @logInfo
62
- private readonly _topic: PublicKey;
64
+ private readonly _topic: Promise<PublicKey>;
63
65
 
64
66
  private readonly _feeds = new Set<FeedWrapper<FeedMessage>>();
65
67
  private readonly _sessions = new ComplexMap<PublicKey, SpaceProtocolSession>(PublicKey.hash);
@@ -86,7 +88,7 @@ export class SpaceProtocol {
86
88
  this._onAuthFailure = onAuthFailure;
87
89
  this.blobSync = new BlobSync({ blobStore });
88
90
 
89
- this._topic = PublicKey.from(discoveryKey(sha256(topic.toHex())));
91
+ this._topic = subtleCrypto.digest('SHA-256', topic.asBuffer()).then(discoveryKey).then(PublicKey.from);
90
92
  }
91
93
 
92
94
  // TODO(burdon): Create abstraction for Space (e.g., add keys and have provider).
@@ -120,7 +122,7 @@ export class SpaceProtocol {
120
122
  this._connection = await this._networkManager.joinSwarm({
121
123
  protocolProvider: this._createProtocolProvider(credentials),
122
124
  peerId: this._swarmIdentity.peerKey,
123
- topic: this._topic,
125
+ topic: await this._topic,
124
126
  topology: new MMSTTopology(topologyConfig),
125
127
  label: `Protocol swarm: ${this._topic}`,
126
128
  });
@@ -204,6 +206,10 @@ export class SpaceProtocolSession implements WireProtocol {
204
206
  return this._authStatus;
205
207
  }
206
208
 
209
+ get stats(): Event<ConnectionInfo.StreamStats[]> {
210
+ return this._teleport.stats;
211
+ }
212
+
207
213
  // TODO(dmaretskyi): Allow to pass in extra extensions.
208
214
  constructor({ wireParams, swarmIdentity, onSessionAuth, onAuthFailure, blobSync }: SpaceProtocolSessionParams) {
209
215
  this._wireParams = wireParams;
@@ -22,7 +22,6 @@ describe('space/space', () => {
22
22
  const space = await agent.createSpace();
23
23
 
24
24
  await space.open();
25
-
26
25
  expect(space.isOpen).toBeTruthy();
27
26
  afterTest(() => space.close());
28
27
 
@@ -130,7 +129,6 @@ describe('space/space', () => {
130
129
  const space1 = await agent.createSpace();
131
130
 
132
131
  await space1.open();
133
-
134
132
  expect(space1.isOpen).toBeTruthy();
135
133
  afterTest(() => space1.close());
136
134
 
@@ -152,7 +150,6 @@ describe('space/space', () => {
152
150
  const space2 = await agent.createSpace(agent.identityKey, space1.key, space1.genesisFeedKey, space1.dataFeedKey);
153
151
 
154
152
  await space2.open();
155
-
156
153
  await space2.controlPipeline.state!.waitUntilTimeframe(space2.controlPipeline.state!.endTimeframe);
157
154
  await space2.initializeDataPipeline();
158
155
  await space2.dataPipeline.ensureEpochInitialized();
@@ -166,6 +163,52 @@ describe('space/space', () => {
166
163
  await testLocalDatabase(space2.dataPipeline);
167
164
  });
168
165
 
166
+ test('re-open', async () => {
167
+ const builder = new TestAgentBuilder();
168
+ afterTest(async () => await builder.close());
169
+ const agent = await builder.createPeer();
170
+ const space = await agent.createSpace();
171
+
172
+ let objectCount: number;
173
+ {
174
+ await space.open();
175
+ afterTest(() => space.close());
176
+ expect(space.isOpen).toBeTruthy();
177
+
178
+ await agent.spaceGenesis(space);
179
+
180
+ await space.controlPipeline.state!.waitUntilTimeframe(space.controlPipeline.state!.endTimeframe);
181
+ await space.initializeDataPipeline();
182
+ await space.dataPipeline.ensureEpochInitialized();
183
+
184
+ assert(space.dataPipeline.databaseHost);
185
+ await testLocalDatabase(space.dataPipeline);
186
+
187
+ objectCount = space.dataPipeline.itemManager.entities.size;
188
+
189
+ await space.close();
190
+ expect(space.isOpen).toBeFalsy();
191
+ }
192
+
193
+ // Re-open.
194
+ {
195
+ await space.open();
196
+ expect(space.isOpen).toBeTruthy();
197
+
198
+ await space.controlPipeline.state!.waitUntilTimeframe(space.controlPipeline.state!.endTimeframe);
199
+ await space.initializeDataPipeline();
200
+ await space.dataPipeline.ensureEpochInitialized();
201
+
202
+ space.dataPipeline.setTargetTimeframe(space.dataPipeline.pipelineState!.endTimeframe);
203
+ await space.dataPipeline.pipelineState!.waitUntilReachedTargetTimeframe();
204
+
205
+ assert(space.dataPipeline.databaseHost);
206
+ expect(space.dataPipeline.itemManager.entities.size).toEqual(objectCount);
207
+
208
+ await testLocalDatabase(space.dataPipeline);
209
+ }
210
+ });
211
+
169
212
  test('create epoch', async () => {
170
213
  const builder = new TestAgentBuilder();
171
214
  afterTest(async () => await builder.close());
@@ -173,7 +216,6 @@ describe('space/space', () => {
173
216
  const space1 = await agent.createSpace();
174
217
 
175
218
  await space1.open();
176
-
177
219
  expect(space1.isOpen).toBeTruthy();
178
220
  afterTest(() => space1.close());
179
221
 
@@ -214,7 +256,6 @@ describe('space/space', () => {
214
256
  const space2 = await agent.createSpace(agent.identityKey, space1.key, space1.genesisFeedKey, space1.dataFeedKey);
215
257
 
216
258
  await space2.open();
217
-
218
259
  await space2.controlPipeline.state!.waitUntilTimeframe(space2.controlPipeline.state!.endTimeframe);
219
260
  await space2.initializeDataPipeline();
220
261
  await space2.dataPipeline.ensureEpochInitialized();
@@ -2,10 +2,10 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import assert from 'node:assert';
5
+ import invariant from 'tiny-invariant';
6
6
 
7
7
  import { Event, synchronized, trackLeaks, Lock } from '@dxos/async';
8
- import { CredentialConsumer, FeedInfo } from '@dxos/credentials';
8
+ import { FeedInfo } from '@dxos/credentials';
9
9
  import { FeedOptions, FeedWrapper } from '@dxos/feed-store';
10
10
  import { PublicKey } from '@dxos/keys';
11
11
  import { log, logInfo } from '@dxos/log';
@@ -63,15 +63,12 @@ export class Space {
63
63
  private readonly _dataPipeline: DataPipeline;
64
64
  private readonly _snapshotManager: SnapshotManager;
65
65
 
66
- // Processes epoch credentials.
67
- private _dataPipelineCredentialConsumer?: CredentialConsumer<any> = undefined;
68
-
69
66
  private _isOpen = false;
70
67
  private _controlFeed?: FeedWrapper<FeedMessage>;
71
68
  private _dataFeed?: FeedWrapper<FeedMessage>;
72
69
 
73
70
  constructor(params: SpaceParams) {
74
- assert(params.spaceKey && params.feedProvider);
71
+ invariant(params.spaceKey && params.feedProvider);
75
72
  this._key = params.spaceKey;
76
73
  this._genesisFeedKey = params.genesisFeed.key;
77
74
  this._feedProvider = params.feedProvider;
@@ -181,14 +178,14 @@ export class Space {
181
178
  }
182
179
 
183
180
  setControlFeed(feed: FeedWrapper<FeedMessage>) {
184
- assert(!this._controlFeed, 'Control feed already set.');
181
+ invariant(!this._controlFeed, 'Control feed already set.');
185
182
  this._controlFeed = feed;
186
183
  this._controlPipeline.setWriteFeed(feed);
187
184
  return this;
188
185
  }
189
186
 
190
187
  setDataFeed(feed: FeedWrapper<FeedMessage>) {
191
- assert(!this._dataFeed, 'Data feed already set.');
188
+ invariant(!this._dataFeed, 'Data feed already set.');
192
189
  this._dataFeed = feed;
193
190
  this._dataPipeline.pipeline?.setWriteFeed(feed);
194
191
  return this;
@@ -217,12 +214,7 @@ export class Space {
217
214
  // Order is important.
218
215
  await this._controlPipeline.start();
219
216
  await this.protocol.start();
220
-
221
- this._dataPipelineCredentialConsumer = this._controlPipeline.spaceState.registerProcessor(
222
- this._dataPipeline.createCredentialProcessor(),
223
- );
224
-
225
- await this._dataPipelineCredentialConsumer.open();
217
+ await this._controlPipeline.spaceState.addCredentialProcessor(this._dataPipeline);
226
218
 
227
219
  this._isOpen = true;
228
220
  log('opened');
@@ -234,8 +226,7 @@ export class Space {
234
226
  if (!this._isOpen) {
235
227
  return;
236
228
  }
237
- await this._dataPipelineCredentialConsumer?.close();
238
- this._dataPipelineCredentialConsumer = undefined;
229
+ await this._controlPipeline.spaceState.removeCredentialProcessor(this._dataPipeline);
239
230
 
240
231
  await this._dataPipeline.close();
241
232
 
@@ -250,7 +241,7 @@ export class Space {
250
241
  @synchronized
251
242
  async initializeDataPipeline() {
252
243
  log('initializeDataPipeline');
253
- assert(this._isOpen, 'Space must be open to initialize data pipeline.');
244
+ invariant(this._isOpen, 'Space must be open to initialize data pipeline.');
254
245
  await this._dataPipeline.open();
255
246
  }
256
247
  }
@@ -45,7 +45,7 @@ export const createRemoteDatabaseFromDataServiceHost = async (
45
45
  const dataService = new DataServiceImpl(dataServiceSubscriptions);
46
46
 
47
47
  const spaceKey = PublicKey.random();
48
- dataServiceSubscriptions.registerSpace(spaceKey, dataServiceHost);
48
+ await dataServiceSubscriptions.registerSpace(spaceKey, dataServiceHost);
49
49
 
50
50
  const itemManager = new ItemManager(modelFactory);
51
51
  const backend = new DatabaseProxy(dataService, itemManager, spaceKey);
@@ -42,7 +42,7 @@ const createDatabaseWithFeeds = async () => {
42
42
  const dataService = new DataServiceImpl(dataServiceSubscriptions);
43
43
 
44
44
  const spaceKey = PublicKey.random();
45
- dataServiceSubscriptions.registerSpace(spaceKey, host.createDataServiceHost());
45
+ await dataServiceSubscriptions.registerSpace(spaceKey, host.createDataServiceHost());
46
46
 
47
47
  const proxy = new DatabaseProxy(dataService, new ItemManager(modelFactory), spaceKey);
48
48
  await proxy.open(new ModelFactory().registerModel(DocumentModel));