@dxos/echo-pipeline 0.6.2 → 0.6.3-main.0308ae2

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 (66) hide show
  1. package/dist/lib/browser/{chunk-UJQ5VS5V.mjs → chunk-6MJEONOX.mjs} +2569 -1066
  2. package/dist/lib/browser/chunk-6MJEONOX.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +12 -1049
  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 +224 -2
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node/{chunk-RH6TDRML.cjs → chunk-PT5LWMPA.cjs} +3185 -1710
  9. package/dist/lib/node/chunk-PT5LWMPA.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +37 -1056
  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 +238 -13
  14. package/dist/lib/node/testing/index.cjs.map +4 -4
  15. package/dist/types/src/automerge/automerge-host.d.ts +29 -2
  16. package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
  17. package/dist/types/src/automerge/collection-synchronizer.d.ts +61 -0
  18. package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -0
  19. package/dist/types/src/automerge/collection-synchronizer.test.d.ts +2 -0
  20. package/dist/types/src/automerge/collection-synchronizer.test.d.ts.map +1 -0
  21. package/dist/types/src/automerge/echo-network-adapter.d.ts +9 -2
  22. package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
  23. package/dist/types/src/automerge/echo-replicator.d.ts +7 -0
  24. package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
  25. package/dist/types/src/automerge/heads-store.d.ts +1 -1
  26. package/dist/types/src/automerge/heads-store.d.ts.map +1 -1
  27. package/dist/types/src/automerge/index.d.ts +2 -0
  28. package/dist/types/src/automerge/index.d.ts.map +1 -1
  29. package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +3 -1
  30. package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
  31. package/dist/types/src/automerge/mesh-echo-replicator.d.ts +2 -2
  32. package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
  33. package/dist/types/src/automerge/network-protocol.d.ts +31 -0
  34. package/dist/types/src/automerge/network-protocol.d.ts.map +1 -0
  35. package/dist/types/src/automerge/space-collection.d.ts +4 -0
  36. package/dist/types/src/automerge/space-collection.d.ts.map +1 -0
  37. package/dist/types/src/db-host/data-service.d.ts +2 -1
  38. package/dist/types/src/db-host/data-service.d.ts.map +1 -1
  39. package/dist/types/src/db-host/documents-synchronizer.d.ts +1 -1
  40. package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
  41. package/dist/types/src/testing/index.d.ts +1 -0
  42. package/dist/types/src/testing/index.d.ts.map +1 -1
  43. package/dist/types/src/testing/test-replicator.d.ts +46 -0
  44. package/dist/types/src/testing/test-replicator.d.ts.map +1 -0
  45. package/package.json +33 -33
  46. package/src/automerge/automerge-host.test.ts +76 -14
  47. package/src/automerge/automerge-host.ts +219 -32
  48. package/src/automerge/automerge-repo.test.ts +2 -1
  49. package/src/automerge/collection-synchronizer.test.ts +91 -0
  50. package/src/automerge/collection-synchronizer.ts +204 -0
  51. package/src/automerge/echo-network-adapter.test.ts +5 -1
  52. package/src/automerge/echo-network-adapter.ts +69 -4
  53. package/src/automerge/echo-replicator.ts +9 -0
  54. package/src/automerge/heads-store.ts +6 -9
  55. package/src/automerge/index.ts +2 -0
  56. package/src/automerge/mesh-echo-replicator-connection.ts +6 -1
  57. package/src/automerge/mesh-echo-replicator.ts +28 -7
  58. package/src/automerge/network-protocol.ts +45 -0
  59. package/src/automerge/space-collection.ts +14 -0
  60. package/src/db-host/data-service.ts +26 -12
  61. package/src/db-host/documents-synchronizer.ts +17 -5
  62. package/src/metadata/metadata-store.ts +1 -1
  63. package/src/testing/index.ts +1 -0
  64. package/src/testing/test-replicator.ts +194 -0
  65. package/dist/lib/browser/chunk-UJQ5VS5V.mjs.map +0 -7
  66. package/dist/lib/node/chunk-RH6TDRML.cjs.map +0 -7
@@ -137,14 +137,39 @@ var DocumentsSynchronizer = class extends Resource {
137
137
  this._pendingUpdates = /* @__PURE__ */ new Set();
138
138
  this._sendUpdatesJob = void 0;
139
139
  }
140
- async addDocuments(documentIds) {
140
+ addDocuments(documentIds, retryCounter = 0) {
141
+ if (retryCounter > 3) {
142
+ log.warn("Failed to load document, retry limit reached", {
143
+ documentIds
144
+ }, {
145
+ F: __dxlog_file2,
146
+ L: 49,
147
+ S: this,
148
+ C: (f, a) => f(...a)
149
+ });
150
+ return;
151
+ }
141
152
  for (const documentId of documentIds) {
142
153
  const doc = this._params.repo.find(documentId);
143
- await doc.whenReady();
144
- this._startSync(doc);
145
- this._pendingUpdates.add(doc.documentId);
154
+ doc.whenReady().then(() => {
155
+ this._startSync(doc);
156
+ this._pendingUpdates.add(doc.documentId);
157
+ this._sendUpdatesJob.trigger();
158
+ }).catch((error) => {
159
+ log.warn("Failed to load document, wraparound", {
160
+ documentId,
161
+ error
162
+ }, {
163
+ F: __dxlog_file2,
164
+ L: 63,
165
+ S: this,
166
+ C: (f, a) => f(...a)
167
+ });
168
+ this.addDocuments([
169
+ documentId
170
+ ], retryCounter + 1);
171
+ });
146
172
  }
147
- this._sendUpdatesJob.trigger();
148
173
  }
149
174
  removeDocuments(documentIds) {
150
175
  for (const documentId of documentIds) {
@@ -179,7 +204,7 @@ var DocumentsSynchronizer = class extends Resource {
179
204
  documentId: doc.documentId
180
205
  }, {
181
206
  F: __dxlog_file2,
182
- L: 90,
207
+ L: 102,
183
208
  S: this,
184
209
  C: (f, a) => f(...a)
185
210
  });
@@ -222,7 +247,7 @@ var DocumentsSynchronizer = class extends Resource {
222
247
  const syncState = this._syncStates.get(documentId);
223
248
  invariant2(syncState, "Sync state for document not found", {
224
249
  F: __dxlog_file2,
225
- L: 131,
250
+ L: 143,
226
251
  S: this,
227
252
  A: [
228
253
  "syncState",
@@ -244,7 +269,7 @@ var DocumentsSynchronizer = class extends Resource {
244
269
  const syncState = this._syncStates.get(documentId);
245
270
  invariant2(syncState, "Sync state for document not found", {
246
271
  F: __dxlog_file2,
247
- L: 146,
272
+ L: 158,
248
273
  S: this,
249
274
  A: [
250
275
  "syncState",
@@ -262,111 +287,276 @@ var DocumentsSynchronizer = class extends Resource {
262
287
  }
263
288
  };
264
289
 
265
- // packages/core/echo/echo-pipeline/src/db-host/data-service.ts
266
- import { Stream } from "@dxos/codec-protobuf";
267
- import { invariant as invariant3 } from "@dxos/invariant";
268
- import { log as log2 } from "@dxos/log";
269
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/db-host/data-service.ts";
270
- var DataServiceImpl = class {
290
+ // packages/core/echo/echo-pipeline/src/automerge/collection-synchronizer.ts
291
+ import { asyncReturn, Event, scheduleTask, scheduleTaskInterval } from "@dxos/async";
292
+ import { next as am } from "@dxos/automerge/automerge";
293
+ import { Resource as Resource2 } from "@dxos/context";
294
+ import { defaultMap } from "@dxos/util";
295
+ var MIN_QUERY_INTERVAL = 5e3;
296
+ var POLL_INTERVAL = 3e4;
297
+ var CollectionSynchronizer = class extends Resource2 {
271
298
  constructor(params) {
299
+ super();
272
300
  /**
273
- * Map of subscriptions.
274
- * subscriptionId -> DocumentsSynchronizer
301
+ * CollectionId -> State.
275
302
  */
276
- this._subscriptions = /* @__PURE__ */ new Map();
277
- this._automergeHost = params.automergeHost;
278
- this._updateIndexes = params.updateIndexes;
303
+ this._perCollectionStates = /* @__PURE__ */ new Map();
304
+ this._connectedPeers = /* @__PURE__ */ new Set();
305
+ this.remoteStateUpdated = new Event();
306
+ this._sendCollectionState = params.sendCollectionState;
307
+ this._queryCollectionState = params.queryCollectionState;
308
+ this._shouldSyncCollection = params.shouldSyncCollection;
279
309
  }
280
- subscribe(request) {
281
- return new Stream(({ next, ready }) => {
282
- const synchronizer = new DocumentsSynchronizer({
283
- repo: this._automergeHost.repo,
284
- sendUpdates: (updates) => next(updates)
285
- });
286
- synchronizer.open().then(() => {
287
- this._subscriptions.set(request.subscriptionId, synchronizer);
288
- ready();
289
- }).catch((err) => log2.catch(err, void 0, {
290
- F: __dxlog_file3,
291
- L: 62,
292
- S: this,
293
- C: (f, a) => f(...a)
294
- }));
295
- return () => synchronizer.close();
310
+ async _open(ctx) {
311
+ scheduleTaskInterval(this._ctx, async () => {
312
+ for (const collectionId of this._perCollectionStates.keys()) {
313
+ this.refreshCollection(collectionId);
314
+ await asyncReturn();
315
+ }
316
+ }, POLL_INTERVAL);
317
+ }
318
+ getRegisteredCollectionIds() {
319
+ return [
320
+ ...this._perCollectionStates.keys()
321
+ ];
322
+ }
323
+ getLocalCollectionState(collectionId) {
324
+ return this._getPerCollectionState(collectionId).localState;
325
+ }
326
+ setLocalCollectionState(collectionId, state) {
327
+ this._getPerCollectionState(collectionId).localState = state;
328
+ queueMicrotask(async () => {
329
+ if (!this._ctx.disposed) {
330
+ this._refreshInterestedPeers(collectionId);
331
+ this.refreshCollection(collectionId);
332
+ }
296
333
  });
297
334
  }
298
- async updateSubscription(request) {
299
- const synchronizer = this._subscriptions.get(request.subscriptionId);
300
- invariant3(synchronizer, "Subscription not found", {
301
- F: __dxlog_file3,
302
- L: 69,
303
- S: this,
304
- A: [
305
- "synchronizer",
306
- "'Subscription not found'"
307
- ]
335
+ getRemoteCollectionStates(collectionId) {
336
+ return this._getPerCollectionState(collectionId).remoteStates;
337
+ }
338
+ refreshCollection(collectionId) {
339
+ let scheduleAnotherRefresh = false;
340
+ const state = this._getPerCollectionState(collectionId);
341
+ for (const peerId of this._connectedPeers) {
342
+ if (state.interestedPeers.has(peerId)) {
343
+ const lastQueried = state.lastQueried.get(peerId) ?? 0;
344
+ if (Date.now() - lastQueried > MIN_QUERY_INTERVAL) {
345
+ state.lastQueried.set(peerId, Date.now());
346
+ this._queryCollectionState(collectionId, peerId);
347
+ } else {
348
+ scheduleAnotherRefresh = true;
349
+ }
350
+ }
351
+ }
352
+ if (scheduleAnotherRefresh) {
353
+ scheduleTask(this._ctx, () => this.refreshCollection(collectionId), MIN_QUERY_INTERVAL);
354
+ }
355
+ }
356
+ /**
357
+ * Callback when a connection to a peer is established.
358
+ */
359
+ onConnectionOpen(peerId) {
360
+ this._connectedPeers.add(peerId);
361
+ queueMicrotask(async () => {
362
+ if (this._ctx.disposed) {
363
+ return;
364
+ }
365
+ for (const [collectionId, state] of this._perCollectionStates.entries()) {
366
+ if (this._shouldSyncCollection(collectionId, peerId)) {
367
+ state.interestedPeers.add(peerId);
368
+ state.lastQueried.set(peerId, Date.now());
369
+ this._queryCollectionState(collectionId, peerId);
370
+ }
371
+ }
308
372
  });
309
- if (request.addIds?.length) {
310
- await synchronizer.addDocuments(request.addIds);
373
+ }
374
+ /**
375
+ * Callback when a connection to a peer is closed.
376
+ */
377
+ onConnectionClosed(peerId) {
378
+ this._connectedPeers.delete(peerId);
379
+ for (const perCollectionState of this._perCollectionStates.values()) {
380
+ perCollectionState.remoteStates.delete(peerId);
311
381
  }
312
- if (request.removeIds?.length) {
313
- await synchronizer.removeDocuments(request.removeIds);
382
+ }
383
+ /**
384
+ * Callback when a peer queries the state of a collection.
385
+ */
386
+ onCollectionStateQueried(collectionId, peerId) {
387
+ const perCollectionState = this._getPerCollectionState(collectionId);
388
+ if (perCollectionState.localState) {
389
+ this._sendCollectionState(collectionId, peerId, perCollectionState.localState);
314
390
  }
315
391
  }
316
- async update(request) {
317
- if (!request.updates) {
318
- return;
392
+ /**
393
+ * Callback when a peer sends the state of a collection.
394
+ */
395
+ onRemoteStateReceived(collectionId, peerId, state) {
396
+ const perCollectionState = this._getPerCollectionState(collectionId);
397
+ perCollectionState.remoteStates.set(peerId, state);
398
+ this.remoteStateUpdated.emit({
399
+ peerId,
400
+ collectionId
401
+ });
402
+ }
403
+ _getPerCollectionState(collectionId) {
404
+ return defaultMap(this._perCollectionStates, collectionId, () => ({
405
+ localState: void 0,
406
+ remoteStates: /* @__PURE__ */ new Map(),
407
+ interestedPeers: /* @__PURE__ */ new Set(),
408
+ lastQueried: /* @__PURE__ */ new Map()
409
+ }));
410
+ }
411
+ _refreshInterestedPeers(collectionId) {
412
+ for (const peerId of this._connectedPeers) {
413
+ if (this._shouldSyncCollection(collectionId, peerId)) {
414
+ this._getPerCollectionState(collectionId).interestedPeers.add(peerId);
415
+ } else {
416
+ this._getPerCollectionState(collectionId).interestedPeers.delete(peerId);
417
+ }
319
418
  }
320
- const synchronizer = this._subscriptions.get(request.subscriptionId);
321
- invariant3(synchronizer, "Subscription not found", {
322
- F: __dxlog_file3,
323
- L: 84,
324
- S: this,
325
- A: [
326
- "synchronizer",
327
- "'Subscription not found'"
328
- ]
329
- });
330
- synchronizer.update(request.updates);
331
419
  }
332
- async flush(request) {
333
- await this._automergeHost.flush(request);
420
+ };
421
+ var diffCollectionState = (local, remote) => {
422
+ const allDocuments = /* @__PURE__ */ new Set([
423
+ ...Object.keys(local.documents),
424
+ ...Object.keys(remote.documents)
425
+ ]);
426
+ const different = [];
427
+ for (const documentId of allDocuments) {
428
+ if (!local.documents[documentId] || !remote.documents[documentId] || !am.equals(local.documents[documentId], remote.documents[documentId])) {
429
+ different.push(documentId);
430
+ }
334
431
  }
335
- async getDocumentHeads(request) {
336
- const entries = await Promise.all(request.documentIds?.map(async (documentId) => {
337
- const heads = await this._automergeHost.getHeads(documentId);
338
- return {
339
- documentId,
340
- heads
341
- };
342
- }) ?? []);
343
- return {
344
- heads: {
345
- entries
432
+ return {
433
+ different
434
+ };
435
+ };
436
+
437
+ // packages/core/echo/echo-pipeline/src/automerge/leveldb-storage-adapter.ts
438
+ import { LifecycleState, Resource as Resource3 } from "@dxos/context";
439
+ var LevelDBStorageAdapter = class extends Resource3 {
440
+ constructor(_params) {
441
+ super();
442
+ this._params = _params;
443
+ }
444
+ async load(keyArray) {
445
+ try {
446
+ if (this._lifecycleState !== LifecycleState.OPEN) {
447
+ return void 0;
346
448
  }
347
- };
449
+ return await this._params.db.get(keyArray, {
450
+ ...encodingOptions
451
+ });
452
+ } catch (err) {
453
+ if (isLevelDbNotFoundError(err)) {
454
+ return void 0;
455
+ }
456
+ throw err;
457
+ }
348
458
  }
349
- async waitUntilHeadsReplicated(request, options) {
350
- await this._automergeHost.waitUntilHeadsReplicated(request.heads);
459
+ async save(keyArray, binary) {
460
+ if (this._lifecycleState !== LifecycleState.OPEN) {
461
+ return void 0;
462
+ }
463
+ const batch = this._params.db.batch();
464
+ await this._params.callbacks?.beforeSave?.({
465
+ path: keyArray,
466
+ batch
467
+ });
468
+ batch.put(keyArray, Buffer.from(binary), {
469
+ ...encodingOptions
470
+ });
471
+ await batch.write();
472
+ await this._params.callbacks?.afterSave?.(keyArray);
351
473
  }
352
- async reIndexHeads(request, options) {
353
- await this._automergeHost.reIndexHeads(request.documentIds ?? []);
474
+ async remove(keyArray) {
475
+ if (this._lifecycleState !== LifecycleState.OPEN) {
476
+ return void 0;
477
+ }
478
+ await this._params.db.del(keyArray, {
479
+ ...encodingOptions
480
+ });
354
481
  }
355
- async updateIndexes() {
356
- await this._updateIndexes();
482
+ async loadRange(keyPrefix) {
483
+ if (this._lifecycleState !== LifecycleState.OPEN) {
484
+ return [];
485
+ }
486
+ const result = [];
487
+ for await (const [key, value] of this._params.db.iterator({
488
+ gte: keyPrefix,
489
+ lte: [
490
+ ...keyPrefix,
491
+ "\uFFFF"
492
+ ],
493
+ ...encodingOptions
494
+ })) {
495
+ result.push({
496
+ key,
497
+ data: value
498
+ });
499
+ }
500
+ return result;
501
+ }
502
+ async removeRange(keyPrefix) {
503
+ if (this._lifecycleState !== LifecycleState.OPEN) {
504
+ return void 0;
505
+ }
506
+ const batch = this._params.db.batch();
507
+ for await (const [key] of this._params.db.iterator({
508
+ gte: keyPrefix,
509
+ lte: [
510
+ ...keyPrefix,
511
+ "\uFFFF"
512
+ ],
513
+ ...encodingOptions
514
+ })) {
515
+ batch.del(key, {
516
+ ...encodingOptions
517
+ });
518
+ }
519
+ await batch.write();
357
520
  }
358
521
  };
522
+ var keyEncoder = {
523
+ encode: (key) => Buffer.from(key.map((k) => k.replaceAll("%", "%25").replaceAll("-", "%2D")).join("-")),
524
+ decode: (key) => Buffer.from(key).toString().split("-").map((k) => k.replaceAll("%2D", "-").replaceAll("%25", "%")),
525
+ format: "buffer"
526
+ };
527
+ var encodingOptions = {
528
+ keyEncoding: keyEncoder,
529
+ valueEncoding: "buffer"
530
+ };
531
+ var isLevelDbNotFoundError = (err) => err.code === "LEVEL_NOT_FOUND";
359
532
 
360
- // packages/core/echo/echo-pipeline/src/metadata/metadata-store.ts
361
- import CRC32 from "crc-32";
362
- import { Event, scheduleTaskInterval, synchronized } from "@dxos/async";
363
- import { Context } from "@dxos/context";
533
+ // packages/core/echo/echo-pipeline/src/automerge/automerge-host.ts
534
+ import { Event as Event2, asyncTimeout } from "@dxos/async";
535
+ import { next as automerge, getBackend, getHeads, isAutomerge, equals as headsEquals, save } from "@dxos/automerge/automerge";
536
+ import { Repo } from "@dxos/automerge/automerge-repo";
537
+ import { Context, Resource as Resource4, cancelWithContext as cancelWithContext2 } from "@dxos/context";
364
538
  import { invariant as invariant4 } from "@dxos/invariant";
365
539
  import { PublicKey as PublicKey2 } from "@dxos/keys";
366
540
  import { log as log3 } from "@dxos/log";
367
- import { DataCorruptionError, STORAGE_VERSION, schema as schema4 } from "@dxos/protocols";
368
- import { Invitation, SpaceState } from "@dxos/protocols/proto/dxos/client/services";
369
- import { ComplexMap, arrayToBuffer, forEachAsync, isNotNullOrUndefined } from "@dxos/util";
541
+ import { objectPointerCodec } from "@dxos/protocols";
542
+ import { trace } from "@dxos/tracing";
543
+ import { mapValues } from "@dxos/util";
544
+
545
+ // packages/core/echo/echo-pipeline/src/automerge/echo-network-adapter.ts
546
+ import { synchronized, Trigger } from "@dxos/async";
547
+ import { NetworkAdapter } from "@dxos/automerge/automerge-repo";
548
+ import { LifecycleState as LifecycleState2 } from "@dxos/context";
549
+ import { invariant as invariant3 } from "@dxos/invariant";
550
+ import { log as log2 } from "@dxos/log";
551
+ import { nonNullable } from "@dxos/util";
552
+
553
+ // packages/core/echo/echo-pipeline/src/automerge/network-protocol.ts
554
+ var MESSAGE_TYPE_COLLECTION_QUERY = "collection-query";
555
+ var isCollectionQueryMessage = (message) => message.type === MESSAGE_TYPE_COLLECTION_QUERY;
556
+ var MESSAGE_TYPE_COLLECTION_STATE = "collection-state";
557
+ var isCollectionStateMessage = (message) => message.type === MESSAGE_TYPE_COLLECTION_STATE;
558
+
559
+ // packages/core/echo/echo-pipeline/src/automerge/echo-network-adapter.ts
370
560
  function _ts_decorate(decorators, target, key, desc) {
371
561
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
372
562
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
@@ -377,334 +567,348 @@ function _ts_decorate(decorators, target, key, desc) {
377
567
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
378
568
  return c > 3 && r && Object.defineProperty(target, key, r), r;
379
569
  }
380
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/metadata/metadata-store.ts";
381
- var EXPIRED_INVITATION_CLEANUP_INTERVAL = 60 * 60 * 1e3;
382
- var emptyEchoMetadata = () => ({
383
- version: STORAGE_VERSION,
384
- spaces: [],
385
- created: /* @__PURE__ */ new Date(),
386
- updated: /* @__PURE__ */ new Date()
387
- });
388
- var emptyLargeSpaceMetadata = () => ({});
389
- var EchoMetadata = schema4.getCodecForType("dxos.echo.metadata.EchoMetadata");
390
- var LargeSpaceMetadata = schema4.getCodecForType("dxos.echo.metadata.LargeSpaceMetadata");
391
- var MetadataStore = class {
392
- constructor(directory) {
393
- this._metadata = emptyEchoMetadata();
394
- this._spaceLargeMetadata = new ComplexMap(PublicKey2.hash);
395
- this._metadataFile = void 0;
396
- this.update = new Event();
397
- this._invitationCleanupCtx = new Context(void 0, {
398
- F: __dxlog_file4,
399
- L: 53
570
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/echo-network-adapter.ts";
571
+ var EchoNetworkAdapter = class extends NetworkAdapter {
572
+ constructor(_params) {
573
+ super();
574
+ this._params = _params;
575
+ this._replicators = /* @__PURE__ */ new Set();
576
+ this._connections = /* @__PURE__ */ new Map();
577
+ this._lifecycleState = LifecycleState2.CLOSED;
578
+ this._connected = new Trigger();
579
+ }
580
+ connect(peerId, peerMetadata) {
581
+ this.peerId = peerId;
582
+ this.peerMetadata = peerMetadata;
583
+ this._connected.wake();
584
+ }
585
+ send(message) {
586
+ const connectionEntry = this._connections.get(message.targetId);
587
+ if (!connectionEntry) {
588
+ throw new Error("Connection not found.");
589
+ }
590
+ connectionEntry.writer.write(message).catch((err) => {
591
+ if (connectionEntry.isOpen) {
592
+ log2.catch(err, void 0, {
593
+ F: __dxlog_file3,
594
+ L: 63,
595
+ S: this,
596
+ C: (f, a) => f(...a)
597
+ });
598
+ }
400
599
  });
401
- this._directory = directory;
402
- }
403
- get metadata() {
404
- return this._metadata;
405
- }
406
- get version() {
407
- return this._metadata.version ?? 0;
408
600
  }
409
- /**
410
- * Returns a list of currently saved spaces. The list and objects in it can be modified addSpace and
411
- * addSpaceFeed functions.
412
- */
413
- get spaces() {
414
- return this._metadata.spaces ?? [];
601
+ disconnect() {
415
602
  }
416
- async _readFile(file, codec2) {
417
- try {
418
- const { size: fileLength } = await file.stat();
419
- if (fileLength < 8) {
420
- return;
421
- }
422
- const dataSize = fromBytesInt32(await file.read(0, 4));
423
- const checksum = fromBytesInt32(await file.read(4, 4));
424
- log3("loaded", {
425
- size: dataSize,
426
- checksum,
427
- name: file.filename
428
- }, {
429
- F: __dxlog_file4,
430
- L: 89,
431
- S: this,
432
- C: (f, a) => f(...a)
433
- });
434
- if (fileLength < dataSize + 8) {
435
- throw new DataCorruptionError("Metadata size is smaller than expected.", {
436
- fileLength,
437
- dataSize
438
- });
439
- }
440
- const data = await file.read(8, dataSize);
441
- const calculatedChecksum = CRC32.buf(data);
442
- if (calculatedChecksum !== checksum) {
443
- throw new DataCorruptionError("Metadata checksum is invalid.");
444
- }
445
- return codec2.decode(data);
446
- } finally {
447
- await file.close();
603
+ async open() {
604
+ if (this._lifecycleState === LifecycleState2.OPEN) {
605
+ return;
448
606
  }
449
- }
450
- /**
451
- * @internal
452
- */
453
- async _writeFile(file, codec2, data) {
454
- const encoded = arrayToBuffer(codec2.encode(data));
455
- const checksum = CRC32.buf(encoded);
456
- const result = Buffer.alloc(8 + encoded.length);
457
- result.writeInt32LE(encoded.length, 0);
458
- result.writeInt32LE(checksum, 4);
459
- encoded.copy(result, 8);
460
- await file.write(0, result);
461
- log3("saved", {
462
- size: encoded.length,
463
- checksum
464
- }, {
465
- F: __dxlog_file4,
466
- L: 124,
607
+ this._lifecycleState = LifecycleState2.OPEN;
608
+ log2("emit ready", void 0, {
609
+ F: __dxlog_file3,
610
+ L: 79,
467
611
  S: this,
468
612
  C: (f, a) => f(...a)
469
613
  });
614
+ this.emit("ready", {
615
+ network: this
616
+ });
470
617
  }
471
618
  async close() {
472
- await this._invitationCleanupCtx.dispose();
473
- await this.flush();
474
- await this._metadataFile?.close();
475
- this._metadataFile = void 0;
476
- this._metadata = emptyEchoMetadata();
477
- this._spaceLargeMetadata.clear();
478
- }
479
- /**
480
- * Loads metadata from persistent storage.
481
- */
482
- async load() {
483
- if (!this._metadataFile || this._metadataFile.closed) {
484
- this._metadataFile = this._directory.getOrCreateFile("EchoMetadata");
619
+ if (this._lifecycleState === LifecycleState2.CLOSED) {
620
+ return this;
485
621
  }
486
- try {
487
- const metadata = await this._readFile(this._metadataFile, EchoMetadata);
488
- if (metadata) {
489
- this._metadata = metadata;
490
- }
491
- this._metadata.spaces?.forEach((space) => {
492
- space.state ??= SpaceState.ACTIVE;
493
- });
494
- } catch (err) {
495
- log3.error("failed to load metadata", {
496
- err
497
- }, {
498
- F: __dxlog_file4,
499
- L: 156,
500
- S: this,
501
- C: (f, a) => f(...a)
502
- });
503
- this._metadata = emptyEchoMetadata();
622
+ for (const replicator of this._replicators) {
623
+ await replicator.disconnect();
504
624
  }
505
- await forEachAsync([
506
- this._metadata.identity?.haloSpace.key,
507
- ...this._metadata.spaces?.map((space) => space.key) ?? []
508
- ].filter(isNotNullOrUndefined), async (key) => {
509
- try {
510
- await this._loadSpaceLargeMetadata(key);
511
- } catch (err) {
512
- log3.error("failed to load space large metadata", {
513
- err
514
- }, {
515
- F: __dxlog_file4,
516
- L: 168,
517
- S: this,
518
- C: (f, a) => f(...a)
519
- });
520
- }
521
- });
522
- scheduleTaskInterval(this._invitationCleanupCtx, async () => {
523
- for (const invitation of this._metadata.invitations ?? []) {
524
- if (hasInvitationExpired(invitation) || isLegacyInvitationFormat(invitation)) {
525
- await this.removeInvitation(invitation.invitationId);
526
- }
527
- }
528
- }, EXPIRED_INVITATION_CLEANUP_INTERVAL);
625
+ this._replicators.clear();
626
+ this._lifecycleState = LifecycleState2.CLOSED;
529
627
  }
530
- async _save() {
531
- const data = {
532
- ...this._metadata,
533
- version: STORAGE_VERSION,
534
- created: this._metadata.created ?? /* @__PURE__ */ new Date(),
535
- updated: /* @__PURE__ */ new Date()
536
- };
537
- this.update.emit(data);
538
- const file = this._directory.getOrCreateFile("EchoMetadata");
539
- await this._writeFile(file, EchoMetadata, data);
628
+ async whenConnected() {
629
+ await this._connected.wait({
630
+ timeout: 1e4
631
+ });
540
632
  }
541
- async _loadSpaceLargeMetadata(key) {
542
- const file = this._directory.getOrCreateFile(`space_${key.toHex()}_large`);
543
- try {
544
- const metadata = await this._readFile(file, LargeSpaceMetadata);
545
- if (metadata) {
546
- this._spaceLargeMetadata.set(key, metadata);
547
- }
548
- } catch (err) {
549
- log3.error("failed to load space large metadata", {
550
- err
551
- }, {
552
- F: __dxlog_file4,
553
- L: 210,
554
- S: this,
555
- C: (f, a) => f(...a)
556
- });
557
- }
633
+ async addReplicator(replicator) {
634
+ invariant3(this._lifecycleState === LifecycleState2.OPEN, void 0, {
635
+ F: __dxlog_file3,
636
+ L: 105,
637
+ S: this,
638
+ A: [
639
+ "this._lifecycleState === LifecycleState.OPEN",
640
+ ""
641
+ ]
642
+ });
643
+ invariant3(this.peerId, void 0, {
644
+ F: __dxlog_file3,
645
+ L: 106,
646
+ S: this,
647
+ A: [
648
+ "this.peerId",
649
+ ""
650
+ ]
651
+ });
652
+ invariant3(!this._replicators.has(replicator), void 0, {
653
+ F: __dxlog_file3,
654
+ L: 107,
655
+ S: this,
656
+ A: [
657
+ "!this._replicators.has(replicator)",
658
+ ""
659
+ ]
660
+ });
661
+ this._replicators.add(replicator);
662
+ await replicator.connect({
663
+ peerId: this.peerId,
664
+ onConnectionOpen: this._onConnectionOpen.bind(this),
665
+ onConnectionClosed: this._onConnectionClosed.bind(this),
666
+ onConnectionAuthScopeChanged: this._onConnectionAuthScopeChanged.bind(this),
667
+ getContainingSpaceForDocument: this._params.getContainingSpaceForDocument
668
+ });
558
669
  }
559
- async _saveSpaceLargeMetadata(key) {
560
- const data = this._getLargeSpaceMetadata(key);
561
- const file = this._directory.getOrCreateFile(`space_${key.toHex()}_large`);
562
- await this._writeFile(file, LargeSpaceMetadata, data);
670
+ async removeReplicator(replicator) {
671
+ invariant3(this._lifecycleState === LifecycleState2.OPEN, void 0, {
672
+ F: __dxlog_file3,
673
+ L: 121,
674
+ S: this,
675
+ A: [
676
+ "this._lifecycleState === LifecycleState.OPEN",
677
+ ""
678
+ ]
679
+ });
680
+ invariant3(this._replicators.has(replicator), void 0, {
681
+ F: __dxlog_file3,
682
+ L: 122,
683
+ S: this,
684
+ A: [
685
+ "this._replicators.has(replicator)",
686
+ ""
687
+ ]
688
+ });
689
+ await replicator.disconnect();
690
+ this._replicators.delete(replicator);
563
691
  }
564
- async flush() {
565
- await this._directory.flush();
692
+ async shouldAdvertise(peerId, params) {
693
+ const connection = this._connections.get(peerId);
694
+ if (!connection) {
695
+ return false;
696
+ }
697
+ return connection.connection.shouldAdvertise(params);
566
698
  }
567
- _getSpace(spaceKey) {
568
- if (this._metadata.identity?.haloSpace.key.equals(spaceKey)) {
569
- return this._metadata.identity.haloSpace;
699
+ shouldSyncCollection(peerId, params) {
700
+ const connection = this._connections.get(peerId);
701
+ if (!connection) {
702
+ return false;
570
703
  }
571
- const space = this.spaces.find((space2) => space2.key === spaceKey);
572
- invariant4(space, "Space not found", {
573
- F: __dxlog_file4,
574
- L: 232,
704
+ return connection.connection.shouldSyncCollection(params);
705
+ }
706
+ queryCollectionState(collectionId, targetId) {
707
+ const message = {
708
+ type: "collection-query",
709
+ senderId: this.peerId,
710
+ targetId,
711
+ collectionId
712
+ };
713
+ this.send(message);
714
+ }
715
+ sendCollectionState(collectionId, targetId, state) {
716
+ const message = {
717
+ type: "collection-state",
718
+ senderId: this.peerId,
719
+ targetId,
720
+ collectionId,
721
+ state
722
+ };
723
+ this.send(message);
724
+ }
725
+ // TODO(dmaretskyi): Remove.
726
+ getPeersInterestedInCollection(collectionId) {
727
+ return Array.from(this._connections.values()).map((connection) => {
728
+ return connection.connection.shouldSyncCollection({
729
+ collectionId
730
+ }) ? connection.connection.peerId : null;
731
+ }).filter(nonNullable);
732
+ }
733
+ _onConnectionOpen(connection) {
734
+ log2("Connection opened", {
735
+ peerId: connection.peerId
736
+ }, {
737
+ F: __dxlog_file3,
738
+ L: 178,
739
+ S: this,
740
+ C: (f, a) => f(...a)
741
+ });
742
+ invariant3(!this._connections.has(connection.peerId), void 0, {
743
+ F: __dxlog_file3,
744
+ L: 179,
575
745
  S: this,
576
746
  A: [
577
- "space",
578
- "'Space not found'"
747
+ "!this._connections.has(connection.peerId as PeerId)",
748
+ ""
579
749
  ]
580
750
  });
581
- return space;
751
+ const reader = connection.readable.getReader();
752
+ const writer = connection.writable.getWriter();
753
+ const connectionEntry = {
754
+ connection,
755
+ reader,
756
+ writer,
757
+ isOpen: true
758
+ };
759
+ this._connections.set(connection.peerId, connectionEntry);
760
+ queueMicrotask(async () => {
761
+ try {
762
+ while (true) {
763
+ const { done, value } = await reader.read();
764
+ if (done) {
765
+ break;
766
+ }
767
+ this._onMessage(value);
768
+ }
769
+ } catch (err) {
770
+ if (connectionEntry.isOpen) {
771
+ log2.catch(err, void 0, {
772
+ F: __dxlog_file3,
773
+ L: 198,
774
+ S: this,
775
+ C: (f, a) => f(...a)
776
+ });
777
+ }
778
+ }
779
+ });
780
+ log2("emit peer-candidate", {
781
+ peerId: connection.peerId
782
+ }, {
783
+ F: __dxlog_file3,
784
+ L: 203,
785
+ S: this,
786
+ C: (f, a) => f(...a)
787
+ });
788
+ this._emitPeerCandidate(connection);
582
789
  }
583
- _getLargeSpaceMetadata(key) {
584
- let entry = this._spaceLargeMetadata.get(key);
585
- if (entry) {
586
- return entry;
790
+ _onMessage(message) {
791
+ if (isCollectionQueryMessage(message)) {
792
+ this._params.onCollectionStateQueried(message.collectionId, message.senderId);
793
+ } else if (isCollectionStateMessage(message)) {
794
+ this._params.onCollectionStateReceived(message.collectionId, message.senderId, message.state);
795
+ } else {
796
+ this.emit("message", message);
587
797
  }
588
- entry = emptyLargeSpaceMetadata();
589
- this._spaceLargeMetadata.set(key, entry);
590
- return entry;
591
798
  }
592
799
  /**
593
- * Clears storage - doesn't work for now.
800
+ * Trigger doc-synchronizer shared documents set recalculation. Happens on peer-candidate.
801
+ * TODO(y): replace with a proper API call when sharePolicy update becomes supported by automerge-repo
594
802
  */
595
- async clear() {
596
- log3("clearing all metadata", void 0, {
597
- F: __dxlog_file4,
598
- L: 251,
803
+ _onConnectionAuthScopeChanged(connection) {
804
+ log2("Connection auth scope changed", {
805
+ peerId: connection.peerId
806
+ }, {
807
+ F: __dxlog_file3,
808
+ L: 222,
599
809
  S: this,
600
810
  C: (f, a) => f(...a)
601
811
  });
602
- await this._directory.delete();
603
- this._metadata = emptyEchoMetadata();
604
- }
605
- getIdentityRecord() {
606
- return this._metadata.identity;
607
- }
608
- async setIdentityRecord(record) {
609
- invariant4(!this._metadata.identity, "Cannot overwrite existing identity in metadata", {
610
- F: __dxlog_file4,
611
- L: 261,
812
+ const entry = this._connections.get(connection.peerId);
813
+ invariant3(entry, void 0, {
814
+ F: __dxlog_file3,
815
+ L: 224,
612
816
  S: this,
613
817
  A: [
614
- "!this._metadata.identity",
615
- "'Cannot overwrite existing identity in metadata'"
818
+ "entry",
819
+ ""
616
820
  ]
617
821
  });
618
- this._metadata.identity = record;
619
- await this._save();
620
- await this.flush();
621
- }
622
- getInvitations() {
623
- return this._metadata.invitations ?? [];
624
- }
625
- async addInvitation(invitation) {
626
- if (this._metadata.invitations?.find((i) => i.invitationId === invitation.invitationId)) {
627
- return;
628
- }
629
- (this._metadata.invitations ??= []).push(invitation);
630
- await this._save();
631
- await this.flush();
632
- }
633
- async removeInvitation(invitationId) {
634
- this._metadata.invitations = (this._metadata.invitations ?? []).filter((i) => i.invitationId !== invitationId);
635
- await this._save();
636
- await this.flush();
822
+ this.emit("peer-disconnected", {
823
+ peerId: connection.peerId
824
+ });
825
+ this._emitPeerCandidate(connection);
637
826
  }
638
- async addSpace(record) {
639
- invariant4(!(this._metadata.spaces ?? []).find((space) => space.key === record.key), "Cannot overwrite existing space in metadata", {
640
- F: __dxlog_file4,
641
- L: 289,
827
+ _onConnectionClosed(connection) {
828
+ log2("Connection closed", {
829
+ peerId: connection.peerId
830
+ }, {
831
+ F: __dxlog_file3,
832
+ L: 230,
833
+ S: this,
834
+ C: (f, a) => f(...a)
835
+ });
836
+ const entry = this._connections.get(connection.peerId);
837
+ invariant3(entry, void 0, {
838
+ F: __dxlog_file3,
839
+ L: 232,
642
840
  S: this,
643
841
  A: [
644
- "!(this._metadata.spaces ?? []).find((space) => space.key === record.key)",
645
- "'Cannot overwrite existing space in metadata'"
842
+ "entry",
843
+ ""
646
844
  ]
647
845
  });
648
- (this._metadata.spaces ??= []).push(record);
649
- await this._save();
650
- await this.flush();
651
- }
652
- async setSpaceDataLatestTimeframe(spaceKey, timeframe) {
653
- this._getSpace(spaceKey).dataTimeframe = timeframe;
654
- await this._save();
655
- }
656
- async setSpaceControlLatestTimeframe(spaceKey, timeframe) {
657
- this._getSpace(spaceKey).controlTimeframe = timeframe;
658
- await this._save();
659
- await this.flush();
660
- }
661
- async setCache(spaceKey, cache) {
662
- this._getSpace(spaceKey).cache = cache;
663
- await this._save();
664
- }
665
- async setWritableFeedKeys(spaceKey, controlFeedKey, dataFeedKey) {
666
- const space = this._getSpace(spaceKey);
667
- space.controlFeedKey = controlFeedKey;
668
- space.dataFeedKey = dataFeedKey;
669
- await this._save();
670
- await this.flush();
671
- }
672
- async setSpaceState(spaceKey, state) {
673
- this._getSpace(spaceKey).state = state;
674
- await this._save();
675
- await this.flush();
676
- }
677
- getSpaceControlPipelineSnapshot(spaceKey) {
678
- return this._getLargeSpaceMetadata(spaceKey).controlPipelineSnapshot;
846
+ entry.isOpen = false;
847
+ this.emit("peer-disconnected", {
848
+ peerId: connection.peerId
849
+ });
850
+ void entry.reader.cancel().catch((err) => log2.catch(err, void 0, {
851
+ F: __dxlog_file3,
852
+ L: 237,
853
+ S: this,
854
+ C: (f, a) => f(...a)
855
+ }));
856
+ void entry.writer.abort().catch((err) => log2.catch(err, void 0, {
857
+ F: __dxlog_file3,
858
+ L: 238,
859
+ S: this,
860
+ C: (f, a) => f(...a)
861
+ }));
862
+ this._connections.delete(connection.peerId);
679
863
  }
680
- async setSpaceControlPipelineSnapshot(spaceKey, snapshot) {
681
- this._getLargeSpaceMetadata(spaceKey).controlPipelineSnapshot = snapshot;
682
- await this._saveSpaceLargeMetadata(spaceKey);
683
- await this.flush();
864
+ _emitPeerCandidate(connection) {
865
+ this.emit("peer-candidate", {
866
+ peerId: connection.peerId,
867
+ peerMetadata: createEchoPeerMetadata()
868
+ });
684
869
  }
685
870
  };
686
871
  _ts_decorate([
687
872
  synchronized
688
- ], MetadataStore.prototype, "load", null);
873
+ ], EchoNetworkAdapter.prototype, "open", null);
689
874
  _ts_decorate([
690
875
  synchronized
691
- ], MetadataStore.prototype, "_save", null);
876
+ ], EchoNetworkAdapter.prototype, "close", null);
692
877
  _ts_decorate([
693
878
  synchronized
694
- ], MetadataStore.prototype, "_saveSpaceLargeMetadata", null);
695
- var fromBytesInt32 = (buf) => buf.readInt32LE(0);
696
- var hasInvitationExpired = (invitation) => {
697
- return Boolean(invitation.created && invitation.lifetime && invitation.lifetime !== 0 && invitation.created.getTime() + invitation.lifetime * 1e3 < Date.now());
698
- };
699
- var isLegacyInvitationFormat = (invitation) => {
700
- return invitation.type === Invitation.Type.MULTIUSE;
879
+ ], EchoNetworkAdapter.prototype, "addReplicator", null);
880
+ _ts_decorate([
881
+ synchronized
882
+ ], EchoNetworkAdapter.prototype, "removeReplicator", null);
883
+ var createEchoPeerMetadata = () => ({
884
+ // TODO(dmaretskyi): Refactor this.
885
+ dxos_peerSource: "EchoNetworkAdapter"
886
+ });
887
+ var isEchoPeerMetadata = (metadata) => metadata?.dxos_peerSource === "EchoNetworkAdapter";
888
+
889
+ // packages/core/echo/echo-pipeline/src/automerge/heads-store.ts
890
+ import { headsEncoding } from "@dxos/indexing";
891
+ var HeadsStore = class {
892
+ constructor({ db }) {
893
+ this._db = db;
894
+ }
895
+ setHeads(documentId, heads, batch) {
896
+ batch.put(documentId, heads, {
897
+ sublevel: this._db,
898
+ keyEncoding: "utf8",
899
+ valueEncoding: headsEncoding
900
+ });
901
+ }
902
+ // TODO(dmaretskyi): Make batched.
903
+ async getHeads(documentIds) {
904
+ return this._db.getMany(documentIds, {
905
+ keyEncoding: "utf8",
906
+ valueEncoding: headsEncoding
907
+ });
908
+ }
701
909
  };
702
910
 
703
- // packages/core/echo/echo-pipeline/src/pipeline/timeframe-clock.ts
704
- import { Event as Event2 } from "@dxos/async";
705
- import { timed } from "@dxos/debug";
706
- import { log as log4 } from "@dxos/log";
707
- import { Timeframe } from "@dxos/timeframe";
911
+ // packages/core/echo/echo-pipeline/src/automerge/automerge-host.ts
708
912
  function _ts_decorate2(decorators, target, key, desc) {
709
913
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
710
914
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
@@ -715,238 +919,845 @@ function _ts_decorate2(decorators, target, key, desc) {
715
919
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
716
920
  return c > 3 && r && Object.defineProperty(target, key, r), r;
717
921
  }
718
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/pipeline/timeframe-clock.ts";
719
- var mapTimeframeToFeedIndexes = (timeframe) => timeframe.frames().map(([feedKey, index]) => ({
720
- feedKey,
721
- index
722
- }));
723
- var mapFeedIndexesToTimeframe = (indexes) => new Timeframe(indexes.map(({ feedKey, index }) => [
724
- feedKey,
725
- index
726
- ]));
727
- var startAfter = (timeframe) => timeframe.frames().map(([feedKey, index]) => ({
728
- feedKey,
729
- index: index + 1
730
- }));
731
- var TimeframeClock = class {
732
- constructor(_timeframe = new Timeframe()) {
733
- this._timeframe = _timeframe;
734
- this.update = new Event2();
735
- this._pendingTimeframe = _timeframe;
922
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/automerge-host.ts";
923
+ var AutomergeHost = class extends Resource4 {
924
+ constructor({ db, indexMetadataStore }) {
925
+ super();
926
+ this._echoNetworkAdapter = new EchoNetworkAdapter({
927
+ getContainingSpaceForDocument: this._getContainingSpaceForDocument.bind(this),
928
+ onCollectionStateQueried: this._onCollectionStateQueried.bind(this),
929
+ onCollectionStateReceived: this._onCollectionStateReceived.bind(this)
930
+ });
931
+ this._collectionSynchronizer = new CollectionSynchronizer({
932
+ queryCollectionState: this._queryCollectionState.bind(this),
933
+ sendCollectionState: this._sendCollectionState.bind(this),
934
+ shouldSyncCollection: this._shouldSyncCollection.bind(this)
935
+ });
936
+ this._db = db;
937
+ this._storage = new LevelDBStorageAdapter({
938
+ db: db.sublevel("automerge"),
939
+ callbacks: {
940
+ beforeSave: async (params) => this._beforeSave(params),
941
+ afterSave: async (key) => this._afterSave(key)
942
+ }
943
+ });
944
+ this._headsStore = new HeadsStore({
945
+ db: db.sublevel("heads")
946
+ });
947
+ this._indexMetadataStore = indexMetadataStore;
736
948
  }
737
- /**
738
- * Timeframe that was processed by ECHO.
739
- */
740
- get timeframe() {
741
- return this._timeframe;
949
+ async _open() {
950
+ this._peerId = `host-${PublicKey2.random().toHex()}`;
951
+ await this._storage.open?.();
952
+ this._repo = new Repo({
953
+ peerId: this._peerId,
954
+ sharePolicy: this._sharePolicy.bind(this),
955
+ storage: this._storage,
956
+ network: [
957
+ // Upstream swarm.
958
+ this._echoNetworkAdapter
959
+ ]
960
+ });
961
+ Event2.wrap(this._echoNetworkAdapter, "peer-candidate").on(this._ctx, (e) => this._onPeerConnected(e.peerId));
962
+ Event2.wrap(this._echoNetworkAdapter, "peer-disconnected").on(this._ctx, (e) => this._onPeerDisconnected(e.peerId));
963
+ this._collectionSynchronizer.remoteStateUpdated.on(this._ctx, ({ collectionId, peerId }) => {
964
+ this._onRemoteCollectionStateUpdated(collectionId, peerId);
965
+ });
966
+ await this._echoNetworkAdapter.open();
967
+ await this._collectionSynchronizer.open();
968
+ await this._echoNetworkAdapter.open();
969
+ await this._echoNetworkAdapter.whenConnected();
970
+ }
971
+ async _close() {
972
+ await this._collectionSynchronizer.close();
973
+ await this._storage.close?.();
974
+ await this._echoNetworkAdapter.close();
975
+ await this._ctx.dispose();
742
976
  }
743
977
  /**
744
- * Timeframe that is currently being processed by ECHO.
745
- * Will be equal to `timeframe` after the processing is complete.
978
+ * @deprecated To be abstracted away.
746
979
  */
747
- get pendingTimeframe() {
748
- return this._pendingTimeframe;
980
+ get repo() {
981
+ return this._repo;
749
982
  }
750
- setTimeframe(timeframe) {
751
- this._timeframe = timeframe;
752
- this._pendingTimeframe = timeframe;
753
- this.update.emit(this._timeframe);
983
+ get peerId() {
984
+ return this._peerId;
754
985
  }
755
- updatePendingTimeframe(key, seq) {
756
- this._pendingTimeframe = Timeframe.merge(this._pendingTimeframe, new Timeframe([
757
- [
758
- key,
759
- seq
760
- ]
761
- ]));
986
+ get loadedDocsCount() {
987
+ return Object.keys(this._repo.handles).length;
762
988
  }
763
- updateTimeframe() {
764
- this._timeframe = this._pendingTimeframe;
765
- this.update.emit(this._timeframe);
989
+ async addReplicator(replicator) {
990
+ await this._echoNetworkAdapter.addReplicator(replicator);
766
991
  }
767
- hasGaps(timeframe) {
768
- const gaps = Timeframe.dependencies(timeframe, this._timeframe);
769
- return !gaps.isEmpty();
992
+ async removeReplicator(replicator) {
993
+ await this._echoNetworkAdapter.removeReplicator(replicator);
770
994
  }
771
- async waitUntilReached(target) {
772
- log4("waitUntilReached", {
773
- target,
774
- current: this._timeframe
775
- }, {
776
- F: __dxlog_file5,
777
- L: 70,
778
- S: this,
779
- C: (f, a) => f(...a)
995
+ /**
996
+ * Loads the document handle from the repo and waits for it to be ready.
997
+ */
998
+ async loadDoc(ctx, documentId, opts) {
999
+ let handle;
1000
+ if (typeof documentId === "string") {
1001
+ handle = this._repo.handles[documentId];
1002
+ }
1003
+ if (!handle) {
1004
+ handle = this._repo.find(documentId);
1005
+ }
1006
+ if (!handle.isReady()) {
1007
+ if (!opts?.timeout) {
1008
+ await cancelWithContext2(ctx, handle.whenReady());
1009
+ } else {
1010
+ await cancelWithContext2(ctx, asyncTimeout(handle.whenReady(), opts.timeout));
1011
+ }
1012
+ }
1013
+ return handle;
1014
+ }
1015
+ /**
1016
+ * Create new persisted document.
1017
+ */
1018
+ createDoc(initialValue, opts) {
1019
+ if (opts?.preserveHistory) {
1020
+ if (!isAutomerge(initialValue)) {
1021
+ throw new TypeError("Initial value must be an Automerge document");
1022
+ }
1023
+ return this._repo.import(save(initialValue));
1024
+ } else {
1025
+ return this._repo.create(initialValue);
1026
+ }
1027
+ }
1028
+ async waitUntilHeadsReplicated(heads) {
1029
+ const entries = heads.entries;
1030
+ if (!entries?.length) {
1031
+ return;
1032
+ }
1033
+ const documentIds = entries.map((entry) => entry.documentId);
1034
+ const documentHeads = await this.getHeads(documentIds);
1035
+ const headsToWait = entries.filter((entry, index) => {
1036
+ const targetHeads = entry.heads;
1037
+ if (!targetHeads || targetHeads.length === 0) {
1038
+ return false;
1039
+ }
1040
+ const currentHeads = documentHeads[index];
1041
+ return !(currentHeads !== null && headsEquals(currentHeads, targetHeads));
780
1042
  });
781
- await this.update.waitForCondition(() => {
782
- log4("check if reached", {
783
- target,
784
- current: this._timeframe,
785
- deps: Timeframe.dependencies(target, this._timeframe)
1043
+ if (headsToWait.length > 0) {
1044
+ await Promise.all(headsToWait.map(async (entry, index) => {
1045
+ const handle = await this.loadDoc(Context.default(void 0, {
1046
+ F: __dxlog_file4,
1047
+ L: 223
1048
+ }), entry.documentId);
1049
+ await waitForHeads(handle, entry.heads);
1050
+ }));
1051
+ }
1052
+ await this._repo.flush(documentIds.filter((documentId) => !!this._repo.handles[documentId]));
1053
+ }
1054
+ async reIndexHeads(documentIds) {
1055
+ for (const documentId of documentIds) {
1056
+ log3.info("re-indexing heads for document", {
1057
+ documentId
786
1058
  }, {
787
- F: __dxlog_file5,
788
- L: 72,
1059
+ F: __dxlog_file4,
1060
+ L: 235,
789
1061
  S: this,
790
1062
  C: (f, a) => f(...a)
791
1063
  });
792
- return Timeframe.dependencies(target, this._timeframe).isEmpty();
793
- });
794
- }
795
- };
796
- _ts_decorate2([
797
- timed(5e3)
798
- ], TimeframeClock.prototype, "waitUntilReached", null);
799
-
800
- // packages/core/echo/echo-pipeline/src/pipeline/pipeline.ts
801
- import { Event as Event3, sleepWithContext, synchronized as synchronized2, Trigger } from "@dxos/async";
802
- import { Context as Context2, rejectOnDispose } from "@dxos/context";
803
- import { failUndefined } from "@dxos/debug";
804
- import { FeedSetIterator } from "@dxos/feed-store";
805
- import { invariant as invariant6 } from "@dxos/invariant";
806
- import { PublicKey as PublicKey3 } from "@dxos/keys";
807
- import { log as log6 } from "@dxos/log";
808
- import { Timeframe as Timeframe2 } from "@dxos/timeframe";
809
- import { ComplexMap as ComplexMap2 } from "@dxos/util";
810
-
811
- // packages/core/echo/echo-pipeline/src/pipeline/message-selector.ts
812
- import { invariant as invariant5 } from "@dxos/invariant";
813
- import { log as log5 } from "@dxos/log";
814
- var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/pipeline/message-selector.ts";
815
- var createMessageSelector = (timeframeClock) => {
816
- return (messages) => {
817
- for (let i = 0; i < messages.length; i++) {
818
- const { data: { timeframe } } = messages[i];
819
- invariant5(timeframe, void 0, {
820
- F: __dxlog_file6,
821
- L: 25,
822
- S: void 0,
1064
+ const handle = this._repo.find(documentId);
1065
+ await handle.whenReady([
1066
+ "ready",
1067
+ "requesting"
1068
+ ]);
1069
+ if (handle.inState([
1070
+ "requesting"
1071
+ ])) {
1072
+ log3.warn("document is not available locally, skipping", {
1073
+ documentId
1074
+ }, {
1075
+ F: __dxlog_file4,
1076
+ L: 239,
1077
+ S: this,
1078
+ C: (f, a) => f(...a)
1079
+ });
1080
+ continue;
1081
+ }
1082
+ const doc = handle.docSync();
1083
+ invariant4(doc, void 0, {
1084
+ F: __dxlog_file4,
1085
+ L: 244,
1086
+ S: this,
823
1087
  A: [
824
- "timeframe",
1088
+ "doc",
825
1089
  ""
826
1090
  ]
827
1091
  });
828
- if (!timeframeClock.hasGaps(timeframe)) {
829
- return i;
830
- }
1092
+ const heads = getHeads(doc);
1093
+ const batch = this._db.batch();
1094
+ this._headsStore.setHeads(documentId, heads, batch);
1095
+ await batch.write();
831
1096
  }
832
- log5("Skipping...", void 0, {
833
- F: __dxlog_file6,
834
- L: 33,
835
- S: void 0,
1097
+ log3.info("done re-indexing heads", void 0, {
1098
+ F: __dxlog_file4,
1099
+ L: 251,
1100
+ S: this,
836
1101
  C: (f, a) => f(...a)
837
1102
  });
838
- };
839
- };
840
-
841
- // packages/core/echo/echo-pipeline/src/pipeline/pipeline.ts
842
- function _ts_decorate3(decorators, target, key, desc) {
843
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
844
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
845
- r = Reflect.decorate(decorators, target, key, desc);
846
- else
847
- for (var i = decorators.length - 1; i >= 0; i--)
848
- if (d = decorators[i])
849
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
850
- return c > 3 && r && Object.defineProperty(target, key, r), r;
851
- }
852
- var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/pipeline/pipeline.ts";
853
- var PipelineState = class {
854
- constructor(_feeds, _timeframeClock) {
855
- this._feeds = _feeds;
856
- this._timeframeClock = _timeframeClock;
857
- this._ctx = new Context2(void 0, {
858
- F: __dxlog_file7,
859
- L: 41
860
- });
861
- this.timeframeUpdate = this._timeframeClock.update;
862
- this.stalled = new Event3();
863
- this._startTimeframe = new Timeframe2();
864
- this._reachedTarget = false;
1103
+ }
1104
+ // TODO(dmaretskyi): Share based on HALO permissions and space affinity.
1105
+ // Hosts, running in the worker, don't share documents unless requested by other peers.
1106
+ // NOTE: If both peers return sharePolicy=false the replication will not happen
1107
+ // https://github.com/automerge/automerge-repo/pull/292
1108
+ async _sharePolicy(peerId, documentId) {
1109
+ if (peerId.startsWith("client-")) {
1110
+ return false;
1111
+ }
1112
+ if (!documentId) {
1113
+ return false;
1114
+ }
1115
+ const peerMetadata = this.repo.peerMetadataByPeerId[peerId];
1116
+ if (isEchoPeerMetadata(peerMetadata)) {
1117
+ return this._echoNetworkAdapter.shouldAdvertise(peerId, {
1118
+ documentId
1119
+ });
1120
+ }
1121
+ return false;
1122
+ }
1123
+ async _beforeSave({ path, batch }) {
1124
+ const handle = this._repo.handles[path[0]];
1125
+ if (!handle) {
1126
+ return;
1127
+ }
1128
+ const doc = handle.docSync();
1129
+ if (!doc) {
1130
+ return;
1131
+ }
1132
+ const heads = getHeads(doc);
1133
+ this._headsStore.setHeads(handle.documentId, heads, batch);
1134
+ const spaceKey = getSpaceKeyFromDoc(doc) ?? void 0;
1135
+ const objectIds = Object.keys(doc.objects ?? {});
1136
+ const encodedIds = objectIds.map((objectId) => objectPointerCodec.encode({
1137
+ documentId: handle.documentId,
1138
+ objectId,
1139
+ spaceKey
1140
+ }));
1141
+ const idToLastHash = new Map(encodedIds.map((id) => [
1142
+ id,
1143
+ heads
1144
+ ]));
1145
+ this._indexMetadataStore.markDirty(idToLastHash, batch);
1146
+ }
1147
+ _shouldSyncCollection(collectionId, peerId) {
1148
+ const peerMetadata = this._repo.peerMetadataByPeerId[peerId];
1149
+ if (isEchoPeerMetadata(peerMetadata)) {
1150
+ return this._echoNetworkAdapter.shouldSyncCollection(peerId, {
1151
+ collectionId
1152
+ });
1153
+ }
1154
+ return false;
865
1155
  }
866
1156
  /**
867
- * Latest theoretical timeframe based on the last mutation in each feed.
868
- * NOTE: This might never be reached if the mutation dependencies
1157
+ * Called by AutomergeStorageAdapter after levelDB batch commit.
869
1158
  */
870
- // TODO(dmaretskyi): Rename `totalTimeframe`? or `lastTimeframe`.
871
- get endTimeframe() {
872
- return mapFeedIndexesToTimeframe(Array.from(this._feeds.values()).filter((feed) => feed.length > 0).map((feed) => ({
873
- feedKey: feed.key,
874
- index: feed.length - 1
875
- })));
1159
+ async _afterSave(path) {
1160
+ this._indexMetadataStore.notifyMarkedDirty();
1161
+ const documentId = path[0];
1162
+ const document = this._repo.handles[documentId]?.docSync();
1163
+ if (document) {
1164
+ const heads = getHeads(document);
1165
+ this._onHeadsChanged(documentId, heads);
1166
+ }
876
1167
  }
877
- get startTimeframe() {
878
- return this._startTimeframe;
1168
+ _automergeDocs() {
1169
+ return mapValues(this._repo.handles, (handle) => ({
1170
+ state: handle.state,
1171
+ hasDoc: !!handle.docSync(),
1172
+ heads: handle.docSync() ? automerge.getHeads(handle.docSync()) : null,
1173
+ data: handle.docSync() && mapValues(handle.docSync(), (value, key) => {
1174
+ try {
1175
+ switch (key) {
1176
+ case "access":
1177
+ case "links":
1178
+ return value;
1179
+ case "objects":
1180
+ return Object.keys(value);
1181
+ default:
1182
+ return `${value}`;
1183
+ }
1184
+ } catch (err) {
1185
+ return `${err}`;
1186
+ }
1187
+ })
1188
+ }));
879
1189
  }
880
- get timeframe() {
881
- return this._timeframeClock.timeframe;
1190
+ _automergePeers() {
1191
+ return this._repo.peers;
882
1192
  }
883
- get pendingTimeframe() {
884
- return this._timeframeClock.pendingTimeframe;
1193
+ async _getContainingSpaceForDocument(documentId) {
1194
+ const doc = this._repo.handles[documentId]?.docSync();
1195
+ if (!doc) {
1196
+ return null;
1197
+ }
1198
+ const spaceKeyHex = getSpaceKeyFromDoc(doc);
1199
+ if (!spaceKeyHex) {
1200
+ return null;
1201
+ }
1202
+ return PublicKey2.from(spaceKeyHex);
885
1203
  }
886
- get targetTimeframe() {
887
- return this._targetTimeframe ? this._targetTimeframe : new Timeframe2();
1204
+ /**
1205
+ * Flush documents to disk.
1206
+ */
1207
+ async flush({ documentIds } = {}) {
1208
+ await this._repo.flush(documentIds);
888
1209
  }
889
- get reachedTarget() {
890
- return this._reachedTarget;
1210
+ async getHeads(documentIds) {
1211
+ const result = [];
1212
+ const storeRequestIds = [];
1213
+ const storeResultIndices = [];
1214
+ for (const documentId of documentIds) {
1215
+ const doc = this._repo.handles[documentId]?.docSync();
1216
+ if (doc) {
1217
+ result.push(getHeads(doc));
1218
+ } else {
1219
+ storeRequestIds.push(documentId);
1220
+ storeResultIndices.push(result.length);
1221
+ result.push(void 0);
1222
+ }
1223
+ }
1224
+ if (storeRequestIds.length > 0) {
1225
+ const storedHeads = await this._headsStore.getHeads(storeRequestIds);
1226
+ for (let i = 0; i < storedHeads.length; i++) {
1227
+ result[storeResultIndices[i]] = storedHeads[i];
1228
+ }
1229
+ }
1230
+ return result;
891
1231
  }
892
- get feeds() {
893
- return Array.from(this._feeds.values());
1232
+ //
1233
+ // Collection sync.
1234
+ //
1235
+ getLocalCollectionState(collectionId) {
1236
+ return this._collectionSynchronizer.getLocalCollectionState(collectionId);
894
1237
  }
895
- async waitUntilTimeframe(target) {
896
- await this._timeframeClock.waitUntilReached(target);
1238
+ getRemoteCollectionStates(collectionId) {
1239
+ return this._collectionSynchronizer.getRemoteCollectionStates(collectionId);
897
1240
  }
898
- setTargetTimeframe(target) {
899
- this._targetTimeframe = target;
1241
+ refreshCollection(collectionId) {
1242
+ this._collectionSynchronizer.refreshCollection(collectionId);
1243
+ }
1244
+ async getCollectionSyncState(collectionId) {
1245
+ const result = {
1246
+ peers: []
1247
+ };
1248
+ const localState = this.getLocalCollectionState(collectionId);
1249
+ const remoteState = this.getRemoteCollectionStates(collectionId);
1250
+ if (!localState) {
1251
+ return result;
1252
+ }
1253
+ for (const [peerId, state] of remoteState) {
1254
+ const diff = diffCollectionState(localState, state);
1255
+ result.peers.push({
1256
+ peerId,
1257
+ differentDocuments: diff.different.length
1258
+ });
1259
+ }
1260
+ return result;
900
1261
  }
901
1262
  /**
902
- * Wait until the pipeline processes all messages in the feed and reaches the target timeframe if that is set.
903
- *
904
- * This function will resolve immediately if the pipeline is stalled.
905
- *
906
- * @param timeout Timeout in milliseconds to specify the maximum wait time.
1263
+ * Update the local collection state based on the locally stored document heads.
907
1264
  */
908
- async waitUntilReachedTargetTimeframe({ ctx = new Context2(void 0, {
909
- F: __dxlog_file7,
910
- L: 129
911
- }), timeout, breakOnStall = true } = {}) {
912
- log6("waitUntilReachedTargetTimeframe", {
913
- timeout,
914
- current: this.timeframe,
915
- target: this.targetTimeframe
1265
+ async updateLocalCollectionState(collectionId, documentIds) {
1266
+ const heads = await this.getHeads(documentIds);
1267
+ const documents = Object.fromEntries(heads.map((heads2, index) => [
1268
+ documentIds[index],
1269
+ heads2 ?? []
1270
+ ]));
1271
+ this._collectionSynchronizer.setLocalCollectionState(collectionId, {
1272
+ documents
1273
+ });
1274
+ }
1275
+ _onCollectionStateQueried(collectionId, peerId) {
1276
+ this._collectionSynchronizer.onCollectionStateQueried(collectionId, peerId);
1277
+ }
1278
+ _onCollectionStateReceived(collectionId, peerId, state) {
1279
+ this._collectionSynchronizer.onRemoteStateReceived(collectionId, peerId, decodeCollectionState(state));
1280
+ }
1281
+ _queryCollectionState(collectionId, peerId) {
1282
+ this._echoNetworkAdapter.queryCollectionState(collectionId, peerId);
1283
+ }
1284
+ _sendCollectionState(collectionId, peerId, state) {
1285
+ this._echoNetworkAdapter.sendCollectionState(collectionId, peerId, encodeCollectionState(state));
1286
+ }
1287
+ _onPeerConnected(peerId) {
1288
+ this._collectionSynchronizer.onConnectionOpen(peerId);
1289
+ }
1290
+ _onPeerDisconnected(peerId) {
1291
+ this._collectionSynchronizer.onConnectionClosed(peerId);
1292
+ }
1293
+ _onRemoteCollectionStateUpdated(collectionId, peerId) {
1294
+ const localState = this._collectionSynchronizer.getLocalCollectionState(collectionId);
1295
+ const remoteState = this._collectionSynchronizer.getRemoteCollectionStates(collectionId).get(peerId);
1296
+ if (!localState || !remoteState) {
1297
+ return;
1298
+ }
1299
+ const { different } = diffCollectionState(localState, remoteState);
1300
+ if (different.length === 0) {
1301
+ return;
1302
+ }
1303
+ log3.info("replication documents after collection sync", {
1304
+ count: different.length
916
1305
  }, {
917
- F: __dxlog_file7,
918
- L: 133,
1306
+ F: __dxlog_file4,
1307
+ L: 486,
919
1308
  S: this,
920
1309
  C: (f, a) => f(...a)
921
1310
  });
922
- this._reachedTargetPromise ??= Promise.race([
923
- this._timeframeClock.update.waitForCondition(() => {
924
- return Timeframe2.dependencies(this.targetTimeframe, this.timeframe).isEmpty();
925
- }),
926
- ...breakOnStall ? [
927
- this.stalled.discardParameter().waitForCount(1)
928
- ] : []
929
- ]);
930
- let done = false;
931
- if (timeout) {
932
- return Promise.race([
933
- rejectOnDispose(ctx),
934
- rejectOnDispose(this._ctx),
935
- this._reachedTargetPromise.then(() => {
936
- done = true;
937
- this._reachedTarget = true;
1311
+ for (const documentId of different) {
1312
+ this._repo.find(documentId);
1313
+ }
1314
+ }
1315
+ _onHeadsChanged(documentId, heads) {
1316
+ for (const collectionId of this._collectionSynchronizer.getRegisteredCollectionIds()) {
1317
+ const state = this._collectionSynchronizer.getLocalCollectionState(collectionId);
1318
+ if (state?.documents[documentId]) {
1319
+ const newState = structuredClone(state);
1320
+ newState.documents[documentId] = heads;
1321
+ this._collectionSynchronizer.setLocalCollectionState(collectionId, newState);
1322
+ }
1323
+ }
1324
+ }
1325
+ };
1326
+ _ts_decorate2([
1327
+ trace.info()
1328
+ ], AutomergeHost.prototype, "_peerId", void 0);
1329
+ _ts_decorate2([
1330
+ trace.info({
1331
+ depth: null
1332
+ })
1333
+ ], AutomergeHost.prototype, "_automergeDocs", null);
1334
+ _ts_decorate2([
1335
+ trace.info({
1336
+ depth: null
1337
+ })
1338
+ ], AutomergeHost.prototype, "_automergePeers", null);
1339
+ _ts_decorate2([
1340
+ trace.span({
1341
+ showInBrowserTimeline: true
1342
+ })
1343
+ ], AutomergeHost.prototype, "flush", null);
1344
+ AutomergeHost = _ts_decorate2([
1345
+ trace.resource()
1346
+ ], AutomergeHost);
1347
+ var getSpaceKeyFromDoc = (doc) => {
1348
+ const rawSpaceKey = doc.access?.spaceKey ?? doc.experimental_spaceKey;
1349
+ if (rawSpaceKey == null) {
1350
+ return null;
1351
+ }
1352
+ return String(rawSpaceKey);
1353
+ };
1354
+ var waitForHeads = async (handle, heads) => {
1355
+ const unavailableHeads = new Set(heads);
1356
+ await handle.whenReady();
1357
+ await Event2.wrap(handle, "change").waitForCondition(() => {
1358
+ for (const changeHash of unavailableHeads.values()) {
1359
+ if (changeIsPresentInDoc(handle.docSync(), changeHash)) {
1360
+ unavailableHeads.delete(changeHash);
1361
+ }
1362
+ }
1363
+ return unavailableHeads.size === 0;
1364
+ });
1365
+ };
1366
+ var changeIsPresentInDoc = (doc, changeHash) => {
1367
+ return !!getBackend(doc).getChangeByHash(changeHash);
1368
+ };
1369
+ var decodeCollectionState = (state) => {
1370
+ invariant4(typeof state === "object" && state !== null, "Invalid state", {
1371
+ F: __dxlog_file4,
1372
+ L: 539,
1373
+ S: void 0,
1374
+ A: [
1375
+ "typeof state === 'object' && state !== null",
1376
+ "'Invalid state'"
1377
+ ]
1378
+ });
1379
+ return state;
1380
+ };
1381
+ var encodeCollectionState = (state) => {
1382
+ return state;
1383
+ };
1384
+
1385
+ // packages/core/echo/echo-pipeline/src/automerge/space-collection.ts
1386
+ import { invariant as invariant5 } from "@dxos/invariant";
1387
+ import { SpaceId } from "@dxos/keys";
1388
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/space-collection.ts";
1389
+ var deriveCollectionIdFromSpaceId = (spaceId) => `space:${spaceId}`;
1390
+ var getSpaceIdFromCollectionId = (collectionId) => {
1391
+ const spaceId = collectionId.replace(/^space:/, "");
1392
+ invariant5(SpaceId.isValid(spaceId), void 0, {
1393
+ F: __dxlog_file5,
1394
+ L: 12,
1395
+ S: void 0,
1396
+ A: [
1397
+ "SpaceId.isValid(spaceId)",
1398
+ ""
1399
+ ]
1400
+ });
1401
+ return spaceId;
1402
+ };
1403
+
1404
+ // packages/core/echo/echo-pipeline/src/space/auth.ts
1405
+ import { runInContext, scheduleTask as scheduleTask2 } from "@dxos/async";
1406
+ import { Context as Context2 } from "@dxos/context";
1407
+ import { randomBytes } from "@dxos/crypto";
1408
+ import { invariant as invariant6 } from "@dxos/invariant";
1409
+ import { log as log4 } from "@dxos/log";
1410
+ import { schema as schema4 } from "@dxos/protocols";
1411
+ import { RpcExtension } from "@dxos/teleport";
1412
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/auth.ts";
1413
+ var AuthExtension = class extends RpcExtension {
1414
+ constructor(_authParams) {
1415
+ super({
1416
+ requested: {
1417
+ AuthService: schema4.getService("dxos.mesh.teleport.auth.AuthService")
1418
+ },
1419
+ exposed: {
1420
+ AuthService: schema4.getService("dxos.mesh.teleport.auth.AuthService")
1421
+ },
1422
+ timeout: 60 * 1e3
1423
+ });
1424
+ this._authParams = _authParams;
1425
+ this._ctx = new Context2({
1426
+ onError: (err) => {
1427
+ log4.catch(err, void 0, {
1428
+ F: __dxlog_file6,
1429
+ L: 28,
1430
+ S: this,
1431
+ C: (f, a) => f(...a)
1432
+ });
1433
+ }
1434
+ }, {
1435
+ F: __dxlog_file6,
1436
+ L: 26
1437
+ });
1438
+ }
1439
+ async getHandlers() {
1440
+ return {
1441
+ AuthService: {
1442
+ authenticate: async ({ challenge }) => {
1443
+ try {
1444
+ const credential = await this._authParams.provider(challenge);
1445
+ if (!credential) {
1446
+ throw new Error("auth rejected");
1447
+ }
1448
+ return {
1449
+ credential
1450
+ };
1451
+ } catch (err) {
1452
+ log4.error("failed to generate auth credentials", err, {
1453
+ F: __dxlog_file6,
1454
+ L: 55,
1455
+ S: this,
1456
+ C: (f, a) => f(...a)
1457
+ });
1458
+ throw new Error("auth rejected");
1459
+ }
1460
+ }
1461
+ }
1462
+ };
1463
+ }
1464
+ async onOpen(context) {
1465
+ await super.onOpen(context);
1466
+ scheduleTask2(this._ctx, async () => {
1467
+ try {
1468
+ const challenge = randomBytes(32);
1469
+ const { credential } = await this.rpc.AuthService.authenticate({
1470
+ challenge
1471
+ });
1472
+ invariant6(credential?.length > 0, "invalid credential", {
1473
+ F: __dxlog_file6,
1474
+ L: 69,
1475
+ S: this,
1476
+ A: [
1477
+ "credential?.length > 0",
1478
+ "'invalid credential'"
1479
+ ]
1480
+ });
1481
+ const success = await this._authParams.verifier(challenge, credential);
1482
+ invariant6(success, "credential not verified", {
1483
+ F: __dxlog_file6,
1484
+ L: 71,
1485
+ S: this,
1486
+ A: [
1487
+ "success",
1488
+ "'credential not verified'"
1489
+ ]
1490
+ });
1491
+ runInContext(this._ctx, () => this._authParams.onAuthSuccess());
1492
+ } catch (err) {
1493
+ log4("auth failed", err, {
1494
+ F: __dxlog_file6,
1495
+ L: 74,
1496
+ S: this,
1497
+ C: (f, a) => f(...a)
1498
+ });
1499
+ this.close();
1500
+ this._authParams.onAuthFailure();
1501
+ }
1502
+ });
1503
+ }
1504
+ async onClose() {
1505
+ await this._ctx.dispose();
1506
+ await super.onClose();
1507
+ }
1508
+ async onAbort() {
1509
+ await this._ctx.dispose();
1510
+ await super.onAbort();
1511
+ }
1512
+ };
1513
+
1514
+ // packages/core/echo/echo-pipeline/src/pipeline/timeframe-clock.ts
1515
+ import { Event as Event3 } from "@dxos/async";
1516
+ import { timed } from "@dxos/debug";
1517
+ import { log as log5 } from "@dxos/log";
1518
+ import { Timeframe } from "@dxos/timeframe";
1519
+ function _ts_decorate3(decorators, target, key, desc) {
1520
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1521
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
1522
+ r = Reflect.decorate(decorators, target, key, desc);
1523
+ else
1524
+ for (var i = decorators.length - 1; i >= 0; i--)
1525
+ if (d = decorators[i])
1526
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1527
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1528
+ }
1529
+ var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/pipeline/timeframe-clock.ts";
1530
+ var mapTimeframeToFeedIndexes = (timeframe) => timeframe.frames().map(([feedKey, index]) => ({
1531
+ feedKey,
1532
+ index
1533
+ }));
1534
+ var mapFeedIndexesToTimeframe = (indexes) => new Timeframe(indexes.map(({ feedKey, index }) => [
1535
+ feedKey,
1536
+ index
1537
+ ]));
1538
+ var startAfter = (timeframe) => timeframe.frames().map(([feedKey, index]) => ({
1539
+ feedKey,
1540
+ index: index + 1
1541
+ }));
1542
+ var TimeframeClock = class {
1543
+ constructor(_timeframe = new Timeframe()) {
1544
+ this._timeframe = _timeframe;
1545
+ this.update = new Event3();
1546
+ this._pendingTimeframe = _timeframe;
1547
+ }
1548
+ /**
1549
+ * Timeframe that was processed by ECHO.
1550
+ */
1551
+ get timeframe() {
1552
+ return this._timeframe;
1553
+ }
1554
+ /**
1555
+ * Timeframe that is currently being processed by ECHO.
1556
+ * Will be equal to `timeframe` after the processing is complete.
1557
+ */
1558
+ get pendingTimeframe() {
1559
+ return this._pendingTimeframe;
1560
+ }
1561
+ setTimeframe(timeframe) {
1562
+ this._timeframe = timeframe;
1563
+ this._pendingTimeframe = timeframe;
1564
+ this.update.emit(this._timeframe);
1565
+ }
1566
+ updatePendingTimeframe(key, seq) {
1567
+ this._pendingTimeframe = Timeframe.merge(this._pendingTimeframe, new Timeframe([
1568
+ [
1569
+ key,
1570
+ seq
1571
+ ]
1572
+ ]));
1573
+ }
1574
+ updateTimeframe() {
1575
+ this._timeframe = this._pendingTimeframe;
1576
+ this.update.emit(this._timeframe);
1577
+ }
1578
+ hasGaps(timeframe) {
1579
+ const gaps = Timeframe.dependencies(timeframe, this._timeframe);
1580
+ return !gaps.isEmpty();
1581
+ }
1582
+ async waitUntilReached(target) {
1583
+ log5("waitUntilReached", {
1584
+ target,
1585
+ current: this._timeframe
1586
+ }, {
1587
+ F: __dxlog_file7,
1588
+ L: 70,
1589
+ S: this,
1590
+ C: (f, a) => f(...a)
1591
+ });
1592
+ await this.update.waitForCondition(() => {
1593
+ log5("check if reached", {
1594
+ target,
1595
+ current: this._timeframe,
1596
+ deps: Timeframe.dependencies(target, this._timeframe)
1597
+ }, {
1598
+ F: __dxlog_file7,
1599
+ L: 72,
1600
+ S: this,
1601
+ C: (f, a) => f(...a)
1602
+ });
1603
+ return Timeframe.dependencies(target, this._timeframe).isEmpty();
1604
+ });
1605
+ }
1606
+ };
1607
+ _ts_decorate3([
1608
+ timed(5e3)
1609
+ ], TimeframeClock.prototype, "waitUntilReached", null);
1610
+
1611
+ // packages/core/echo/echo-pipeline/src/pipeline/pipeline.ts
1612
+ import { Event as Event4, sleepWithContext, synchronized as synchronized2, Trigger as Trigger2 } from "@dxos/async";
1613
+ import { Context as Context3, rejectOnDispose } from "@dxos/context";
1614
+ import { failUndefined } from "@dxos/debug";
1615
+ import { FeedSetIterator } from "@dxos/feed-store";
1616
+ import { invariant as invariant8 } from "@dxos/invariant";
1617
+ import { PublicKey as PublicKey3 } from "@dxos/keys";
1618
+ import { log as log7 } from "@dxos/log";
1619
+ import { Timeframe as Timeframe2 } from "@dxos/timeframe";
1620
+ import { ComplexMap } from "@dxos/util";
1621
+
1622
+ // packages/core/echo/echo-pipeline/src/pipeline/message-selector.ts
1623
+ import { invariant as invariant7 } from "@dxos/invariant";
1624
+ import { log as log6 } from "@dxos/log";
1625
+ var __dxlog_file8 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/pipeline/message-selector.ts";
1626
+ var createMessageSelector = (timeframeClock) => {
1627
+ return (messages) => {
1628
+ for (let i = 0; i < messages.length; i++) {
1629
+ const { data: { timeframe } } = messages[i];
1630
+ invariant7(timeframe, void 0, {
1631
+ F: __dxlog_file8,
1632
+ L: 25,
1633
+ S: void 0,
1634
+ A: [
1635
+ "timeframe",
1636
+ ""
1637
+ ]
1638
+ });
1639
+ if (!timeframeClock.hasGaps(timeframe)) {
1640
+ return i;
1641
+ }
1642
+ }
1643
+ log6("Skipping...", void 0, {
1644
+ F: __dxlog_file8,
1645
+ L: 33,
1646
+ S: void 0,
1647
+ C: (f, a) => f(...a)
1648
+ });
1649
+ };
1650
+ };
1651
+
1652
+ // packages/core/echo/echo-pipeline/src/pipeline/pipeline.ts
1653
+ function _ts_decorate4(decorators, target, key, desc) {
1654
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1655
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
1656
+ r = Reflect.decorate(decorators, target, key, desc);
1657
+ else
1658
+ for (var i = decorators.length - 1; i >= 0; i--)
1659
+ if (d = decorators[i])
1660
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1661
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1662
+ }
1663
+ var __dxlog_file9 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/pipeline/pipeline.ts";
1664
+ var PipelineState = class {
1665
+ constructor(_feeds, _timeframeClock) {
1666
+ this._feeds = _feeds;
1667
+ this._timeframeClock = _timeframeClock;
1668
+ this._ctx = new Context3(void 0, {
1669
+ F: __dxlog_file9,
1670
+ L: 41
1671
+ });
1672
+ this.timeframeUpdate = this._timeframeClock.update;
1673
+ this.stalled = new Event4();
1674
+ this._startTimeframe = new Timeframe2();
1675
+ this._reachedTarget = false;
1676
+ }
1677
+ /**
1678
+ * Latest theoretical timeframe based on the last mutation in each feed.
1679
+ * NOTE: This might never be reached if the mutation dependencies
1680
+ */
1681
+ // TODO(dmaretskyi): Rename `totalTimeframe`? or `lastTimeframe`.
1682
+ get endTimeframe() {
1683
+ return mapFeedIndexesToTimeframe(Array.from(this._feeds.values()).filter((feed) => feed.length > 0).map((feed) => ({
1684
+ feedKey: feed.key,
1685
+ index: feed.length - 1
1686
+ })));
1687
+ }
1688
+ get startTimeframe() {
1689
+ return this._startTimeframe;
1690
+ }
1691
+ get timeframe() {
1692
+ return this._timeframeClock.timeframe;
1693
+ }
1694
+ get pendingTimeframe() {
1695
+ return this._timeframeClock.pendingTimeframe;
1696
+ }
1697
+ get targetTimeframe() {
1698
+ return this._targetTimeframe ? this._targetTimeframe : new Timeframe2();
1699
+ }
1700
+ get reachedTarget() {
1701
+ return this._reachedTarget;
1702
+ }
1703
+ get feeds() {
1704
+ return Array.from(this._feeds.values());
1705
+ }
1706
+ async waitUntilTimeframe(target) {
1707
+ await this._timeframeClock.waitUntilReached(target);
1708
+ }
1709
+ setTargetTimeframe(target) {
1710
+ this._targetTimeframe = target;
1711
+ }
1712
+ /**
1713
+ * Wait until the pipeline processes all messages in the feed and reaches the target timeframe if that is set.
1714
+ *
1715
+ * This function will resolve immediately if the pipeline is stalled.
1716
+ *
1717
+ * @param timeout Timeout in milliseconds to specify the maximum wait time.
1718
+ */
1719
+ async waitUntilReachedTargetTimeframe({ ctx = new Context3(void 0, {
1720
+ F: __dxlog_file9,
1721
+ L: 129
1722
+ }), timeout, breakOnStall = true } = {}) {
1723
+ log7("waitUntilReachedTargetTimeframe", {
1724
+ timeout,
1725
+ current: this.timeframe,
1726
+ target: this.targetTimeframe
1727
+ }, {
1728
+ F: __dxlog_file9,
1729
+ L: 133,
1730
+ S: this,
1731
+ C: (f, a) => f(...a)
1732
+ });
1733
+ this._reachedTargetPromise ??= Promise.race([
1734
+ this._timeframeClock.update.waitForCondition(() => {
1735
+ return Timeframe2.dependencies(this.targetTimeframe, this.timeframe).isEmpty();
1736
+ }),
1737
+ ...breakOnStall ? [
1738
+ this.stalled.discardParameter().waitForCount(1)
1739
+ ] : []
1740
+ ]);
1741
+ let done = false;
1742
+ if (timeout) {
1743
+ return Promise.race([
1744
+ rejectOnDispose(ctx),
1745
+ rejectOnDispose(this._ctx),
1746
+ this._reachedTargetPromise.then(() => {
1747
+ done = true;
1748
+ this._reachedTarget = true;
938
1749
  }),
939
1750
  sleepWithContext(this._ctx, timeout).then(() => {
940
1751
  if (done) {
941
1752
  return;
942
1753
  }
943
- log6.warn("waitUntilReachedTargetTimeframe timed out", {
1754
+ log7.warn("waitUntilReachedTargetTimeframe timed out", {
944
1755
  timeout,
945
1756
  current: this.timeframe,
946
1757
  target: this.targetTimeframe,
947
1758
  dependencies: Timeframe2.dependencies(this.targetTimeframe, this.timeframe)
948
1759
  }, {
949
- F: __dxlog_file7,
1760
+ F: __dxlog_file9,
950
1761
  L: 161,
951
1762
  S: this,
952
1763
  C: (f, a) => f(...a)
@@ -961,14 +1772,14 @@ var PipelineState = class {
961
1772
  var Pipeline = class {
962
1773
  constructor() {
963
1774
  this._timeframeClock = new TimeframeClock(new Timeframe2());
964
- this._feeds = new ComplexMap2(PublicKey3.hash);
1775
+ this._feeds = new ComplexMap(PublicKey3.hash);
965
1776
  // External state accessor.
966
1777
  this._state = new PipelineState(this._feeds, this._timeframeClock);
967
1778
  // Waits for the message consumer to process the message and yield control back to the pipeline.
968
- this._processingTrigger = new Trigger().wake();
969
- this._pauseTrigger = new Trigger().wake();
1779
+ this._processingTrigger = new Trigger2().wake();
1780
+ this._pauseTrigger = new Trigger2().wake();
970
1781
  // Pending downloads.
971
- this._downloads = new ComplexMap2((value) => PublicKey3.hash(value.key));
1782
+ this._downloads = new ComplexMap((value) => PublicKey3.hash(value.key));
972
1783
  this._isStopping = false;
973
1784
  this._isStarted = false;
974
1785
  this._isBeingConsumed = false;
@@ -978,8 +1789,8 @@ var Pipeline = class {
978
1789
  return this._state;
979
1790
  }
980
1791
  get writer() {
981
- invariant6(this._writer, "Writer not set.", {
982
- F: __dxlog_file7,
1792
+ invariant8(this._writer, "Writer not set.", {
1793
+ F: __dxlog_file9,
983
1794
  L: 243,
984
1795
  S: this,
985
1796
  A: [
@@ -1007,8 +1818,8 @@ var Pipeline = class {
1007
1818
  }
1008
1819
  }
1009
1820
  setWriteFeed(feed) {
1010
- invariant6(!this._writer, "Writer already set.", {
1011
- F: __dxlog_file7,
1821
+ invariant8(!this._writer, "Writer already set.", {
1822
+ F: __dxlog_file9,
1012
1823
  L: 270,
1013
1824
  S: this,
1014
1825
  A: [
@@ -1016,8 +1827,8 @@ var Pipeline = class {
1016
1827
  "'Writer already set.'"
1017
1828
  ]
1018
1829
  });
1019
- invariant6(feed.properties.writable, "Feed must be writable.", {
1020
- F: __dxlog_file7,
1830
+ invariant8(feed.properties.writable, "Feed must be writable.", {
1831
+ F: __dxlog_file9,
1021
1832
  L: 271,
1022
1833
  S: this,
1023
1834
  A: [
@@ -1031,8 +1842,8 @@ var Pipeline = class {
1031
1842
  }), feed.createFeedWriter());
1032
1843
  }
1033
1844
  async start() {
1034
- invariant6(!this._isStarted, "Pipeline is already started.", {
1035
- F: __dxlog_file7,
1845
+ invariant8(!this._isStarted, "Pipeline is already started.", {
1846
+ F: __dxlog_file9,
1036
1847
  L: 284,
1037
1848
  S: this,
1038
1849
  A: [
@@ -1040,8 +1851,8 @@ var Pipeline = class {
1040
1851
  "'Pipeline is already started.'"
1041
1852
  ]
1042
1853
  });
1043
- log6("starting...", void 0, {
1044
- F: __dxlog_file7,
1854
+ log7("starting...", void 0, {
1855
+ F: __dxlog_file9,
1045
1856
  L: 285,
1046
1857
  S: this,
1047
1858
  C: (f, a) => f(...a)
@@ -1049,8 +1860,8 @@ var Pipeline = class {
1049
1860
  await this._initIterator();
1050
1861
  await this._feedSetIterator.open();
1051
1862
  this._isStarted = true;
1052
- log6("started", void 0, {
1053
- F: __dxlog_file7,
1863
+ log7("started", void 0, {
1864
+ F: __dxlog_file9,
1054
1865
  L: 289,
1055
1866
  S: this,
1056
1867
  C: (f, a) => f(...a)
@@ -1062,8 +1873,8 @@ var Pipeline = class {
1062
1873
  }
1063
1874
  }
1064
1875
  async stop() {
1065
- log6("stopping...", void 0, {
1066
- F: __dxlog_file7,
1876
+ log7("stopping...", void 0, {
1877
+ F: __dxlog_file9,
1067
1878
  L: 300,
1068
1879
  S: this,
1069
1880
  C: (f, a) => f(...a)
@@ -1076,15 +1887,15 @@ var Pipeline = class {
1076
1887
  await this._feedSetIterator?.close();
1077
1888
  await this._processingTrigger.wait();
1078
1889
  await this._state._ctx.dispose();
1079
- this._state._ctx = new Context2(void 0, {
1080
- F: __dxlog_file7,
1890
+ this._state._ctx = new Context3(void 0, {
1891
+ F: __dxlog_file9,
1081
1892
  L: 309
1082
1893
  });
1083
1894
  this._state._reachedTargetPromise = void 0;
1084
1895
  this._state._reachedTarget = false;
1085
1896
  this._isStarted = false;
1086
- log6("stopped", void 0, {
1087
- F: __dxlog_file7,
1897
+ log7("stopped", void 0, {
1898
+ F: __dxlog_file9,
1088
1899
  L: 313,
1089
1900
  S: this,
1090
1901
  C: (f, a) => f(...a)
@@ -1095,8 +1906,8 @@ var Pipeline = class {
1095
1906
  * The pipeline will start processing messages AFTER this timeframe.
1096
1907
  */
1097
1908
  async setCursor(timeframe) {
1098
- invariant6(!this._isStarted || this._isPaused, "Invalid state.", {
1099
- F: __dxlog_file7,
1909
+ invariant8(!this._isStarted || this._isPaused, "Invalid state.", {
1910
+ F: __dxlog_file9,
1100
1911
  L: 322,
1101
1912
  S: this,
1102
1913
  A: [
@@ -1124,8 +1935,8 @@ var Pipeline = class {
1124
1935
  this._isPaused = true;
1125
1936
  }
1126
1937
  async unpause() {
1127
- invariant6(this._isPaused, "Pipeline is not paused.", {
1128
- F: __dxlog_file7,
1938
+ invariant8(this._isPaused, "Pipeline is not paused.", {
1939
+ F: __dxlog_file9,
1129
1940
  L: 351,
1130
1941
  S: this,
1131
1942
  A: [
@@ -1144,8 +1955,8 @@ var Pipeline = class {
1144
1955
  * Updates the timeframe clock after the message has bee processed.
1145
1956
  */
1146
1957
  async *consume() {
1147
- invariant6(!this._isBeingConsumed, "Pipeline is already being consumed.", {
1148
- F: __dxlog_file7,
1958
+ invariant8(!this._isBeingConsumed, "Pipeline is already being consumed.", {
1959
+ F: __dxlog_file9,
1149
1960
  L: 366,
1150
1961
  S: this,
1151
1962
  A: [
@@ -1154,8 +1965,8 @@ var Pipeline = class {
1154
1965
  ]
1155
1966
  });
1156
1967
  this._isBeingConsumed = true;
1157
- invariant6(this._feedSetIterator, "Iterator not initialized.", {
1158
- F: __dxlog_file7,
1968
+ invariant8(this._feedSetIterator, "Iterator not initialized.", {
1969
+ F: __dxlog_file9,
1159
1970
  L: 369,
1160
1971
  S: this,
1161
1972
  A: [
@@ -1168,8 +1979,8 @@ var Pipeline = class {
1168
1979
  while (!this._isStopping) {
1169
1980
  await this._pauseTrigger.wait();
1170
1981
  if (lastFeedSetIterator !== this._feedSetIterator) {
1171
- invariant6(this._feedSetIterator, "Iterator not initialized.", {
1172
- F: __dxlog_file7,
1982
+ invariant8(this._feedSetIterator, "Iterator not initialized.", {
1983
+ F: __dxlog_file9,
1173
1984
  L: 378,
1174
1985
  S: this,
1175
1986
  A: [
@@ -1199,12 +2010,12 @@ var Pipeline = class {
1199
2010
  }
1200
2011
  const timeframe = this._state._startTimeframe;
1201
2012
  const seq = timeframe.get(feed.key) ?? -1;
1202
- log6("download", {
2013
+ log7("download", {
1203
2014
  feed: feed.key.truncate(),
1204
2015
  seq,
1205
2016
  length: feed.length
1206
2017
  }, {
1207
- F: __dxlog_file7,
2018
+ F: __dxlog_file9,
1208
2019
  L: 407,
1209
2020
  S: this,
1210
2021
  C: (f, a) => f(...a)
@@ -1215,10 +2026,10 @@ var Pipeline = class {
1215
2026
  }, (err, data) => {
1216
2027
  if (err) {
1217
2028
  } else {
1218
- log6.info("downloaded", {
2029
+ log7.info("downloaded", {
1219
2030
  data
1220
2031
  }, {
1221
- F: __dxlog_file7,
2032
+ F: __dxlog_file9,
1222
2033
  L: 412,
1223
2034
  S: this,
1224
2035
  C: (f, a) => f(...a)
@@ -1233,8 +2044,8 @@ var Pipeline = class {
1233
2044
  stallTimeout: 1e3
1234
2045
  });
1235
2046
  this._feedSetIterator.stalled.on((iterator) => {
1236
- log6.warn(`Stalled after ${iterator.options.stallTimeout}ms with ${iterator.size} feeds.`, void 0, {
1237
- F: __dxlog_file7,
2047
+ log7.warn(`Stalled after ${iterator.options.stallTimeout}ms with ${iterator.size} feeds.`, void 0, {
2048
+ F: __dxlog_file9,
1238
2049
  L: 426,
1239
2050
  S: this,
1240
2051
  C: (f, a) => f(...a)
@@ -1246,142 +2057,32 @@ var Pipeline = class {
1246
2057
  }
1247
2058
  }
1248
2059
  };
1249
- _ts_decorate3([
2060
+ _ts_decorate4([
1250
2061
  synchronized2
1251
2062
  ], Pipeline.prototype, "start", null);
1252
- _ts_decorate3([
2063
+ _ts_decorate4([
1253
2064
  synchronized2
1254
2065
  ], Pipeline.prototype, "stop", null);
1255
- _ts_decorate3([
2066
+ _ts_decorate4([
1256
2067
  synchronized2
1257
2068
  ], Pipeline.prototype, "setCursor", null);
1258
- _ts_decorate3([
2069
+ _ts_decorate4([
1259
2070
  synchronized2
1260
2071
  ], Pipeline.prototype, "pause", null);
1261
- _ts_decorate3([
2072
+ _ts_decorate4([
1262
2073
  synchronized2
1263
2074
  ], Pipeline.prototype, "unpause", null);
1264
2075
 
1265
- // packages/core/echo/echo-pipeline/src/space/auth.ts
1266
- import { runInContext, scheduleTask } from "@dxos/async";
1267
- import { Context as Context3 } from "@dxos/context";
1268
- import { randomBytes } from "@dxos/crypto";
1269
- import { invariant as invariant7 } from "@dxos/invariant";
1270
- import { log as log7 } from "@dxos/log";
1271
- import { schema as schema5 } from "@dxos/protocols";
1272
- import { RpcExtension } from "@dxos/teleport";
1273
- var __dxlog_file8 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/auth.ts";
1274
- var AuthExtension = class extends RpcExtension {
1275
- constructor(_authParams) {
1276
- super({
1277
- requested: {
1278
- AuthService: schema5.getService("dxos.mesh.teleport.auth.AuthService")
1279
- },
1280
- exposed: {
1281
- AuthService: schema5.getService("dxos.mesh.teleport.auth.AuthService")
1282
- },
1283
- timeout: 60 * 1e3
1284
- });
1285
- this._authParams = _authParams;
1286
- this._ctx = new Context3({
1287
- onError: (err) => {
1288
- log7.catch(err, void 0, {
1289
- F: __dxlog_file8,
1290
- L: 28,
1291
- S: this,
1292
- C: (f, a) => f(...a)
1293
- });
1294
- }
1295
- }, {
1296
- F: __dxlog_file8,
1297
- L: 26
1298
- });
1299
- }
1300
- async getHandlers() {
1301
- return {
1302
- AuthService: {
1303
- authenticate: async ({ challenge }) => {
1304
- try {
1305
- const credential = await this._authParams.provider(challenge);
1306
- if (!credential) {
1307
- throw new Error("auth rejected");
1308
- }
1309
- return {
1310
- credential
1311
- };
1312
- } catch (err) {
1313
- log7.error("failed to generate auth credentials", err, {
1314
- F: __dxlog_file8,
1315
- L: 55,
1316
- S: this,
1317
- C: (f, a) => f(...a)
1318
- });
1319
- throw new Error("auth rejected");
1320
- }
1321
- }
1322
- }
1323
- };
1324
- }
1325
- async onOpen(context) {
1326
- await super.onOpen(context);
1327
- scheduleTask(this._ctx, async () => {
1328
- try {
1329
- const challenge = randomBytes(32);
1330
- const { credential } = await this.rpc.AuthService.authenticate({
1331
- challenge
1332
- });
1333
- invariant7(credential?.length > 0, "invalid credential", {
1334
- F: __dxlog_file8,
1335
- L: 69,
1336
- S: this,
1337
- A: [
1338
- "credential?.length > 0",
1339
- "'invalid credential'"
1340
- ]
1341
- });
1342
- const success = await this._authParams.verifier(challenge, credential);
1343
- invariant7(success, "credential not verified", {
1344
- F: __dxlog_file8,
1345
- L: 71,
1346
- S: this,
1347
- A: [
1348
- "success",
1349
- "'credential not verified'"
1350
- ]
1351
- });
1352
- runInContext(this._ctx, () => this._authParams.onAuthSuccess());
1353
- } catch (err) {
1354
- log7("auth failed", err, {
1355
- F: __dxlog_file8,
1356
- L: 74,
1357
- S: this,
1358
- C: (f, a) => f(...a)
1359
- });
1360
- this.close();
1361
- this._authParams.onAuthFailure();
1362
- }
1363
- });
1364
- }
1365
- async onClose() {
1366
- await this._ctx.dispose();
1367
- await super.onClose();
1368
- }
1369
- async onAbort() {
1370
- await this._ctx.dispose();
1371
- await super.onAbort();
1372
- }
1373
- };
1374
-
1375
2076
  // packages/core/echo/echo-pipeline/src/space/space.ts
1376
- import { Event as Event4, Mutex, synchronized as synchronized3, trackLeaks as trackLeaks2 } from "@dxos/async";
1377
- import { LifecycleState, Resource as Resource2 } from "@dxos/context";
2077
+ import { Event as Event5, Mutex, synchronized as synchronized3, trackLeaks as trackLeaks2 } from "@dxos/async";
2078
+ import { LifecycleState as LifecycleState3, Resource as Resource5 } from "@dxos/context";
1378
2079
  import { subtleCrypto as subtleCrypto2 } from "@dxos/crypto";
1379
- import { invariant as invariant8 } from "@dxos/invariant";
1380
- import { PublicKey as PublicKey5, SpaceId } from "@dxos/keys";
2080
+ import { invariant as invariant9 } from "@dxos/invariant";
2081
+ import { PublicKey as PublicKey5, SpaceId as SpaceId2 } from "@dxos/keys";
1381
2082
  import { log as log9, logInfo } from "@dxos/log";
1382
2083
  import { AdmittedFeed as AdmittedFeed2 } from "@dxos/protocols/proto/dxos/halo/credentials";
1383
- import { trace as trace2 } from "@dxos/tracing";
1384
- import { Callback as Callback2, ComplexMap as ComplexMap3 } from "@dxos/util";
2084
+ import { trace as trace3 } from "@dxos/tracing";
2085
+ import { Callback as Callback2, ComplexMap as ComplexMap2 } from "@dxos/util";
1385
2086
 
1386
2087
  // packages/core/echo/echo-pipeline/src/space/control-pipeline.ts
1387
2088
  import { DeferredTask, sleepWithContext as sleepWithContext2, trackLeaks } from "@dxos/async";
@@ -1391,9 +2092,9 @@ import { PublicKey as PublicKey4 } from "@dxos/keys";
1391
2092
  import { log as log8 } from "@dxos/log";
1392
2093
  import { AdmittedFeed } from "@dxos/protocols/proto/dxos/halo/credentials";
1393
2094
  import { Timeframe as Timeframe3 } from "@dxos/timeframe";
1394
- import { TimeSeriesCounter, TimeUsageCounter, trace } from "@dxos/tracing";
2095
+ import { TimeSeriesCounter, TimeUsageCounter, trace as trace2 } from "@dxos/tracing";
1395
2096
  import { Callback, tracer } from "@dxos/util";
1396
- function _ts_decorate4(decorators, target, key, desc) {
2097
+ function _ts_decorate5(decorators, target, key, desc) {
1397
2098
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1398
2099
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
1399
2100
  r = Reflect.decorate(decorators, target, key, desc);
@@ -1403,14 +2104,14 @@ function _ts_decorate4(decorators, target, key, desc) {
1403
2104
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1404
2105
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1405
2106
  }
1406
- var __dxlog_file9 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/control-pipeline.ts";
2107
+ var __dxlog_file10 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/control-pipeline.ts";
1407
2108
  var TIMEFRAME_SAVE_DEBOUNCE_INTERVAL = 500;
1408
2109
  var CONTROL_PIPELINE_SNAPSHOT_DELAY = 1e4;
1409
2110
  var USE_SNAPSHOTS = true;
1410
2111
  var ControlPipeline = class {
1411
2112
  constructor({ spaceKey, genesisFeed, feedProvider, metadataStore }) {
1412
2113
  this._ctx = new Context4(void 0, {
1413
- F: __dxlog_file9,
2114
+ F: __dxlog_file10,
1414
2115
  L: 47
1415
2116
  });
1416
2117
  this._lastTimeframeSaveTime = Date.now();
@@ -1430,7 +2131,7 @@ var ControlPipeline = class {
1430
2131
  log8("feed admitted", {
1431
2132
  key: info.key
1432
2133
  }, {
1433
- F: __dxlog_file9,
2134
+ F: __dxlog_file10,
1434
2135
  L: 82,
1435
2136
  S: this,
1436
2137
  C: (f, a) => f(...a)
@@ -1444,7 +2145,7 @@ var ControlPipeline = class {
1444
2145
  }
1445
2146
  } catch (err) {
1446
2147
  log8.catch(err, void 0, {
1447
- F: __dxlog_file9,
2148
+ F: __dxlog_file10,
1448
2149
  L: 93,
1449
2150
  S: this,
1450
2151
  C: (f, a) => f(...a)
@@ -1476,7 +2177,7 @@ var ControlPipeline = class {
1476
2177
  present: !!snapshot,
1477
2178
  tf: snapshot?.timeframe
1478
2179
  }, {
1479
- F: __dxlog_file9,
2180
+ F: __dxlog_file10,
1480
2181
  L: 123,
1481
2182
  S: this,
1482
2183
  C: (f, a) => f(...a)
@@ -1485,20 +2186,20 @@ var ControlPipeline = class {
1485
2186
  await this._processSnapshot(snapshot);
1486
2187
  }
1487
2188
  log8("starting...", void 0, {
1488
- F: __dxlog_file9,
2189
+ F: __dxlog_file10,
1489
2190
  L: 128,
1490
2191
  S: this,
1491
2192
  C: (f, a) => f(...a)
1492
2193
  });
1493
2194
  setTimeout(async () => {
1494
2195
  void this._consumePipeline(new Context4(void 0, {
1495
- F: __dxlog_file9,
2196
+ F: __dxlog_file10,
1496
2197
  L: 130
1497
2198
  }));
1498
2199
  });
1499
2200
  await this._pipeline.start();
1500
2201
  log8("started", void 0, {
1501
- F: __dxlog_file9,
2202
+ F: __dxlog_file10,
1502
2203
  L: 134,
1503
2204
  S: this,
1504
2205
  C: (f, a) => f(...a)
@@ -1515,7 +2216,7 @@ var ControlPipeline = class {
1515
2216
  log8.warn("credential processing failed from snapshot", {
1516
2217
  message
1517
2218
  }, {
1518
- F: __dxlog_file9,
2219
+ F: __dxlog_file10,
1519
2220
  L: 147,
1520
2221
  S: this,
1521
2222
  C: (f, a) => f(...a)
@@ -1537,7 +2238,7 @@ var ControlPipeline = class {
1537
2238
  key: this._spaceKey,
1538
2239
  snapshot
1539
2240
  }, {
1540
- F: __dxlog_file9,
2241
+ F: __dxlog_file10,
1541
2242
  L: 163,
1542
2243
  S: this,
1543
2244
  C: (f, a) => f(...a)
@@ -1552,7 +2253,7 @@ var ControlPipeline = class {
1552
2253
  await this._processMessage(ctx, msg);
1553
2254
  } catch (err) {
1554
2255
  log8.catch(err, void 0, {
1555
- F: __dxlog_file9,
2256
+ F: __dxlog_file10,
1556
2257
  L: 176,
1557
2258
  S: this,
1558
2259
  C: (f, a) => f(...a)
@@ -1566,7 +2267,7 @@ var ControlPipeline = class {
1566
2267
  key: msg.feedKey,
1567
2268
  seq: msg.seq
1568
2269
  }, {
1569
- F: __dxlog_file9,
2270
+ F: __dxlog_file10,
1570
2271
  L: 186,
1571
2272
  S: this,
1572
2273
  C: (f, a) => f(...a)
@@ -1581,7 +2282,7 @@ var ControlPipeline = class {
1581
2282
  log8.warn("processing failed", {
1582
2283
  msg
1583
2284
  }, {
1584
- F: __dxlog_file9,
2285
+ F: __dxlog_file10,
1585
2286
  L: 195,
1586
2287
  S: this,
1587
2288
  C: (f, a) => f(...a)
@@ -1600,7 +2301,7 @@ var ControlPipeline = class {
1600
2301
  }
1601
2302
  async stop() {
1602
2303
  log8("stopping...", void 0, {
1603
- F: __dxlog_file9,
2304
+ F: __dxlog_file10,
1604
2305
  L: 215,
1605
2306
  S: this,
1606
2307
  C: (f, a) => f(...a)
@@ -1609,7 +2310,7 @@ var ControlPipeline = class {
1609
2310
  await this._pipeline.stop();
1610
2311
  await this._saveTargetTimeframe(this._pipeline.state.timeframe);
1611
2312
  log8("stopped", void 0, {
1612
- F: __dxlog_file9,
2313
+ F: __dxlog_file10,
1613
2314
  L: 219,
1614
2315
  S: this,
1615
2316
  C: (f, a) => f(...a)
@@ -1622,7 +2323,7 @@ var ControlPipeline = class {
1622
2323
  this._targetTimeframe = newTimeframe;
1623
2324
  } catch (err) {
1624
2325
  log8(err, void 0, {
1625
- F: __dxlog_file9,
2326
+ F: __dxlog_file10,
1626
2327
  L: 228,
1627
2328
  S: this,
1628
2329
  C: (f, a) => f(...a)
@@ -1630,30 +2331,30 @@ var ControlPipeline = class {
1630
2331
  }
1631
2332
  }
1632
2333
  };
1633
- _ts_decorate4([
1634
- trace.metricsCounter()
2334
+ _ts_decorate5([
2335
+ trace2.metricsCounter()
1635
2336
  ], ControlPipeline.prototype, "_usage", void 0);
1636
- _ts_decorate4([
1637
- trace.metricsCounter()
2337
+ _ts_decorate5([
2338
+ trace2.metricsCounter()
1638
2339
  ], ControlPipeline.prototype, "_mutations", void 0);
1639
- _ts_decorate4([
1640
- trace.span({
2340
+ _ts_decorate5([
2341
+ trace2.span({
1641
2342
  showInBrowserTimeline: true
1642
2343
  })
1643
2344
  ], ControlPipeline.prototype, "start", null);
1644
- _ts_decorate4([
1645
- trace.span()
2345
+ _ts_decorate5([
2346
+ trace2.span()
1646
2347
  ], ControlPipeline.prototype, "_consumePipeline", null);
1647
- _ts_decorate4([
1648
- trace.span()
2348
+ _ts_decorate5([
2349
+ trace2.span()
1649
2350
  ], ControlPipeline.prototype, "_processMessage", null);
1650
- ControlPipeline = _ts_decorate4([
1651
- trace.resource(),
2351
+ ControlPipeline = _ts_decorate5([
2352
+ trace2.resource(),
1652
2353
  trackLeaks("start", "stop")
1653
2354
  ], ControlPipeline);
1654
2355
 
1655
2356
  // packages/core/echo/echo-pipeline/src/space/space.ts
1656
- function _ts_decorate5(decorators, target, key, desc) {
2357
+ function _ts_decorate6(decorators, target, key, desc) {
1657
2358
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1658
2359
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
1659
2360
  r = Reflect.decorate(decorators, target, key, desc);
@@ -1663,15 +2364,15 @@ function _ts_decorate5(decorators, target, key, desc) {
1663
2364
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1664
2365
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1665
2366
  }
1666
- var __dxlog_file10 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/space.ts";
1667
- var Space = class extends Resource2 {
2367
+ var __dxlog_file11 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/space.ts";
2368
+ var Space = class extends Resource5 {
1668
2369
  constructor(params) {
1669
2370
  super();
1670
2371
  this._addFeedMutex = new Mutex();
1671
2372
  this.onCredentialProcessed = new Callback2();
1672
- this.stateUpdate = new Event4();
1673
- invariant8(params.spaceKey && params.feedProvider, void 0, {
1674
- F: __dxlog_file10,
2373
+ this.stateUpdate = new Event5();
2374
+ invariant9(params.spaceKey && params.feedProvider, void 0, {
2375
+ F: __dxlog_file11,
1675
2376
  L: 78,
1676
2377
  S: this,
1677
2378
  A: [
@@ -1705,7 +2406,7 @@ var Space = class extends Resource2 {
1705
2406
  log9("onCredentialProcessed", {
1706
2407
  credential
1707
2408
  }, {
1708
- F: __dxlog_file10,
2409
+ F: __dxlog_file11,
1709
2410
  L: 106,
1710
2411
  S: this,
1711
2412
  C: (f, a) => f(...a)
@@ -1716,7 +2417,7 @@ var Space = class extends Resource2 {
1716
2417
  log9("onDelegatedInvitation", {
1717
2418
  invitation
1718
2419
  }, {
1719
- F: __dxlog_file10,
2420
+ F: __dxlog_file11,
1720
2421
  L: 110,
1721
2422
  S: this,
1722
2423
  C: (f, a) => f(...a)
@@ -1727,7 +2428,7 @@ var Space = class extends Resource2 {
1727
2428
  log9("onDelegatedInvitationRemoved", {
1728
2429
  invitation
1729
2430
  }, {
1730
- F: __dxlog_file10,
2431
+ F: __dxlog_file11,
1731
2432
  L: 114,
1732
2433
  S: this,
1733
2434
  C: (f, a) => f(...a)
@@ -1741,7 +2442,7 @@ var Space = class extends Resource2 {
1741
2442
  m.role
1742
2443
  ])
1743
2444
  }), {
1744
- F: __dxlog_file10,
2445
+ F: __dxlog_file11,
1745
2446
  L: 118,
1746
2447
  S: this,
1747
2448
  C: (f, a) => f(...a)
@@ -1758,7 +2459,7 @@ var Space = class extends Resource2 {
1758
2459
  return this._key;
1759
2460
  }
1760
2461
  get isOpen() {
1761
- return this._lifecycleState === LifecycleState.OPEN;
2462
+ return this._lifecycleState === LifecycleState3.OPEN;
1762
2463
  }
1763
2464
  get genesisFeedKey() {
1764
2465
  return this._genesisFeedKey;
@@ -1782,8 +2483,8 @@ var Space = class extends Resource2 {
1782
2483
  return this._snapshotManager;
1783
2484
  }
1784
2485
  async setControlFeed(feed) {
1785
- invariant8(!this._controlFeed, "Control feed already set.", {
1786
- F: __dxlog_file10,
2486
+ invariant9(!this._controlFeed, "Control feed already set.", {
2487
+ F: __dxlog_file11,
1787
2488
  L: 171,
1788
2489
  S: this,
1789
2490
  A: [
@@ -1796,8 +2497,8 @@ var Space = class extends Resource2 {
1796
2497
  return this;
1797
2498
  }
1798
2499
  async setDataFeed(feed) {
1799
- invariant8(!this._dataFeed, "Data feed already set.", {
1800
- F: __dxlog_file10,
2500
+ invariant9(!this._dataFeed, "Data feed already set.", {
2501
+ F: __dxlog_file11,
1801
2502
  L: 178,
1802
2503
  S: this,
1803
2504
  A: [
@@ -1816,7 +2517,7 @@ var Space = class extends Resource2 {
1816
2517
  }
1817
2518
  async _open(ctx) {
1818
2519
  log9("opening...", void 0, {
1819
- F: __dxlog_file10,
2520
+ F: __dxlog_file11,
1820
2521
  L: 192,
1821
2522
  S: this,
1822
2523
  C: (f, a) => f(...a)
@@ -1824,7 +2525,7 @@ var Space = class extends Resource2 {
1824
2525
  await this._controlPipeline.start();
1825
2526
  await this.protocol.start();
1826
2527
  log9("opened", void 0, {
1827
- F: __dxlog_file10,
2528
+ F: __dxlog_file11,
1828
2529
  L: 198,
1829
2530
  S: this,
1830
2531
  C: (f, a) => f(...a)
@@ -1834,7 +2535,7 @@ var Space = class extends Resource2 {
1834
2535
  log9("closing...", {
1835
2536
  key: this._key
1836
2537
  }, {
1837
- F: __dxlog_file10,
2538
+ F: __dxlog_file11,
1838
2539
  L: 203,
1839
2540
  S: this,
1840
2541
  C: (f, a) => f(...a)
@@ -1842,67 +2543,67 @@ var Space = class extends Resource2 {
1842
2543
  await this.protocol.stop();
1843
2544
  await this._controlPipeline.stop();
1844
2545
  log9("closed", void 0, {
1845
- F: __dxlog_file10,
2546
+ F: __dxlog_file11,
1846
2547
  L: 209,
1847
2548
  S: this,
1848
2549
  C: (f, a) => f(...a)
1849
2550
  });
1850
2551
  }
1851
2552
  };
1852
- _ts_decorate5([
1853
- trace2.info()
2553
+ _ts_decorate6([
2554
+ trace3.info()
1854
2555
  ], Space.prototype, "protocol", void 0);
1855
- _ts_decorate5([
1856
- trace2.info()
2556
+ _ts_decorate6([
2557
+ trace3.info()
1857
2558
  ], Space.prototype, "_controlPipeline", void 0);
1858
- _ts_decorate5([
2559
+ _ts_decorate6([
1859
2560
  logInfo,
1860
- trace2.info()
2561
+ trace3.info()
1861
2562
  ], Space.prototype, "id", null);
1862
- _ts_decorate5([
2563
+ _ts_decorate6([
1863
2564
  logInfo,
1864
- trace2.info()
2565
+ trace3.info()
1865
2566
  ], Space.prototype, "key", null);
1866
- _ts_decorate5([
1867
- trace2.span()
2567
+ _ts_decorate6([
2568
+ trace3.span()
1868
2569
  ], Space.prototype, "_open", null);
1869
- _ts_decorate5([
2570
+ _ts_decorate6([
1870
2571
  synchronized3
1871
2572
  ], Space.prototype, "_close", null);
1872
- Space = _ts_decorate5([
2573
+ Space = _ts_decorate6([
1873
2574
  trackLeaks2("open", "close"),
1874
- trace2.resource()
2575
+ trace3.resource()
1875
2576
  ], Space);
1876
- var SPACE_IDS_CACHE = new ComplexMap3(PublicKey5.hash);
2577
+ var SPACE_IDS_CACHE = new ComplexMap2(PublicKey5.hash);
1877
2578
  var createIdFromSpaceKey = async (spaceKey) => {
1878
2579
  const cachedValue = SPACE_IDS_CACHE.get(spaceKey);
1879
2580
  if (cachedValue !== void 0) {
1880
2581
  return cachedValue;
1881
2582
  }
1882
2583
  const digest = await subtleCrypto2.digest("SHA-256", spaceKey.asUint8Array());
1883
- const bytes = new Uint8Array(digest).slice(0, SpaceId.byteLength);
1884
- const spaceId = SpaceId.encode(bytes);
2584
+ const bytes = new Uint8Array(digest).slice(0, SpaceId2.byteLength);
2585
+ const spaceId = SpaceId2.encode(bytes);
1885
2586
  SPACE_IDS_CACHE.set(spaceKey, spaceId);
1886
2587
  return spaceId;
1887
2588
  };
1888
2589
 
1889
2590
  // packages/core/echo/echo-pipeline/src/space/admission-discovery-extension.ts
1890
- import { scheduleTask as scheduleTask2 } from "@dxos/async";
2591
+ import { scheduleTask as scheduleTask3 } from "@dxos/async";
1891
2592
  import { Context as Context5 } from "@dxos/context";
1892
- import { ProtocolError, schema as schema6 } from "@dxos/protocols";
2593
+ import { ProtocolError, schema as schema5 } from "@dxos/protocols";
1893
2594
  import { RpcExtension as RpcExtension2 } from "@dxos/teleport";
1894
- var __dxlog_file11 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/admission-discovery-extension.ts";
2595
+ var __dxlog_file12 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/admission-discovery-extension.ts";
1895
2596
  var CredentialRetrieverExtension = class extends RpcExtension2 {
1896
2597
  constructor(_request, _onResult) {
1897
2598
  super({
1898
2599
  requested: {
1899
- AdmissionDiscoveryService: schema6.getService("dxos.mesh.teleport.AdmissionDiscoveryService")
2600
+ AdmissionDiscoveryService: schema5.getService("dxos.mesh.teleport.AdmissionDiscoveryService")
1900
2601
  }
1901
2602
  });
1902
2603
  this._request = _request;
1903
2604
  this._onResult = _onResult;
1904
2605
  this._ctx = new Context5(void 0, {
1905
- F: __dxlog_file11,
2606
+ F: __dxlog_file12,
1906
2607
  L: 25
1907
2608
  });
1908
2609
  }
@@ -1911,7 +2612,7 @@ var CredentialRetrieverExtension = class extends RpcExtension2 {
1911
2612
  }
1912
2613
  async onOpen(context) {
1913
2614
  await super.onOpen(context);
1914
- scheduleTask2(this._ctx, async () => {
2615
+ scheduleTask3(this._ctx, async () => {
1915
2616
  try {
1916
2617
  const result = await this.rpc.AdmissionDiscoveryService.getAdmissionCredential(this._request);
1917
2618
  this._onResult.wake(result.admissionCredential);
@@ -1919,267 +2620,719 @@ var CredentialRetrieverExtension = class extends RpcExtension2 {
1919
2620
  context.close(err);
1920
2621
  }
1921
2622
  });
1922
- }
1923
- async onClose() {
1924
- await this._ctx.dispose();
1925
- }
1926
- async onAbort() {
1927
- await this._ctx.dispose();
1928
- }
1929
- };
1930
- var CredentialServerExtension = class extends RpcExtension2 {
1931
- constructor(_space) {
1932
- super({
1933
- exposed: {
1934
- AdmissionDiscoveryService: schema6.getService("dxos.mesh.teleport.AdmissionDiscoveryService")
2623
+ }
2624
+ async onClose() {
2625
+ await this._ctx.dispose();
2626
+ }
2627
+ async onAbort() {
2628
+ await this._ctx.dispose();
2629
+ }
2630
+ };
2631
+ var CredentialServerExtension = class extends RpcExtension2 {
2632
+ constructor(_space) {
2633
+ super({
2634
+ exposed: {
2635
+ AdmissionDiscoveryService: schema5.getService("dxos.mesh.teleport.AdmissionDiscoveryService")
2636
+ }
2637
+ });
2638
+ this._space = _space;
2639
+ }
2640
+ async getHandlers() {
2641
+ return {
2642
+ AdmissionDiscoveryService: {
2643
+ getAdmissionCredential: async (request) => {
2644
+ const memberInfo = this._space.spaceState.members.get(request.memberKey);
2645
+ if (!memberInfo?.credential) {
2646
+ throw new ProtocolError("Space member not found.", request);
2647
+ }
2648
+ return {
2649
+ admissionCredential: memberInfo.credential
2650
+ };
2651
+ }
2652
+ }
2653
+ };
2654
+ }
2655
+ };
2656
+
2657
+ // packages/core/echo/echo-pipeline/src/space/space-protocol.ts
2658
+ import { discoveryKey, subtleCrypto as subtleCrypto3 } from "@dxos/crypto";
2659
+ import { PublicKey as PublicKey6 } from "@dxos/keys";
2660
+ import { log as log10, logInfo as logInfo2 } from "@dxos/log";
2661
+ import { MMSTTopology } from "@dxos/network-manager";
2662
+ import { Teleport } from "@dxos/teleport";
2663
+ import { BlobSync } from "@dxos/teleport-extension-object-sync";
2664
+ import { ReplicatorExtension } from "@dxos/teleport-extension-replicator";
2665
+ import { trace as trace4 } from "@dxos/tracing";
2666
+ import { ComplexMap as ComplexMap3 } from "@dxos/util";
2667
+ function _ts_decorate7(decorators, target, key, desc) {
2668
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2669
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
2670
+ r = Reflect.decorate(decorators, target, key, desc);
2671
+ else
2672
+ for (var i = decorators.length - 1; i >= 0; i--)
2673
+ if (d = decorators[i])
2674
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2675
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2676
+ }
2677
+ var __dxlog_file13 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/space-protocol.ts";
2678
+ var MOCK_AUTH_PROVIDER = async (nonce) => Buffer.from("mock");
2679
+ var MOCK_AUTH_VERIFIER = async (nonce, credential) => true;
2680
+ var SpaceProtocol = class {
2681
+ constructor({ topic, swarmIdentity, networkManager, onSessionAuth, onAuthFailure, blobStore }) {
2682
+ this._feeds = /* @__PURE__ */ new Set();
2683
+ this._sessions = new ComplexMap3(PublicKey6.hash);
2684
+ // TODO(burdon): Move to config (with sensible defaults).
2685
+ this._topology = new MMSTTopology({
2686
+ originateConnections: 4,
2687
+ maxPeers: 10,
2688
+ sampleSize: 20
2689
+ });
2690
+ this._spaceKey = topic;
2691
+ this._networkManager = networkManager;
2692
+ this._swarmIdentity = swarmIdentity;
2693
+ this._onSessionAuth = onSessionAuth;
2694
+ this._onAuthFailure = onAuthFailure;
2695
+ this.blobSync = new BlobSync({
2696
+ blobStore
2697
+ });
2698
+ this._topic = subtleCrypto3.digest("SHA-256", topic.asBuffer()).then(discoveryKey).then(PublicKey6.from);
2699
+ }
2700
+ get sessions() {
2701
+ return this._sessions;
2702
+ }
2703
+ get feeds() {
2704
+ return this._feeds;
2705
+ }
2706
+ get _ownPeerKey() {
2707
+ return this._swarmIdentity.peerKey;
2708
+ }
2709
+ // TODO(burdon): Create abstraction for Space (e.g., add keys and have provider).
2710
+ addFeed(feed) {
2711
+ log10("addFeed", {
2712
+ key: feed.key
2713
+ }, {
2714
+ F: __dxlog_file13,
2715
+ L: 109,
2716
+ S: this,
2717
+ C: (f, a) => f(...a)
2718
+ });
2719
+ this._feeds.add(feed);
2720
+ for (const session of this._sessions.values()) {
2721
+ session.replicator.addFeed(feed);
2722
+ }
2723
+ }
2724
+ // TODO(burdon): Rename open? Common open/close interfaces for all services?
2725
+ async start() {
2726
+ if (this._connection) {
2727
+ return;
2728
+ }
2729
+ const credentials = await this._swarmIdentity.credentialProvider(Buffer.from(""));
2730
+ await this.blobSync.open();
2731
+ log10("starting...", void 0, {
2732
+ F: __dxlog_file13,
2733
+ L: 128,
2734
+ S: this,
2735
+ C: (f, a) => f(...a)
2736
+ });
2737
+ const topic = await this._topic;
2738
+ this._connection = await this._networkManager.joinSwarm({
2739
+ protocolProvider: this._createProtocolProvider(credentials),
2740
+ peerId: this._swarmIdentity.peerKey,
2741
+ topic,
2742
+ topology: this._topology,
2743
+ label: `swarm ${topic.truncate()} for space ${this._spaceKey.truncate()}`
2744
+ });
2745
+ log10("started", void 0, {
2746
+ F: __dxlog_file13,
2747
+ L: 138,
2748
+ S: this,
2749
+ C: (f, a) => f(...a)
2750
+ });
2751
+ }
2752
+ updateTopology() {
2753
+ this._topology.forceUpdate();
2754
+ }
2755
+ async stop() {
2756
+ await this.blobSync.close();
2757
+ if (this._connection) {
2758
+ log10("stopping...", void 0, {
2759
+ F: __dxlog_file13,
2760
+ L: 149,
2761
+ S: this,
2762
+ C: (f, a) => f(...a)
2763
+ });
2764
+ await this._connection.close();
2765
+ log10("stopped", void 0, {
2766
+ F: __dxlog_file13,
2767
+ L: 151,
2768
+ S: this,
2769
+ C: (f, a) => f(...a)
2770
+ });
2771
+ }
2772
+ }
2773
+ _createProtocolProvider(credentials) {
2774
+ return (wireParams) => {
2775
+ const session = new SpaceProtocolSession({
2776
+ wireParams,
2777
+ swarmIdentity: this._swarmIdentity,
2778
+ onSessionAuth: this._onSessionAuth,
2779
+ onAuthFailure: this._onAuthFailure,
2780
+ blobSync: this.blobSync
2781
+ });
2782
+ this._sessions.set(wireParams.remotePeerId, session);
2783
+ for (const feed of this._feeds) {
2784
+ session.replicator.addFeed(feed);
2785
+ }
2786
+ return session;
2787
+ };
2788
+ }
2789
+ };
2790
+ _ts_decorate7([
2791
+ logInfo2,
2792
+ trace4.info()
2793
+ ], SpaceProtocol.prototype, "_topic", void 0);
2794
+ _ts_decorate7([
2795
+ trace4.info()
2796
+ ], SpaceProtocol.prototype, "_spaceKey", void 0);
2797
+ _ts_decorate7([
2798
+ logInfo2
2799
+ ], SpaceProtocol.prototype, "_ownPeerKey", null);
2800
+ SpaceProtocol = _ts_decorate7([
2801
+ trace4.resource()
2802
+ ], SpaceProtocol);
2803
+ var AuthStatus;
2804
+ (function(AuthStatus2) {
2805
+ AuthStatus2["INITIAL"] = "INITIAL";
2806
+ AuthStatus2["SUCCESS"] = "SUCCESS";
2807
+ AuthStatus2["FAILURE"] = "FAILURE";
2808
+ })(AuthStatus || (AuthStatus = {}));
2809
+ var SpaceProtocolSession = class {
2810
+ // TODO(dmaretskyi): Allow to pass in extra extensions.
2811
+ constructor({ wireParams, swarmIdentity, onSessionAuth, onAuthFailure, blobSync }) {
2812
+ // TODO(dmaretskyi): Start with upload=false when switching it on the fly works.
2813
+ this.replicator = new ReplicatorExtension().setOptions({
2814
+ upload: true
2815
+ });
2816
+ this._authStatus = "INITIAL";
2817
+ this._wireParams = wireParams;
2818
+ this._swarmIdentity = swarmIdentity;
2819
+ this._onSessionAuth = onSessionAuth;
2820
+ this._onAuthFailure = onAuthFailure;
2821
+ this._blobSync = blobSync;
2822
+ this._teleport = new Teleport(wireParams);
2823
+ }
2824
+ get authStatus() {
2825
+ return this._authStatus;
2826
+ }
2827
+ get stats() {
2828
+ return this._teleport.stats;
2829
+ }
2830
+ get stream() {
2831
+ return this._teleport.stream;
2832
+ }
2833
+ async open(sessionId) {
2834
+ await this._teleport.open(sessionId);
2835
+ this._teleport.addExtension("dxos.mesh.teleport.auth", new AuthExtension({
2836
+ provider: this._swarmIdentity.credentialProvider,
2837
+ verifier: this._swarmIdentity.credentialAuthenticator,
2838
+ onAuthSuccess: () => {
2839
+ log10("Peer authenticated", void 0, {
2840
+ F: __dxlog_file13,
2841
+ L: 248,
2842
+ S: this,
2843
+ C: (f, a) => f(...a)
2844
+ });
2845
+ this._authStatus = "SUCCESS";
2846
+ this._onSessionAuth?.(this._teleport);
2847
+ },
2848
+ onAuthFailure: () => {
2849
+ this._authStatus = "FAILURE";
2850
+ this._onAuthFailure?.(this._teleport);
2851
+ }
2852
+ }));
2853
+ this._teleport.addExtension("dxos.mesh.teleport.replicator", this.replicator);
2854
+ this._teleport.addExtension("dxos.mesh.teleport.blobsync", this._blobSync.createExtension());
2855
+ }
2856
+ async close() {
2857
+ log10("close", void 0, {
2858
+ F: __dxlog_file13,
2859
+ L: 264,
2860
+ S: this,
2861
+ C: (f, a) => f(...a)
2862
+ });
2863
+ await this._teleport.close();
2864
+ }
2865
+ async abort() {
2866
+ await this._teleport.abort();
2867
+ }
2868
+ };
2869
+ _ts_decorate7([
2870
+ logInfo2
2871
+ ], SpaceProtocolSession.prototype, "_wireParams", void 0);
2872
+ _ts_decorate7([
2873
+ logInfo2
2874
+ ], SpaceProtocolSession.prototype, "authStatus", null);
2875
+
2876
+ // packages/core/echo/echo-pipeline/src/space/space-manager.ts
2877
+ import { synchronized as synchronized4, trackLeaks as trackLeaks3, Trigger as Trigger3 } from "@dxos/async";
2878
+ import { failUndefined as failUndefined2 } from "@dxos/debug";
2879
+ import { PublicKey as PublicKey8 } from "@dxos/keys";
2880
+ import { log as log14 } from "@dxos/log";
2881
+ import { trace as trace5 } from "@dxos/protocols";
2882
+ import { ComplexMap as ComplexMap4 } from "@dxos/util";
2883
+
2884
+ // packages/core/echo/echo-pipeline/src/db-host/data-service.ts
2885
+ import { Stream } from "@dxos/codec-protobuf";
2886
+ import { invariant as invariant12 } from "@dxos/invariant";
2887
+ import { SpaceId as SpaceId3 } from "@dxos/keys";
2888
+ import { log as log13 } from "@dxos/log";
2889
+
2890
+ // packages/core/echo/echo-pipeline/src/automerge/mesh-echo-replicator.ts
2891
+ import { invariant as invariant11 } from "@dxos/invariant";
2892
+ import { PublicKey as PublicKey7 } from "@dxos/keys";
2893
+ import { log as log12 } from "@dxos/log";
2894
+ import { ComplexSet, defaultMap as defaultMap2 } from "@dxos/util";
2895
+
2896
+ // packages/core/echo/echo-pipeline/src/automerge/mesh-echo-replicator-connection.ts
2897
+ import { cbor } from "@dxos/automerge/automerge-repo";
2898
+ import { Resource as Resource6 } from "@dxos/context";
2899
+ import { invariant as invariant10 } from "@dxos/invariant";
2900
+ import { log as log11 } from "@dxos/log";
2901
+ import { AutomergeReplicator } from "@dxos/teleport-extension-automerge-replicator";
2902
+ var __dxlog_file14 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/mesh-echo-replicator-connection.ts";
2903
+ var DEFAULT_FACTORY = (params) => new AutomergeReplicator(...params);
2904
+ var MeshReplicatorConnection = class extends Resource6 {
2905
+ constructor(_params) {
2906
+ super();
2907
+ this._params = _params;
2908
+ this.remoteDeviceKey = null;
2909
+ this._remotePeerId = null;
2910
+ this._isEnabled = false;
2911
+ let readableStreamController;
2912
+ this.readable = new ReadableStream({
2913
+ start: (controller) => {
2914
+ readableStreamController = controller;
2915
+ this._ctx.onDispose(() => controller.close());
2916
+ }
2917
+ });
2918
+ this.writable = new WritableStream({
2919
+ write: async (message, controller) => {
2920
+ invariant10(this._isEnabled, "Writing to a disabled connection", {
2921
+ F: __dxlog_file14,
2922
+ L: 48,
2923
+ S: this,
2924
+ A: [
2925
+ "this._isEnabled",
2926
+ "'Writing to a disabled connection'"
2927
+ ]
2928
+ });
2929
+ try {
2930
+ await this.replicatorExtension.sendSyncMessage({
2931
+ payload: cbor.encode(message)
2932
+ });
2933
+ } catch (err) {
2934
+ controller.error(err);
2935
+ this._disconnectIfEnabled();
2936
+ }
1935
2937
  }
1936
2938
  });
1937
- this._space = _space;
1938
- }
1939
- async getHandlers() {
1940
- return {
1941
- AdmissionDiscoveryService: {
1942
- getAdmissionCredential: async (request) => {
1943
- const memberInfo = this._space.spaceState.members.get(request.memberKey);
1944
- if (!memberInfo?.credential) {
1945
- throw new ProtocolError("Space member not found.", request);
2939
+ const createAutomergeReplicator = this._params.replicatorFactory ?? DEFAULT_FACTORY;
2940
+ this.replicatorExtension = createAutomergeReplicator([
2941
+ {
2942
+ peerId: this._params.ownPeerId
2943
+ },
2944
+ {
2945
+ onStartReplication: async (info, remotePeerId) => {
2946
+ this.remoteDeviceKey = remotePeerId;
2947
+ this._remotePeerId = info.id;
2948
+ log11("onStartReplication", {
2949
+ id: info.id,
2950
+ thisPeerId: this.peerId,
2951
+ remotePeerId: remotePeerId.toHex()
2952
+ }, {
2953
+ F: __dxlog_file14,
2954
+ L: 82,
2955
+ S: this,
2956
+ C: (f, a) => f(...a)
2957
+ });
2958
+ this._params.onRemoteConnected();
2959
+ },
2960
+ onSyncMessage: async ({ payload }) => {
2961
+ if (!this._isEnabled) {
2962
+ return;
1946
2963
  }
1947
- return {
1948
- admissionCredential: memberInfo.credential
1949
- };
2964
+ const message = cbor.decode(payload);
2965
+ readableStreamController.enqueue(message);
2966
+ },
2967
+ onClose: async () => {
2968
+ this._disconnectIfEnabled();
1950
2969
  }
1951
2970
  }
1952
- };
2971
+ ]);
1953
2972
  }
1954
- };
1955
-
1956
- // packages/core/echo/echo-pipeline/src/space/space-protocol.ts
1957
- import { discoveryKey, subtleCrypto as subtleCrypto3 } from "@dxos/crypto";
1958
- import { PublicKey as PublicKey6 } from "@dxos/keys";
1959
- import { log as log10, logInfo as logInfo2 } from "@dxos/log";
1960
- import { MMSTTopology } from "@dxos/network-manager";
1961
- import { Teleport } from "@dxos/teleport";
1962
- import { BlobSync } from "@dxos/teleport-extension-object-sync";
1963
- import { ReplicatorExtension } from "@dxos/teleport-extension-replicator";
1964
- import { trace as trace3 } from "@dxos/tracing";
1965
- import { ComplexMap as ComplexMap4 } from "@dxos/util";
1966
- function _ts_decorate6(decorators, target, key, desc) {
1967
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1968
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
1969
- r = Reflect.decorate(decorators, target, key, desc);
1970
- else
1971
- for (var i = decorators.length - 1; i >= 0; i--)
1972
- if (d = decorators[i])
1973
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1974
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1975
- }
1976
- var __dxlog_file12 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/space-protocol.ts";
1977
- var MOCK_AUTH_PROVIDER = async (nonce) => Buffer.from("mock");
1978
- var MOCK_AUTH_VERIFIER = async (nonce, credential) => true;
1979
- var SpaceProtocol = class {
1980
- constructor({ topic, swarmIdentity, networkManager, onSessionAuth, onAuthFailure, blobStore }) {
1981
- this._feeds = /* @__PURE__ */ new Set();
1982
- this._sessions = new ComplexMap4(PublicKey6.hash);
1983
- // TODO(burdon): Move to config (with sensible defaults).
1984
- this._topology = new MMSTTopology({
1985
- originateConnections: 4,
1986
- maxPeers: 10,
1987
- sampleSize: 20
1988
- });
1989
- this._spaceKey = topic;
1990
- this._networkManager = networkManager;
1991
- this._swarmIdentity = swarmIdentity;
1992
- this._onSessionAuth = onSessionAuth;
1993
- this._onAuthFailure = onAuthFailure;
1994
- this.blobSync = new BlobSync({
1995
- blobStore
1996
- });
1997
- this._topic = subtleCrypto3.digest("SHA-256", topic.asBuffer()).then(discoveryKey).then(PublicKey6.from);
2973
+ _disconnectIfEnabled() {
2974
+ if (this._isEnabled) {
2975
+ this._params.onRemoteDisconnected();
2976
+ }
1998
2977
  }
1999
- get sessions() {
2000
- return this._sessions;
2978
+ get peerId() {
2979
+ invariant10(this._remotePeerId != null, "Remote peer has not connected yet.", {
2980
+ F: __dxlog_file14,
2981
+ L: 108,
2982
+ S: this,
2983
+ A: [
2984
+ "this._remotePeerId != null",
2985
+ "'Remote peer has not connected yet.'"
2986
+ ]
2987
+ });
2988
+ return this._remotePeerId;
2001
2989
  }
2002
- get feeds() {
2003
- return this._feeds;
2990
+ async shouldAdvertise(params) {
2991
+ return this._params.shouldAdvertise(params);
2004
2992
  }
2005
- get _ownPeerKey() {
2006
- return this._swarmIdentity.peerKey;
2993
+ shouldSyncCollection(params) {
2994
+ return this._params.shouldSyncCollection(params);
2007
2995
  }
2008
- // TODO(burdon): Create abstraction for Space (e.g., add keys and have provider).
2009
- addFeed(feed) {
2010
- log10("addFeed", {
2011
- key: feed.key
2012
- }, {
2013
- F: __dxlog_file12,
2014
- L: 109,
2996
+ /**
2997
+ * Start exchanging messages with the remote peer.
2998
+ * Call after the remote peer has connected.
2999
+ */
3000
+ enable() {
3001
+ invariant10(this._remotePeerId != null, "Remote peer has not connected yet.", {
3002
+ F: __dxlog_file14,
3003
+ L: 125,
2015
3004
  S: this,
2016
- C: (f, a) => f(...a)
3005
+ A: [
3006
+ "this._remotePeerId != null",
3007
+ "'Remote peer has not connected yet.'"
3008
+ ]
2017
3009
  });
2018
- this._feeds.add(feed);
2019
- for (const session of this._sessions.values()) {
2020
- session.replicator.addFeed(feed);
2021
- }
3010
+ this._isEnabled = true;
2022
3011
  }
2023
- // TODO(burdon): Rename open? Common open/close interfaces for all services?
2024
- async start() {
2025
- if (this._connection) {
2026
- return;
3012
+ /**
3013
+ * Stop exchanging messages with the remote peer.
3014
+ */
3015
+ disable() {
3016
+ this._isEnabled = false;
3017
+ }
3018
+ };
3019
+
3020
+ // packages/core/echo/echo-pipeline/src/automerge/mesh-echo-replicator.ts
3021
+ var __dxlog_file15 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/automerge/mesh-echo-replicator.ts";
3022
+ var MeshEchoReplicator = class {
3023
+ constructor() {
3024
+ this._connections = /* @__PURE__ */ new Set();
3025
+ /**
3026
+ * Using automerge peerId as a key.
3027
+ */
3028
+ this._connectionsPerPeer = /* @__PURE__ */ new Map();
3029
+ /**
3030
+ * spaceId -> deviceKey[]
3031
+ */
3032
+ this._authorizedDevices = /* @__PURE__ */ new Map();
3033
+ this._context = null;
3034
+ }
3035
+ async connect(context) {
3036
+ this._context = context;
3037
+ }
3038
+ async disconnect() {
3039
+ for (const connection of this._connectionsPerPeer.values()) {
3040
+ this._context?.onConnectionClosed(connection);
2027
3041
  }
2028
- const credentials = await this._swarmIdentity.credentialProvider(Buffer.from(""));
2029
- await this.blobSync.open();
2030
- log10("starting...", void 0, {
2031
- F: __dxlog_file12,
2032
- L: 128,
3042
+ for (const connection of this._connections) {
3043
+ await connection.close();
3044
+ }
3045
+ this._connections.clear();
3046
+ this._connectionsPerPeer.clear();
3047
+ this._context = null;
3048
+ }
3049
+ createExtension(extensionFactory) {
3050
+ invariant11(this._context, void 0, {
3051
+ F: __dxlog_file15,
3052
+ L: 56,
2033
3053
  S: this,
2034
- C: (f, a) => f(...a)
3054
+ A: [
3055
+ "this._context",
3056
+ ""
3057
+ ]
2035
3058
  });
2036
- const topic = await this._topic;
2037
- this._connection = await this._networkManager.joinSwarm({
2038
- protocolProvider: this._createProtocolProvider(credentials),
2039
- peerId: this._swarmIdentity.peerKey,
2040
- topic,
2041
- topology: this._topology,
2042
- label: `swarm ${topic.truncate()} for space ${this._spaceKey.truncate()}`
3059
+ const connection = new MeshReplicatorConnection({
3060
+ ownPeerId: this._context.peerId,
3061
+ replicatorFactory: extensionFactory,
3062
+ onRemoteConnected: async () => {
3063
+ log12("onRemoteConnected", {
3064
+ peerId: connection.peerId
3065
+ }, {
3066
+ F: __dxlog_file15,
3067
+ L: 62,
3068
+ S: this,
3069
+ C: (f, a) => f(...a)
3070
+ });
3071
+ invariant11(this._context, void 0, {
3072
+ F: __dxlog_file15,
3073
+ L: 63,
3074
+ S: this,
3075
+ A: [
3076
+ "this._context",
3077
+ ""
3078
+ ]
3079
+ });
3080
+ if (this._connectionsPerPeer.has(connection.peerId)) {
3081
+ this._context.onConnectionAuthScopeChanged(connection);
3082
+ } else {
3083
+ this._connectionsPerPeer.set(connection.peerId, connection);
3084
+ this._context.onConnectionOpen(connection);
3085
+ connection.enable();
3086
+ }
3087
+ },
3088
+ onRemoteDisconnected: async () => {
3089
+ log12("onRemoteDisconnected", {
3090
+ peerId: connection.peerId
3091
+ }, {
3092
+ F: __dxlog_file15,
3093
+ L: 74,
3094
+ S: this,
3095
+ C: (f, a) => f(...a)
3096
+ });
3097
+ this._context?.onConnectionClosed(connection);
3098
+ this._connectionsPerPeer.delete(connection.peerId);
3099
+ connection.disable();
3100
+ this._connections.delete(connection);
3101
+ },
3102
+ shouldAdvertise: async (params) => {
3103
+ log12("shouldAdvertise", {
3104
+ peerId: connection.peerId,
3105
+ documentId: params.documentId
3106
+ }, {
3107
+ F: __dxlog_file15,
3108
+ L: 81,
3109
+ S: this,
3110
+ C: (f, a) => f(...a)
3111
+ });
3112
+ invariant11(this._context, void 0, {
3113
+ F: __dxlog_file15,
3114
+ L: 82,
3115
+ S: this,
3116
+ A: [
3117
+ "this._context",
3118
+ ""
3119
+ ]
3120
+ });
3121
+ try {
3122
+ const spaceKey = await this._context.getContainingSpaceForDocument(params.documentId);
3123
+ if (!spaceKey) {
3124
+ log12("space key not found for share policy check", {
3125
+ peerId: connection.peerId,
3126
+ documentId: params.documentId
3127
+ }, {
3128
+ F: __dxlog_file15,
3129
+ L: 86,
3130
+ S: this,
3131
+ C: (f, a) => f(...a)
3132
+ });
3133
+ return false;
3134
+ }
3135
+ const spaceId = await createIdFromSpaceKey(spaceKey);
3136
+ const authorizedDevices = this._authorizedDevices.get(spaceId);
3137
+ if (!connection.remoteDeviceKey) {
3138
+ log12("device key not found for share policy check", {
3139
+ peerId: connection.peerId,
3140
+ documentId: params.documentId
3141
+ }, {
3142
+ F: __dxlog_file15,
3143
+ L: 98,
3144
+ S: this,
3145
+ C: (f, a) => f(...a)
3146
+ });
3147
+ return false;
3148
+ }
3149
+ const isAuthorized = authorizedDevices?.has(connection.remoteDeviceKey) ?? false;
3150
+ log12("share policy check", {
3151
+ localPeer: this._context.peerId,
3152
+ remotePeer: connection.peerId,
3153
+ documentId: params.documentId,
3154
+ deviceKey: connection.remoteDeviceKey,
3155
+ spaceKey,
3156
+ isAuthorized
3157
+ }, {
3158
+ F: __dxlog_file15,
3159
+ L: 106,
3160
+ S: this,
3161
+ C: (f, a) => f(...a)
3162
+ });
3163
+ return isAuthorized;
3164
+ } catch (err) {
3165
+ log12.catch(err, void 0, {
3166
+ F: __dxlog_file15,
3167
+ L: 116,
3168
+ S: this,
3169
+ C: (f, a) => f(...a)
3170
+ });
3171
+ return false;
3172
+ }
3173
+ },
3174
+ shouldSyncCollection: ({ collectionId }) => {
3175
+ const spaceId = getSpaceIdFromCollectionId(collectionId);
3176
+ const authorizedDevices = this._authorizedDevices.get(spaceId);
3177
+ if (!connection.remoteDeviceKey) {
3178
+ log12("device key not found for collection sync check", {
3179
+ peerId: connection.peerId,
3180
+ collectionId
3181
+ }, {
3182
+ F: __dxlog_file15,
3183
+ L: 126,
3184
+ S: this,
3185
+ C: (f, a) => f(...a)
3186
+ });
3187
+ return false;
3188
+ }
3189
+ const isAuthorized = authorizedDevices?.has(connection.remoteDeviceKey) ?? false;
3190
+ return isAuthorized;
3191
+ }
2043
3192
  });
2044
- log10("started", void 0, {
2045
- F: __dxlog_file12,
2046
- L: 138,
3193
+ this._connections.add(connection);
3194
+ return connection.replicatorExtension;
3195
+ }
3196
+ async authorizeDevice(spaceKey, deviceKey) {
3197
+ log12("authorizeDevice", {
3198
+ spaceKey,
3199
+ deviceKey
3200
+ }, {
3201
+ F: __dxlog_file15,
3202
+ L: 143,
2047
3203
  S: this,
2048
3204
  C: (f, a) => f(...a)
2049
3205
  });
3206
+ const spaceId = await createIdFromSpaceKey(spaceKey);
3207
+ defaultMap2(this._authorizedDevices, spaceId, () => new ComplexSet(PublicKey7.hash)).add(deviceKey);
3208
+ for (const connection of this._connections) {
3209
+ if (connection.remoteDeviceKey && connection.remoteDeviceKey.equals(deviceKey)) {
3210
+ if (this._connectionsPerPeer.has(connection.peerId)) {
3211
+ this._context?.onConnectionAuthScopeChanged(connection);
3212
+ }
3213
+ }
3214
+ }
2050
3215
  }
2051
- updateTopology() {
2052
- this._topology.forceUpdate();
3216
+ };
3217
+
3218
+ // packages/core/echo/echo-pipeline/src/db-host/data-service.ts
3219
+ var __dxlog_file16 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/db-host/data-service.ts";
3220
+ var DataServiceImpl = class {
3221
+ constructor(params) {
3222
+ /**
3223
+ * Map of subscriptions.
3224
+ * subscriptionId -> DocumentsSynchronizer
3225
+ */
3226
+ this._subscriptions = /* @__PURE__ */ new Map();
3227
+ this._automergeHost = params.automergeHost;
3228
+ this._updateIndexes = params.updateIndexes;
2053
3229
  }
2054
- async stop() {
2055
- await this.blobSync.close();
2056
- if (this._connection) {
2057
- log10("stopping...", void 0, {
2058
- F: __dxlog_file12,
2059
- L: 149,
2060
- S: this,
2061
- C: (f, a) => f(...a)
3230
+ subscribe(request) {
3231
+ return new Stream(({ next, ready }) => {
3232
+ const synchronizer = new DocumentsSynchronizer({
3233
+ repo: this._automergeHost.repo,
3234
+ sendUpdates: (updates) => next(updates)
2062
3235
  });
2063
- await this._connection.close();
2064
- log10("stopped", void 0, {
2065
- F: __dxlog_file12,
2066
- L: 151,
3236
+ synchronizer.open().then(() => {
3237
+ this._subscriptions.set(request.subscriptionId, synchronizer);
3238
+ ready();
3239
+ }).catch((err) => log13.catch(err, void 0, {
3240
+ F: __dxlog_file16,
3241
+ L: 64,
2067
3242
  S: this,
2068
3243
  C: (f, a) => f(...a)
2069
- });
3244
+ }));
3245
+ return () => synchronizer.close();
3246
+ });
3247
+ }
3248
+ async updateSubscription(request) {
3249
+ const synchronizer = this._subscriptions.get(request.subscriptionId);
3250
+ invariant12(synchronizer, "Subscription not found", {
3251
+ F: __dxlog_file16,
3252
+ L: 71,
3253
+ S: this,
3254
+ A: [
3255
+ "synchronizer",
3256
+ "'Subscription not found'"
3257
+ ]
3258
+ });
3259
+ if (request.addIds?.length) {
3260
+ await synchronizer.addDocuments(request.addIds);
3261
+ }
3262
+ if (request.removeIds?.length) {
3263
+ await synchronizer.removeDocuments(request.removeIds);
3264
+ }
3265
+ }
3266
+ async update(request) {
3267
+ if (!request.updates) {
3268
+ return;
2070
3269
  }
3270
+ const synchronizer = this._subscriptions.get(request.subscriptionId);
3271
+ invariant12(synchronizer, "Subscription not found", {
3272
+ F: __dxlog_file16,
3273
+ L: 86,
3274
+ S: this,
3275
+ A: [
3276
+ "synchronizer",
3277
+ "'Subscription not found'"
3278
+ ]
3279
+ });
3280
+ synchronizer.update(request.updates);
2071
3281
  }
2072
- _createProtocolProvider(credentials) {
2073
- return (wireParams) => {
2074
- const session = new SpaceProtocolSession({
2075
- wireParams,
2076
- swarmIdentity: this._swarmIdentity,
2077
- onSessionAuth: this._onSessionAuth,
2078
- onAuthFailure: this._onAuthFailure,
2079
- blobSync: this.blobSync
2080
- });
2081
- this._sessions.set(wireParams.remotePeerId, session);
2082
- for (const feed of this._feeds) {
2083
- session.replicator.addFeed(feed);
3282
+ async flush(request) {
3283
+ await this._automergeHost.flush(request);
3284
+ }
3285
+ async getDocumentHeads(request) {
3286
+ const documentIds = request.documentIds;
3287
+ if (!documentIds) {
3288
+ return {
3289
+ heads: {
3290
+ entries: []
3291
+ }
3292
+ };
3293
+ }
3294
+ const heads = await this._automergeHost.getHeads(documentIds);
3295
+ return {
3296
+ heads: {
3297
+ entries: heads.map((heads2, idx) => ({
3298
+ documentId: documentIds[idx],
3299
+ heads: heads2
3300
+ }))
2084
3301
  }
2085
- return session;
2086
3302
  };
2087
3303
  }
2088
- };
2089
- _ts_decorate6([
2090
- logInfo2,
2091
- trace3.info()
2092
- ], SpaceProtocol.prototype, "_topic", void 0);
2093
- _ts_decorate6([
2094
- trace3.info()
2095
- ], SpaceProtocol.prototype, "_spaceKey", void 0);
2096
- _ts_decorate6([
2097
- logInfo2
2098
- ], SpaceProtocol.prototype, "_ownPeerKey", null);
2099
- SpaceProtocol = _ts_decorate6([
2100
- trace3.resource()
2101
- ], SpaceProtocol);
2102
- var AuthStatus;
2103
- (function(AuthStatus2) {
2104
- AuthStatus2["INITIAL"] = "INITIAL";
2105
- AuthStatus2["SUCCESS"] = "SUCCESS";
2106
- AuthStatus2["FAILURE"] = "FAILURE";
2107
- })(AuthStatus || (AuthStatus = {}));
2108
- var SpaceProtocolSession = class {
2109
- // TODO(dmaretskyi): Allow to pass in extra extensions.
2110
- constructor({ wireParams, swarmIdentity, onSessionAuth, onAuthFailure, blobSync }) {
2111
- // TODO(dmaretskyi): Start with upload=false when switching it on the fly works.
2112
- this.replicator = new ReplicatorExtension().setOptions({
2113
- upload: true
2114
- });
2115
- this._authStatus = "INITIAL";
2116
- this._wireParams = wireParams;
2117
- this._swarmIdentity = swarmIdentity;
2118
- this._onSessionAuth = onSessionAuth;
2119
- this._onAuthFailure = onAuthFailure;
2120
- this._blobSync = blobSync;
2121
- this._teleport = new Teleport(wireParams);
2122
- }
2123
- get authStatus() {
2124
- return this._authStatus;
2125
- }
2126
- get stats() {
2127
- return this._teleport.stats;
3304
+ async waitUntilHeadsReplicated(request, options) {
3305
+ await this._automergeHost.waitUntilHeadsReplicated(request.heads);
2128
3306
  }
2129
- get stream() {
2130
- return this._teleport.stream;
3307
+ async reIndexHeads(request, options) {
3308
+ await this._automergeHost.reIndexHeads(request.documentIds ?? []);
2131
3309
  }
2132
- async open(sessionId) {
2133
- await this._teleport.open(sessionId);
2134
- this._teleport.addExtension("dxos.mesh.teleport.auth", new AuthExtension({
2135
- provider: this._swarmIdentity.credentialProvider,
2136
- verifier: this._swarmIdentity.credentialAuthenticator,
2137
- onAuthSuccess: () => {
2138
- log10("Peer authenticated", void 0, {
2139
- F: __dxlog_file12,
2140
- L: 248,
2141
- S: this,
2142
- C: (f, a) => f(...a)
2143
- });
2144
- this._authStatus = "SUCCESS";
2145
- this._onSessionAuth?.(this._teleport);
2146
- },
2147
- onAuthFailure: () => {
2148
- this._authStatus = "FAILURE";
2149
- this._onAuthFailure?.(this._teleport);
2150
- }
2151
- }));
2152
- this._teleport.addExtension("dxos.mesh.teleport.replicator", this.replicator);
2153
- this._teleport.addExtension("dxos.mesh.teleport.blobsync", this._blobSync.createExtension());
3310
+ async updateIndexes() {
3311
+ await this._updateIndexes();
2154
3312
  }
2155
- async close() {
2156
- log10("close", void 0, {
2157
- F: __dxlog_file12,
2158
- L: 264,
3313
+ async getSpaceSyncState(request, options) {
3314
+ invariant12(SpaceId3.isValid(request.spaceId), void 0, {
3315
+ F: __dxlog_file16,
3316
+ L: 127,
2159
3317
  S: this,
2160
- C: (f, a) => f(...a)
3318
+ A: [
3319
+ "SpaceId.isValid(request.spaceId)",
3320
+ ""
3321
+ ]
2161
3322
  });
2162
- await this._teleport.close();
2163
- }
2164
- async abort() {
2165
- await this._teleport.abort();
3323
+ const collectionId = deriveCollectionIdFromSpaceId(request.spaceId);
3324
+ const state = await this._automergeHost.getCollectionSyncState(collectionId);
3325
+ return {
3326
+ peers: state.peers.map((peer) => ({
3327
+ peerId: peer.peerId,
3328
+ documentsToReconcile: peer.differentDocuments
3329
+ }))
3330
+ };
2166
3331
  }
2167
3332
  };
2168
- _ts_decorate6([
2169
- logInfo2
2170
- ], SpaceProtocolSession.prototype, "_wireParams", void 0);
2171
- _ts_decorate6([
2172
- logInfo2
2173
- ], SpaceProtocolSession.prototype, "authStatus", null);
2174
3333
 
2175
3334
  // packages/core/echo/echo-pipeline/src/space/space-manager.ts
2176
- import { synchronized as synchronized4, trackLeaks as trackLeaks3, Trigger as Trigger2 } from "@dxos/async";
2177
- import { failUndefined as failUndefined2 } from "@dxos/debug";
2178
- import { PublicKey as PublicKey7 } from "@dxos/keys";
2179
- import { log as log11 } from "@dxos/log";
2180
- import { trace as trace4 } from "@dxos/protocols";
2181
- import { ComplexMap as ComplexMap5 } from "@dxos/util";
2182
- function _ts_decorate7(decorators, target, key, desc) {
3335
+ function _ts_decorate8(decorators, target, key, desc) {
2183
3336
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2184
3337
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
2185
3338
  r = Reflect.decorate(decorators, target, key, desc);
@@ -2189,11 +3342,11 @@ function _ts_decorate7(decorators, target, key, desc) {
2189
3342
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2190
3343
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2191
3344
  }
2192
- var __dxlog_file13 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/space-manager.ts";
3345
+ var __dxlog_file17 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/space/space-manager.ts";
2193
3346
  var SpaceManager = class {
2194
3347
  constructor({ feedStore, networkManager, metadataStore, snapshotStore, blobStore }) {
2195
- this._spaces = new ComplexMap5(PublicKey7.hash);
2196
- this._instanceId = PublicKey7.random().toHex();
3348
+ this._spaces = new ComplexMap4(PublicKey8.hash);
3349
+ this._instanceId = PublicKey8.random().toHex();
2197
3350
  this._feedStore = feedStore;
2198
3351
  this._networkManager = networkManager;
2199
3352
  this._metadataStore = metadataStore;
@@ -2212,18 +3365,18 @@ var SpaceManager = class {
2212
3365
  ].map((space) => space.close()));
2213
3366
  }
2214
3367
  async constructSpace({ metadata, swarmIdentity, onAuthorizedConnection, onAuthFailure, onDelegatedInvitationStatusChange, onMemberRolesChanged, memberKey }) {
2215
- log11.trace("dxos.echo.space-manager.construct-space", trace4.begin({
3368
+ log14.trace("dxos.echo.space-manager.construct-space", trace5.begin({
2216
3369
  id: this._instanceId
2217
3370
  }), {
2218
- F: __dxlog_file13,
3371
+ F: __dxlog_file17,
2219
3372
  L: 103,
2220
3373
  S: this,
2221
3374
  C: (f, a) => f(...a)
2222
3375
  });
2223
- log11("constructing space...", {
3376
+ log14("constructing space...", {
2224
3377
  spaceKey: metadata.genesisFeedKey
2225
3378
  }, {
2226
- F: __dxlog_file13,
3379
+ F: __dxlog_file17,
2227
3380
  L: 104,
2228
3381
  S: this,
2229
3382
  C: (f, a) => f(...a)
@@ -2253,10 +3406,10 @@ var SpaceManager = class {
2253
3406
  onMemberRolesChanged
2254
3407
  });
2255
3408
  this._spaces.set(space.key, space);
2256
- log11.trace("dxos.echo.space-manager.construct-space", trace4.end({
3409
+ log14.trace("dxos.echo.space-manager.construct-space", trace5.end({
2257
3410
  id: this._instanceId
2258
3411
  }), {
2259
- F: __dxlog_file13,
3412
+ F: __dxlog_file17,
2260
3413
  L: 135,
2261
3414
  S: this,
2262
3415
  C: (f, a) => f(...a)
@@ -2265,93 +3418,439 @@ var SpaceManager = class {
2265
3418
  }
2266
3419
  async requestSpaceAdmissionCredential(params) {
2267
3420
  const traceKey = "dxos.echo.space-manager.request-space-admission";
2268
- log11.trace(traceKey, trace4.begin({
3421
+ log14.trace(traceKey, trace5.begin({
2269
3422
  id: this._instanceId
2270
3423
  }), {
2271
- F: __dxlog_file13,
3424
+ F: __dxlog_file17,
2272
3425
  L: 141,
2273
3426
  S: this,
2274
3427
  C: (f, a) => f(...a)
2275
3428
  });
2276
- log11("requesting space admission credential...", {
3429
+ log14("requesting space admission credential...", {
2277
3430
  spaceKey: params.spaceKey
2278
3431
  }, {
2279
- F: __dxlog_file13,
2280
- L: 142,
3432
+ F: __dxlog_file17,
3433
+ L: 142,
3434
+ S: this,
3435
+ C: (f, a) => f(...a)
3436
+ });
3437
+ const onCredentialResolved = new Trigger3();
3438
+ const protocol = new SpaceProtocol({
3439
+ topic: params.spaceKey,
3440
+ swarmIdentity: params.swarmIdentity,
3441
+ networkManager: this._networkManager,
3442
+ onSessionAuth: (session) => {
3443
+ session.addExtension("dxos.mesh.teleport.admission-discovery", new CredentialRetrieverExtension({
3444
+ spaceKey: params.spaceKey,
3445
+ memberKey: params.identityKey
3446
+ }, onCredentialResolved));
3447
+ },
3448
+ onAuthFailure: (session) => session.close(),
3449
+ blobStore: this._blobStore
3450
+ });
3451
+ try {
3452
+ await protocol.start();
3453
+ const credential = await onCredentialResolved.wait({
3454
+ timeout: params.timeout
3455
+ });
3456
+ log14.trace(traceKey, trace5.end({
3457
+ id: this._instanceId
3458
+ }), {
3459
+ F: __dxlog_file17,
3460
+ L: 165,
3461
+ S: this,
3462
+ C: (f, a) => f(...a)
3463
+ });
3464
+ return credential;
3465
+ } catch (err) {
3466
+ log14.trace(traceKey, trace5.error({
3467
+ id: this._instanceId,
3468
+ error: err
3469
+ }), {
3470
+ F: __dxlog_file17,
3471
+ L: 168,
3472
+ S: this,
3473
+ C: (f, a) => f(...a)
3474
+ });
3475
+ throw err;
3476
+ } finally {
3477
+ await protocol.stop();
3478
+ }
3479
+ }
3480
+ };
3481
+ _ts_decorate8([
3482
+ synchronized4
3483
+ ], SpaceManager.prototype, "open", null);
3484
+ _ts_decorate8([
3485
+ synchronized4
3486
+ ], SpaceManager.prototype, "close", null);
3487
+ SpaceManager = _ts_decorate8([
3488
+ trackLeaks3("open", "close")
3489
+ ], SpaceManager);
3490
+
3491
+ // packages/core/echo/echo-pipeline/src/metadata/metadata-store.ts
3492
+ import CRC32 from "crc-32";
3493
+ import { Event as Event6, scheduleTaskInterval as scheduleTaskInterval2, synchronized as synchronized5 } from "@dxos/async";
3494
+ import { Context as Context6 } from "@dxos/context";
3495
+ import { invariant as invariant13 } from "@dxos/invariant";
3496
+ import { PublicKey as PublicKey9 } from "@dxos/keys";
3497
+ import { log as log15 } from "@dxos/log";
3498
+ import { DataCorruptionError, STORAGE_VERSION, schema as schema6 } from "@dxos/protocols";
3499
+ import { Invitation, SpaceState } from "@dxos/protocols/proto/dxos/client/services";
3500
+ import { ComplexMap as ComplexMap5, arrayToBuffer, forEachAsync, isNotNullOrUndefined } from "@dxos/util";
3501
+ function _ts_decorate9(decorators, target, key, desc) {
3502
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3503
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
3504
+ r = Reflect.decorate(decorators, target, key, desc);
3505
+ else
3506
+ for (var i = decorators.length - 1; i >= 0; i--)
3507
+ if (d = decorators[i])
3508
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
3509
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
3510
+ }
3511
+ var __dxlog_file18 = "/home/runner/work/dxos/dxos/packages/core/echo/echo-pipeline/src/metadata/metadata-store.ts";
3512
+ var EXPIRED_INVITATION_CLEANUP_INTERVAL = 60 * 60 * 1e3;
3513
+ var emptyEchoMetadata = () => ({
3514
+ version: STORAGE_VERSION,
3515
+ spaces: [],
3516
+ created: /* @__PURE__ */ new Date(),
3517
+ updated: /* @__PURE__ */ new Date()
3518
+ });
3519
+ var emptyLargeSpaceMetadata = () => ({});
3520
+ var EchoMetadata = schema6.getCodecForType("dxos.echo.metadata.EchoMetadata");
3521
+ var LargeSpaceMetadata = schema6.getCodecForType("dxos.echo.metadata.LargeSpaceMetadata");
3522
+ var MetadataStore = class {
3523
+ constructor(directory) {
3524
+ this._metadata = emptyEchoMetadata();
3525
+ this._spaceLargeMetadata = new ComplexMap5(PublicKey9.hash);
3526
+ this._metadataFile = void 0;
3527
+ this.update = new Event6();
3528
+ this._invitationCleanupCtx = new Context6(void 0, {
3529
+ F: __dxlog_file18,
3530
+ L: 53
3531
+ });
3532
+ this._directory = directory;
3533
+ }
3534
+ get metadata() {
3535
+ return this._metadata;
3536
+ }
3537
+ get version() {
3538
+ return this._metadata.version ?? 0;
3539
+ }
3540
+ /**
3541
+ * Returns a list of currently saved spaces. The list and objects in it can be modified addSpace and
3542
+ * addSpaceFeed functions.
3543
+ */
3544
+ get spaces() {
3545
+ return this._metadata.spaces ?? [];
3546
+ }
3547
+ async _readFile(file, codec2) {
3548
+ try {
3549
+ const { size: fileLength } = await file.stat();
3550
+ if (fileLength < 8) {
3551
+ return;
3552
+ }
3553
+ const dataSize = fromBytesInt32(await file.read(0, 4));
3554
+ const checksum = fromBytesInt32(await file.read(4, 4));
3555
+ log15("loaded", {
3556
+ size: dataSize,
3557
+ checksum,
3558
+ name: file.filename
3559
+ }, {
3560
+ F: __dxlog_file18,
3561
+ L: 89,
3562
+ S: this,
3563
+ C: (f, a) => f(...a)
3564
+ });
3565
+ if (fileLength < dataSize + 8) {
3566
+ throw new DataCorruptionError("Metadata size is smaller than expected.", {
3567
+ fileLength,
3568
+ dataSize
3569
+ });
3570
+ }
3571
+ const data = await file.read(8, dataSize);
3572
+ const calculatedChecksum = CRC32.buf(data);
3573
+ if (calculatedChecksum !== checksum) {
3574
+ throw new DataCorruptionError("Metadata checksum is invalid.");
3575
+ }
3576
+ return codec2.decode(data);
3577
+ } finally {
3578
+ await file.close();
3579
+ }
3580
+ }
3581
+ /**
3582
+ * @internal
3583
+ */
3584
+ async _writeFile(file, codec2, data) {
3585
+ const encoded = arrayToBuffer(codec2.encode(data));
3586
+ const checksum = CRC32.buf(encoded);
3587
+ const result = Buffer.alloc(8 + encoded.length);
3588
+ result.writeInt32LE(encoded.length, 0);
3589
+ result.writeInt32LE(checksum, 4);
3590
+ encoded.copy(result, 8);
3591
+ await file.write(0, result);
3592
+ log15("saved", {
3593
+ size: encoded.length,
3594
+ checksum
3595
+ }, {
3596
+ F: __dxlog_file18,
3597
+ L: 124,
2281
3598
  S: this,
2282
3599
  C: (f, a) => f(...a)
2283
3600
  });
2284
- const onCredentialResolved = new Trigger2();
2285
- const protocol = new SpaceProtocol({
2286
- topic: params.spaceKey,
2287
- swarmIdentity: params.swarmIdentity,
2288
- networkManager: this._networkManager,
2289
- onSessionAuth: (session) => {
2290
- session.addExtension("dxos.mesh.teleport.admission-discovery", new CredentialRetrieverExtension({
2291
- spaceKey: params.spaceKey,
2292
- memberKey: params.identityKey
2293
- }, onCredentialResolved));
2294
- },
2295
- onAuthFailure: (session) => session.close(),
2296
- blobStore: this._blobStore
2297
- });
3601
+ }
3602
+ async close() {
3603
+ await this._invitationCleanupCtx.dispose();
3604
+ await this.flush();
3605
+ await this._metadataFile?.close();
3606
+ this._metadataFile = void 0;
3607
+ this._metadata = emptyEchoMetadata();
3608
+ this._spaceLargeMetadata.clear();
3609
+ }
3610
+ /**
3611
+ * Loads metadata from persistent storage.
3612
+ */
3613
+ async load() {
3614
+ if (!this._metadataFile || this._metadataFile.closed) {
3615
+ this._metadataFile = this._directory.getOrCreateFile("EchoMetadata");
3616
+ }
2298
3617
  try {
2299
- await protocol.start();
2300
- const credential = await onCredentialResolved.wait({
2301
- timeout: params.timeout
3618
+ const metadata = await this._readFile(this._metadataFile, EchoMetadata);
3619
+ if (metadata) {
3620
+ this._metadata = metadata;
3621
+ }
3622
+ this._metadata.spaces?.forEach((space) => {
3623
+ space.state ??= SpaceState.SPACE_ACTIVE;
2302
3624
  });
2303
- log11.trace(traceKey, trace4.end({
2304
- id: this._instanceId
2305
- }), {
2306
- F: __dxlog_file13,
2307
- L: 165,
3625
+ } catch (err) {
3626
+ log15.error("failed to load metadata", {
3627
+ err
3628
+ }, {
3629
+ F: __dxlog_file18,
3630
+ L: 156,
2308
3631
  S: this,
2309
3632
  C: (f, a) => f(...a)
2310
3633
  });
2311
- return credential;
3634
+ this._metadata = emptyEchoMetadata();
3635
+ }
3636
+ await forEachAsync([
3637
+ this._metadata.identity?.haloSpace.key,
3638
+ ...this._metadata.spaces?.map((space) => space.key) ?? []
3639
+ ].filter(isNotNullOrUndefined), async (key) => {
3640
+ try {
3641
+ await this._loadSpaceLargeMetadata(key);
3642
+ } catch (err) {
3643
+ log15.error("failed to load space large metadata", {
3644
+ err
3645
+ }, {
3646
+ F: __dxlog_file18,
3647
+ L: 168,
3648
+ S: this,
3649
+ C: (f, a) => f(...a)
3650
+ });
3651
+ }
3652
+ });
3653
+ scheduleTaskInterval2(this._invitationCleanupCtx, async () => {
3654
+ for (const invitation of this._metadata.invitations ?? []) {
3655
+ if (hasInvitationExpired(invitation) || isLegacyInvitationFormat(invitation)) {
3656
+ await this.removeInvitation(invitation.invitationId);
3657
+ }
3658
+ }
3659
+ }, EXPIRED_INVITATION_CLEANUP_INTERVAL);
3660
+ }
3661
+ async _save() {
3662
+ const data = {
3663
+ ...this._metadata,
3664
+ version: STORAGE_VERSION,
3665
+ created: this._metadata.created ?? /* @__PURE__ */ new Date(),
3666
+ updated: /* @__PURE__ */ new Date()
3667
+ };
3668
+ this.update.emit(data);
3669
+ const file = this._directory.getOrCreateFile("EchoMetadata");
3670
+ await this._writeFile(file, EchoMetadata, data);
3671
+ }
3672
+ async _loadSpaceLargeMetadata(key) {
3673
+ const file = this._directory.getOrCreateFile(`space_${key.toHex()}_large`);
3674
+ try {
3675
+ const metadata = await this._readFile(file, LargeSpaceMetadata);
3676
+ if (metadata) {
3677
+ this._spaceLargeMetadata.set(key, metadata);
3678
+ }
2312
3679
  } catch (err) {
2313
- log11.trace(traceKey, trace4.error({
2314
- id: this._instanceId,
2315
- error: err
2316
- }), {
2317
- F: __dxlog_file13,
2318
- L: 168,
3680
+ log15.error("failed to load space large metadata", {
3681
+ err
3682
+ }, {
3683
+ F: __dxlog_file18,
3684
+ L: 210,
2319
3685
  S: this,
2320
3686
  C: (f, a) => f(...a)
2321
3687
  });
2322
- throw err;
2323
- } finally {
2324
- await protocol.stop();
2325
3688
  }
2326
3689
  }
3690
+ async _saveSpaceLargeMetadata(key) {
3691
+ const data = this._getLargeSpaceMetadata(key);
3692
+ const file = this._directory.getOrCreateFile(`space_${key.toHex()}_large`);
3693
+ await this._writeFile(file, LargeSpaceMetadata, data);
3694
+ }
3695
+ async flush() {
3696
+ await this._directory.flush();
3697
+ }
3698
+ _getSpace(spaceKey) {
3699
+ if (this._metadata.identity?.haloSpace.key.equals(spaceKey)) {
3700
+ return this._metadata.identity.haloSpace;
3701
+ }
3702
+ const space = this.spaces.find((space2) => space2.key === spaceKey);
3703
+ invariant13(space, "Space not found", {
3704
+ F: __dxlog_file18,
3705
+ L: 232,
3706
+ S: this,
3707
+ A: [
3708
+ "space",
3709
+ "'Space not found'"
3710
+ ]
3711
+ });
3712
+ return space;
3713
+ }
3714
+ _getLargeSpaceMetadata(key) {
3715
+ let entry = this._spaceLargeMetadata.get(key);
3716
+ if (entry) {
3717
+ return entry;
3718
+ }
3719
+ entry = emptyLargeSpaceMetadata();
3720
+ this._spaceLargeMetadata.set(key, entry);
3721
+ return entry;
3722
+ }
3723
+ /**
3724
+ * Clears storage - doesn't work for now.
3725
+ */
3726
+ async clear() {
3727
+ log15("clearing all metadata", void 0, {
3728
+ F: __dxlog_file18,
3729
+ L: 251,
3730
+ S: this,
3731
+ C: (f, a) => f(...a)
3732
+ });
3733
+ await this._directory.delete();
3734
+ this._metadata = emptyEchoMetadata();
3735
+ }
3736
+ getIdentityRecord() {
3737
+ return this._metadata.identity;
3738
+ }
3739
+ async setIdentityRecord(record) {
3740
+ invariant13(!this._metadata.identity, "Cannot overwrite existing identity in metadata", {
3741
+ F: __dxlog_file18,
3742
+ L: 261,
3743
+ S: this,
3744
+ A: [
3745
+ "!this._metadata.identity",
3746
+ "'Cannot overwrite existing identity in metadata'"
3747
+ ]
3748
+ });
3749
+ this._metadata.identity = record;
3750
+ await this._save();
3751
+ await this.flush();
3752
+ }
3753
+ getInvitations() {
3754
+ return this._metadata.invitations ?? [];
3755
+ }
3756
+ async addInvitation(invitation) {
3757
+ if (this._metadata.invitations?.find((i) => i.invitationId === invitation.invitationId)) {
3758
+ return;
3759
+ }
3760
+ (this._metadata.invitations ??= []).push(invitation);
3761
+ await this._save();
3762
+ await this.flush();
3763
+ }
3764
+ async removeInvitation(invitationId) {
3765
+ this._metadata.invitations = (this._metadata.invitations ?? []).filter((i) => i.invitationId !== invitationId);
3766
+ await this._save();
3767
+ await this.flush();
3768
+ }
3769
+ async addSpace(record) {
3770
+ invariant13(!(this._metadata.spaces ?? []).find((space) => space.key === record.key), "Cannot overwrite existing space in metadata", {
3771
+ F: __dxlog_file18,
3772
+ L: 289,
3773
+ S: this,
3774
+ A: [
3775
+ "!(this._metadata.spaces ?? []).find((space) => space.key === record.key)",
3776
+ "'Cannot overwrite existing space in metadata'"
3777
+ ]
3778
+ });
3779
+ (this._metadata.spaces ??= []).push(record);
3780
+ await this._save();
3781
+ await this.flush();
3782
+ }
3783
+ async setSpaceDataLatestTimeframe(spaceKey, timeframe) {
3784
+ this._getSpace(spaceKey).dataTimeframe = timeframe;
3785
+ await this._save();
3786
+ }
3787
+ async setSpaceControlLatestTimeframe(spaceKey, timeframe) {
3788
+ this._getSpace(spaceKey).controlTimeframe = timeframe;
3789
+ await this._save();
3790
+ await this.flush();
3791
+ }
3792
+ async setCache(spaceKey, cache) {
3793
+ this._getSpace(spaceKey).cache = cache;
3794
+ await this._save();
3795
+ }
3796
+ async setWritableFeedKeys(spaceKey, controlFeedKey, dataFeedKey) {
3797
+ const space = this._getSpace(spaceKey);
3798
+ space.controlFeedKey = controlFeedKey;
3799
+ space.dataFeedKey = dataFeedKey;
3800
+ await this._save();
3801
+ await this.flush();
3802
+ }
3803
+ async setSpaceState(spaceKey, state) {
3804
+ this._getSpace(spaceKey).state = state;
3805
+ await this._save();
3806
+ await this.flush();
3807
+ }
3808
+ getSpaceControlPipelineSnapshot(spaceKey) {
3809
+ return this._getLargeSpaceMetadata(spaceKey).controlPipelineSnapshot;
3810
+ }
3811
+ async setSpaceControlPipelineSnapshot(spaceKey, snapshot) {
3812
+ this._getLargeSpaceMetadata(spaceKey).controlPipelineSnapshot = snapshot;
3813
+ await this._saveSpaceLargeMetadata(spaceKey);
3814
+ await this.flush();
3815
+ }
3816
+ };
3817
+ _ts_decorate9([
3818
+ synchronized5
3819
+ ], MetadataStore.prototype, "load", null);
3820
+ _ts_decorate9([
3821
+ synchronized5
3822
+ ], MetadataStore.prototype, "_save", null);
3823
+ _ts_decorate9([
3824
+ synchronized5
3825
+ ], MetadataStore.prototype, "_saveSpaceLargeMetadata", null);
3826
+ var fromBytesInt32 = (buf) => buf.readInt32LE(0);
3827
+ var hasInvitationExpired = (invitation) => {
3828
+ return Boolean(invitation.created && invitation.lifetime && invitation.lifetime !== 0 && invitation.created.getTime() + invitation.lifetime * 1e3 < Date.now());
3829
+ };
3830
+ var isLegacyInvitationFormat = (invitation) => {
3831
+ return invitation.type === Invitation.Type.MULTIUSE;
2327
3832
  };
2328
- _ts_decorate7([
2329
- synchronized4
2330
- ], SpaceManager.prototype, "open", null);
2331
- _ts_decorate7([
2332
- synchronized4
2333
- ], SpaceManager.prototype, "close", null);
2334
- SpaceManager = _ts_decorate7([
2335
- trackLeaks3("open", "close")
2336
- ], SpaceManager);
2337
3833
 
2338
3834
  export {
2339
- Buffer,
2340
3835
  codec,
2341
3836
  valueEncoding,
2342
3837
  createMappedFeedWriter,
2343
3838
  SnapshotManager,
2344
3839
  SnapshotStore,
2345
3840
  DocumentsSynchronizer,
2346
- DataServiceImpl,
2347
- MetadataStore,
2348
- hasInvitationExpired,
3841
+ diffCollectionState,
3842
+ LevelDBStorageAdapter,
3843
+ encodingOptions,
3844
+ AutomergeHost,
3845
+ getSpaceKeyFromDoc,
3846
+ deriveCollectionIdFromSpaceId,
3847
+ getSpaceIdFromCollectionId,
3848
+ AuthExtension,
2349
3849
  mapTimeframeToFeedIndexes,
2350
3850
  mapFeedIndexesToTimeframe,
2351
3851
  startAfter,
2352
3852
  TimeframeClock,
2353
3853
  Pipeline,
2354
- AuthExtension,
2355
3854
  Space,
2356
3855
  createIdFromSpaceKey,
2357
3856
  CredentialRetrieverExtension,
@@ -2361,6 +3860,10 @@ export {
2361
3860
  SpaceProtocol,
2362
3861
  AuthStatus,
2363
3862
  SpaceProtocolSession,
2364
- SpaceManager
3863
+ SpaceManager,
3864
+ MeshEchoReplicator,
3865
+ DataServiceImpl,
3866
+ MetadataStore,
3867
+ hasInvitationExpired
2365
3868
  };
2366
- //# sourceMappingURL=chunk-UJQ5VS5V.mjs.map
3869
+ //# sourceMappingURL=chunk-6MJEONOX.mjs.map