@dxos/echo-pipeline 0.5.0 → 0.5.1-main.2ed734f

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.
@@ -2,11 +2,19 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { asyncTimeout } from '@dxos/async';
6
- import { next as automerge } from '@dxos/automerge/automerge';
7
- import { Repo, type DocumentId, type PeerId, type StorageAdapterInterface } from '@dxos/automerge/automerge-repo';
5
+ import { Event } from '@dxos/async';
6
+ import { type Doc, next as automerge, getBackend, type Heads } from '@dxos/automerge/automerge';
7
+ import {
8
+ type DocHandle,
9
+ Repo,
10
+ type DocumentId,
11
+ type PeerId,
12
+ type StorageAdapterInterface,
13
+ type DocHandleChangePayload,
14
+ } from '@dxos/automerge/automerge-repo';
8
15
  import { type Stream } from '@dxos/codec-protobuf';
9
16
  import { Context, type Lifecycle } from '@dxos/context';
17
+ import { invariant } from '@dxos/invariant';
10
18
  import { PublicKey } from '@dxos/keys';
11
19
  import { log } from '@dxos/log';
12
20
  import {
@@ -24,7 +32,7 @@ import { LevelDBStorageAdapter, type StorageCallbacks } from './leveldb-storage-
24
32
  import { LocalHostNetworkAdapter } from './local-host-network-adapter';
25
33
  import { MeshNetworkAdapter } from './mesh-network-adapter';
26
34
  import { levelMigration } from './migrations';
27
- import { type SubLevelDB } from './types';
35
+ import { type SpaceDoc, type SubLevelDB } from './types';
28
36
 
29
37
  // TODO: Remove
30
38
  export type { DocumentId };
@@ -185,16 +193,17 @@ export class AutomergeHost {
185
193
  // Methods for client-services.
186
194
  //
187
195
  @trace.span({ showInBrowserTimeline: true })
188
- async flush({ documentIds }: FlushRequest): Promise<void> {
196
+ async flush({ states }: FlushRequest): Promise<void> {
189
197
  // Note: Wait for all requested documents to be loaded/synced from thin-client.
190
- await Promise.all(documentIds?.map((id) => this._repo.find(id as DocumentId).whenReady()) ?? []);
191
-
192
- // TODO(dmaretskyi): Workaround until the flush issue gets resolved.
193
- try {
194
- await asyncTimeout(this._repo.flush(documentIds as DocumentId[]), 500);
195
- } catch (err) {
196
- log.warn('flush error', { documentIds, err });
197
- }
198
+ await Promise.all(
199
+ states?.map(async ({ heads, documentId }) => {
200
+ invariant(heads, 'heads are required for flush');
201
+ const handle = this.repo.handles[documentId as DocumentId] ?? this._repo.find(documentId as DocumentId);
202
+ await waitForHeads(handle, heads);
203
+ }) ?? [],
204
+ );
205
+
206
+ await this._repo.flush(states?.map(({ documentId }) => documentId as DocumentId));
198
207
  }
199
208
 
200
209
  syncRepo(request: SyncRepoRequest): Stream<SyncRepoResponse> {
@@ -232,3 +241,26 @@ export const getSpaceKeyFromDoc = (doc: any): string | null => {
232
241
 
233
242
  return String(rawSpaceKey);
234
243
  };
244
+
245
+ const waitForHeads = async (handle: DocHandle<SpaceDoc>, heads: Heads) => {
246
+ await handle.whenReady();
247
+ const unavailableHeads = new Set(heads);
248
+
249
+ await Event.wrap<DocHandleChangePayload<SpaceDoc>>(handle, 'change').waitForCondition(() => {
250
+ // Check if unavailable heads became available.
251
+ for (const changeHash of unavailableHeads.values()) {
252
+ if (changeIsPresentInDoc(handle.docSync(), changeHash)) {
253
+ unavailableHeads.delete(changeHash);
254
+ }
255
+ }
256
+
257
+ if (unavailableHeads.size === 0) {
258
+ return true;
259
+ }
260
+ return false;
261
+ });
262
+ };
263
+
264
+ const changeIsPresentInDoc = (doc: Doc<any>, changeHash: string): boolean => {
265
+ return !!getBackend(doc).getChangeByHash(changeHash);
266
+ };
@@ -2,19 +2,25 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { expect } from 'chai';
6
+
7
+ import { change, clone, from, getBackend, getHeads } from '@dxos/automerge/automerge';
5
8
  import { Repo } from '@dxos/automerge/automerge-repo';
6
9
  import { randomBytes } from '@dxos/crypto';
7
- import { StorageType, createStorage } from '@dxos/random-access-storage';
8
- import { describe, test } from '@dxos/test';
10
+ import { describe, openAndClose, test } from '@dxos/test';
9
11
 
10
- import { AutomergeStorageAdapter } from './automerge-storage-adapter';
12
+ import { LevelDBStorageAdapter } from './leveldb-storage-adapter';
13
+ import { createTestLevel } from '../testing';
11
14
 
12
15
  describe('AutomergeRepo', () => {
13
- // Currently failing
14
- test.skip('flush', async () => {
16
+ test('flush', async () => {
17
+ const level = createTestLevel();
18
+ const storage = new LevelDBStorageAdapter({ db: level.sublevel('automerge') });
19
+ await openAndClose(level, storage);
20
+
15
21
  const repo = new Repo({
16
22
  network: [],
17
- storage: new AutomergeStorageAdapter(createStorage({ type: StorageType.NODE }).createDirectory()),
23
+ storage,
18
24
  });
19
25
  const handle = repo.create<{ field?: string }>();
20
26
 
@@ -26,4 +32,30 @@ describe('AutomergeRepo', () => {
26
32
  await p;
27
33
  }
28
34
  });
35
+
36
+ test('getChangeByHash', async () => {
37
+ const level = createTestLevel();
38
+ const storage = new LevelDBStorageAdapter({ db: level.sublevel('automerge') });
39
+ await openAndClose(level, storage);
40
+
41
+ const doc = from({ foo: 'bar' });
42
+ const copy = clone(doc);
43
+ const newDoc = change(copy, 'change', (doc: any) => {
44
+ doc.foo = 'baz';
45
+ });
46
+
47
+ {
48
+ const heads = getHeads(newDoc);
49
+ const changes = heads.map((hash) => getBackend(newDoc).getChangeByHash(hash));
50
+ expect(changes.length).to.equal(1);
51
+ expect(changes[0]).to.not.be.null;
52
+ }
53
+
54
+ {
55
+ const heads = getHeads(newDoc);
56
+ const changes = heads.map((hash) => getBackend(doc).getChangeByHash(hash));
57
+ expect(changes.length).to.equal(1);
58
+ expect(changes[0]).to.be.null;
59
+ }
60
+ });
29
61
  });
@@ -8,4 +8,5 @@ import { PublicKey } from '@dxos/keys';
8
8
 
9
9
  import { type LevelDB } from '../automerge/types';
10
10
 
11
- export const createTestLevel = (): LevelDB => new Level<string, string>(`/tmp/dxos-${PublicKey.random().toHex()}`);
11
+ export const createTestLevel = (path = `/tmp/dxos-${PublicKey.random().toHex()}`): LevelDB =>
12
+ new Level<string, string>(path);