@dxos/echo-db 2.33.5-dev.b9f5bea6 → 2.33.5-dev.cb045a79

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 (138) hide show
  1. package/dist/src/api/index.js +5 -1
  2. package/dist/src/api/index.js.map +1 -1
  3. package/dist/src/echo.d.ts.map +1 -1
  4. package/dist/src/echo.js +1 -1
  5. package/dist/src/echo.js.map +1 -1
  6. package/dist/src/echo.test.js +0 -4
  7. package/dist/src/echo.test.js.map +1 -1
  8. package/dist/src/halo/halo-factory.d.ts +2 -2
  9. package/dist/src/halo/halo-factory.d.ts.map +1 -1
  10. package/dist/src/halo/halo-factory.js +2 -1
  11. package/dist/src/halo/halo-factory.js.map +1 -1
  12. package/dist/src/halo/halo-party.d.ts +2 -2
  13. package/dist/src/halo/halo-party.d.ts.map +1 -1
  14. package/dist/src/halo/halo-party.js +1 -1
  15. package/dist/src/halo/halo-party.js.map +1 -1
  16. package/dist/src/halo/halo.d.ts +2 -2
  17. package/dist/src/halo/halo.d.ts.map +1 -1
  18. package/dist/src/halo/index.js +5 -1
  19. package/dist/src/halo/index.js.map +1 -1
  20. package/dist/src/index.js +5 -1
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/invitations/index.js +5 -1
  23. package/dist/src/invitations/index.js.map +1 -1
  24. package/dist/src/invitations/invitation-descriptor.js +5 -1
  25. package/dist/src/invitations/invitation-descriptor.js.map +1 -1
  26. package/dist/src/packlets/database/data-mirror.test.js +2 -2
  27. package/dist/src/packlets/database/data-mirror.test.js.map +1 -1
  28. package/dist/src/packlets/database/database-backend.d.ts +4 -5
  29. package/dist/src/packlets/database/database-backend.d.ts.map +1 -1
  30. package/dist/src/packlets/database/database-backend.js +5 -6
  31. package/dist/src/packlets/database/database-backend.js.map +1 -1
  32. package/dist/src/packlets/database/index.js +5 -1
  33. package/dist/src/packlets/database/index.js.map +1 -1
  34. package/dist/src/packlets/database/item-demuxer.d.ts +2 -2
  35. package/dist/src/packlets/database/item-demuxer.d.ts.map +1 -1
  36. package/dist/src/packlets/database/item-demuxer.js +2 -3
  37. package/dist/src/packlets/database/item-demuxer.js.map +1 -1
  38. package/dist/src/packlets/database/item-demuxer.test.js +13 -9
  39. package/dist/src/packlets/database/item-demuxer.test.js.map +1 -1
  40. package/dist/src/packlets/database/selection/index.js +5 -1
  41. package/dist/src/packlets/database/selection/index.js.map +1 -1
  42. package/dist/src/packlets/database/testing.d.ts.map +1 -1
  43. package/dist/src/packlets/database/testing.js +3 -4
  44. package/dist/src/packlets/database/testing.js.map +1 -1
  45. package/dist/src/packlets/database/timeframe-clock.d.ts +1 -0
  46. package/dist/src/packlets/database/timeframe-clock.d.ts.map +1 -1
  47. package/dist/src/packlets/database/timeframe-clock.js +3 -0
  48. package/dist/src/packlets/database/timeframe-clock.js.map +1 -1
  49. package/dist/src/parties/data-party.d.ts +5 -4
  50. package/dist/src/parties/data-party.d.ts.map +1 -1
  51. package/dist/src/parties/data-party.js +22 -5
  52. package/dist/src/parties/data-party.js.map +1 -1
  53. package/dist/src/parties/data-party.test.js +20 -1
  54. package/dist/src/parties/data-party.test.js.map +1 -1
  55. package/dist/src/parties/index.js +5 -1
  56. package/dist/src/parties/index.js.map +1 -1
  57. package/dist/src/parties/party-factory.d.ts +4 -3
  58. package/dist/src/parties/party-factory.d.ts.map +1 -1
  59. package/dist/src/parties/party-factory.js +13 -9
  60. package/dist/src/parties/party-factory.js.map +1 -1
  61. package/dist/src/parties/party-manager.d.ts.map +1 -1
  62. package/dist/src/parties/party-manager.js +6 -3
  63. package/dist/src/parties/party-manager.js.map +1 -1
  64. package/dist/src/parties/party-manager.test.js +2 -2
  65. package/dist/src/parties/party-manager.test.js.map +1 -1
  66. package/dist/src/pipeline/{pipeline.d.ts → feed-muxer.d.ts} +8 -11
  67. package/dist/src/pipeline/feed-muxer.d.ts.map +1 -0
  68. package/dist/src/pipeline/{pipeline.js → feed-muxer.js} +33 -40
  69. package/dist/src/pipeline/feed-muxer.js.map +1 -0
  70. package/dist/src/pipeline/feed-muxer.test.d.ts +2 -0
  71. package/dist/src/pipeline/feed-muxer.test.d.ts.map +1 -0
  72. package/dist/src/pipeline/{pipeline.test.js → feed-muxer.test.js} +16 -13
  73. package/dist/src/pipeline/feed-muxer.test.js.map +1 -0
  74. package/dist/src/pipeline/index.d.ts +2 -2
  75. package/dist/src/pipeline/index.d.ts.map +1 -1
  76. package/dist/src/pipeline/index.js +7 -3
  77. package/dist/src/pipeline/index.js.map +1 -1
  78. package/dist/src/pipeline/metadata-store.d.ts +2 -1
  79. package/dist/src/pipeline/metadata-store.d.ts.map +1 -1
  80. package/dist/src/pipeline/metadata-store.js +6 -0
  81. package/dist/src/pipeline/metadata-store.js.map +1 -1
  82. package/dist/src/pipeline/party-feed-provider.d.ts +2 -3
  83. package/dist/src/pipeline/party-feed-provider.d.ts.map +1 -1
  84. package/dist/src/pipeline/party-feed-provider.js +2 -17
  85. package/dist/src/pipeline/party-feed-provider.js.map +1 -1
  86. package/dist/src/pipeline/{party-core.d.ts → party-pipeline.d.ts} +6 -6
  87. package/dist/src/pipeline/party-pipeline.d.ts.map +1 -0
  88. package/dist/src/pipeline/{party-core.js → party-pipeline.js} +21 -17
  89. package/dist/src/pipeline/party-pipeline.js.map +1 -0
  90. package/dist/src/pipeline/party-pipeline.test.d.ts +2 -0
  91. package/dist/src/pipeline/party-pipeline.test.d.ts.map +1 -0
  92. package/dist/src/pipeline/{party-core.test.js → party-pipeline.test.js} +33 -16
  93. package/dist/src/pipeline/party-pipeline.test.js.map +1 -0
  94. package/dist/src/protocol/index.js +5 -1
  95. package/dist/src/protocol/index.js.map +1 -1
  96. package/dist/src/snapshots/index.js +5 -1
  97. package/dist/src/snapshots/index.js.map +1 -1
  98. package/dist/src/snapshots/snapshot-generator.d.ts +2 -2
  99. package/dist/src/snapshots/snapshot-generator.d.ts.map +1 -1
  100. package/dist/src/snapshots/snapshot-generator.js.map +1 -1
  101. package/dist/src/testing/index.js +5 -1
  102. package/dist/src/testing/index.js.map +1 -1
  103. package/dist/tsconfig.tsbuildinfo +1 -1
  104. package/package.json +18 -18
  105. package/src/echo.test.ts +0 -4
  106. package/src/echo.ts +1 -0
  107. package/src/halo/halo-factory.ts +4 -3
  108. package/src/halo/halo-party.ts +4 -4
  109. package/src/halo/halo.ts +2 -2
  110. package/src/packlets/database/data-mirror.test.ts +2 -2
  111. package/src/packlets/database/database-backend.ts +7 -6
  112. package/src/packlets/database/item-demuxer.test.ts +20 -18
  113. package/src/packlets/database/item-demuxer.ts +5 -4
  114. package/src/packlets/database/testing.ts +3 -6
  115. package/src/packlets/database/timeframe-clock.ts +4 -0
  116. package/src/parties/data-party.test.ts +33 -3
  117. package/src/parties/data-party.ts +27 -6
  118. package/src/parties/party-factory.ts +17 -14
  119. package/src/parties/party-manager.test.ts +3 -1
  120. package/src/parties/party-manager.ts +7 -2
  121. package/src/pipeline/{pipeline.test.ts → feed-muxer.test.ts} +19 -14
  122. package/src/pipeline/{pipeline.ts → feed-muxer.ts} +38 -51
  123. package/src/pipeline/index.ts +2 -2
  124. package/src/pipeline/metadata-store.ts +7 -1
  125. package/src/pipeline/party-feed-provider.ts +3 -16
  126. package/src/pipeline/{party-core.test.ts → party-pipeline.test.ts} +31 -9
  127. package/src/pipeline/{party-core.ts → party-pipeline.ts} +27 -14
  128. package/src/snapshots/snapshot-generator.ts +2 -2
  129. package/dist/src/pipeline/party-core.d.ts.map +0 -1
  130. package/dist/src/pipeline/party-core.js.map +0 -1
  131. package/dist/src/pipeline/party-core.test.d.ts +0 -2
  132. package/dist/src/pipeline/party-core.test.d.ts.map +0 -1
  133. package/dist/src/pipeline/party-core.test.js.map +0 -1
  134. package/dist/src/pipeline/pipeline.d.ts.map +0 -1
  135. package/dist/src/pipeline/pipeline.js.map +0 -1
  136. package/dist/src/pipeline/pipeline.test.d.ts +0 -2
  137. package/dist/src/pipeline/pipeline.test.d.ts.map +0 -1
  138. package/dist/src/pipeline/pipeline.test.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/echo-db",
3
- "version": "2.33.5-dev.b9f5bea6",
3
+ "version": "2.33.5-dev.cb045a79",
4
4
  "description": "ECHO database.",
5
5
  "license": "MIT",
6
6
  "main": "dist/src/index.js",
@@ -10,22 +10,22 @@
10
10
  "src"
11
11
  ],
12
12
  "dependencies": {
13
- "@dxos/async": "2.33.5-dev.b9f5bea6",
14
- "@dxos/codec-protobuf": "2.33.5-dev.b9f5bea6",
15
- "@dxos/credentials": "2.33.5-dev.b9f5bea6",
16
- "@dxos/crypto": "2.33.5-dev.b9f5bea6",
17
- "@dxos/debug": "2.33.5-dev.b9f5bea6",
18
- "@dxos/echo-protocol": "2.33.5-dev.b9f5bea6",
19
- "@dxos/feed-store": "2.33.5-dev.b9f5bea6",
20
- "@dxos/mesh-protocol": "2.33.5-dev.b9f5bea6",
21
- "@dxos/model-factory": "2.33.5-dev.b9f5bea6",
22
- "@dxos/network-manager": "2.33.5-dev.b9f5bea6",
23
- "@dxos/object-model": "2.33.5-dev.b9f5bea6",
24
- "@dxos/protocol-plugin-presence": "2.33.5-dev.b9f5bea6",
25
- "@dxos/protocol-plugin-replicator": "2.33.5-dev.b9f5bea6",
26
- "@dxos/protocols": "2.33.5-dev.b9f5bea6",
27
- "@dxos/random-access-multi-storage": "2.33.5-dev.b9f5bea6",
28
- "@dxos/util": "2.33.5-dev.b9f5bea6",
13
+ "@dxos/async": "2.33.5-dev.cb045a79",
14
+ "@dxos/codec-protobuf": "2.33.5-dev.cb045a79",
15
+ "@dxos/credentials": "2.33.5-dev.cb045a79",
16
+ "@dxos/crypto": "2.33.5-dev.cb045a79",
17
+ "@dxos/debug": "2.33.5-dev.cb045a79",
18
+ "@dxos/echo-protocol": "2.33.5-dev.cb045a79",
19
+ "@dxos/feed-store": "2.33.5-dev.cb045a79",
20
+ "@dxos/mesh-protocol": "2.33.5-dev.cb045a79",
21
+ "@dxos/model-factory": "2.33.5-dev.cb045a79",
22
+ "@dxos/network-manager": "2.33.5-dev.cb045a79",
23
+ "@dxos/object-model": "2.33.5-dev.cb045a79",
24
+ "@dxos/protocol-plugin-presence": "2.33.5-dev.cb045a79",
25
+ "@dxos/protocol-plugin-replicator": "2.33.5-dev.cb045a79",
26
+ "@dxos/protocols": "2.33.5-dev.cb045a79",
27
+ "@dxos/random-access-multi-storage": "2.33.5-dev.cb045a79",
28
+ "@dxos/util": "2.33.5-dev.cb045a79",
29
29
  "base-x": "~3.0.9",
30
30
  "buffer-json-encoding": "^1.0.2",
31
31
  "debug": "^4.3.3",
@@ -56,7 +56,7 @@
56
56
  "faker": "^5.1.0",
57
57
  "memdown": "^5.1.0",
58
58
  "mocha": "~8.4.0",
59
- "typescript": "^4.5.2"
59
+ "typescript": "^4.7.2"
60
60
  },
61
61
  "publishConfig": {
62
62
  "access": "public"
package/src/echo.test.ts CHANGED
@@ -707,10 +707,6 @@ describe('ECHO', () => {
707
707
  expect(partyA.isOpen).toBe(true);
708
708
  expect(partyA.isActive).toBe(true);
709
709
 
710
- await partyA.database
711
- .select({ type: 'example:item/test' })
712
- .exec()
713
- .update.waitFor(result => result.entities.length > 0);
714
710
  expect(partyA.database.select({ type: 'example:item/test' }).exec().entities.length).toEqual(1);
715
711
  }).timeout(10_000);
716
712
 
package/src/echo.ts CHANGED
@@ -143,6 +143,7 @@ export class ECHO {
143
143
  this._modelFactory,
144
144
  this._snapshotStore,
145
145
  feedProviderFactory,
146
+ this._metadataStore,
146
147
  options
147
148
  );
148
149
 
@@ -22,7 +22,7 @@ import { ObjectModel } from '@dxos/object-model';
22
22
 
23
23
  import { createHaloPartyAdmissionMessage, GreetingInitiator, HaloRecoveryInitiator, InvitationDescriptor, InvitationDescriptorType, OfflineInvitationClaimer } from '../invitations';
24
24
  import { PARTY_ITEM_TYPE } from '../parties';
25
- import { PartyFeedProvider, PartyOptions } from '../pipeline';
25
+ import { PartyFeedProvider, PipelineOptions } from '../pipeline';
26
26
  import { CredentialsSigner } from '../protocol/credentials-signer';
27
27
  import { SnapshotStore } from '../snapshots';
28
28
  import {
@@ -50,19 +50,20 @@ export class HaloFactory {
50
50
  private readonly _snapshotStore: SnapshotStore,
51
51
  private readonly _feedProviderFactory: (partyKey: PublicKey) => PartyFeedProvider,
52
52
  private readonly _keyring: Keyring,
53
- private readonly _options: PartyOptions = {}
53
+ private readonly _options: PipelineOptions = {}
54
54
  ) {}
55
55
 
56
56
  async constructParty (feedHints: PublicKey[]): Promise<HaloParty> {
57
57
  const credentialsSigner = CredentialsSigner.createDirectDeviceSigner(this._keyring);
58
58
  const feedProvider = this._feedProviderFactory(credentialsSigner.getIdentityKey().publicKey);
59
+ const writableFeed = await feedProvider.createOrOpenWritableFeed();
59
60
  const halo = new HaloParty(
60
61
  this._modelFactory,
61
62
  this._snapshotStore,
62
63
  feedProvider,
63
64
  credentialsSigner,
64
65
  this._networkManager,
65
- feedHints,
66
+ [...feedHints, writableFeed.key],
66
67
  undefined,
67
68
  this._options
68
69
  );
@@ -14,7 +14,7 @@ import { NetworkManager } from '@dxos/network-manager';
14
14
 
15
15
  import { InvitationAuthenticator, InvitationDescriptor, InvitationFactory, InvitationOptions } from '../invitations';
16
16
  import { PARTY_ITEM_TYPE } from '../parties';
17
- import { PartyFeedProvider, PartyProtocolFactory, PartyCore, PartyOptions } from '../pipeline';
17
+ import { PartyFeedProvider, PartyProtocolFactory, PartyPipeline, PipelineOptions } from '../pipeline';
18
18
  import { createAuthenticator, createAuthPlugin, createCredentialsProvider, createHaloRecoveryPlugin } from '../protocol';
19
19
  import { CredentialsSigner } from '../protocol/credentials-signer';
20
20
  import { createReplicatorPlugin } from '../protocol/replicator-plugin';
@@ -41,7 +41,7 @@ export interface JoinedParty {
41
41
  export class HaloParty {
42
42
  public readonly update = new Event<void>();
43
43
 
44
- private readonly _partyCore: PartyCore;
44
+ private readonly _partyCore: PartyPipeline;
45
45
  private _invitationManager?: InvitationFactory;
46
46
  private _protocol?: PartyProtocolFactory;
47
47
 
@@ -56,9 +56,9 @@ export class HaloParty {
56
56
  private readonly _networkManager: NetworkManager,
57
57
  private readonly _feedHints: PublicKey[] = [],
58
58
  private readonly _initialTimeframe: Timeframe | undefined,
59
- _options: PartyOptions
59
+ _options: PipelineOptions
60
60
  ) {
61
- this._partyCore = new PartyCore(
61
+ this._partyCore = new PartyPipeline(
62
62
  _credentialsSigner.getIdentityKey().publicKey,
63
63
  _feedProvider,
64
64
  modelFactory,
package/src/halo/halo.ts CHANGED
@@ -15,7 +15,7 @@ import { NetworkManager } from '@dxos/network-manager';
15
15
  import { ResultSet } from '../api';
16
16
  import { InvitationAuthenticator, InvitationDescriptor, InvitationOptions } from '../invitations';
17
17
  import { OpenProgress } from '../parties';
18
- import { MetadataStore, PartyOptions, PartyFeedProvider } from '../pipeline';
18
+ import { MetadataStore, PipelineOptions, PartyFeedProvider } from '../pipeline';
19
19
  import { SnapshotStore } from '../snapshots';
20
20
  import { Contact } from './contact-manager';
21
21
  import { HaloFactory } from './halo-factory';
@@ -36,7 +36,7 @@ export interface HaloConfiguration {
36
36
  modelFactory: ModelFactory,
37
37
  snapshotStore: SnapshotStore,
38
38
  feedProviderFactory: (partyKey: PublicKey) => PartyFeedProvider,
39
- options: PartyOptions
39
+ options: PipelineOptions
40
40
  }
41
41
 
42
42
  /**
@@ -26,8 +26,8 @@ describe('DataMirror', () => {
26
26
  const itemManager = new ItemManager(modelFactory, PublicKey.random(), feed);
27
27
  const itemDemuxer = new ItemDemuxer(itemManager, modelFactory, { snapshots: true });
28
28
 
29
- const stream = itemDemuxer.open();
30
- feed.written.on(([msg, meta]) => stream.write({
29
+ const process = itemDemuxer.open();
30
+ feed.written.on(([msg, meta]) => process({
31
31
  data: msg,
32
32
  meta: { ...meta, memberKey: PublicKey.random(), timeframe: new Timeframe() }
33
33
  } as any));
@@ -10,7 +10,7 @@ import { ModelFactory } from '@dxos/model-factory';
10
10
 
11
11
  import { DataMirror } from './data-mirror';
12
12
  import { DataServiceHost } from './data-service-host';
13
- import { ItemDemuxer, ItemDemuxerOptions } from './item-demuxer';
13
+ import { EchoProcessor, ItemDemuxer, ItemDemuxerOptions } from './item-demuxer';
14
14
  import { ItemManager } from './item-manager';
15
15
 
16
16
  const log = debug('dxos:echo-db:database-backend');
@@ -42,12 +42,11 @@ export interface DatabaseBackend {
42
42
  * Write operations result in mutations being written to the outgoing stream.
43
43
  */
44
44
  export class FeedDatabaseBackend implements DatabaseBackend {
45
- private _itemDemuxerInboundStream!: NodeJS.WritableStream
45
+ private _echoProcessor!: EchoProcessor;
46
46
  private _itemManager!: ItemManager;
47
47
  private _itemDemuxer!: ItemDemuxer;
48
48
 
49
49
  constructor (
50
- private readonly _inboundStream: NodeJS.ReadableStream,
51
50
  private readonly _outboundStream: FeedWriter<EchoEnvelope> | undefined,
52
51
  private readonly _snapshot?: DatabaseSnapshot,
53
52
  private readonly _options: ItemDemuxerOptions = {}
@@ -56,16 +55,18 @@ export class FeedDatabaseBackend implements DatabaseBackend {
56
55
  async open (itemManager: ItemManager, modelFactory: ModelFactory) {
57
56
  this._itemManager = itemManager;
58
57
  this._itemDemuxer = new ItemDemuxer(itemManager, modelFactory, this._options);
59
- this._itemDemuxerInboundStream = this._itemDemuxer.open();
60
- this._inboundStream.pipe(this._itemDemuxerInboundStream);
58
+ this._echoProcessor = this._itemDemuxer.open();
61
59
 
62
60
  if (this._snapshot) {
63
61
  await this._itemDemuxer.restoreFromSnapshot(this._snapshot);
64
62
  }
65
63
  }
66
64
 
65
+ get echoProcessor () {
66
+ return this._echoProcessor;
67
+ }
68
+
67
69
  async close () {
68
- this._inboundStream?.unpipe(this._itemDemuxerInboundStream);
69
70
  }
70
71
 
71
72
  get isReadOnly (): boolean {
@@ -9,8 +9,7 @@ import { it as test } from 'mocha';
9
9
  import { latch } from '@dxos/async';
10
10
  import { createId, PublicKey } from '@dxos/crypto';
11
11
  import { checkType } from '@dxos/debug';
12
- import { createMockFeedWriterFromStream, EchoEnvelope, IEchoStream, MockFeedWriter, Timeframe } from '@dxos/echo-protocol';
13
- import { createTransform } from '@dxos/feed-store';
12
+ import { EchoEnvelope, MockFeedWriter, Timeframe } from '@dxos/echo-protocol';
14
13
  import { ModelFactory, TestModel } from '@dxos/model-factory';
15
14
  import { ObjectModel } from '@dxos/object-model';
16
15
 
@@ -32,7 +31,7 @@ describe('Item demuxer', () => {
32
31
  const itemDemuxer = new ItemDemuxer(itemManager, modelFactory);
33
32
 
34
33
  const inboundStream = itemDemuxer.open();
35
- feedWriter.written.on(([msg, meta]) => inboundStream.write({
34
+ feedWriter.written.on(([msg, meta]) => inboundStream({
36
35
  data: msg,
37
36
  meta: { ...meta, memberKey }
38
37
  } as any));
@@ -96,28 +95,31 @@ describe('Item demuxer', () => {
96
95
  const modelFactory = new ModelFactory()
97
96
  .registerModel(ObjectModel);
98
97
 
99
- const writeStream = createTransform<EchoEnvelope, IEchoStream>(
100
- async (message: EchoEnvelope): Promise<IEchoStream> => ({
101
- meta: {
102
- feedKey: PublicKey.random(),
103
- memberKey: PublicKey.random(),
104
- seq: 0,
105
- timeframe: new Timeframe()
106
- },
107
- data: message
108
- })
109
- );
110
- const itemManager = new ItemManager(modelFactory, PublicKey.random(), createMockFeedWriterFromStream(writeStream));
98
+ const itemManager = new ItemManager(modelFactory, PublicKey.random(), {
99
+ write: async (message) => {
100
+ void processEchoMessage(message);
101
+ return { feedKey: PublicKey.random(), seq: 0 };
102
+ }
103
+ });
111
104
  const itemDemuxer = new ItemDemuxer(itemManager, modelFactory);
112
- writeStream.pipe(itemDemuxer.open());
105
+ const processor = itemDemuxer.open();
106
+ const processEchoMessage = (message: EchoEnvelope) => processor({
107
+ meta: {
108
+ feedKey: PublicKey.random(),
109
+ memberKey: PublicKey.random(),
110
+ seq: 0,
111
+ timeframe: new Timeframe()
112
+ },
113
+ data: message
114
+ });
113
115
 
114
- writeStream.write(checkType<EchoEnvelope>({
116
+ void processEchoMessage(checkType<EchoEnvelope>({
115
117
  itemId: 'foo',
116
118
  genesis: {
117
119
  modelType: TestModel.meta.type
118
120
  }
119
121
  }));
120
- writeStream.write(checkType<EchoEnvelope>({
122
+ void processEchoMessage(checkType<EchoEnvelope>({
121
123
  itemId: 'bar',
122
124
  genesis: {
123
125
  modelType: ObjectModel.meta.type
@@ -8,7 +8,6 @@ import debug from 'debug';
8
8
  import { Event } from '@dxos/async';
9
9
  import { failUndefined } from '@dxos/debug';
10
10
  import { DatabaseSnapshot, IEchoStream, ItemID, ItemSnapshot, LinkSnapshot } from '@dxos/echo-protocol';
11
- import { createWritable } from '@dxos/feed-store';
12
11
  import { Model, ModelFactory, ModelMessage } from '@dxos/model-factory';
13
12
 
14
13
  import { Entity } from './entity';
@@ -22,6 +21,8 @@ export interface ItemDemuxerOptions {
22
21
  snapshots?: boolean
23
22
  }
24
23
 
24
+ export type EchoProcessor = (message: IEchoStream) => Promise<void>
25
+
25
26
  /**
26
27
  * Creates a stream that consumes `IEchoStream` messages and routes them to the associated items.
27
28
  * @param itemManager
@@ -35,7 +36,7 @@ export class ItemDemuxer {
35
36
  private readonly _options: ItemDemuxerOptions = {}
36
37
  ) {}
37
38
 
38
- open (): NodeJS.WritableStream {
39
+ open (): EchoProcessor {
39
40
  this._modelFactory.registered.on(async model => {
40
41
  for (const item of this._itemManager.getUninitializedEntities()) {
41
42
  if (item._stateManager.modelType === model.meta.type) {
@@ -46,7 +47,7 @@ export class ItemDemuxer {
46
47
 
47
48
  // TODO(burdon): Factor out.
48
49
  // TODO(burdon): Should this implement some "back-pressure" (hints) to the PartyProcessor?
49
- return createWritable<IEchoStream>(async (message: IEchoStream) => {
50
+ return async (message: IEchoStream) => {
50
51
  const { data: { itemId, genesis, itemMutation, mutation, snapshot }, meta } = message;
51
52
  assert(itemId);
52
53
 
@@ -111,7 +112,7 @@ export class ItemDemuxer {
111
112
  }
112
113
 
113
114
  this.mutation.emit(message);
114
- });
115
+ };
115
116
  }
116
117
 
117
118
  createSnapshot (): DatabaseSnapshot {
@@ -2,8 +2,6 @@
2
2
  // Copyright 2021 DXOS.org
3
3
  //
4
4
 
5
- import { Readable } from 'stream';
6
-
7
5
  import { PublicKey } from '@dxos/crypto';
8
6
  import { EchoEnvelope, MockFeedWriter, Timeframe } from '@dxos/echo-protocol';
9
7
  import { ModelFactory } from '@dxos/model-factory';
@@ -15,12 +13,11 @@ import { FeedDatabaseBackend, RemoteDatabaseBackend } from './database-backend';
15
13
 
16
14
  export const createInMemoryDatabase = async (modelFactory: ModelFactory) => {
17
15
  const feed = new MockFeedWriter<EchoEnvelope>();
18
- const inboundStream = new Readable({ read: () => {}, objectMode: true });
19
- feed.written.on(([data, meta]) => inboundStream.push({ data, meta: { ...meta, memberKey: PublicKey.random(), timeframe: new Timeframe([[meta.feedKey, meta.seq]]) } }));
20
-
16
+ const backend = new FeedDatabaseBackend(feed, undefined, { snapshots: true });
17
+ feed.written.on(([data, meta]) => backend.echoProcessor({ data, meta: { ...meta, memberKey: PublicKey.random(), timeframe: new Timeframe([[meta.feedKey, meta.seq]]) } }));
21
18
  const database = new Database(
22
19
  modelFactory,
23
- new FeedDatabaseBackend(inboundStream, feed, undefined, { snapshots: true }),
20
+ backend,
24
21
  PublicKey.random()
25
22
  );
26
23
 
@@ -28,4 +28,8 @@ export class TimeframeClock {
28
28
  const gaps = Timeframe.dependencies(timeframe, this._timeframe);
29
29
  return !gaps.isEmpty();
30
30
  }
31
+
32
+ async waitUntilReached (target: Timeframe) {
33
+ await this.update.waitForCondition(() => Timeframe.dependencies(target, this._timeframe).isEmpty());
34
+ }
31
35
  }
@@ -31,17 +31,20 @@ describe('DataParty', () => {
31
31
  const modelFactory = new ModelFactory().registerModel(ObjectModel);
32
32
  const networkManager = new NetworkManager();
33
33
  const partyFeedProvider = new PartyFeedProvider(metadataStore, identity.keyring, feedStore, partyKey);
34
+ const writableFeed = await partyFeedProvider.createOrOpenWritableFeed();
34
35
 
35
- return new DataParty(
36
+ const party = new DataParty(
36
37
  partyKey,
37
38
  modelFactory,
38
39
  snapshotStore,
39
40
  partyFeedProvider,
41
+ metadataStore,
40
42
  identity.createCredentialsSigner(),
41
43
  identity.preferences,
42
- networkManager,
43
- feedHints
44
+ networkManager
44
45
  );
46
+ party._setFeedHints([...feedHints, writableFeed.key]);
47
+ return party;
45
48
  };
46
49
 
47
50
  test('open & close', async () => {
@@ -74,6 +77,33 @@ describe('DataParty', () => {
74
77
  await party.close();
75
78
  });
76
79
 
80
+ test('data is immediately available after re-opening', async () => {
81
+ const keyring = new Keyring();
82
+ const identity = await createTestIdentityCredentials(keyring);
83
+ const partyKey = await keyring.createKeyRecord({ type: KeyType.PARTY });
84
+ const party = await createParty(identity, partyKey.publicKey, []);
85
+ await party.open();
86
+
87
+ const feed = await party.getWriteFeed();
88
+ await party.credentialsWriter.write(createPartyGenesisMessage(
89
+ keyring,
90
+ partyKey,
91
+ feed.key,
92
+ partyKey
93
+ ));
94
+
95
+ for (let i = 0; i < 10; i++) {
96
+ await party.database.createItem({ type: 'test:item' });
97
+ }
98
+
99
+ await party.close();
100
+ await party.open();
101
+
102
+ expect(party.database.select({ type: 'test:item' }).exec().entities).toHaveLength(10);
103
+
104
+ await party.close();
105
+ });
106
+
77
107
  test('authenticates its own credentials', async () => {
78
108
  const keyring = new Keyring();
79
109
  const identity = await createTestIdentityCredentials(keyring);
@@ -17,7 +17,7 @@ import { ResultSet } from '../api';
17
17
  import { ActivationOptions, PartyPreferences, Preferences } from '../halo';
18
18
  import { InvitationFactory } from '../invitations';
19
19
  import { Database, Item } from '../packlets/database';
20
- import { PartyFeedProvider, PartyProtocolFactory, PartyCore, PartyOptions } from '../pipeline';
20
+ import { PartyFeedProvider, PartyProtocolFactory, PartyPipeline, PipelineOptions, MetadataStore } from '../pipeline';
21
21
  import { createAuthPlugin, createOfflineInvitationPlugin, createAuthenticator, createCredentialsProvider } from '../protocol';
22
22
  import { CredentialsSigner } from '../protocol/credentials-signer';
23
23
  import { createReplicatorPlugin } from '../protocol/replicator-plugin';
@@ -42,25 +42,26 @@ export interface PartyMember {
42
42
  export class DataParty {
43
43
  public readonly update = new Event<void>();
44
44
 
45
- private readonly _partyCore: PartyCore;
45
+ private readonly _partyCore: PartyPipeline;
46
46
  private readonly _preferences?: PartyPreferences;
47
47
  private _invitationManager?: InvitationFactory;
48
48
  private _protocol?: PartyProtocolFactory;
49
+ private _feedHints: PublicKey[] = []
49
50
 
50
51
  constructor (
51
52
  partyKey: PartyKey,
52
53
  modelFactory: ModelFactory,
53
54
  snapshotStore: SnapshotStore,
54
55
  private readonly _feedProvider: PartyFeedProvider,
56
+ private readonly _metadataStore: MetadataStore,
55
57
  private readonly _credentialsSigner: CredentialsSigner,
56
58
  // TODO(dmaretskyi): Pull this out to a higher level. Should preferences be part of client API instead?
57
59
  private readonly _profilePreferences: Preferences | undefined,
58
60
  private readonly _networkManager: NetworkManager,
59
- private readonly _feedHints: PublicKey[] = [],
60
61
  private readonly _initialTimeframe?: Timeframe,
61
- _options: PartyOptions = {}
62
+ _options: PipelineOptions = {}
62
63
  ) {
63
- this._partyCore = new PartyCore(
64
+ this._partyCore = new PartyPipeline(
64
65
  partyKey,
65
66
  _feedProvider,
66
67
  modelFactory,
@@ -140,6 +141,13 @@ export class DataParty {
140
141
  await this._preferences?.setLastKnownTitle(title);
141
142
  }
142
143
 
144
+ /**
145
+ * @internal
146
+ */
147
+ _setFeedHints (feedHints: PublicKey[]) {
148
+ this._feedHints = feedHints;
149
+ }
150
+
143
151
  /**
144
152
  * Opens the pipeline and connects the streams.
145
153
  */
@@ -150,9 +158,19 @@ export class DataParty {
150
158
  return this;
151
159
  }
152
160
 
161
+ // TODO(dmaretskyi): May be undefined in some tests.
162
+ const party = this._metadataStore.getParty(this._partyCore.key);
163
+
153
164
  await this._partyCore.open({
154
165
  feedHints: this._feedHints,
155
- initialTimeframe: this._initialTimeframe
166
+ initialTimeframe: this._initialTimeframe,
167
+ targetTimeframe: party?.latestTimeframe
168
+ });
169
+
170
+ // Keep updating latest reached timeframe in the metadata.
171
+ // This timeframe will be waited for when opening the party next time.
172
+ this._partyCore.timeframeUpdate.on(timeframe => {
173
+ void this._metadataStore.setTimeframe(this._partyCore.key, timeframe);
156
174
  });
157
175
 
158
176
  this._invitationManager = new InvitationFactory(
@@ -198,6 +216,9 @@ export class DataParty {
198
216
  return this;
199
217
  }
200
218
 
219
+ // Save the latest reached timeframe.
220
+ await this._metadataStore.setTimeframe(this._partyCore.key, this._partyCore.timeframe);
221
+
201
222
  await this._partyCore.close();
202
223
  await this._protocol?.stop();
203
224
 
@@ -25,7 +25,7 @@ import {
25
25
  GreetingInitiator, InvitationDescriptor, InvitationDescriptorType, OfflineInvitationClaimer
26
26
  } from '../invitations';
27
27
  import { IdentityNotInitializedError } from '../packlets/errors';
28
- import { PartyFeedProvider, PartyOptions } from '../pipeline';
28
+ import { MetadataStore, PartyFeedProvider, PipelineOptions } from '../pipeline';
29
29
  import { IdentityCredentialsProvider } from '../protocol/identity-credentials';
30
30
  import { SnapshotStore } from '../snapshots';
31
31
  import { DataParty, PARTY_ITEM_TYPE } from './data-party';
@@ -42,7 +42,8 @@ export class PartyFactory {
42
42
  private readonly _modelFactory: ModelFactory,
43
43
  private readonly _snapshotStore: SnapshotStore,
44
44
  private readonly _feedProviderFactory: (partyKey: PublicKey) => PartyFeedProvider,
45
- private readonly _options: PartyOptions = {}
45
+ private readonly _metadataStore: MetadataStore,
46
+ private readonly _options: PipelineOptions = {}
46
47
  ) {}
47
48
 
48
49
  /**
@@ -56,11 +57,13 @@ export class PartyFactory {
56
57
  const partyKey = await identity.keyring.createKeyRecord({ type: KeyType.PARTY });
57
58
  const party = await this.constructParty(partyKey.publicKey);
58
59
 
60
+ const writableFeed = await party.getWriteFeed();
61
+ // Hint at the newly created writable feed so that we can start replicating from it.
62
+ party._setFeedHints([writableFeed.key]);
63
+
59
64
  // Connect the pipeline.
60
65
  await party.open();
61
66
 
62
- const writableFeed = await party.getWriteFeed();
63
-
64
67
  // PartyGenesis (self-signed by Party).
65
68
  await party.credentialsWriter.write(createPartyGenesisMessage(
66
69
  identity.keyring,
@@ -110,12 +113,9 @@ export class PartyFactory {
110
113
  * @param partyKey
111
114
  * @param hints
112
115
  */
113
- async constructParty (partyKey: PartyKey, feedHints: PublicKey[] = [], initialTimeframe?: Timeframe) {
116
+ async constructParty (partyKey: PartyKey, initialTimeframe?: Timeframe) {
114
117
  const identity = this._identityProvider() ?? raise(new IdentityNotInitializedError());
115
118
 
116
- // TODO(marik-d): Support read-only parties if this feed doesn't exist?
117
- const feedProvider = this._feedProviderFactory(partyKey);
118
-
119
119
  //
120
120
  // Create the party.
121
121
  //
@@ -123,11 +123,11 @@ export class PartyFactory {
123
123
  partyKey,
124
124
  this._modelFactory,
125
125
  this._snapshotStore,
126
- feedProvider,
126
+ this._feedProviderFactory(partyKey),
127
+ this._metadataStore,
127
128
  identity.createCredentialsSigner(),
128
129
  identity.preferences,
129
130
  this._networkManager,
130
- feedHints,
131
131
  initialTimeframe,
132
132
  this._options
133
133
  );
@@ -139,7 +139,7 @@ export class PartyFactory {
139
139
  assert(snapshot.partyKey);
140
140
  log(`Constructing ${humanize(snapshot.partyKey)} from snapshot at ${JSON.stringify(snapshot.timeframe)}.`);
141
141
 
142
- const party = await this.constructParty(PublicKey.from(snapshot.partyKey), [], snapshot.timeframe);
142
+ const party = await this.constructParty(PublicKey.from(snapshot.partyKey), snapshot.timeframe);
143
143
  await party.restoreFromSnapshot(snapshot);
144
144
  return party;
145
145
  }
@@ -172,7 +172,8 @@ export class PartyFactory {
172
172
 
173
173
  await initiator.connect();
174
174
  const { partyKey, hints } = await initiator.redeemInvitation(secretProvider);
175
- const party = await this.constructParty(partyKey, hints);
175
+ const party = await this.constructParty(partyKey);
176
+ party._setFeedHints(hints);
176
177
  await party.open();
177
178
  await initiator.destroy();
178
179
 
@@ -197,11 +198,13 @@ export class PartyFactory {
197
198
  const partyKey = await identity.keyring.createKeyRecord({ type: KeyType.PARTY });
198
199
  const party = await this.constructParty(partyKey.publicKey);
199
200
 
201
+ const writableFeed = await party.getWriteFeed();
202
+ // Hint at the newly created writable feed so that we can start replicating from it.
203
+ party._setFeedHints([writableFeed.key]);
204
+
200
205
  // Connect the pipeline.
201
206
  await party.open();
202
207
 
203
- const writableFeed = await party.getWriteFeed();
204
-
205
208
  // PartyGenesis (self-signed by Party).
206
209
  await party.credentialsWriter.write(createPartyGenesisMessage(
207
210
  identity.keyring,
@@ -71,6 +71,7 @@ const setup = async () => {
71
71
  modelFactory,
72
72
  snapshotStore,
73
73
  feedProviderFactory,
74
+ metadataStore,
74
75
  {
75
76
  writeLogger: messageLogger('<<<'),
76
77
  readLogger: messageLogger('>>>')
@@ -161,7 +162,8 @@ describe('Party manager', () => {
161
162
  networkManager,
162
163
  modelFactory,
163
164
  snapshotStore,
164
- feedProviderFactory
165
+ feedProviderFactory,
166
+ metadataStore
165
167
  );
166
168
  const partyManager = new PartyManager(metadataStore, snapshotStore, () => identity, partyFactory);
167
169
 
@@ -9,7 +9,7 @@ import unionWith from 'lodash.unionwith';
9
9
  import { Event, synchronized } from '@dxos/async';
10
10
  import { KeyHint, KeyType, SecretProvider } from '@dxos/credentials';
11
11
  import { PublicKey } from '@dxos/crypto';
12
- import { timed } from '@dxos/debug';
12
+ import { failUndefined, timed } from '@dxos/debug';
13
13
  import { PartyKey, PartySnapshot } from '@dxos/echo-protocol';
14
14
  import { ComplexMap, boolGuard } from '@dxos/util';
15
15
 
@@ -94,9 +94,13 @@ export class PartyManager {
94
94
  const partyKey = partyKeys[i];
95
95
  if (!this._parties.has(partyKey)) {
96
96
  const snapshot = await this._snapshotStore.load(partyKey);
97
+
98
+ const metadata = this._metadataStore.getParty(partyKey) ?? failUndefined();
99
+
97
100
  const party = snapshot
98
101
  ? await this._partyFactory.constructPartyFromSnapshot(snapshot)
99
102
  : await this._partyFactory.constructParty(partyKey);
103
+ party._setFeedHints(metadata.feedKeys ?? []);
100
104
 
101
105
  const isActive = identity?.preferences?.isPartyActive(partyKey) ?? true;
102
106
  if (isActive) {
@@ -173,7 +177,8 @@ export class PartyManager {
173
177
  }
174
178
 
175
179
  log(`Adding party partyKey=${partyKey.toHex()} hints=${feedHints.length}`);
176
- const party = await this._partyFactory.constructParty(partyKey, feedHints);
180
+ const party = await this._partyFactory.constructParty(partyKey);
181
+ party._setFeedHints(feedHints);
177
182
  await party.open();
178
183
  await this._metadataStore.addParty(party.key);
179
184
  this._setParty(party);