@dxos/echo-pipeline 0.4.9 → 0.4.10-main.05b9ab6

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 (77) hide show
  1. package/dist/lib/browser/{chunk-RTEEJ723.mjs → chunk-KMWJLYEQ.mjs} +77 -57
  2. package/dist/lib/browser/chunk-KMWJLYEQ.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +617 -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 +10 -2
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node/{chunk-7VZVCCNF.cjs → chunk-YZA42CKA.cjs} +82 -62
  9. package/dist/lib/node/chunk-YZA42CKA.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +635 -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 +20 -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/control-pipeline.d.ts +3 -1
  42. package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
  43. package/dist/types/src/space/space-manager.d.ts +3 -1
  44. package/dist/types/src/space/space-manager.d.ts.map +1 -1
  45. package/dist/types/src/space/space.d.ts +6 -9
  46. package/dist/types/src/space/space.d.ts.map +1 -1
  47. package/dist/types/src/testing/index.d.ts +1 -0
  48. package/dist/types/src/testing/index.d.ts.map +1 -1
  49. package/dist/types/src/testing/level.d.ts +3 -0
  50. package/dist/types/src/testing/level.d.ts.map +1 -0
  51. package/dist/types/src/testing/test-agent-builder.d.ts +2 -2
  52. package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
  53. package/package.json +33 -30
  54. package/src/automerge/automerge-doc-loader.test.ts +97 -0
  55. package/src/automerge/automerge-doc-loader.ts +244 -0
  56. package/src/automerge/automerge-host.test.ts +22 -8
  57. package/src/automerge/automerge-host.ts +66 -118
  58. package/src/automerge/automerge-repo.test.ts +29 -0
  59. package/src/automerge/index.ts +4 -0
  60. package/src/automerge/level.test.ts +82 -0
  61. package/src/automerge/leveldb-storage-adapter.ts +117 -0
  62. package/src/automerge/local-host-network-adapter.ts +19 -13
  63. package/src/automerge/migrations.ts +41 -0
  64. package/src/automerge/reference.ts +31 -0
  65. package/src/automerge/storage-adapter.test.ts +90 -0
  66. package/src/automerge/types.ts +86 -0
  67. package/src/db-host/data-service.ts +1 -1
  68. package/src/metadata/metadata-store.ts +17 -8
  69. package/src/space/control-pipeline.ts +11 -1
  70. package/src/space/space-manager.ts +4 -0
  71. package/src/space/space.test.ts +7 -7
  72. package/src/space/space.ts +17 -22
  73. package/src/testing/index.ts +1 -0
  74. package/src/testing/level.ts +11 -0
  75. package/src/testing/test-agent-builder.ts +1 -0
  76. package/dist/lib/browser/chunk-RTEEJ723.mjs.map +0 -7
  77. 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-KMWJLYEQ.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: 100,
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: 107,
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: 116,
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: 122,
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: 132,
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: 196,
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: 221,
609
700
  S: this,
610
701
  C: (f, a) => f(...a)
611
702
  });
@@ -625,27 +716,14 @@ _ts_decorate([
625
716
  depth: null
626
717
  })
627
718
  ], AutomergeHost.prototype, "_automergePeers", null);
719
+ _ts_decorate([
720
+ trace.span({
721
+ showInBrowserTimeline: true
722
+ })
723
+ ], AutomergeHost.prototype, "flush", null);
628
724
  AutomergeHost = _ts_decorate([
629
725
  trace.resource()
630
726
  ], 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
727
  var getSpaceKeyFromDoc = (doc) => {
650
728
  const rawSpaceKey = doc.access?.spaceKey ?? doc.experimental_spaceKey;
651
729
  if (rawSpaceKey == null) {
@@ -653,18 +731,335 @@ var getSpaceKeyFromDoc = (doc) => {
653
731
  }
654
732
  return String(rawSpaceKey);
655
733
  };
734
+
735
+ // packages/core/echo/echo-pipeline/src/automerge/automerge-doc-loader.ts
736
+ import { Event } from "@dxos/async";
737
+ import { cancelWithContext } from "@dxos/context";
738
+ import { warnAfterTimeout } from "@dxos/debug";
739
+ import { invariant as invariant3 } from "@dxos/invariant";
740
+ import { log as log4 } from "@dxos/log";
741
+ import { trace as trace2 } from "@dxos/tracing";
742
+ function _ts_decorate2(decorators, target, key, desc) {
743
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
744
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
745
+ r = Reflect.decorate(decorators, target, key, desc);
746
+ else
747
+ for (var i = decorators.length - 1; i >= 0; i--)
748
+ if (d = decorators[i])
749
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
750
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
751
+ }
752
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/automerge-doc-loader.ts";
753
+ var AutomergeDocumentLoaderImpl = class {
754
+ constructor(_spaceKey, _repo) {
755
+ this._spaceKey = _spaceKey;
756
+ this._repo = _repo;
757
+ this._spaceRootDocHandle = null;
758
+ this._objectDocumentHandles = /* @__PURE__ */ new Map();
759
+ this._objectsPendingDocumentLoad = /* @__PURE__ */ new Set();
760
+ this.onObjectDocumentLoaded = new Event();
761
+ }
762
+ getAllHandles() {
763
+ return [
764
+ ...new Set(this._objectDocumentHandles.values())
765
+ ];
766
+ }
767
+ async loadSpaceRootDocHandle(ctx, spaceState) {
768
+ if (this._spaceRootDocHandle != null) {
769
+ return;
770
+ }
771
+ if (!spaceState.rootUrl) {
772
+ log4.error("Database opened with no rootUrl", {
773
+ spaceKey: this._spaceKey
774
+ }, {
775
+ F: __dxlog_file5,
776
+ L: 69,
777
+ S: this,
778
+ C: (f, a) => f(...a)
779
+ });
780
+ this._createContextBoundSpaceRootDocument(ctx);
781
+ } else {
782
+ const existingDocHandle = await this._initDocHandle(ctx, spaceState.rootUrl);
783
+ const doc = existingDocHandle.docSync();
784
+ invariant3(doc, void 0, {
785
+ F: __dxlog_file5,
786
+ L: 74,
787
+ S: this,
788
+ A: [
789
+ "doc",
790
+ ""
791
+ ]
792
+ });
793
+ if (doc.access == null) {
794
+ this._initDocAccess(existingDocHandle);
795
+ }
796
+ this._spaceRootDocHandle = existingDocHandle;
797
+ }
798
+ }
799
+ loadObjectDocument(objectId) {
800
+ invariant3(this._spaceRootDocHandle, void 0, {
801
+ F: __dxlog_file5,
802
+ L: 83,
803
+ S: this,
804
+ A: [
805
+ "this._spaceRootDocHandle",
806
+ ""
807
+ ]
808
+ });
809
+ if (this._objectDocumentHandles.has(objectId) || this._objectsPendingDocumentLoad.has(objectId)) {
810
+ return;
811
+ }
812
+ const spaceRootDoc = this._spaceRootDocHandle.docSync();
813
+ invariant3(spaceRootDoc, void 0, {
814
+ F: __dxlog_file5,
815
+ L: 88,
816
+ S: this,
817
+ A: [
818
+ "spaceRootDoc",
819
+ ""
820
+ ]
821
+ });
822
+ const documentUrl = (spaceRootDoc.links ?? {})[objectId];
823
+ if (documentUrl == null) {
824
+ this._objectsPendingDocumentLoad.add(objectId);
825
+ log4.info("loading delayed until object links are initialized", {
826
+ objectId
827
+ }, {
828
+ F: __dxlog_file5,
829
+ L: 92,
830
+ S: this,
831
+ C: (f, a) => f(...a)
832
+ });
833
+ return;
834
+ }
835
+ this._loadLinkedObjects({
836
+ [objectId]: documentUrl
837
+ });
838
+ }
839
+ onObjectLinksUpdated(links) {
840
+ if (!links) {
841
+ return;
842
+ }
843
+ const linksAwaitingLoad = Object.entries(links).filter(([objectId]) => this._objectsPendingDocumentLoad.has(objectId));
844
+ this._loadLinkedObjects(Object.fromEntries(linksAwaitingLoad));
845
+ linksAwaitingLoad.forEach(([objectId]) => this._objectsPendingDocumentLoad.delete(objectId));
846
+ }
847
+ getSpaceRootDocHandle() {
848
+ invariant3(this._spaceRootDocHandle, void 0, {
849
+ F: __dxlog_file5,
850
+ L: 110,
851
+ S: this,
852
+ A: [
853
+ "this._spaceRootDocHandle",
854
+ ""
855
+ ]
856
+ });
857
+ return this._spaceRootDocHandle;
858
+ }
859
+ createDocumentForObject(objectId) {
860
+ invariant3(this._spaceRootDocHandle, void 0, {
861
+ F: __dxlog_file5,
862
+ L: 115,
863
+ S: this,
864
+ A: [
865
+ "this._spaceRootDocHandle",
866
+ ""
867
+ ]
868
+ });
869
+ const spaceDocHandle = this._repo.create();
870
+ this._initDocAccess(spaceDocHandle);
871
+ this.onObjectBoundToDocument(spaceDocHandle, objectId);
872
+ this._spaceRootDocHandle.change((newDoc) => {
873
+ newDoc.links ??= {};
874
+ newDoc.links[objectId] = spaceDocHandle.url;
875
+ });
876
+ return spaceDocHandle;
877
+ }
878
+ onObjectBoundToDocument(handle, objectId) {
879
+ this._objectDocumentHandles.set(objectId, handle);
880
+ }
881
+ clearHandleReferences() {
882
+ const objectsWithHandles = [
883
+ ...this._objectDocumentHandles.keys()
884
+ ];
885
+ this._objectDocumentHandles.clear();
886
+ this._spaceRootDocHandle = null;
887
+ return objectsWithHandles;
888
+ }
889
+ _loadLinkedObjects(links) {
890
+ if (!links) {
891
+ return;
892
+ }
893
+ for (const [objectId, automergeUrl] of Object.entries(links)) {
894
+ const logMeta = {
895
+ objectId,
896
+ automergeUrl
897
+ };
898
+ const objectDocumentHandle = this._objectDocumentHandles.get(objectId);
899
+ if (objectDocumentHandle != null && objectDocumentHandle.url !== automergeUrl) {
900
+ log4.warn("object already inlined in a different document, ignoring the link", {
901
+ ...logMeta,
902
+ actualDocumentUrl: objectDocumentHandle.url
903
+ }, {
904
+ F: __dxlog_file5,
905
+ L: 145,
906
+ S: this,
907
+ C: (f, a) => f(...a)
908
+ });
909
+ continue;
910
+ }
911
+ if (objectDocumentHandle?.url === automergeUrl) {
912
+ log4.warn("object document was already loaded", logMeta, {
913
+ F: __dxlog_file5,
914
+ L: 152,
915
+ S: this,
916
+ C: (f, a) => f(...a)
917
+ });
918
+ continue;
919
+ }
920
+ const handle = this._repo.find(automergeUrl);
921
+ log4.debug("document loading triggered", logMeta, {
922
+ F: __dxlog_file5,
923
+ L: 156,
924
+ S: this,
925
+ C: (f, a) => f(...a)
926
+ });
927
+ this._objectDocumentHandles.set(objectId, handle);
928
+ void this._createObjectOnDocumentLoad(handle, objectId);
929
+ }
930
+ }
931
+ async _initDocHandle(ctx, url) {
932
+ const docHandle = this._repo.find(url);
933
+ while (true) {
934
+ try {
935
+ await warnAfterTimeout(5e3, "Automerge root doc load timeout (AutomergeDb)", async () => {
936
+ await cancelWithContext(ctx, docHandle.whenReady());
937
+ });
938
+ break;
939
+ } catch (err) {
940
+ if (`${err}`.includes("Timeout")) {
941
+ log4.info("wraparound", {
942
+ id: docHandle.documentId,
943
+ state: docHandle.state
944
+ }, {
945
+ F: __dxlog_file5,
946
+ L: 172,
947
+ S: this,
948
+ C: (f, a) => f(...a)
949
+ });
950
+ continue;
951
+ }
952
+ throw err;
953
+ }
954
+ }
955
+ if (docHandle.state === "unavailable") {
956
+ throw new Error("Automerge document is unavailable");
957
+ }
958
+ return docHandle;
959
+ }
960
+ _createContextBoundSpaceRootDocument(ctx) {
961
+ const docHandle = this._repo.create();
962
+ this._spaceRootDocHandle = docHandle;
963
+ ctx.onDispose(() => {
964
+ docHandle.delete();
965
+ this._spaceRootDocHandle = null;
966
+ });
967
+ }
968
+ _initDocAccess(handle) {
969
+ handle.change((newDoc) => {
970
+ newDoc.access ??= {
971
+ spaceKey: this._spaceKey.toHex()
972
+ };
973
+ newDoc.access.spaceKey = this._spaceKey.toHex();
974
+ });
975
+ }
976
+ async _createObjectOnDocumentLoad(handle, objectId) {
977
+ try {
978
+ await handle.doc([
979
+ "ready"
980
+ ]);
981
+ const logMeta = {
982
+ objectId,
983
+ docUrl: handle.url
984
+ };
985
+ if (this.onObjectDocumentLoaded.listenerCount() === 0) {
986
+ log4.info("document loaded after all listeners were removed", logMeta, {
987
+ F: __dxlog_file5,
988
+ L: 208,
989
+ S: this,
990
+ C: (f, a) => f(...a)
991
+ });
992
+ return;
993
+ }
994
+ const objectDocHandle = this._objectDocumentHandles.get(objectId);
995
+ if (objectDocHandle?.url !== handle.url) {
996
+ log4.warn("object was rebound while a document was loading, discarding handle", logMeta, {
997
+ F: __dxlog_file5,
998
+ L: 213,
999
+ S: this,
1000
+ C: (f, a) => f(...a)
1001
+ });
1002
+ return;
1003
+ }
1004
+ this.onObjectDocumentLoaded.emit({
1005
+ handle,
1006
+ objectId
1007
+ });
1008
+ } catch (err) {
1009
+ const shouldRetryLoading = this.onObjectDocumentLoaded.listenerCount() > 0;
1010
+ log4.warn("failed to load a document", {
1011
+ objectId,
1012
+ automergeUrl: handle.url,
1013
+ retryLoading: shouldRetryLoading,
1014
+ err
1015
+ }, {
1016
+ F: __dxlog_file5,
1017
+ L: 219,
1018
+ S: this,
1019
+ C: (f, a) => f(...a)
1020
+ });
1021
+ if (shouldRetryLoading) {
1022
+ await this._createObjectOnDocumentLoad(handle, objectId);
1023
+ }
1024
+ }
1025
+ }
1026
+ };
1027
+ _ts_decorate2([
1028
+ trace2.span({
1029
+ showInBrowserTimeline: true
1030
+ })
1031
+ ], AutomergeDocumentLoaderImpl.prototype, "loadSpaceRootDocHandle", null);
1032
+ AutomergeDocumentLoaderImpl = _ts_decorate2([
1033
+ trace2.resource()
1034
+ ], AutomergeDocumentLoaderImpl);
1035
+
1036
+ // packages/core/echo/echo-pipeline/src/automerge/reference.ts
1037
+ import { Reference } from "@dxos/echo-schema";
1038
+ var REFERENCE_TYPE_TAG = "dxos.echo.model.document.Reference";
1039
+ var encodeReference = (reference) => ({
1040
+ "@type": REFERENCE_TYPE_TAG,
1041
+ // NOTE: Automerge do not support undefined values, so we need to use null instead.
1042
+ itemId: reference.itemId ?? null,
1043
+ protocol: reference.protocol ?? null,
1044
+ host: reference.host ?? null
1045
+ });
1046
+ var decodeReference = (value) => new Reference(value.itemId, value.protocol ?? void 0, value.host ?? void 0);
1047
+ var isEncodedReferenceObject = (value) => typeof value === "object" && value !== null && value["@type"] === REFERENCE_TYPE_TAG;
656
1048
  export {
657
1049
  AuthExtension,
658
1050
  AuthStatus,
1051
+ AutomergeDocumentLoaderImpl,
659
1052
  AutomergeHost,
660
1053
  AutomergeStorageAdapter,
661
1054
  DataServiceImpl,
1055
+ LevelDBStorageAdapter,
662
1056
  LocalHostNetworkAdapter,
663
1057
  MOCK_AUTH_PROVIDER,
664
1058
  MOCK_AUTH_VERIFIER,
665
1059
  MeshNetworkAdapter,
666
1060
  MetadataStore,
667
1061
  Pipeline,
1062
+ REFERENCE_TYPE_TAG,
668
1063
  SnapshotManager,
669
1064
  SnapshotStore,
670
1065
  Space,
@@ -674,7 +1069,12 @@ export {
674
1069
  TimeframeClock,
675
1070
  codec,
676
1071
  createMappedFeedWriter,
1072
+ decodeReference,
1073
+ encodeReference,
1074
+ encodingOptions,
677
1075
  getSpaceKeyFromDoc,
1076
+ hasInvitationExpired,
1077
+ isEncodedReferenceObject,
678
1078
  mapFeedIndexesToTimeframe,
679
1079
  mapTimeframeToFeedIndexes,
680
1080
  startAfter,