@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
@@ -2,6 +2,7 @@ import "@dxos/node-std/globals";
2
2
  import {
3
3
  AuthExtension,
4
4
  AuthStatus,
5
+ Buffer,
5
6
  DataServiceImpl,
6
7
  MOCK_AUTH_PROVIDER,
7
8
  MOCK_AUTH_VERIFIER,
@@ -16,142 +17,130 @@ import {
16
17
  TimeframeClock,
17
18
  codec,
18
19
  createMappedFeedWriter,
20
+ hasInvitationExpired,
19
21
  mapFeedIndexesToTimeframe,
20
22
  mapTimeframeToFeedIndexes,
21
23
  startAfter,
22
24
  valueEncoding
23
- } from "./chunk-RTEEJ723.mjs";
25
+ } from "./chunk-SYE4EK33.mjs";
24
26
 
25
27
  // packages/core/echo/echo-pipeline/src/automerge/automerge-host.ts
26
- import { next as automerge, getHeads } from "@dxos/automerge/automerge";
28
+ import { asyncTimeout } from "@dxos/async";
29
+ import { next as automerge } from "@dxos/automerge/automerge";
27
30
  import { Repo } from "@dxos/automerge/automerge-repo";
28
- import { IndexedDBStorageAdapter } from "@dxos/automerge/automerge-repo-storage-indexeddb";
29
31
  import { Context } from "@dxos/context";
30
32
  import { PublicKey } from "@dxos/keys";
31
33
  import { log as log3 } from "@dxos/log";
32
- import { idCodec } from "@dxos/protocols";
33
- import { StorageType } from "@dxos/random-access-storage";
34
34
  import { trace } from "@dxos/tracing";
35
35
  import { ComplexMap, ComplexSet, defaultMap, mapValues } from "@dxos/util";
36
36
 
37
- // packages/core/echo/echo-pipeline/src/automerge/automerge-storage-adapter.ts
38
- import { arrayToBuffer, bufferToArray } from "@dxos/util";
39
- var AutomergeStorageAdapter = class {
40
- constructor(_directory) {
41
- this._directory = _directory;
42
- this._state = "opened";
37
+ // packages/core/echo/echo-pipeline/src/automerge/leveldb-storage-adapter.ts
38
+ import { LifecycleState, Resource } from "@dxos/context";
39
+ var LevelDBStorageAdapter = class extends Resource {
40
+ constructor(_params) {
41
+ super();
42
+ this._params = _params;
43
43
  }
44
- async load(key) {
45
- if (this._state !== "opened") {
46
- return void 0;
47
- }
48
- const filename = this._getFilename(key);
49
- const file = this._directory.getOrCreateFile(filename);
50
- const { size } = await file.stat();
51
- if (!size || size === 0) {
52
- return void 0;
44
+ async load(keyArray) {
45
+ try {
46
+ if (this._lifecycleState !== LifecycleState.OPEN) {
47
+ return void 0;
48
+ }
49
+ return await this._params.db.get(keyArray, {
50
+ ...encodingOptions
51
+ });
52
+ } catch (err) {
53
+ if (isLevelDbNotFoundError(err)) {
54
+ return void 0;
55
+ }
56
+ throw err;
53
57
  }
54
- const buffer = await file.read(0, size);
55
- return bufferToArray(buffer);
56
58
  }
57
- async save(key, data) {
58
- if (this._state !== "opened") {
59
+ async save(keyArray, binary) {
60
+ if (this._lifecycleState !== LifecycleState.OPEN) {
59
61
  return void 0;
60
62
  }
61
- const filename = this._getFilename(key);
62
- const file = this._directory.getOrCreateFile(filename);
63
- await file.write(0, arrayToBuffer(data));
64
- await file.truncate?.(data.length);
65
- await file.flush?.();
63
+ const batch = this._params.db.batch();
64
+ await this._params.callbacks?.beforeSave?.({
65
+ path: keyArray,
66
+ batch
67
+ });
68
+ batch.put(keyArray, Buffer.from(binary), {
69
+ ...encodingOptions
70
+ });
71
+ await batch.write();
72
+ await this._params.callbacks?.afterSave?.(keyArray);
66
73
  }
67
- async remove(key) {
68
- if (this._state !== "opened") {
74
+ async remove(keyArray) {
75
+ if (this._lifecycleState !== LifecycleState.OPEN) {
69
76
  return void 0;
70
77
  }
71
- const filename = this._getFilename(key);
72
- const file = this._directory.getOrCreateFile(filename);
73
- await file.destroy();
78
+ await this._params.db.del(keyArray, {
79
+ ...encodingOptions
80
+ });
74
81
  }
75
82
  async loadRange(keyPrefix) {
76
- if (this._state !== "opened") {
83
+ if (this._lifecycleState !== LifecycleState.OPEN) {
77
84
  return [];
78
85
  }
79
- const filename = this._getFilename(keyPrefix);
80
- const entries = await this._directory.list();
81
- return Promise.all(entries.filter((entry) => entry.startsWith(filename)).map(async (entry) => {
82
- const file = this._directory.getOrCreateFile(entry);
83
- const { size } = await file.stat();
84
- const buffer = await file.read(0, size);
85
- return {
86
- key: this._getKeyFromFilename(entry),
87
- data: bufferToArray(buffer)
88
- };
89
- }));
86
+ const result = [];
87
+ for await (const [key, value] of this._params.db.iterator({
88
+ gte: keyPrefix,
89
+ lte: [
90
+ ...keyPrefix,
91
+ "\uFFFF"
92
+ ],
93
+ ...encodingOptions
94
+ })) {
95
+ result.push({
96
+ key,
97
+ data: value
98
+ });
99
+ }
100
+ return result;
90
101
  }
91
102
  async removeRange(keyPrefix) {
92
- if (this._state !== "opened") {
103
+ if (this._lifecycleState !== LifecycleState.OPEN) {
93
104
  return void 0;
94
105
  }
95
- const filename = this._getFilename(keyPrefix);
96
- const entries = await this._directory.list();
97
- await Promise.all(entries.filter((entry) => entry.startsWith(filename)).map(async (entry) => {
98
- const file = this._directory.getOrCreateFile(entry);
99
- await file.destroy();
100
- }));
101
- }
102
- async close() {
103
- this._state = "closed";
104
- }
105
- _getFilename(key) {
106
- return key.map((k) => k.replaceAll("%", "%25").replaceAll("-", "%2D")).join("-");
107
- }
108
- _getKeyFromFilename(filename) {
109
- return filename.split("-").map((k) => k.replaceAll("%2D", "-").replaceAll("%25", "%"));
110
- }
111
- };
112
-
113
- // packages/core/echo/echo-pipeline/src/automerge/automerge-storage–wrapper.ts
114
- var AutomergeStorageWrapper = class {
115
- constructor({ storage, callbacks }) {
116
- this._storage = storage;
117
- this._callbacks = callbacks;
118
- }
119
- async load(key) {
120
- return this._storage.load(key);
121
- }
122
- async save(key, value) {
123
- await this._callbacks.beforeSave?.(key);
124
- await this._storage.save(key, value);
125
- await this._callbacks.afterSave?.(key);
126
- }
127
- async remove(key) {
128
- return this._storage.remove(key);
129
- }
130
- async loadRange(keyPrefix) {
131
- return this._storage.loadRange(keyPrefix);
132
- }
133
- async removeRange(keyPrefix) {
134
- return this._storage.removeRange(keyPrefix);
135
- }
136
- async close() {
137
- if (this._storage instanceof AutomergeStorageAdapter) {
138
- return this._storage.close();
106
+ const batch = this._params.db.batch();
107
+ for await (const [key] of this._params.db.iterator({
108
+ gte: keyPrefix,
109
+ lte: [
110
+ ...keyPrefix,
111
+ "\uFFFF"
112
+ ],
113
+ ...encodingOptions
114
+ })) {
115
+ batch.del(key, {
116
+ ...encodingOptions
117
+ });
139
118
  }
119
+ await batch.write();
140
120
  }
141
121
  };
122
+ var keyEncoder = {
123
+ encode: (key) => Buffer.from(key.map((k) => k.replaceAll("%", "%25").replaceAll("-", "%2D")).join("-")),
124
+ decode: (key) => Buffer.from(key).toString().split("-").map((k) => k.replaceAll("%2D", "-").replaceAll("%25", "%"))
125
+ };
126
+ var encodingOptions = {
127
+ keyEncoding: keyEncoder,
128
+ valueEncoding: "buffer"
129
+ };
130
+ var isLevelDbNotFoundError = (err) => err.code === "LEVEL_NOT_FOUND";
142
131
 
143
132
  // packages/core/echo/echo-pipeline/src/automerge/local-host-network-adapter.ts
144
133
  import { Trigger } from "@dxos/async";
145
134
  import { NetworkAdapter, cbor } from "@dxos/automerge/automerge-repo";
146
135
  import { Stream } from "@dxos/codec-protobuf";
147
136
  import { invariant } from "@dxos/invariant";
148
- import { log } from "@dxos/log";
149
137
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/local-host-network-adapter.ts";
150
138
  var LocalHostNetworkAdapter = class extends NetworkAdapter {
151
139
  constructor() {
152
140
  super(...arguments);
153
141
  this._peers = /* @__PURE__ */ new Map();
154
142
  this._connected = new Trigger();
143
+ this._isConnected = false;
155
144
  }
156
145
  /**
157
146
  * Emits `ready` event. That signals to `Repo` that it can start using the adapter.
@@ -161,15 +150,21 @@ var LocalHostNetworkAdapter = class extends NetworkAdapter {
161
150
  network: this
162
151
  });
163
152
  }
153
+ /**
154
+ * Called by `Repo` to connect to the network.
155
+ *
156
+ * @param peerId Our peer Id.
157
+ */
164
158
  connect(peerId) {
165
159
  this.peerId = peerId;
160
+ this._isConnected = true;
166
161
  this._connected.wake();
167
162
  }
168
163
  send(message) {
169
164
  const peer = this._peers.get(message.targetId);
170
165
  invariant(peer, "Peer not found.", {
171
166
  F: __dxlog_file,
172
- L: 45,
167
+ L: 51,
173
168
  S: this,
174
169
  A: [
175
170
  "peer",
@@ -184,12 +179,17 @@ var LocalHostNetworkAdapter = class extends NetworkAdapter {
184
179
  }
185
180
  disconnect() {
186
181
  }
182
+ async whenConnected() {
183
+ await this._connected.wait({
184
+ timeout: 1e4
185
+ });
186
+ }
187
187
  syncRepo({ id, syncMessage }) {
188
188
  const peerId = this._getPeerId(id);
189
189
  return new Stream(({ next, close }) => {
190
190
  invariant(!this._peers.has(peerId), "Peer already connected.", {
191
191
  F: __dxlog_file,
192
- L: 63,
192
+ L: 73,
193
193
  S: this,
194
194
  A: [
195
195
  "!this._peers.has(peerId)",
@@ -211,35 +211,47 @@ var LocalHostNetworkAdapter = class extends NetworkAdapter {
211
211
  });
212
212
  }
213
213
  });
214
- this._connected.wait({
215
- timeout: 1e3
216
- }).then(() => {
217
- this.emit("peer-candidate", {
218
- peerMetadata: {},
219
- peerId
220
- });
221
- }).catch((err) => log.catch(err, void 0, {
214
+ invariant(this._isConnected, void 0, {
222
215
  F: __dxlog_file,
223
- L: 88,
216
+ L: 90,
224
217
  S: this,
225
- C: (f, a) => f(...a)
226
- }));
218
+ A: [
219
+ "this._isConnected",
220
+ ""
221
+ ]
222
+ });
223
+ this.emit("peer-candidate", {
224
+ peerMetadata: {},
225
+ peerId
226
+ });
227
227
  });
228
228
  }
229
229
  async sendSyncMessage({ id, syncMessage }) {
230
- await this._connected.wait({
231
- timeout: 1e3
230
+ invariant(this._isConnected, void 0, {
231
+ F: __dxlog_file,
232
+ L: 99,
233
+ S: this,
234
+ A: [
235
+ "this._isConnected",
236
+ ""
237
+ ]
232
238
  });
233
239
  const message = cbor.decode(syncMessage);
234
240
  this.emit("message", message);
235
241
  }
236
242
  async getHostInfo() {
237
- await this._connected.wait({
238
- timeout: 1e3
243
+ invariant(this._isConnected, void 0, {
244
+ F: __dxlog_file,
245
+ L: 105,
246
+ S: this,
247
+ A: [
248
+ "this._isConnected",
249
+ ""
250
+ ]
239
251
  });
240
252
  invariant(this.peerId, "Peer id not set.", {
241
253
  F: __dxlog_file,
242
- L: 100,
254
+ L: 106,
243
255
  S: this,
244
256
  A: [
245
257
  "this.peerId",
@@ -259,7 +271,7 @@ var LocalHostNetworkAdapter = class extends NetworkAdapter {
259
271
  import { Trigger as Trigger2 } from "@dxos/async";
260
272
  import { NetworkAdapter as NetworkAdapter2, cbor as cbor2 } from "@dxos/automerge/automerge-repo";
261
273
  import { invariant as invariant2 } from "@dxos/invariant";
262
- import { log as log2 } from "@dxos/log";
274
+ import { log } from "@dxos/log";
263
275
  import { AutomergeReplicator } from "@dxos/teleport-extension-automerge-replicator";
264
276
  var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/mesh-network-adapter.ts";
265
277
  var MeshNetworkAdapter = class extends NetworkAdapter2 {
@@ -294,7 +306,7 @@ var MeshNetworkAdapter = class extends NetworkAdapter2 {
294
306
  });
295
307
  extension.sendSyncMessage({
296
308
  payload: cbor2.encode(message)
297
- }).catch((err) => log2.catch(err, void 0, {
309
+ }).catch((err) => log.catch(err, void 0, {
298
310
  F: __dxlog_file2,
299
311
  L: 39,
300
312
  S: this,
@@ -319,7 +331,7 @@ var MeshNetworkAdapter = class extends NetworkAdapter2 {
319
331
  }, {
320
332
  onStartReplication: async (info, remotePeerId) => {
321
333
  await this._connected.wait();
322
- log2("onStartReplication", {
334
+ log("onStartReplication", {
323
335
  id: info.id,
324
336
  thisPeerId: this.peerId,
325
337
  remotePeerId: remotePeerId.toHex()
@@ -332,7 +344,7 @@ var MeshNetworkAdapter = class extends NetworkAdapter2 {
332
344
  if (!this._extensions.has(info.id)) {
333
345
  peerInfo = info;
334
346
  this._extensions.set(info.id, extension);
335
- log2("peer-candidate", {
347
+ log("peer-candidate", {
336
348
  id: info.id,
337
349
  thisPeerId: this.peerId,
338
350
  remotePeerId: remotePeerId.toHex()
@@ -372,6 +384,118 @@ var MeshNetworkAdapter = class extends NetworkAdapter2 {
372
384
  }
373
385
  };
374
386
 
387
+ // packages/core/echo/echo-pipeline/src/automerge/migrations.ts
388
+ import { IndexedDBStorageAdapter } from "@dxos/automerge/automerge-repo-storage-indexeddb";
389
+ import { log as log2 } from "@dxos/log";
390
+ import { StorageType } from "@dxos/random-access-storage";
391
+
392
+ // packages/core/echo/echo-pipeline/src/automerge/automerge-storage-adapter.ts
393
+ import { arrayToBuffer, bufferToArray } from "@dxos/util";
394
+ var AutomergeStorageAdapter = class {
395
+ constructor(_directory) {
396
+ this._directory = _directory;
397
+ this._state = "opened";
398
+ }
399
+ async load(key) {
400
+ if (this._state !== "opened") {
401
+ return void 0;
402
+ }
403
+ const filename = this._getFilename(key);
404
+ const file = this._directory.getOrCreateFile(filename);
405
+ const { size } = await file.stat();
406
+ if (!size || size === 0) {
407
+ return void 0;
408
+ }
409
+ const buffer = await file.read(0, size);
410
+ return bufferToArray(buffer);
411
+ }
412
+ async save(key, data) {
413
+ if (this._state !== "opened") {
414
+ return void 0;
415
+ }
416
+ const filename = this._getFilename(key);
417
+ const file = this._directory.getOrCreateFile(filename);
418
+ await file.write(0, arrayToBuffer(data));
419
+ await file.truncate?.(data.length);
420
+ await file.flush?.();
421
+ }
422
+ async remove(key) {
423
+ if (this._state !== "opened") {
424
+ return void 0;
425
+ }
426
+ const filename = this._getFilename(key);
427
+ const file = this._directory.getOrCreateFile(filename);
428
+ await file.destroy();
429
+ }
430
+ async loadRange(keyPrefix) {
431
+ if (this._state !== "opened") {
432
+ return [];
433
+ }
434
+ const filename = this._getFilename(keyPrefix);
435
+ const entries = await this._directory.list();
436
+ return Promise.all(entries.filter((entry) => entry.startsWith(filename)).map(async (entry) => {
437
+ const file = this._directory.getOrCreateFile(entry);
438
+ const { size } = await file.stat();
439
+ const buffer = await file.read(0, size);
440
+ return {
441
+ key: this._getKeyFromFilename(entry),
442
+ data: bufferToArray(buffer)
443
+ };
444
+ }));
445
+ }
446
+ async removeRange(keyPrefix) {
447
+ if (this._state !== "opened") {
448
+ return void 0;
449
+ }
450
+ const filename = this._getFilename(keyPrefix);
451
+ const entries = await this._directory.list();
452
+ await Promise.all(entries.filter((entry) => entry.startsWith(filename)).map(async (entry) => {
453
+ const file = this._directory.getOrCreateFile(entry);
454
+ await file.destroy();
455
+ }));
456
+ }
457
+ async close() {
458
+ this._state = "closed";
459
+ }
460
+ _getFilename(key) {
461
+ return key.map((k) => k.replaceAll("%", "%25").replaceAll("-", "%2D")).join("-");
462
+ }
463
+ _getKeyFromFilename(filename) {
464
+ return filename.split("-").map((k) => k.replaceAll("%2D", "-").replaceAll("%25", "%"));
465
+ }
466
+ };
467
+
468
+ // packages/core/echo/echo-pipeline/src/automerge/migrations.ts
469
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/migrations.ts";
470
+ var levelMigration = async ({ db, directory }) => {
471
+ const isNewLevel = !await db.iterator({
472
+ ...encodingOptions
473
+ }).next();
474
+ if (!isNewLevel) {
475
+ return;
476
+ }
477
+ const oldStorageAdapter = directory.type === StorageType.IDB ? new IndexedDBStorageAdapter(directory.path, "data") : new AutomergeStorageAdapter(directory);
478
+ const chunks = await oldStorageAdapter.loadRange([]);
479
+ if (chunks.length === 0) {
480
+ return;
481
+ }
482
+ const batch = db.batch();
483
+ log2.info("found chunks on old storage adapter", {
484
+ chunks: chunks.length
485
+ }, {
486
+ F: __dxlog_file3,
487
+ L: 36,
488
+ S: void 0,
489
+ C: (f, a) => f(...a)
490
+ });
491
+ for (const { key, data } of await oldStorageAdapter.loadRange([])) {
492
+ data && batch.put(key, data, {
493
+ ...encodingOptions
494
+ });
495
+ }
496
+ await batch.write();
497
+ };
498
+
375
499
  // packages/core/echo/echo-pipeline/src/automerge/automerge-host.ts
376
500
  function _ts_decorate(decorators, target, key, desc) {
377
501
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -383,29 +507,32 @@ function _ts_decorate(decorators, target, key, desc) {
383
507
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
384
508
  return c > 3 && r && Object.defineProperty(target, key, r), r;
385
509
  }
386
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/automerge-host.ts";
510
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/automerge-host.ts";
387
511
  var AutomergeHost = class {
388
- constructor({ directory, metadata }) {
512
+ constructor({ directory, db, storageCallbacks }) {
389
513
  this._ctx = new Context();
390
514
  /**
391
515
  * spaceKey -> deviceKey[]
392
516
  */
393
517
  this._authorizedDevices = new ComplexMap(PublicKey.hash);
394
- this._updatingMetadata = /* @__PURE__ */ new Map();
395
518
  this._requestedDocs = /* @__PURE__ */ new Set();
396
- this._metadata = metadata;
397
- this._meshNetwork = new MeshNetworkAdapter();
398
- this._clientNetwork = new LocalHostNetworkAdapter();
399
- this._storage = new AutomergeStorageWrapper({
400
- storage: (
401
- // TODO(mykola): Delete specific handling of IDB storage.
402
- directory.type === StorageType.IDB ? new IndexedDBStorageAdapter(directory.path, "data") : new AutomergeStorageAdapter(directory)
403
- ),
404
- callbacks: {
405
- beforeSave: (params) => this._beforeSave(params)
406
- }
519
+ this._directory = directory;
520
+ this._db = db;
521
+ this._storageCallbacks = storageCallbacks;
522
+ }
523
+ async open() {
524
+ this._directory && await levelMigration({
525
+ db: this._db,
526
+ directory: this._directory
527
+ });
528
+ this._storage = new LevelDBStorageAdapter({
529
+ db: this._db,
530
+ callbacks: this._storageCallbacks
407
531
  });
532
+ await this._storage.open?.();
408
533
  this._peerId = `host-${PublicKey.random().toHex()}`;
534
+ this._meshNetwork = new MeshNetworkAdapter();
535
+ this._clientNetwork = new LocalHostNetworkAdapter();
409
536
  this._repo = new Repo({
410
537
  peerId: this._peerId,
411
538
  network: [
@@ -430,8 +557,8 @@ var AutomergeHost = class {
430
557
  documentId,
431
558
  isRequested
432
559
  }, {
433
- F: __dxlog_file3,
434
- L: 96,
560
+ F: __dxlog_file4,
561
+ L: 99,
435
562
  S: this,
436
563
  C: (f, a) => f(...a)
437
564
  });
@@ -444,8 +571,8 @@ var AutomergeHost = class {
444
571
  peerId,
445
572
  documentId
446
573
  }, {
447
- F: __dxlog_file3,
448
- L: 103,
574
+ F: __dxlog_file4,
575
+ L: 106,
449
576
  S: this,
450
577
  C: (f, a) => f(...a)
451
578
  });
@@ -458,8 +585,8 @@ var AutomergeHost = class {
458
585
  peerId,
459
586
  documentId
460
587
  }, {
461
- F: __dxlog_file3,
462
- L: 112,
588
+ F: __dxlog_file4,
589
+ L: 115,
463
590
  S: this,
464
591
  C: (f, a) => f(...a)
465
592
  });
@@ -475,16 +602,16 @@ var AutomergeHost = class {
475
602
  spaceKey,
476
603
  isAuthorized
477
604
  }, {
478
- F: __dxlog_file3,
479
- L: 118,
605
+ F: __dxlog_file4,
606
+ L: 121,
480
607
  S: this,
481
608
  C: (f, a) => f(...a)
482
609
  });
483
610
  return isAuthorized;
484
611
  } catch (err) {
485
612
  log3.catch(err, void 0, {
486
- F: __dxlog_file3,
487
- L: 128,
613
+ F: __dxlog_file4,
614
+ L: 131,
488
615
  S: this,
489
616
  C: (f, a) => f(...a)
490
617
  });
@@ -494,69 +621,22 @@ var AutomergeHost = class {
494
621
  });
495
622
  this._clientNetwork.ready();
496
623
  this._meshNetwork.ready();
497
- {
498
- const listener = ({ handle }) => this._onDocument(handle);
499
- this._repo.on("document", listener);
500
- this._ctx.onDispose(() => {
501
- this._repo.off("document", listener);
502
- });
503
- }
624
+ await this._clientNetwork.whenConnected();
625
+ }
626
+ async close() {
627
+ await this._storage.close?.();
628
+ await this._clientNetwork.close();
629
+ await this._ctx.dispose();
504
630
  }
505
631
  get repo() {
506
632
  return this._repo;
507
633
  }
508
- async _beforeSave(path) {
509
- const id = path[0];
510
- if (this._updatingMetadata.has(id)) {
511
- return this._updatingMetadata.get(id);
512
- }
513
- }
514
- _onDocument(handle) {
515
- const listener = (event) => this._onUpdate(event);
516
- handle.on("change", listener);
517
- this._ctx.onDispose(() => {
518
- handle.off("change", listener);
519
- });
520
- }
521
- _onUpdate(event) {
522
- if (this._metadata == null) {
523
- return;
524
- }
525
- const objectIds = getInlineChanges(event);
526
- if (objectIds.length === 0) {
527
- return;
528
- }
529
- const heads = getHeads(event.doc);
530
- const lastAvailableHash = heads.join("");
531
- if (!lastAvailableHash) {
532
- return;
533
- }
534
- const encodedIds = objectIds.map((objectId) => idCodec.encode({
535
- documentId: event.handle.documentId,
536
- objectId
537
- }));
538
- const idToLastHash = new Map(encodedIds.map((id) => [
539
- id,
540
- lastAvailableHash
541
- ]));
542
- const markingDirtyPromise = this._metadata.markDirty(idToLastHash).then(() => {
543
- this._updatingMetadata.delete(event.handle.documentId);
544
- }).catch((err) => {
545
- this._ctx.disposed && log3.catch(err, void 0, {
546
- F: __dxlog_file3,
547
- L: 188,
548
- S: this,
549
- C: (f, a) => f(...a)
550
- });
551
- });
552
- this._updatingMetadata.set(event.handle.documentId, markingDirtyPromise);
553
- }
554
634
  _automergeDocs() {
555
635
  return mapValues(this._repo.handles, (handle) => ({
556
636
  state: handle.state,
557
637
  hasDoc: !!handle.docSync(),
558
638
  heads: handle.docSync() ? automerge.getHeads(handle.docSync()) : null,
559
- data: handle.docSync()?.doc && mapValues(handle.docSync()?.doc, (value, key) => {
639
+ data: handle.docSync() && mapValues(handle.docSync(), (value, key) => {
560
640
  try {
561
641
  switch (key) {
562
642
  case "access":
@@ -576,14 +656,25 @@ var AutomergeHost = class {
576
656
  _automergePeers() {
577
657
  return this._repo.peers;
578
658
  }
579
- async close() {
580
- await this._storage.close();
581
- await this._clientNetwork.close();
582
- await this._ctx.dispose();
583
- }
584
659
  //
585
660
  // Methods for client-services.
586
661
  //
662
+ async flush({ documentIds }) {
663
+ await Promise.all(documentIds?.map((id) => this._repo.find(id).whenReady()) ?? []);
664
+ try {
665
+ await asyncTimeout(this._repo.flush(documentIds), 500);
666
+ } catch (err) {
667
+ log3.warn("flush error", {
668
+ documentIds,
669
+ err
670
+ }, {
671
+ F: __dxlog_file4,
672
+ L: 195,
673
+ S: this,
674
+ C: (f, a) => f(...a)
675
+ });
676
+ }
677
+ }
587
678
  syncRepo(request) {
588
679
  return this._clientNetwork.syncRepo(request);
589
680
  }
@@ -604,8 +695,8 @@ var AutomergeHost = class {
604
695
  spaceKey,
605
696
  deviceKey
606
697
  }, {
607
- F: __dxlog_file3,
608
- L: 255,
698
+ F: __dxlog_file4,
699
+ L: 220,
609
700
  S: this,
610
701
  C: (f, a) => f(...a)
611
702
  });
@@ -628,24 +719,6 @@ _ts_decorate([
628
719
  AutomergeHost = _ts_decorate([
629
720
  trace.resource()
630
721
  ], AutomergeHost);
631
- var getInlineChanges = (event) => {
632
- const inlineChangedObjectIds = /* @__PURE__ */ new Set();
633
- for (const { path } of event.patches) {
634
- if (path.length < 2) {
635
- continue;
636
- }
637
- switch (path[0]) {
638
- case "objects":
639
- if (path.length >= 2) {
640
- inlineChangedObjectIds.add(path[1]);
641
- }
642
- break;
643
- }
644
- }
645
- return [
646
- ...inlineChangedObjectIds
647
- ];
648
- };
649
722
  var getSpaceKeyFromDoc = (doc) => {
650
723
  const rawSpaceKey = doc.access?.spaceKey ?? doc.experimental_spaceKey;
651
724
  if (rawSpaceKey == null) {
@@ -653,18 +726,316 @@ var getSpaceKeyFromDoc = (doc) => {
653
726
  }
654
727
  return String(rawSpaceKey);
655
728
  };
729
+
730
+ // packages/core/echo/echo-pipeline/src/automerge/automerge-doc-loader.ts
731
+ import { Event } from "@dxos/async";
732
+ import { cancelWithContext } from "@dxos/context";
733
+ import { warnAfterTimeout } from "@dxos/debug";
734
+ import { invariant as invariant3 } from "@dxos/invariant";
735
+ import { log as log4 } from "@dxos/log";
736
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/automerge-doc-loader.ts";
737
+ var AutomergeDocumentLoaderImpl = class {
738
+ constructor(_spaceKey, _repo) {
739
+ this._spaceKey = _spaceKey;
740
+ this._repo = _repo;
741
+ this._spaceRootDocHandle = null;
742
+ this._objectDocumentHandles = /* @__PURE__ */ new Map();
743
+ this._objectsPendingDocumentLoad = /* @__PURE__ */ new Set();
744
+ this.onObjectDocumentLoaded = new Event();
745
+ }
746
+ getAllHandles() {
747
+ return [
748
+ ...new Set(this._objectDocumentHandles.values())
749
+ ];
750
+ }
751
+ async loadSpaceRootDocHandle(ctx, spaceState) {
752
+ if (this._spaceRootDocHandle != null) {
753
+ return;
754
+ }
755
+ if (!spaceState.rootUrl) {
756
+ log4.error("Database opened with no rootUrl", {
757
+ spaceKey: this._spaceKey
758
+ }, {
759
+ F: __dxlog_file5,
760
+ L: 66,
761
+ S: this,
762
+ C: (f, a) => f(...a)
763
+ });
764
+ this._createContextBoundSpaceRootDocument(ctx);
765
+ } else {
766
+ const existingDocHandle = await this._initDocHandle(ctx, spaceState.rootUrl);
767
+ const doc = existingDocHandle.docSync();
768
+ invariant3(doc, void 0, {
769
+ F: __dxlog_file5,
770
+ L: 71,
771
+ S: this,
772
+ A: [
773
+ "doc",
774
+ ""
775
+ ]
776
+ });
777
+ if (doc.access == null) {
778
+ this._initDocAccess(existingDocHandle);
779
+ }
780
+ this._spaceRootDocHandle = existingDocHandle;
781
+ }
782
+ }
783
+ loadObjectDocument(objectId) {
784
+ invariant3(this._spaceRootDocHandle, void 0, {
785
+ F: __dxlog_file5,
786
+ L: 80,
787
+ S: this,
788
+ A: [
789
+ "this._spaceRootDocHandle",
790
+ ""
791
+ ]
792
+ });
793
+ if (this._objectDocumentHandles.has(objectId) || this._objectsPendingDocumentLoad.has(objectId)) {
794
+ return;
795
+ }
796
+ const spaceRootDoc = this._spaceRootDocHandle.docSync();
797
+ invariant3(spaceRootDoc, void 0, {
798
+ F: __dxlog_file5,
799
+ L: 85,
800
+ S: this,
801
+ A: [
802
+ "spaceRootDoc",
803
+ ""
804
+ ]
805
+ });
806
+ const documentUrl = (spaceRootDoc.links ?? {})[objectId];
807
+ if (documentUrl == null) {
808
+ this._objectsPendingDocumentLoad.add(objectId);
809
+ log4.info("loading delayed until object links are initialized", {
810
+ objectId
811
+ }, {
812
+ F: __dxlog_file5,
813
+ L: 89,
814
+ S: this,
815
+ C: (f, a) => f(...a)
816
+ });
817
+ return;
818
+ }
819
+ this._loadLinkedObjects({
820
+ [objectId]: documentUrl
821
+ });
822
+ }
823
+ onObjectLinksUpdated(links) {
824
+ if (!links) {
825
+ return;
826
+ }
827
+ const linksAwaitingLoad = Object.entries(links).filter(([objectId]) => this._objectsPendingDocumentLoad.has(objectId));
828
+ this._loadLinkedObjects(Object.fromEntries(linksAwaitingLoad));
829
+ linksAwaitingLoad.forEach(([objectId]) => this._objectsPendingDocumentLoad.delete(objectId));
830
+ }
831
+ getSpaceRootDocHandle() {
832
+ invariant3(this._spaceRootDocHandle, void 0, {
833
+ F: __dxlog_file5,
834
+ L: 107,
835
+ S: this,
836
+ A: [
837
+ "this._spaceRootDocHandle",
838
+ ""
839
+ ]
840
+ });
841
+ return this._spaceRootDocHandle;
842
+ }
843
+ createDocumentForObject(objectId) {
844
+ invariant3(this._spaceRootDocHandle, void 0, {
845
+ F: __dxlog_file5,
846
+ L: 112,
847
+ S: this,
848
+ A: [
849
+ "this._spaceRootDocHandle",
850
+ ""
851
+ ]
852
+ });
853
+ const spaceDocHandle = this._repo.create();
854
+ this._initDocAccess(spaceDocHandle);
855
+ this.onObjectBoundToDocument(spaceDocHandle, objectId);
856
+ this._spaceRootDocHandle.change((newDoc) => {
857
+ newDoc.links ??= {};
858
+ newDoc.links[objectId] = spaceDocHandle.url;
859
+ });
860
+ return spaceDocHandle;
861
+ }
862
+ onObjectBoundToDocument(handle, objectId) {
863
+ this._objectDocumentHandles.set(objectId, handle);
864
+ }
865
+ clearHandleReferences() {
866
+ const objectsWithHandles = [
867
+ ...this._objectDocumentHandles.keys()
868
+ ];
869
+ this._objectDocumentHandles.clear();
870
+ this._spaceRootDocHandle = null;
871
+ return objectsWithHandles;
872
+ }
873
+ _loadLinkedObjects(links) {
874
+ if (!links) {
875
+ return;
876
+ }
877
+ for (const [objectId, automergeUrl] of Object.entries(links)) {
878
+ const logMeta = {
879
+ objectId,
880
+ automergeUrl
881
+ };
882
+ const objectDocumentHandle = this._objectDocumentHandles.get(objectId);
883
+ if (objectDocumentHandle != null && objectDocumentHandle.url !== automergeUrl) {
884
+ log4.warn("object already inlined in a different document, ignoring the link", {
885
+ ...logMeta,
886
+ actualDocumentUrl: objectDocumentHandle.url
887
+ }, {
888
+ F: __dxlog_file5,
889
+ L: 142,
890
+ S: this,
891
+ C: (f, a) => f(...a)
892
+ });
893
+ continue;
894
+ }
895
+ if (objectDocumentHandle?.url === automergeUrl) {
896
+ log4.warn("object document was already loaded", logMeta, {
897
+ F: __dxlog_file5,
898
+ L: 149,
899
+ S: this,
900
+ C: (f, a) => f(...a)
901
+ });
902
+ continue;
903
+ }
904
+ const handle = this._repo.find(automergeUrl);
905
+ log4.debug("document loading triggered", logMeta, {
906
+ F: __dxlog_file5,
907
+ L: 153,
908
+ S: this,
909
+ C: (f, a) => f(...a)
910
+ });
911
+ this._objectDocumentHandles.set(objectId, handle);
912
+ void this._createObjectOnDocumentLoad(handle, objectId);
913
+ }
914
+ }
915
+ async _initDocHandle(ctx, url) {
916
+ const docHandle = this._repo.find(url);
917
+ while (true) {
918
+ try {
919
+ await warnAfterTimeout(5e3, "Automerge root doc load timeout (AutomergeDb)", async () => {
920
+ await cancelWithContext(ctx, docHandle.whenReady());
921
+ });
922
+ break;
923
+ } catch (err) {
924
+ if (`${err}`.includes("Timeout")) {
925
+ log4.info("wraparound", {
926
+ id: docHandle.documentId,
927
+ state: docHandle.state
928
+ }, {
929
+ F: __dxlog_file5,
930
+ L: 169,
931
+ S: this,
932
+ C: (f, a) => f(...a)
933
+ });
934
+ continue;
935
+ }
936
+ throw err;
937
+ }
938
+ }
939
+ if (docHandle.state === "unavailable") {
940
+ throw new Error("Automerge document is unavailable");
941
+ }
942
+ return docHandle;
943
+ }
944
+ _createContextBoundSpaceRootDocument(ctx) {
945
+ const docHandle = this._repo.create();
946
+ this._spaceRootDocHandle = docHandle;
947
+ ctx.onDispose(() => {
948
+ docHandle.delete();
949
+ this._spaceRootDocHandle = null;
950
+ });
951
+ }
952
+ _initDocAccess(handle) {
953
+ handle.change((newDoc) => {
954
+ newDoc.access ??= {
955
+ spaceKey: this._spaceKey.toHex()
956
+ };
957
+ newDoc.access.spaceKey = this._spaceKey.toHex();
958
+ });
959
+ }
960
+ async _createObjectOnDocumentLoad(handle, objectId) {
961
+ try {
962
+ await handle.doc([
963
+ "ready"
964
+ ]);
965
+ const logMeta = {
966
+ objectId,
967
+ docUrl: handle.url
968
+ };
969
+ if (this.onObjectDocumentLoaded.listenerCount() === 0) {
970
+ log4.info("document loaded after all listeners were removed", logMeta, {
971
+ F: __dxlog_file5,
972
+ L: 205,
973
+ S: this,
974
+ C: (f, a) => f(...a)
975
+ });
976
+ return;
977
+ }
978
+ const objectDocHandle = this._objectDocumentHandles.get(objectId);
979
+ if (objectDocHandle?.url !== handle.url) {
980
+ log4.warn("object was rebound while a document was loading, discarding handle", logMeta, {
981
+ F: __dxlog_file5,
982
+ L: 210,
983
+ S: this,
984
+ C: (f, a) => f(...a)
985
+ });
986
+ return;
987
+ }
988
+ this.onObjectDocumentLoaded.emit({
989
+ handle,
990
+ objectId
991
+ });
992
+ } catch (err) {
993
+ const shouldRetryLoading = this.onObjectDocumentLoaded.listenerCount() > 0;
994
+ log4.warn("failed to load a document", {
995
+ objectId,
996
+ automergeUrl: handle.url,
997
+ retryLoading: shouldRetryLoading,
998
+ err
999
+ }, {
1000
+ F: __dxlog_file5,
1001
+ L: 216,
1002
+ S: this,
1003
+ C: (f, a) => f(...a)
1004
+ });
1005
+ if (shouldRetryLoading) {
1006
+ await this._createObjectOnDocumentLoad(handle, objectId);
1007
+ }
1008
+ }
1009
+ }
1010
+ };
1011
+
1012
+ // packages/core/echo/echo-pipeline/src/automerge/reference.ts
1013
+ import { Reference } from "@dxos/echo-db";
1014
+ var REFERENCE_TYPE_TAG = "dxos.echo.model.document.Reference";
1015
+ var encodeReference = (reference) => ({
1016
+ "@type": REFERENCE_TYPE_TAG,
1017
+ // NOTE: Automerge do not support undefined values, so we need to use null instead.
1018
+ itemId: reference.itemId ?? null,
1019
+ protocol: reference.protocol ?? null,
1020
+ host: reference.host ?? null
1021
+ });
1022
+ var decodeReference = (value) => new Reference(value.itemId, value.protocol ?? void 0, value.host ?? void 0);
1023
+ var isEncodedReferenceObject = (value) => typeof value === "object" && value !== null && value["@type"] === REFERENCE_TYPE_TAG;
656
1024
  export {
657
1025
  AuthExtension,
658
1026
  AuthStatus,
1027
+ AutomergeDocumentLoaderImpl,
659
1028
  AutomergeHost,
660
1029
  AutomergeStorageAdapter,
661
1030
  DataServiceImpl,
1031
+ LevelDBStorageAdapter,
662
1032
  LocalHostNetworkAdapter,
663
1033
  MOCK_AUTH_PROVIDER,
664
1034
  MOCK_AUTH_VERIFIER,
665
1035
  MeshNetworkAdapter,
666
1036
  MetadataStore,
667
1037
  Pipeline,
1038
+ REFERENCE_TYPE_TAG,
668
1039
  SnapshotManager,
669
1040
  SnapshotStore,
670
1041
  Space,
@@ -674,7 +1045,12 @@ export {
674
1045
  TimeframeClock,
675
1046
  codec,
676
1047
  createMappedFeedWriter,
1048
+ decodeReference,
1049
+ encodeReference,
1050
+ encodingOptions,
677
1051
  getSpaceKeyFromDoc,
1052
+ hasInvitationExpired,
1053
+ isEncodedReferenceObject,
678
1054
  mapFeedIndexesToTimeframe,
679
1055
  mapTimeframeToFeedIndexes,
680
1056
  startAfter,