@dxos/echo-pipeline 0.4.9 → 0.4.10-main.068c3d8

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 (69) hide show
  1. package/dist/lib/browser/{chunk-RTEEJ723.mjs → chunk-SYE4EK33.mjs} +30 -35
  2. package/dist/lib/browser/chunk-SYE4EK33.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +593 -217
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +8 -2
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node/{chunk-7VZVCCNF.cjs → chunk-WCTX6RNS.cjs} +35 -40
  9. package/dist/lib/node/chunk-WCTX6RNS.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +611 -237
  11. package/dist/lib/node/index.cjs.map +4 -4
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/testing/index.cjs +18 -13
  14. package/dist/lib/node/testing/index.cjs.map +4 -4
  15. package/dist/types/src/automerge/automerge-doc-loader.d.ts +66 -0
  16. package/dist/types/src/automerge/automerge-doc-loader.d.ts.map +1 -0
  17. package/dist/types/src/automerge/automerge-doc-loader.test.d.ts +2 -0
  18. package/dist/types/src/automerge/automerge-doc-loader.test.d.ts.map +1 -0
  19. package/dist/types/src/automerge/automerge-host.d.ts +21 -18
  20. package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
  21. package/dist/types/src/automerge/automerge-repo.test.d.ts +2 -0
  22. package/dist/types/src/automerge/automerge-repo.test.d.ts.map +1 -0
  23. package/dist/types/src/automerge/index.d.ts +4 -0
  24. package/dist/types/src/automerge/index.d.ts.map +1 -1
  25. package/dist/types/src/automerge/level.test.d.ts +2 -0
  26. package/dist/types/src/automerge/level.test.d.ts.map +1 -0
  27. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +30 -0
  28. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -0
  29. package/dist/types/src/automerge/local-host-network-adapter.d.ts +8 -1
  30. package/dist/types/src/automerge/local-host-network-adapter.d.ts.map +1 -1
  31. package/dist/types/src/automerge/migrations.d.ts +7 -0
  32. package/dist/types/src/automerge/migrations.d.ts.map +1 -0
  33. package/dist/types/src/automerge/reference.d.ts +15 -0
  34. package/dist/types/src/automerge/reference.d.ts.map +1 -0
  35. package/dist/types/src/automerge/storage-adapter.test.d.ts +2 -0
  36. package/dist/types/src/automerge/storage-adapter.test.d.ts.map +1 -0
  37. package/dist/types/src/automerge/types.d.ts +73 -0
  38. package/dist/types/src/automerge/types.d.ts.map +1 -0
  39. package/dist/types/src/metadata/metadata-store.d.ts +2 -1
  40. package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
  41. package/dist/types/src/space/space.d.ts +4 -8
  42. package/dist/types/src/space/space.d.ts.map +1 -1
  43. package/dist/types/src/testing/index.d.ts +1 -0
  44. package/dist/types/src/testing/index.d.ts.map +1 -1
  45. package/dist/types/src/testing/level.d.ts +3 -0
  46. package/dist/types/src/testing/level.d.ts.map +1 -0
  47. package/dist/types/src/testing/test-agent-builder.d.ts +2 -2
  48. package/package.json +33 -30
  49. package/src/automerge/automerge-doc-loader.test.ts +97 -0
  50. package/src/automerge/automerge-doc-loader.ts +241 -0
  51. package/src/automerge/automerge-host.test.ts +22 -8
  52. package/src/automerge/automerge-host.ts +65 -118
  53. package/src/automerge/automerge-repo.test.ts +29 -0
  54. package/src/automerge/index.ts +4 -0
  55. package/src/automerge/level.test.ts +64 -0
  56. package/src/automerge/leveldb-storage-adapter.ts +117 -0
  57. package/src/automerge/local-host-network-adapter.ts +19 -13
  58. package/src/automerge/migrations.ts +41 -0
  59. package/src/automerge/reference.ts +31 -0
  60. package/src/automerge/storage-adapter.test.ts +90 -0
  61. package/src/automerge/types.ts +86 -0
  62. package/src/db-host/data-service.ts +1 -1
  63. package/src/metadata/metadata-store.ts +17 -8
  64. package/src/space/space.test.ts +7 -7
  65. package/src/space/space.ts +6 -21
  66. package/src/testing/index.ts +1 -0
  67. package/src/testing/level.ts +11 -0
  68. package/dist/lib/browser/chunk-RTEEJ723.mjs.map +0 -7
  69. package/dist/lib/node/chunk-7VZVCCNF.cjs.map +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/echo-pipeline",
3
- "version": "0.4.9",
3
+ "version": "0.4.10-main.068c3d8",
4
4
  "description": "ECHO database.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -35,36 +35,39 @@
35
35
  "src"
36
36
  ],
37
37
  "dependencies": {
38
+ "abstract-level": "^1.0.2",
38
39
  "crc-32": "^1.2.2",
39
- "@dxos/automerge": "0.4.9",
40
- "@dxos/codec-protobuf": "0.4.9",
41
- "@dxos/async": "0.4.9",
42
- "@dxos/context": "0.4.9",
43
- "@dxos/credentials": "0.4.9",
44
- "@dxos/debug": "0.4.9",
45
- "@dxos/crypto": "0.4.9",
46
- "@dxos/echo-db": "0.4.9",
47
- "@dxos/feed-store": "0.4.9",
48
- "@dxos/hypercore": "0.4.9",
49
- "@dxos/keyring": "0.4.9",
50
- "@dxos/invariant": "0.4.9",
51
- "@dxos/messaging": "0.4.9",
52
- "@dxos/log": "0.4.9",
53
- "@dxos/keys": "0.4.9",
54
- "@dxos/network-manager": "0.4.9",
55
- "@dxos/node-std": "0.4.9",
56
- "@dxos/protocols": "0.4.9",
57
- "@dxos/rpc": "0.4.9",
58
- "@dxos/random-access-storage": "0.4.9",
59
- "@dxos/teleport": "0.4.9",
60
- "@dxos/teleport-extension-automerge-replicator": "0.4.9",
61
- "@dxos/teleport-extension-gossip": "0.4.9",
62
- "@dxos/teleport-extension-object-sync": "0.4.9",
63
- "@dxos/timeframe": "0.4.9",
64
- "@dxos/teleport-extension-replicator": "0.4.9",
65
- "@dxos/tracing": "0.4.9",
66
- "@dxos/typings": "0.4.9",
67
- "@dxos/util": "0.4.9"
40
+ "level": "^8.0.1",
41
+ "level-transcoder": "^1.0.1",
42
+ "@dxos/async": "0.4.10-main.068c3d8",
43
+ "@dxos/automerge": "0.4.10-main.068c3d8",
44
+ "@dxos/codec-protobuf": "0.4.10-main.068c3d8",
45
+ "@dxos/context": "0.4.10-main.068c3d8",
46
+ "@dxos/credentials": "0.4.10-main.068c3d8",
47
+ "@dxos/crypto": "0.4.10-main.068c3d8",
48
+ "@dxos/echo-db": "0.4.10-main.068c3d8",
49
+ "@dxos/debug": "0.4.10-main.068c3d8",
50
+ "@dxos/feed-store": "0.4.10-main.068c3d8",
51
+ "@dxos/invariant": "0.4.10-main.068c3d8",
52
+ "@dxos/hypercore": "0.4.10-main.068c3d8",
53
+ "@dxos/keys": "0.4.10-main.068c3d8",
54
+ "@dxos/log": "0.4.10-main.068c3d8",
55
+ "@dxos/keyring": "0.4.10-main.068c3d8",
56
+ "@dxos/messaging": "0.4.10-main.068c3d8",
57
+ "@dxos/network-manager": "0.4.10-main.068c3d8",
58
+ "@dxos/node-std": "0.4.10-main.068c3d8",
59
+ "@dxos/protocols": "0.4.10-main.068c3d8",
60
+ "@dxos/random-access-storage": "0.4.10-main.068c3d8",
61
+ "@dxos/rpc": "0.4.10-main.068c3d8",
62
+ "@dxos/teleport-extension-automerge-replicator": "0.4.10-main.068c3d8",
63
+ "@dxos/teleport": "0.4.10-main.068c3d8",
64
+ "@dxos/teleport-extension-object-sync": "0.4.10-main.068c3d8",
65
+ "@dxos/teleport-extension-gossip": "0.4.10-main.068c3d8",
66
+ "@dxos/teleport-extension-replicator": "0.4.10-main.068c3d8",
67
+ "@dxos/timeframe": "0.4.10-main.068c3d8",
68
+ "@dxos/tracing": "0.4.10-main.068c3d8",
69
+ "@dxos/typings": "0.4.10-main.068c3d8",
70
+ "@dxos/util": "0.4.10-main.068c3d8"
68
71
  },
69
72
  "devDependencies": {
70
73
  "fast-check": "^3.15.1",
@@ -0,0 +1,97 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { expect } from 'chai';
6
+
7
+ import { sleep } from '@dxos/async';
8
+ import { Repo } from '@dxos/automerge/automerge-repo';
9
+ import { Context } from '@dxos/context';
10
+ import { PublicKey } from '@dxos/keys';
11
+ import { describe, test } from '@dxos/test';
12
+
13
+ import {
14
+ type AutomergeDocumentLoader,
15
+ AutomergeDocumentLoaderImpl,
16
+ type ObjectDocumentLoaded,
17
+ } from './automerge-doc-loader';
18
+ import { type SpaceDoc } from './types';
19
+
20
+ const ctx = new Context();
21
+ const SPACE_KEY = PublicKey.random();
22
+ const randomId = () => PublicKey.random().toHex();
23
+ describe('AutomergeDocumentLoader', () => {
24
+ test('space access is set on root doc handle and it is accessible', async () => {
25
+ const { loader, spaceRootDocHandle } = await setupTest();
26
+ expect(loader.getSpaceRootDocHandle()).not.to.throw;
27
+ expect(spaceRootDocHandle.docSync()?.access?.spaceKey).to.eq(SPACE_KEY.toHex());
28
+ });
29
+
30
+ test('new object document is linked with space and root document', async () => {
31
+ const objectId = randomId();
32
+ const { loader, spaceRootDocHandle } = await setupTest();
33
+ const objectDocHandle = loader.createDocumentForObject(objectId);
34
+ const handle = spaceRootDocHandle.docSync();
35
+ expect(objectDocHandle.docSync()?.access?.spaceKey).to.eq(SPACE_KEY.toHex());
36
+ expect(handle?.links[objectId]).to.eq(objectDocHandle.url);
37
+ });
38
+
39
+ test('listener is invoked after a document is loaded', async () => {
40
+ const objectId = randomId();
41
+ const { loader, repo } = await setupTest();
42
+ const handle = repo.create<SpaceDoc>();
43
+ const docLoadInfo = waitForDocumentLoad(loader, { objectId, handle });
44
+ loadLinkedObjects(loader, { [objectId]: handle.url });
45
+ await sleep(10);
46
+ expect(docLoadInfo.loaded).to.be.true;
47
+ });
48
+
49
+ test('listener is not invoked if an object was rebound during document loading', async () => {
50
+ const objectId = randomId();
51
+ const { loader, repo } = await setupTest();
52
+ const oldDocHandle = repo.create<SpaceDoc>();
53
+ const newDocHandle = repo.create<SpaceDoc>();
54
+ const docLoadInfo = waitForDocumentLoad(loader, { objectId, handle: oldDocHandle });
55
+ loadLinkedObjects(loader, { [objectId]: oldDocHandle.url });
56
+ loader.onObjectBoundToDocument(newDocHandle, objectId);
57
+ await sleep(10);
58
+ expect(docLoadInfo.loaded).to.be.false;
59
+ });
60
+
61
+ test('document link is not loaded if object exists as inline object', async () => {
62
+ const objectId = randomId();
63
+ const { loader, repo } = await setupTest();
64
+ const existingHandle = repo.create<SpaceDoc>();
65
+ loader.onObjectBoundToDocument(existingHandle, objectId);
66
+ const newDocHandle = repo.create<SpaceDoc>();
67
+ const docLoadInfo = waitForDocumentLoad(loader, { objectId, handle: newDocHandle });
68
+ loadLinkedObjects(loader, { [objectId]: existingHandle.url });
69
+ await sleep(10);
70
+ expect(docLoadInfo.loaded).to.be.false;
71
+ });
72
+
73
+ const setupTest = async () => {
74
+ const repo = new Repo({ network: [] });
75
+ const loader = new AutomergeDocumentLoaderImpl(SPACE_KEY, repo);
76
+ const spaceRootDocHandle = repo.create<SpaceDoc>();
77
+ await loader.loadSpaceRootDocHandle(ctx, {
78
+ rootUrl: spaceRootDocHandle.url,
79
+ });
80
+ return { loader, spaceRootDocHandle, repo };
81
+ };
82
+
83
+ const loadLinkedObjects = (loader: AutomergeDocumentLoader, links: SpaceDoc['links']) => {
84
+ Object.keys(links ?? {}).forEach((objectId) => loader.loadObjectDocument(objectId));
85
+ loader.onObjectLinksUpdated(links);
86
+ };
87
+
88
+ const waitForDocumentLoad = (loader: AutomergeDocumentLoader, expected: ObjectDocumentLoaded) => {
89
+ const docLoadInfo = { loaded: false };
90
+ loader.onObjectDocumentLoaded.on((data) => {
91
+ expect(data.objectId).to.eq(expected.objectId);
92
+ expect(data.handle.url).to.eq(expected.handle.url);
93
+ docLoadInfo.loaded = true;
94
+ });
95
+ return docLoadInfo;
96
+ };
97
+ });
@@ -0,0 +1,241 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Event } from '@dxos/async';
6
+ import { type DocHandle, type AutomergeUrl, type DocumentId, type Repo } from '@dxos/automerge/automerge-repo';
7
+ import { cancelWithContext, type Context } from '@dxos/context';
8
+ import { warnAfterTimeout } from '@dxos/debug';
9
+ import { invariant } from '@dxos/invariant';
10
+ import { type PublicKey } from '@dxos/keys';
11
+ import { log } from '@dxos/log';
12
+
13
+ import { type SpaceState, type SpaceDoc } from './types';
14
+
15
+ type SpaceDocumentLinks = SpaceDoc['links'];
16
+
17
+ export interface AutomergeDocumentLoader {
18
+ onObjectDocumentLoaded: Event<ObjectDocumentLoaded>;
19
+
20
+ getAllHandles(): DocHandle<SpaceDoc>[];
21
+
22
+ loadSpaceRootDocHandle(ctx: Context, spaceState: SpaceState): Promise<void>;
23
+ loadObjectDocument(objectId: string): void;
24
+ getSpaceRootDocHandle(): DocHandle<SpaceDoc>;
25
+ createDocumentForObject(objectId: string): DocHandle<SpaceDoc>;
26
+ onObjectLinksUpdated(links: SpaceDocumentLinks): void;
27
+ onObjectBoundToDocument(handle: DocHandle<SpaceDoc>, objectId: string): void;
28
+
29
+ /**
30
+ * @returns objectIds for which we had document handles or were loading one.
31
+ */
32
+ clearHandleReferences(): string[];
33
+ }
34
+
35
+ /**
36
+ * Manages object <-> docHandle binding and automerge document loading.
37
+ */
38
+ export class AutomergeDocumentLoaderImpl implements AutomergeDocumentLoader {
39
+ private _spaceRootDocHandle: DocHandle<SpaceDoc> | null = null;
40
+ /**
41
+ * An object id pointer to a handle of the document where the object is stored inline.
42
+ */
43
+ private readonly _objectDocumentHandles = new Map<string, DocHandle<SpaceDoc>>();
44
+ /**
45
+ * If object was requested via loadObjectDocument but root document links weren't updated yet
46
+ * loading will be triggered in onObjectLinksUpdated callback.
47
+ */
48
+ private readonly _objectsPendingDocumentLoad = new Set<string>();
49
+
50
+ public readonly onObjectDocumentLoaded = new Event<ObjectDocumentLoaded>();
51
+
52
+ constructor(
53
+ private readonly _spaceKey: PublicKey,
54
+ private readonly _repo: Repo,
55
+ ) {}
56
+
57
+ getAllHandles(): DocHandle<SpaceDoc>[] {
58
+ return [...new Set(this._objectDocumentHandles.values())];
59
+ }
60
+
61
+ public async loadSpaceRootDocHandle(ctx: Context, spaceState: SpaceState): Promise<void> {
62
+ if (this._spaceRootDocHandle != null) {
63
+ return;
64
+ }
65
+ if (!spaceState.rootUrl) {
66
+ log.error('Database opened with no rootUrl', { spaceKey: this._spaceKey });
67
+ this._createContextBoundSpaceRootDocument(ctx);
68
+ } else {
69
+ const existingDocHandle = await this._initDocHandle(ctx, spaceState.rootUrl);
70
+ const doc = existingDocHandle.docSync();
71
+ invariant(doc);
72
+ if (doc.access == null) {
73
+ this._initDocAccess(existingDocHandle);
74
+ }
75
+ this._spaceRootDocHandle = existingDocHandle;
76
+ }
77
+ }
78
+
79
+ public loadObjectDocument(objectId: string) {
80
+ invariant(this._spaceRootDocHandle);
81
+ if (this._objectDocumentHandles.has(objectId) || this._objectsPendingDocumentLoad.has(objectId)) {
82
+ return;
83
+ }
84
+ const spaceRootDoc = this._spaceRootDocHandle.docSync();
85
+ invariant(spaceRootDoc);
86
+ const documentUrl = (spaceRootDoc.links ?? {})[objectId];
87
+ if (documentUrl == null) {
88
+ this._objectsPendingDocumentLoad.add(objectId);
89
+ log.info('loading delayed until object links are initialized', { objectId });
90
+ return;
91
+ }
92
+ this._loadLinkedObjects({ [objectId]: documentUrl });
93
+ }
94
+
95
+ public onObjectLinksUpdated(links: SpaceDocumentLinks) {
96
+ if (!links) {
97
+ return;
98
+ }
99
+ const linksAwaitingLoad = Object.entries(links).filter(([objectId]) =>
100
+ this._objectsPendingDocumentLoad.has(objectId),
101
+ );
102
+ this._loadLinkedObjects(Object.fromEntries(linksAwaitingLoad));
103
+ linksAwaitingLoad.forEach(([objectId]) => this._objectsPendingDocumentLoad.delete(objectId));
104
+ }
105
+
106
+ public getSpaceRootDocHandle(): DocHandle<SpaceDoc> {
107
+ invariant(this._spaceRootDocHandle);
108
+ return this._spaceRootDocHandle;
109
+ }
110
+
111
+ public createDocumentForObject(objectId: string): DocHandle<SpaceDoc> {
112
+ invariant(this._spaceRootDocHandle);
113
+ const spaceDocHandle = this._repo.create<SpaceDoc>();
114
+ this._initDocAccess(spaceDocHandle);
115
+ this.onObjectBoundToDocument(spaceDocHandle, objectId);
116
+ this._spaceRootDocHandle.change((newDoc: SpaceDoc) => {
117
+ newDoc.links ??= {};
118
+ newDoc.links[objectId] = spaceDocHandle.url;
119
+ });
120
+ return spaceDocHandle;
121
+ }
122
+
123
+ public onObjectBoundToDocument(handle: DocHandle<SpaceDoc>, objectId: string) {
124
+ this._objectDocumentHandles.set(objectId, handle);
125
+ }
126
+
127
+ public clearHandleReferences(): string[] {
128
+ const objectsWithHandles = [...this._objectDocumentHandles.keys()];
129
+ this._objectDocumentHandles.clear();
130
+ this._spaceRootDocHandle = null;
131
+ return objectsWithHandles;
132
+ }
133
+
134
+ private _loadLinkedObjects(links: SpaceDocumentLinks) {
135
+ if (!links) {
136
+ return;
137
+ }
138
+ for (const [objectId, automergeUrl] of Object.entries(links)) {
139
+ const logMeta = { objectId, automergeUrl };
140
+ const objectDocumentHandle = this._objectDocumentHandles.get(objectId);
141
+ if (objectDocumentHandle != null && objectDocumentHandle.url !== automergeUrl) {
142
+ log.warn('object already inlined in a different document, ignoring the link', {
143
+ ...logMeta,
144
+ actualDocumentUrl: objectDocumentHandle.url,
145
+ });
146
+ continue;
147
+ }
148
+ if (objectDocumentHandle?.url === automergeUrl) {
149
+ log.warn('object document was already loaded', logMeta);
150
+ continue;
151
+ }
152
+ const handle = this._repo.find<SpaceDoc>(automergeUrl as DocumentId);
153
+ log.debug('document loading triggered', logMeta);
154
+ this._objectDocumentHandles.set(objectId, handle);
155
+ void this._createObjectOnDocumentLoad(handle, objectId);
156
+ }
157
+ }
158
+
159
+ private async _initDocHandle(ctx: Context, url: string) {
160
+ const docHandle = this._repo.find<SpaceDoc>(url as DocumentId);
161
+ while (true) {
162
+ try {
163
+ await warnAfterTimeout(5_000, 'Automerge root doc load timeout (AutomergeDb)', async () => {
164
+ await cancelWithContext(ctx, docHandle.whenReady()); // TODO(dmaretskyi): Temporary 5s timeout for debugging.
165
+ });
166
+ break;
167
+ } catch (err) {
168
+ if (`${err}`.includes('Timeout')) {
169
+ log.info('wraparound', { id: docHandle.documentId, state: docHandle.state });
170
+ continue;
171
+ }
172
+
173
+ throw err;
174
+ }
175
+ }
176
+
177
+ if (docHandle.state === 'unavailable') {
178
+ throw new Error('Automerge document is unavailable');
179
+ }
180
+
181
+ return docHandle;
182
+ }
183
+
184
+ private _createContextBoundSpaceRootDocument(ctx: Context) {
185
+ const docHandle = this._repo.create<SpaceDoc>();
186
+ this._spaceRootDocHandle = docHandle;
187
+ ctx.onDispose(() => {
188
+ docHandle.delete();
189
+ this._spaceRootDocHandle = null;
190
+ });
191
+ }
192
+
193
+ private _initDocAccess(handle: DocHandle<SpaceDoc>) {
194
+ handle.change((newDoc: SpaceDoc) => {
195
+ newDoc.access ??= { spaceKey: this._spaceKey.toHex() };
196
+ newDoc.access.spaceKey = this._spaceKey.toHex();
197
+ });
198
+ }
199
+
200
+ private async _createObjectOnDocumentLoad(handle: DocHandle<SpaceDoc>, objectId: string) {
201
+ try {
202
+ await handle.doc(['ready']);
203
+ const logMeta = { objectId, docUrl: handle.url };
204
+ if (this.onObjectDocumentLoaded.listenerCount() === 0) {
205
+ log.info('document loaded after all listeners were removed', logMeta);
206
+ return;
207
+ }
208
+ const objectDocHandle = this._objectDocumentHandles.get(objectId);
209
+ if (objectDocHandle?.url !== handle.url) {
210
+ log.warn('object was rebound while a document was loading, discarding handle', logMeta);
211
+ return;
212
+ }
213
+ this.onObjectDocumentLoaded.emit({ handle, objectId });
214
+ } catch (err) {
215
+ const shouldRetryLoading = this.onObjectDocumentLoaded.listenerCount() > 0;
216
+ log.warn('failed to load a document', {
217
+ objectId,
218
+ automergeUrl: handle.url,
219
+ retryLoading: shouldRetryLoading,
220
+ err,
221
+ });
222
+ if (shouldRetryLoading) {
223
+ await this._createObjectOnDocumentLoad(handle, objectId);
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ export interface ObjectDocumentLoaded {
230
+ handle: DocHandle<SpaceDoc>;
231
+ objectId: string;
232
+ }
233
+
234
+ export interface DocumentChanges {
235
+ createdObjectIds: string[];
236
+ updatedObjectIds: string[];
237
+ objectsToRebind: string[];
238
+ linkedDocuments: {
239
+ [echoId: string]: AutomergeUrl;
240
+ };
241
+ }
@@ -25,35 +25,50 @@ import { arrayToBuffer, bufferToArray } from '@dxos/util';
25
25
  import { AutomergeHost } from './automerge-host';
26
26
  import { AutomergeStorageAdapter } from './automerge-storage-adapter';
27
27
  import { MeshNetworkAdapter } from './mesh-network-adapter';
28
+ import { createTestLevel } from '../testing';
28
29
 
29
30
  describe('AutomergeHost', () => {
30
- test('can create documents', () => {
31
- const host = new AutomergeHost({ directory: createStorage({ type: StorageType.RAM }).createDirectory() });
31
+ test('can create documents', async () => {
32
+ const level = createTestLevel();
33
+ await level.open();
34
+ afterTest(() => level.close());
35
+ const host = new AutomergeHost({
36
+ db: level.sublevel('automerge'),
37
+ });
38
+ await host.open();
39
+ afterTest(() => host.close());
32
40
 
33
41
  const handle = host.repo.create();
34
42
  handle.change((doc: any) => {
35
43
  doc.text = 'Hello world';
36
44
  });
45
+ await host.repo.flush();
37
46
  expect(handle.docSync().text).toEqual('Hello world');
38
47
  });
39
48
 
40
49
  test('changes are preserved in storage', async () => {
41
- const storageDirectory = createStorage({ type: StorageType.RAM }).createDirectory();
50
+ const level = createTestLevel();
51
+ await level.open();
52
+ afterTest(() => level.close());
42
53
 
43
- const host = new AutomergeHost({ directory: storageDirectory });
54
+ const host = new AutomergeHost({ db: level.sublevel('automerge') });
55
+ await host.open();
44
56
  const handle = host.repo.create();
45
57
  handle.change((doc: any) => {
46
58
  doc.text = 'Hello world';
47
59
  });
48
60
  const url = handle.url;
49
61
 
50
- // TODO(dmaretskyi): Is there a way to know when automerge has finished saving?
51
- await sleep(100);
62
+ await host.repo.flush();
63
+ await host.close();
52
64
 
53
- const host2 = new AutomergeHost({ directory: storageDirectory });
65
+ const host2 = new AutomergeHost({ db: level.sublevel('automerge') });
66
+ await host2.open();
67
+ afterTest(() => host2.close());
54
68
  const handle2 = host2.repo.find(url);
55
69
  await handle2.whenReady();
56
70
  expect(handle2.docSync().text).toEqual('Hello world');
71
+ await host2.repo.flush();
57
72
  });
58
73
 
59
74
  test('basic networking', async () => {
@@ -168,7 +183,6 @@ describe('AutomergeHost', () => {
168
183
  const secondHandle = host.create();
169
184
  secondHandle.change((doc: any) => (doc.text = 'Hello world'));
170
185
  await host.find(secondHandle.url).whenReady();
171
- // await sleep(100);
172
186
  allowedDocs.push(secondHandle.documentId);
173
187
 
174
188
  {