@dxos/echo-pipeline 0.1.42-next.6e229fb → 0.1.45-next.45667d4

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 (74) hide show
  1. package/dist/lib/browser/{chunk-4YAT2XJW.mjs → chunk-2NGLSBL4.mjs} +1128 -1017
  2. package/dist/lib/browser/chunk-2NGLSBL4.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1 -3
  4. package/dist/lib/browser/index.mjs.map +1 -1
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +72 -48
  7. package/dist/lib/browser/testing/index.mjs.map +3 -3
  8. package/dist/lib/node/index.cjs +1048 -938
  9. package/dist/lib/node/index.cjs.map +4 -4
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/testing/index.cjs +735 -540
  12. package/dist/lib/node/testing/index.cjs.map +4 -4
  13. package/dist/types/src/dbhost/database-host.d.ts +2 -4
  14. package/dist/types/src/dbhost/database-host.d.ts.map +1 -1
  15. package/dist/types/src/dbhost/snapshot-store.d.ts +2 -0
  16. package/dist/types/src/dbhost/snapshot-store.d.ts.map +1 -1
  17. package/dist/types/src/metadata/metadata-store.d.ts +3 -2
  18. package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
  19. package/dist/types/src/pipeline/pipeline.d.ts +27 -11
  20. package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
  21. package/dist/types/src/pipeline/timeframe-clock.d.ts +1 -0
  22. package/dist/types/src/pipeline/timeframe-clock.d.ts.map +1 -1
  23. package/dist/types/src/space/auth.d.ts +4 -4
  24. package/dist/types/src/space/auth.d.ts.map +1 -1
  25. package/dist/types/src/space/control-pipeline.d.ts +2 -4
  26. package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
  27. package/dist/types/src/space/data-pipeline.d.ts +19 -10
  28. package/dist/types/src/space/data-pipeline.d.ts.map +1 -1
  29. package/dist/types/src/space/index.d.ts +0 -1
  30. package/dist/types/src/space/index.d.ts.map +1 -1
  31. package/dist/types/src/space/space-manager.d.ts +14 -14
  32. package/dist/types/src/space/space-manager.d.ts.map +1 -1
  33. package/dist/types/src/space/space-protocol.d.ts +2 -2
  34. package/dist/types/src/space/space-protocol.d.ts.map +1 -1
  35. package/dist/types/src/space/space.d.ts +20 -7
  36. package/dist/types/src/space/space.d.ts.map +1 -1
  37. package/dist/types/src/testing/database-test-rig.d.ts.map +1 -1
  38. package/dist/types/src/testing/test-agent-builder.d.ts +15 -5
  39. package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
  40. package/package.json +30 -30
  41. package/src/common/feeds.ts +2 -2
  42. package/src/dbhost/data-service-host.ts +10 -10
  43. package/src/dbhost/data-service.ts +1 -1
  44. package/src/dbhost/database-host.ts +1 -9
  45. package/src/dbhost/snapshot-store.test.ts +2 -2
  46. package/src/dbhost/snapshot-store.ts +16 -1
  47. package/src/metadata/metadata-store.ts +14 -16
  48. package/src/pipeline/message-selector.ts +1 -1
  49. package/src/pipeline/pipeline.test.ts +68 -44
  50. package/src/pipeline/pipeline.ts +118 -35
  51. package/src/pipeline/timeframe-clock.ts +8 -2
  52. package/src/space/auth.ts +6 -6
  53. package/src/space/control-pipeline.test.ts +19 -20
  54. package/src/space/control-pipeline.ts +3 -5
  55. package/src/space/data-pipeline.ts +131 -69
  56. package/src/space/index.ts +0 -1
  57. package/src/space/replication.browser.test.ts +8 -8
  58. package/src/space/space-manager.browser.test.ts +3 -3
  59. package/src/space/space-manager.ts +25 -20
  60. package/src/space/space-protocol.browser.test.ts +1 -1
  61. package/src/space/space-protocol.test.ts +6 -6
  62. package/src/space/space-protocol.ts +7 -7
  63. package/src/space/space.test.ts +137 -48
  64. package/src/space/space.ts +82 -40
  65. package/src/testing/database-test-rig.ts +25 -24
  66. package/src/testing/test-agent-builder.ts +88 -49
  67. package/src/testing/test-feed-builder.ts +1 -1
  68. package/src/testing/util.ts +15 -15
  69. package/src/tests/database-unit.test.ts +4 -4
  70. package/src/tests/database.test.ts +1 -1
  71. package/dist/lib/browser/chunk-4YAT2XJW.mjs.map +0 -7
  72. package/dist/types/src/space/genesis.d.ts +0 -5
  73. package/dist/types/src/space/genesis.d.ts.map +0 -1
  74. package/src/space/genesis.ts +0 -69
@@ -116,13 +116,18 @@ var MetadataStore = class {
116
116
  await file.close();
117
117
  }
118
118
  }
119
+ _getSpace(spaceKey) {
120
+ const space = this.spaces.find((space2) => space2.key === spaceKey);
121
+ assert(space, "Space not found");
122
+ return space;
123
+ }
119
124
  /**
120
125
  * Clears storage - doesn't work for now.
121
126
  */
122
127
  async clear() {
123
128
  log("clearing all metadata", {}, {
124
129
  file: "metadata-store.ts",
125
- line: 126,
130
+ line: 132,
126
131
  scope: this,
127
132
  callSite: (f, a) => f(...a)
128
133
  });
@@ -143,20 +148,15 @@ var MetadataStore = class {
143
148
  await this._save();
144
149
  }
145
150
  async setSpaceLatestTimeframe(spaceKey, timeframe) {
146
- const space = this.spaces.find((space2) => space2.key === spaceKey);
147
- assert(space, "Space not found");
148
- space.dataTimeframe = timeframe;
151
+ this._getSpace(spaceKey).dataTimeframe = timeframe;
149
152
  await this._save();
150
153
  }
151
- async setSpaceSnapshot(spaceKey, snapshot) {
152
- const space = this.spaces.find((space2) => space2.key === spaceKey);
153
- assert(space, "Space not found");
154
- space.snapshot = snapshot;
154
+ async setCache(spaceKey, cache) {
155
+ this._getSpace(spaceKey).cache = cache;
155
156
  await this._save();
156
157
  }
157
158
  async setWritableFeedKeys(spaceKey, controlFeedKey, dataFeedKey) {
158
- const space = this.spaces.find((space2) => space2.key === spaceKey);
159
- assert(space, "Space not found");
159
+ const space = this._getSpace(spaceKey);
160
160
  space.controlFeedKey = controlFeedKey;
161
161
  space.dataFeedKey = dataFeedKey;
162
162
  await this._save();
@@ -233,6 +233,11 @@ var TimeframeClock = class {
233
233
  get pendingTimeframe() {
234
234
  return this._pendingTimeframe;
235
235
  }
236
+ setTimeframe(timeframe) {
237
+ this._timeframe = timeframe;
238
+ this._pendingTimeframe = timeframe;
239
+ this.update.emit(this._timeframe);
240
+ }
236
241
  updatePendingTimeframe(key, seq) {
237
242
  this._pendingTimeframe = Timeframe.merge(this._pendingTimeframe, new Timeframe([
238
243
  [
@@ -250,12 +255,12 @@ var TimeframeClock = class {
250
255
  return !gaps.isEmpty();
251
256
  }
252
257
  async waitUntilReached(target) {
253
- log2.info("waitUntilReached", {
258
+ log2("waitUntilReached", {
254
259
  target,
255
260
  current: this._timeframe
256
261
  }, {
257
262
  file: "timeframe-clock.ts",
258
- line: 67,
263
+ line: 73,
259
264
  scope: this,
260
265
  callSite: (f, a) => f(...a)
261
266
  });
@@ -266,7 +271,7 @@ var TimeframeClock = class {
266
271
  deps: Timeframe.dependencies(target, this._timeframe)
267
272
  }, {
268
273
  file: "timeframe-clock.ts",
269
- line: 69,
274
+ line: 75,
270
275
  scope: this,
271
276
  callSite: (f, a) => f(...a)
272
277
  });
@@ -280,12 +285,14 @@ __decorate2([
280
285
 
281
286
  // packages/core/echo/echo-pipeline/src/pipeline/pipeline.ts
282
287
  import assert4 from "@dxos/node-std/assert";
283
- import { sleep, synchronized as synchronized2, Trigger } from "@dxos/async";
288
+ import { Event as Event2, sleep, synchronized as synchronized2, Trigger } from "@dxos/async";
284
289
  import { Context, rejectOnDispose } from "@dxos/context";
290
+ import { failUndefined } from "@dxos/debug";
285
291
  import { FeedSetIterator } from "@dxos/feed-store";
286
292
  import { PublicKey } from "@dxos/keys";
287
293
  import { log as log4 } from "@dxos/log";
288
294
  import { Timeframe as Timeframe2 } from "@dxos/timeframe";
295
+ import { ComplexMap } from "@dxos/util";
289
296
 
290
297
  // packages/core/echo/echo-pipeline/src/pipeline/message-selector.ts
291
298
  import debug from "debug";
@@ -317,10 +324,11 @@ var __decorate3 = function(decorators, target, key, desc) {
317
324
  };
318
325
  var PipelineState = class {
319
326
  // prettier-ignore
320
- constructor(_iterator, _timeframeClock) {
321
- this._iterator = _iterator;
327
+ constructor(_feeds, _timeframeClock) {
328
+ this._feeds = _feeds;
322
329
  this._timeframeClock = _timeframeClock;
323
330
  this.timeframeUpdate = this._timeframeClock.update;
331
+ this.stalled = new Event2();
324
332
  }
325
333
  /**
326
334
  * Latest theoretical timeframe based on the last mutation in each feed.
@@ -328,7 +336,7 @@ var PipelineState = class {
328
336
  */
329
337
  // TODO(dmaretskyi): Rename `totalTimeframe`? or `lastTimeframe`.
330
338
  get endTimeframe() {
331
- return mapFeedIndexesToTimeframe(this._iterator.feeds.filter((feed) => feed.length > 0).map((feed) => ({
339
+ return mapFeedIndexesToTimeframe(Array.from(this._feeds.values()).filter((feed) => feed.length > 0).map((feed) => ({
332
340
  feedKey: feed.key,
333
341
  index: feed.length - 1
334
342
  })));
@@ -343,7 +351,7 @@ var PipelineState = class {
343
351
  return this._targetTimeframe ? this._targetTimeframe : new Timeframe2();
344
352
  }
345
353
  get feeds() {
346
- return this._iterator.feeds;
354
+ return Array.from(this._feeds.values());
347
355
  }
348
356
  async waitUntilTimeframe(target) {
349
357
  await this._timeframeClock.waitUntilReached(target);
@@ -366,7 +374,7 @@ var PipelineState = class {
366
374
  target: this.targetTimeframe
367
375
  }, {
368
376
  file: "pipeline.ts",
369
- line: 105,
377
+ line: 109,
370
378
  scope: this,
371
379
  callSite: (f, a) => f(...a)
372
380
  });
@@ -375,7 +383,7 @@ var PipelineState = class {
375
383
  return Timeframe2.dependencies(this.targetTimeframe, this.timeframe).isEmpty();
376
384
  }),
377
385
  ...breakOnStall ? [
378
- this._iterator.stalled.discardParameter().waitForCount(1)
386
+ this.stalled.discardParameter().waitForCount(1)
379
387
  ] : []
380
388
  ]);
381
389
  let done = false;
@@ -396,7 +404,7 @@ var PipelineState = class {
396
404
  dependencies: Timeframe2.dependencies(this.targetTimeframe, this.timeframe)
397
405
  }, {
398
406
  file: "pipeline.ts",
399
- line: 131,
407
+ line: 135,
400
408
  scope: this,
401
409
  callSite: (f, a) => f(...a)
402
410
  });
@@ -408,25 +416,18 @@ var PipelineState = class {
408
416
  }
409
417
  };
410
418
  var Pipeline = class {
411
- // prettier-ignore
412
- constructor(_initialTimeframe = new Timeframe2()) {
413
- this._initialTimeframe = _initialTimeframe;
414
- this._timeframeClock = new TimeframeClock(this._initialTimeframe);
415
- this._feedSetIterator = new FeedSetIterator(createMessageSelector(this._timeframeClock), {
416
- start: startAfter(this._initialTimeframe),
417
- stallTimeout: 1e3
418
- });
419
- this._state = new PipelineState(this._feedSetIterator, this._timeframeClock);
419
+ constructor() {
420
+ this._timeframeClock = new TimeframeClock(new Timeframe2());
421
+ this._feeds = new ComplexMap(PublicKey.hash);
422
+ // External state accessor.
423
+ this._state = new PipelineState(this._feeds, this._timeframeClock);
424
+ // Waits for the message consumer to process the message and yield control back to the pipeline.
420
425
  this._processingTrigger = new Trigger().wake();
426
+ this._pauseTrigger = new Trigger().wake();
427
+ this._isStopping = false;
428
+ this._isStarted = false;
421
429
  this._isOpen = false;
422
- this._feedSetIterator.stalled.on((iterator) => {
423
- log4.warn(`Stalled after ${iterator.options.stallTimeout}ms with ${iterator.size} feeds.`, {}, {
424
- file: "pipeline.ts",
425
- line: 207,
426
- scope: this,
427
- callSite: (f, a) => f(...a)
428
- });
429
- });
430
+ this._isPaused = false;
430
431
  }
431
432
  get state() {
432
433
  return this._state;
@@ -441,10 +442,13 @@ var Pipeline = class {
441
442
  // NOTE: This cannot be synchronized with `stop` because stop waits for the mutation processing to complete,
442
443
  // which might be opening feeds during the mutation processing, which w
443
444
  async addFeed(feed) {
444
- await this._feedSetIterator.addFeed(feed);
445
+ this._feeds.set(feed.key, feed);
446
+ if (this._feedSetIterator) {
447
+ await this._feedSetIterator.addFeed(feed);
448
+ }
445
449
  }
446
450
  hasFeed(feedKey) {
447
- return this._feedSetIterator.hasFeed(feedKey);
451
+ return this._feeds.has(feedKey);
448
452
  }
449
453
  setWriteFeed(feed) {
450
454
  assert4(!this._writer, "Writer already set.");
@@ -457,33 +461,68 @@ var Pipeline = class {
457
461
  async start() {
458
462
  log4("starting...", {}, {
459
463
  file: "pipeline.ts",
460
- line: 249,
464
+ line: 254,
461
465
  scope: this,
462
466
  callSite: (f, a) => f(...a)
463
467
  });
468
+ await this._initIterator();
464
469
  await this._feedSetIterator.open();
465
470
  log4("started", {}, {
466
471
  file: "pipeline.ts",
467
- line: 251,
472
+ line: 257,
468
473
  scope: this,
469
474
  callSite: (f, a) => f(...a)
470
475
  });
476
+ this._isStarted = true;
471
477
  }
472
478
  async stop() {
479
+ var _a;
473
480
  log4("stopping...", {}, {
474
481
  file: "pipeline.ts",
475
- line: 256,
482
+ line: 263,
476
483
  scope: this,
477
484
  callSite: (f, a) => f(...a)
478
485
  });
479
- await this._feedSetIterator.close();
486
+ this._isStopping = true;
487
+ await ((_a = this._feedSetIterator) == null ? void 0 : _a.close());
480
488
  await this._processingTrigger.wait();
481
489
  log4("stopped", {}, {
482
490
  file: "pipeline.ts",
483
- line: 259,
491
+ line: 267,
484
492
  scope: this,
485
493
  callSite: (f, a) => f(...a)
486
494
  });
495
+ this._isStarted = false;
496
+ }
497
+ /**
498
+ * @param timeframe Timeframe of already processed messages. The pipeline will start processing messages AFTER this timeframe.
499
+ */
500
+ async setCursor(timeframe) {
501
+ assert4(!this._isStarted || this._isPaused, "Invalid state.");
502
+ this._timeframeClock.setTimeframe(timeframe);
503
+ if (this._feedSetIterator) {
504
+ await this._feedSetIterator.close();
505
+ await this._initIterator();
506
+ await this._feedSetIterator.open();
507
+ }
508
+ }
509
+ /**
510
+ * Calling pause while processing will cause a deadlock.
511
+ */
512
+ async pause() {
513
+ assert4(this._isStarted, "Pipeline is not open.");
514
+ if (this._isPaused) {
515
+ return;
516
+ }
517
+ this._pauseTrigger.reset();
518
+ await this._processingTrigger.wait();
519
+ this._isPaused = true;
520
+ }
521
+ async unpause() {
522
+ assert4(this._isStarted, "Pipeline is not open.");
523
+ assert4(this._isPaused, "Pipeline is not paused.");
524
+ this._pauseTrigger.wake();
525
+ this._isPaused = false;
487
526
  }
488
527
  /**
489
528
  * Starts to iterate over the ordered messages from the added feeds.
@@ -492,7 +531,21 @@ var Pipeline = class {
492
531
  async *consume() {
493
532
  assert4(!this._isOpen, "Pipeline is already being consumed.");
494
533
  this._isOpen = true;
495
- for await (const block of this._feedSetIterator) {
534
+ assert4(this._feedSetIterator, "Iterator not initialized.");
535
+ let lastFeedSetIterator = this._feedSetIterator;
536
+ let iterable = lastFeedSetIterator[Symbol.asyncIterator]();
537
+ while (!this._isStopping) {
538
+ await this._pauseTrigger.wait();
539
+ if (lastFeedSetIterator !== this._feedSetIterator) {
540
+ assert4(this._feedSetIterator, "Iterator not initialized.");
541
+ lastFeedSetIterator = this._feedSetIterator;
542
+ iterable = lastFeedSetIterator[Symbol.asyncIterator]();
543
+ }
544
+ const { done, value } = await iterable.next();
545
+ if (done) {
546
+ continue;
547
+ }
548
+ const block = value != null ? value : failUndefined();
496
549
  this._processingTrigger.reset();
497
550
  this._timeframeClock.updatePendingTimeframe(PublicKey.from(block.feedKey), block.seq);
498
551
  yield block;
@@ -501,6 +554,24 @@ var Pipeline = class {
501
554
  }
502
555
  this._isOpen = false;
503
556
  }
557
+ async _initIterator() {
558
+ this._feedSetIterator = new FeedSetIterator(createMessageSelector(this._timeframeClock), {
559
+ start: startAfter(this._timeframeClock.timeframe),
560
+ stallTimeout: 1e3
561
+ });
562
+ this._feedSetIterator.stalled.on((iterator) => {
563
+ log4.warn(`Stalled after ${iterator.options.stallTimeout}ms with ${iterator.size} feeds.`, {}, {
564
+ file: "pipeline.ts",
565
+ line: 357,
566
+ scope: this,
567
+ callSite: (f, a) => f(...a)
568
+ });
569
+ this._state.stalled.emit();
570
+ });
571
+ for (const feed of this._feeds.values()) {
572
+ await this._feedSetIterator.addFeed(feed);
573
+ }
574
+ }
504
575
  };
505
576
  __decorate3([
506
577
  synchronized2
@@ -508,6 +579,15 @@ __decorate3([
508
579
  __decorate3([
509
580
  synchronized2
510
581
  ], Pipeline.prototype, "stop", null);
582
+ __decorate3([
583
+ synchronized2
584
+ ], Pipeline.prototype, "setCursor", null);
585
+ __decorate3([
586
+ synchronized2
587
+ ], Pipeline.prototype, "pause", null);
588
+ __decorate3([
589
+ synchronized2
590
+ ], Pipeline.prototype, "unpause", null);
511
591
 
512
592
  // packages/core/echo/echo-pipeline/src/space/auth.ts
513
593
  import assert5 from "@dxos/node-std/assert";
@@ -527,7 +607,6 @@ var AuthExtension = class extends RpcExtension {
527
607
  AuthService: schema3.getService("dxos.mesh.teleport.auth.AuthService")
528
608
  },
529
609
  timeout: 60 * 1e3
530
- // Long timeout because auth can wait for sync in certain cases.
531
610
  });
532
611
  this._authParams = _authParams;
533
612
  this._ctx = new Context2({
@@ -596,131 +675,150 @@ var AuthExtension = class extends RpcExtension {
596
675
  }
597
676
  };
598
677
 
599
- // packages/core/echo/echo-pipeline/src/space/space.ts
678
+ // packages/core/echo/echo-pipeline/src/dbhost/data-service-host.ts
600
679
  import assert6 from "@dxos/node-std/assert";
601
- import { Event as Event2, synchronized as synchronized3, trackLeaks, Lock } from "@dxos/async";
602
- import { log as log7, logInfo } from "@dxos/log";
603
- import { AdmittedFeed as AdmittedFeed2 } from "@dxos/protocols/proto/dxos/halo/credentials";
604
- import { Callback as Callback2 } from "@dxos/util";
605
-
606
- // packages/core/echo/echo-pipeline/src/space/control-pipeline.ts
607
- import { SpaceStateMachine } from "@dxos/credentials";
608
- import { PublicKey as PublicKey2 } from "@dxos/keys";
680
+ import { Stream } from "@dxos/codec-protobuf";
681
+ import { tagMutationsInBatch, setMetadataOnObject } from "@dxos/echo-db";
609
682
  import { log as log6 } from "@dxos/log";
610
- import { AdmittedFeed } from "@dxos/protocols/proto/dxos/halo/credentials";
611
- import { Callback } from "@dxos/util";
612
- var ControlPipeline = class {
613
- constructor({ spaceKey, genesisFeed, feedProvider, initialTimeframe }) {
614
- this.onFeedAdmitted = new Callback();
615
- this._pipeline = new Pipeline(initialTimeframe);
616
- void this._pipeline.addFeed(genesisFeed);
617
- this._spaceStateMachine = new SpaceStateMachine(spaceKey);
618
- this._spaceStateMachine.onFeedAdmitted.set(async (info) => {
619
- log6("feed admitted", {
620
- key: info.key
621
- }, {
622
- file: "control-pipeline.ts",
623
- line: 41,
624
- scope: this,
625
- callSite: (f, a) => f(...a)
683
+ import { ComplexMap as ComplexMap2 } from "@dxos/util";
684
+ var DataServiceHost = class {
685
+ constructor(_itemManager, _itemDemuxer, _writeStream) {
686
+ this._itemManager = _itemManager;
687
+ this._itemDemuxer = _itemDemuxer;
688
+ this._writeStream = _writeStream;
689
+ this._clientTagMap = new ComplexMap2(([feedKey, seq]) => `${feedKey.toHex()}:${seq}`);
690
+ }
691
+ /**
692
+ * Real-time subscription to data objects in a space.
693
+ */
694
+ subscribe() {
695
+ return new Stream(({ next, ctx }) => {
696
+ const objects = Array.from(this._itemManager.entities.values()).map((entity) => entity.createSnapshot());
697
+ next({
698
+ batch: {
699
+ objects
700
+ }
626
701
  });
627
- if (info.assertion.designation === AdmittedFeed.Designation.CONTROL && !info.key.equals(genesisFeed.key)) {
628
- try {
629
- const feed = await feedProvider(info.key);
630
- await this._pipeline.addFeed(feed);
631
- } catch (err) {
632
- log6.catch(err, {}, {
633
- file: "control-pipeline.ts",
634
- line: 49,
635
- scope: this,
636
- callSite: (f, a) => f(...a)
637
- });
702
+ this._itemDemuxer.mutation.on(ctx, (message) => {
703
+ var _a;
704
+ const { batch, meta } = message;
705
+ log6("message", {
706
+ batch,
707
+ meta
708
+ }, {
709
+ file: "data-service-host.ts",
710
+ line: 49,
711
+ scope: this,
712
+ callSite: (f, a) => f(...a)
713
+ });
714
+ const clientTag = this._clientTagMap.get([
715
+ message.meta.feedKey,
716
+ message.meta.seq
717
+ ]);
718
+ (_a = batch.objects) == null ? void 0 : _a.forEach((object) => {
719
+ setMetadataOnObject(object, meta);
720
+ });
721
+ if (clientTag) {
722
+ tagMutationsInBatch(batch, clientTag);
638
723
  }
639
- }
640
- await this.onFeedAdmitted.callIfSet(info);
724
+ next({
725
+ clientTag,
726
+ feedKey: message.meta.feedKey,
727
+ seq: message.meta.seq,
728
+ batch
729
+ });
730
+ });
641
731
  });
642
- this.onMemberAdmitted = this._spaceStateMachine.onMemberAdmitted;
643
- this.onCredentialProcessed = this._spaceStateMachine.onCredentialProcessed;
644
- }
645
- get spaceState() {
646
- return this._spaceStateMachine;
647
- }
648
- get pipeline() {
649
- return this._pipeline;
650
- }
651
- setWriteFeed(feed) {
652
- this._pipeline.setWriteFeed(feed);
653
732
  }
654
- async start() {
655
- log6("starting...", {}, {
656
- file: "control-pipeline.ts",
657
- line: 73,
733
+ async write(request) {
734
+ var _a, _b, _c;
735
+ assert6(this._writeStream, "Cannot write mutations in readonly mode");
736
+ log6("write", {
737
+ clientTag: request.clientTag,
738
+ objectCount: (_b = (_a = request.batch.objects) == null ? void 0 : _a.length) != null ? _b : 0
739
+ }, {
740
+ file: "data-service-host.ts",
741
+ line: 77,
658
742
  scope: this,
659
743
  callSite: (f, a) => f(...a)
660
744
  });
661
- setTimeout(async () => {
662
- for await (const msg of this._pipeline.consume()) {
663
- try {
664
- log6("processing", {
665
- key: msg.feedKey,
666
- seq: msg.seq
745
+ const message = {
746
+ batch: {
747
+ objects: (_c = request.batch.objects) == null ? void 0 : _c.map((object) => {
748
+ var _a2;
749
+ return {
750
+ ...object,
751
+ mutations: (_a2 = object.mutations) == null ? void 0 : _a2.map((mutation) => ({
752
+ ...mutation,
753
+ meta: void 0
754
+ })),
755
+ meta: void 0
756
+ };
757
+ })
758
+ }
759
+ };
760
+ const receipt = await this._writeStream.write(message, {
761
+ afterWrite: async (receipt2) => {
762
+ if (request.clientTag) {
763
+ log6("tag", {
764
+ clientTag: request.clientTag,
765
+ feedKey: receipt2.feedKey,
766
+ seq: receipt2.seq
667
767
  }, {
668
- file: "control-pipeline.ts",
669
- line: 78,
670
- scope: this,
671
- callSite: (f, a) => f(...a)
672
- });
673
- if (msg.data.payload.credential) {
674
- const result = await this._spaceStateMachine.process(msg.data.payload.credential.credential, PublicKey2.from(msg.feedKey));
675
- if (!result) {
676
- log6.warn("processing failed", {
677
- msg
678
- }, {
679
- file: "control-pipeline.ts",
680
- line: 85,
681
- scope: this,
682
- callSite: (f, a) => f(...a)
683
- });
684
- }
685
- }
686
- } catch (err) {
687
- log6.catch(err, {}, {
688
- file: "control-pipeline.ts",
689
- line: 89,
768
+ file: "data-service-host.ts",
769
+ line: 96,
690
770
  scope: this,
691
771
  callSite: (f, a) => f(...a)
692
772
  });
773
+ this._clientTagMap.set([
774
+ receipt2.feedKey,
775
+ receipt2.seq
776
+ ], request.clientTag);
693
777
  }
694
778
  }
695
779
  });
696
- await this._pipeline.start();
697
- log6("started", {}, {
698
- file: "control-pipeline.ts",
699
- line: 95,
700
- scope: this,
701
- callSite: (f, a) => f(...a)
702
- });
780
+ return receipt;
703
781
  }
704
- async stop() {
705
- log6("stopping...", {}, {
706
- file: "control-pipeline.ts",
707
- line: 99,
708
- scope: this,
709
- callSite: (f, a) => f(...a)
710
- });
711
- await this._pipeline.stop();
712
- log6("stopped", {}, {
713
- file: "control-pipeline.ts",
714
- line: 101,
715
- scope: this,
716
- callSite: (f, a) => f(...a)
717
- });
782
+ };
783
+
784
+ // packages/core/echo/echo-pipeline/src/dbhost/database-host.ts
785
+ import { ItemDemuxer } from "@dxos/echo-db";
786
+ var DatabaseHost = class {
787
+ constructor(_outboundStream) {
788
+ this._outboundStream = _outboundStream;
789
+ }
790
+ get isReadOnly() {
791
+ return !!this._outboundStream;
792
+ }
793
+ get echoProcessor() {
794
+ return this._echoProcessor;
795
+ }
796
+ async open(itemManager, modelFactory) {
797
+ this._itemManager = itemManager;
798
+ this._itemManager._debugLabel = "host";
799
+ this._itemDemuxer = new ItemDemuxer(itemManager, modelFactory);
800
+ this._echoProcessor = this._itemDemuxer.open();
801
+ }
802
+ async close() {
803
+ }
804
+ getWriteStream() {
805
+ return this._outboundStream;
806
+ }
807
+ createSnapshot() {
808
+ return this._itemDemuxer.createSnapshot();
809
+ }
810
+ createDataServiceHost() {
811
+ var _a;
812
+ return new DataServiceHost(this._itemManager, this._itemDemuxer, (_a = this._outboundStream) != null ? _a : void 0);
718
813
  }
719
814
  };
720
815
 
721
- // packages/core/echo/echo-pipeline/src/space/space.ts
722
- var __decorate4 = function(decorators, target, key, desc) {
723
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
816
+ // packages/core/echo/echo-pipeline/src/dbhost/snapshot-manager.ts
817
+ import { trackLeaks } from "@dxos/async";
818
+ import { schema as schema4 } from "@dxos/protocols";
819
+ import { ObjectSync } from "@dxos/teleport-extension-object-sync";
820
+ var __decorate4 = function(decorators, target, key, desc) {
821
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
724
822
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
725
823
  r = Reflect.decorate(decorators, target, key, desc);
726
824
  else
@@ -729,183 +827,164 @@ var __decorate4 = function(decorators, target, key, desc) {
729
827
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
730
828
  return c > 3 && r && Object.defineProperty(target, key, r), r;
731
829
  };
732
- var Space = class Space2 {
733
- constructor({ spaceKey, protocol, genesisFeed, feedProvider }) {
734
- this._addFeedLock = new Lock();
735
- this.onCredentialProcessed = new Callback2();
736
- this.stateUpdate = new Event2();
737
- this._isOpen = false;
738
- assert6(spaceKey && feedProvider);
739
- this._key = spaceKey;
740
- this._genesisFeedKey = genesisFeed.key;
741
- this._feedProvider = feedProvider;
742
- this._controlPipeline = new ControlPipeline({
743
- spaceKey,
744
- genesisFeed,
745
- feedProvider
746
- });
747
- this._controlPipeline.onFeedAdmitted.set(async (info) => {
748
- if (info.assertion.designation === AdmittedFeed2.Designation.DATA) {
749
- await this._addFeedLock.executeSynchronized(async () => {
750
- if (this._dataPipeline) {
751
- if (!this._dataPipeline.hasFeed(info.key)) {
752
- return this._dataPipeline.addFeed(await this._feedProvider(info.key));
753
- }
754
- }
755
- });
756
- }
757
- if (!info.key.equals(genesisFeed.key)) {
758
- this.protocol.addFeed(await feedProvider(info.key));
830
+ var SnapshotManager = class SnapshotManager2 {
831
+ // prettier-ignore
832
+ constructor(_snapshotStore) {
833
+ this._snapshotStore = _snapshotStore;
834
+ this._objectSync = new ObjectSync({
835
+ getObject: async (id) => {
836
+ const snapshot = await this._snapshotStore.loadSnapshot(id);
837
+ if (!snapshot) {
838
+ return void 0;
839
+ }
840
+ return {
841
+ id,
842
+ payload: schema4.getCodecForType("dxos.echo.snapshot.SpaceSnapshot").encodeAsAny(snapshot)
843
+ };
844
+ },
845
+ setObject: async (data) => {
846
+ const snapshot = schema4.getCodecForType("dxos.echo.snapshot.SpaceSnapshot").decode(data.payload);
847
+ await this._snapshotStore.saveSnapshot(snapshot);
759
848
  }
760
849
  });
761
- this._controlPipeline.onCredentialProcessed.set(async (credential) => {
762
- await this.onCredentialProcessed.callIfSet(credential);
763
- log7("onCredentialProcessed", {
764
- credential
765
- }, {
766
- file: "space.ts",
767
- line: 87,
768
- scope: this,
769
- callSite: (f, a) => f(...a)
770
- });
771
- this.stateUpdate.emit();
772
- });
773
- this.protocol = protocol;
774
- this.protocol.addFeed(genesisFeed);
775
850
  }
776
- get key() {
777
- return this._key;
851
+ get objectSync() {
852
+ return this._objectSync;
778
853
  }
779
- get isOpen() {
780
- return this._isOpen;
854
+ async open() {
855
+ await this._objectSync.open();
781
856
  }
782
- get genesisFeedKey() {
783
- return this._genesisFeedKey;
857
+ async close() {
858
+ await this._objectSync.close();
784
859
  }
785
- get controlFeedKey() {
786
- var _a;
787
- return (_a = this._controlFeed) == null ? void 0 : _a.key;
860
+ async load(id) {
861
+ const local = await this._snapshotStore.loadSnapshot(id);
862
+ if (local) {
863
+ return local;
864
+ }
865
+ const remote = await this._objectSync.download(id);
866
+ return schema4.getCodecForType("dxos.echo.snapshot.SpaceSnapshot").decode(remote.payload);
788
867
  }
789
- get dataFeedKey() {
790
- var _a;
791
- return (_a = this._dataFeed) == null ? void 0 : _a.key;
868
+ async store(snapshot) {
869
+ const id = await this._snapshotStore.saveSnapshot(snapshot);
870
+ return id;
792
871
  }
793
- get spaceState() {
794
- return this._controlPipeline.spaceState;
872
+ };
873
+ SnapshotManager = __decorate4([
874
+ trackLeaks("open", "close")
875
+ ], SnapshotManager);
876
+
877
+ // packages/core/echo/echo-pipeline/src/dbhost/snapshot-store.ts
878
+ import { subtleCrypto } from "@dxos/crypto";
879
+ import { schema as schema5 } from "@dxos/protocols";
880
+ var SnapshotStore = class {
881
+ // prettier-ignore
882
+ constructor(_directory) {
883
+ this._directory = _directory;
795
884
  }
796
- /**
797
- * @test-only
798
- */
799
- get controlPipeline() {
800
- return this._controlPipeline.pipeline;
885
+ async saveSnapshot(snapshot) {
886
+ const encoded = schema5.getCodecForType("dxos.echo.snapshot.SpaceSnapshot").encode(snapshot);
887
+ const key = await subtleCrypto.digest("SHA-256", encoded);
888
+ const keyString = Buffer.from(key).toString("hex");
889
+ const file = await this._directory.getOrCreateFile(keyString);
890
+ try {
891
+ await file.write(0, Buffer.from(encoded));
892
+ } finally {
893
+ await file.close();
894
+ }
895
+ return keyString;
801
896
  }
802
- setControlFeed(feed) {
803
- assert6(!this._controlFeed, "Control feed already set.");
804
- this._controlFeed = feed;
805
- this._controlPipeline.setWriteFeed(feed);
806
- return this;
897
+ async loadSnapshot(key) {
898
+ const file = await this._directory.getOrCreateFile(key);
899
+ try {
900
+ const { size } = await file.stat();
901
+ if (size === 0) {
902
+ return void 0;
903
+ }
904
+ const buffer = await file.read(0, size);
905
+ return schema5.getCodecForType("dxos.echo.snapshot.SpaceSnapshot").decode(buffer);
906
+ } finally {
907
+ await file.close();
908
+ }
807
909
  }
808
- setDataFeed(feed) {
809
- var _a;
810
- assert6(!this._dataFeed, "Data feed already set.");
811
- this._dataFeed = feed;
812
- (_a = this._dataPipeline) == null ? void 0 : _a.setWriteFeed(feed);
813
- return this;
910
+ async listSnapshots() {
911
+ const entries = await this._directory.list();
912
+ return await Promise.all(entries.map(async (key) => {
913
+ const { size } = await this._directory.getOrCreateFile(key).stat();
914
+ return {
915
+ key,
916
+ size
917
+ };
918
+ }));
814
919
  }
815
- /**
816
- * Use for diagnostics.
817
- */
818
- getControlFeeds() {
819
- return Array.from(this._controlPipeline.spaceState.feeds.values());
920
+ };
921
+
922
+ // packages/core/echo/echo-pipeline/src/dbhost/data-service.ts
923
+ import assert7 from "@dxos/node-std/assert";
924
+ import { raise } from "@dxos/debug";
925
+ import { PublicKey as PublicKey2 } from "@dxos/keys";
926
+ import { log as log7 } from "@dxos/log";
927
+ import { ComplexMap as ComplexMap3 } from "@dxos/util";
928
+ var DataServiceSubscriptions = class {
929
+ constructor() {
930
+ this._spaces = new ComplexMap3(PublicKey2.hash);
820
931
  }
821
- /**
822
- * Use for diagnostics.
823
- */
824
- // getDataFeeds(): FeedInfo[] {
825
- // return this._dataPipeline?.getFeeds();
826
- // }
827
- async open() {
828
- log7("opening...", {}, {
829
- file: "space.ts",
830
- line: 157,
831
- scope: this,
832
- callSite: (f, a) => f(...a)
833
- });
834
- if (this._isOpen) {
835
- return;
836
- }
837
- await this._controlPipeline.start();
838
- await this.protocol.start();
839
- this._isOpen = true;
840
- log7("opened", {}, {
841
- file: "space.ts",
842
- line: 167,
843
- scope: this,
844
- callSite: (f, a) => f(...a)
845
- });
932
+ clear() {
933
+ this._spaces.clear();
846
934
  }
847
- async close() {
848
- log7("closing...", {
849
- key: this._key
935
+ registerSpace(spaceKey, host) {
936
+ log7("Registering space", {
937
+ spaceKey
850
938
  }, {
851
- file: "space.ts",
852
- line: 172,
939
+ file: "data-service.ts",
940
+ line: 31,
853
941
  scope: this,
854
942
  callSite: (f, a) => f(...a)
855
943
  });
856
- if (!this._isOpen) {
857
- return;
858
- }
859
- await this.protocol.stop();
860
- await this._controlPipeline.stop();
861
- this._isOpen = false;
862
- log7("closed", {}, {
863
- file: "space.ts",
864
- line: 182,
944
+ this._spaces.set(spaceKey, host);
945
+ }
946
+ unregisterSpace(spaceKey) {
947
+ log7("Unregistering space", {
948
+ spaceKey
949
+ }, {
950
+ file: "data-service.ts",
951
+ line: 36,
865
952
  scope: this,
866
953
  callSite: (f, a) => f(...a)
867
954
  });
955
+ this._spaces.delete(spaceKey);
868
956
  }
869
- // TODO(dmaretskyi): Make reusable.
870
- async createDataPipeline({ start }) {
871
- assert6(this._isOpen, "Space must be open to initialize data pipeline.");
872
- assert6(!this._dataPipeline, "Data pipeline already initialized.");
873
- const pipeline = new Pipeline(start);
874
- if (this._dataFeed) {
875
- pipeline.setWriteFeed(this._dataFeed);
876
- }
877
- this._dataPipeline = pipeline;
878
- await this._addFeedLock.executeSynchronized(async () => {
879
- for (const feed of this._controlPipeline.spaceState.feeds.values()) {
880
- if (feed.assertion.designation === AdmittedFeed2.Designation.DATA && !pipeline.hasFeed(feed.key)) {
881
- await pipeline.addFeed(await this._feedProvider(feed.key));
882
- }
883
- }
884
- });
885
- return pipeline;
957
+ getDataService(spaceKey) {
958
+ return this._spaces.get(spaceKey);
959
+ }
960
+ };
961
+ var DataServiceImpl = class {
962
+ constructor(_subscriptions) {
963
+ this._subscriptions = _subscriptions;
964
+ }
965
+ subscribe(request) {
966
+ var _a;
967
+ assert7(request.spaceKey);
968
+ const host = (_a = this._subscriptions.getDataService(request.spaceKey)) != null ? _a : raise(new Error(`space not found: ${request.spaceKey}`));
969
+ return host.subscribe();
970
+ }
971
+ write(request) {
972
+ var _a;
973
+ assert7(request.spaceKey);
974
+ assert7(request.batch);
975
+ const host = (_a = this._subscriptions.getDataService(request.spaceKey)) != null ? _a : raise(new Error(`space not found: ${request.spaceKey}`));
976
+ return host.write(request);
886
977
  }
887
978
  };
888
- __decorate4([
889
- logInfo
890
- ], Space.prototype, "key", null);
891
- __decorate4([
892
- synchronized3
893
- ], Space.prototype, "open", null);
894
- __decorate4([
895
- synchronized3
896
- ], Space.prototype, "close", null);
897
- Space = __decorate4([
898
- trackLeaks("open", "close")
899
- ], Space);
900
979
 
901
- // packages/core/echo/echo-pipeline/src/space/space-protocol.ts
902
- import { discoveryKey, sha256 } from "@dxos/crypto";
903
- import { PublicKey as PublicKey3 } from "@dxos/keys";
904
- import { log as log8, logInfo as logInfo2 } from "@dxos/log";
905
- import { MMSTTopology } from "@dxos/network-manager";
906
- import { Teleport } from "@dxos/teleport";
907
- import { ReplicatorExtension } from "@dxos/teleport-extension-replicator";
908
- import { ComplexMap } from "@dxos/util";
980
+ // packages/core/echo/echo-pipeline/src/space/data-pipeline.ts
981
+ import assert8 from "@dxos/node-std/assert";
982
+ import { Event as Event3, scheduleTask as scheduleTask2, synchronized as synchronized3, trackLeaks as trackLeaks2 } from "@dxos/async";
983
+ import { Context as Context3 } from "@dxos/context";
984
+ import { getCredentialAssertion } from "@dxos/credentials";
985
+ import { getStateMachineFromItem, ItemManager } from "@dxos/echo-db";
986
+ import { log as log8 } from "@dxos/log";
987
+ import { Timeframe as Timeframe3 } from "@dxos/timeframe";
909
988
  var __decorate5 = function(decorators, target, key, desc) {
910
989
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
911
990
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
@@ -916,468 +995,594 @@ var __decorate5 = function(decorators, target, key, desc) {
916
995
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
917
996
  return c > 3 && r && Object.defineProperty(target, key, r), r;
918
997
  };
919
- var MOCK_AUTH_PROVIDER = async (nonce) => Buffer.from("mock");
920
- var MOCK_AUTH_VERIFIER = async (nonce, credential) => true;
921
- var SpaceProtocol = class {
922
- constructor({ topic, swarmIdentity, networkManager, onSessionAuth }) {
923
- this._feeds = /* @__PURE__ */ new Set();
924
- this._sessions = new ComplexMap(PublicKey3.hash);
925
- this._networkManager = networkManager;
926
- this._swarmIdentity = swarmIdentity;
927
- this._onSessionAuth = onSessionAuth;
928
- this._topic = PublicKey3.from(discoveryKey(sha256(topic.toHex())));
929
- }
930
- get sessions() {
931
- return this._sessions;
998
+ var MESSAGES_PER_SNAPSHOT = 10;
999
+ var AUTOMATIC_SNAPSHOT_DEBOUNCE_INTERVAL = 5e3;
1000
+ var TIMEFRAME_SAVE_DEBOUNCE_INTERVAL = 500;
1001
+ var DataPipeline = class DataPipeline2 {
1002
+ constructor(_params) {
1003
+ this._params = _params;
1004
+ this._ctx = new Context3();
1005
+ this._lastAutomaticSnapshotTimeframe = new Timeframe3();
1006
+ this._isOpen = false;
1007
+ this._lastTimeframeSaveTime = 0;
1008
+ this._lastSnapshotSaveTime = 0;
1009
+ this.onNewEpoch = new Event3();
932
1010
  }
933
- get feeds() {
934
- return this._feeds;
1011
+ get isOpen() {
1012
+ return this._isOpen;
935
1013
  }
936
- get _ownPeerKey() {
937
- return this._swarmIdentity.peerKey;
1014
+ get pipeline() {
1015
+ return this._pipeline;
938
1016
  }
939
- // TODO(burdon): Create abstraction for Space (e.g., add keys and have provider).
940
- addFeed(feed) {
941
- log8("addFeed", {
942
- key: feed.key
943
- }, {
944
- file: "space-protocol.ts",
945
- line: 84,
946
- scope: this,
947
- callSite: (f, a) => f(...a)
948
- });
949
- this._feeds.add(feed);
950
- for (const session of this._sessions.values()) {
951
- session.replicator.addFeed(feed);
952
- }
1017
+ get pipelineState() {
1018
+ var _a;
1019
+ return (_a = this._pipeline) == null ? void 0 : _a.state;
953
1020
  }
954
- async start() {
955
- if (this._connection) {
1021
+ setTargetTimeframe(timeframe) {
1022
+ var _a;
1023
+ this._targetTimeframe = timeframe;
1024
+ (_a = this._pipeline) == null ? void 0 : _a.state.setTargetTimeframe(timeframe);
1025
+ }
1026
+ createCredentialProcessor() {
1027
+ return {
1028
+ process: async (credential) => {
1029
+ const assertion = getCredentialAssertion(credential);
1030
+ if (assertion["@type"] !== "dxos.halo.credentials.Epoch") {
1031
+ return;
1032
+ }
1033
+ log8("new epoch", {
1034
+ credential
1035
+ }, {
1036
+ file: "data-pipeline.ts",
1037
+ line: 110,
1038
+ scope: this,
1039
+ callSite: (f, a) => f(...a)
1040
+ });
1041
+ await this._processEpoch(assertion);
1042
+ this.currentEpoch = credential;
1043
+ this.onNewEpoch.emit(credential);
1044
+ }
1045
+ };
1046
+ }
1047
+ async open() {
1048
+ if (this._isOpen) {
956
1049
  return;
957
1050
  }
958
- const credentials = await this._swarmIdentity.credentialProvider(Buffer.from(""));
959
- const topologyConfig = {
960
- originateConnections: 4,
961
- maxPeers: 10,
962
- sampleSize: 20
1051
+ this._pipeline = new Pipeline();
1052
+ await this._params.onPipelineCreated(this._pipeline);
1053
+ await this._pipeline.start();
1054
+ await this._pipeline.pause();
1055
+ if (this._targetTimeframe) {
1056
+ this._pipeline.state.setTargetTimeframe(this._targetTimeframe);
1057
+ }
1058
+ const feedWriter = {
1059
+ write: (data, options) => {
1060
+ assert8(this._pipeline, "Pipeline is not initialized.");
1061
+ assert8(this.currentEpoch, "Epoch is not initialized.");
1062
+ return this._pipeline.writer.write({
1063
+ data
1064
+ }, options);
1065
+ }
963
1066
  };
964
- log8("starting...", {}, {
965
- file: "space-protocol.ts",
966
- line: 107,
967
- scope: this,
968
- callSite: (f, a) => f(...a)
969
- });
970
- this._connection = await this._networkManager.joinSwarm({
971
- protocolProvider: this._createProtocolProvider(credentials),
972
- peerId: this._swarmIdentity.peerKey,
973
- topic: this._topic,
974
- topology: new MMSTTopology(topologyConfig),
975
- label: `Protocol swarm: ${this._topic}`
1067
+ this.databaseHost = new DatabaseHost(feedWriter);
1068
+ this.itemManager = new ItemManager(this._params.modelFactory);
1069
+ await this.databaseHost.open(this.itemManager, this._params.modelFactory);
1070
+ scheduleTask2(this._ctx, async () => {
1071
+ await this._consumePipeline();
976
1072
  });
977
- log8("started", {}, {
978
- file: "space-protocol.ts",
979
- line: 116,
1073
+ this._isOpen = true;
1074
+ }
1075
+ async close() {
1076
+ var _a, _b, _c;
1077
+ if (!this._isOpen) {
1078
+ return;
1079
+ }
1080
+ log8("close", {}, {
1081
+ file: "data-pipeline.ts",
1082
+ line: 162,
980
1083
  scope: this,
981
1084
  callSite: (f, a) => f(...a)
982
1085
  });
983
- }
984
- async stop() {
985
- if (this._connection) {
986
- log8("stopping...", {}, {
987
- file: "space-protocol.ts",
988
- line: 121,
1086
+ this._isOpen = false;
1087
+ await this._ctx.dispose();
1088
+ await ((_a = this._pipeline) == null ? void 0 : _a.stop());
1089
+ try {
1090
+ await this._saveCache();
1091
+ if (this._pipeline) {
1092
+ await this._saveTargetTimeframe(this._pipeline.state.timeframe);
1093
+ }
1094
+ } catch (err) {
1095
+ log8.catch(err, {}, {
1096
+ file: "data-pipeline.ts",
1097
+ line: 175,
989
1098
  scope: this,
990
1099
  callSite: (f, a) => f(...a)
991
1100
  });
992
- await this._connection.close();
993
- log8("stopped", {}, {
994
- file: "space-protocol.ts",
995
- line: 123,
1101
+ }
1102
+ await ((_b = this.databaseHost) == null ? void 0 : _b.close());
1103
+ await ((_c = this.itemManager) == null ? void 0 : _c.destroy());
1104
+ await this._params.snapshotManager.close();
1105
+ }
1106
+ async _consumePipeline() {
1107
+ assert8(this._pipeline, "Pipeline is not initialized.");
1108
+ for await (const msg of this._pipeline.consume()) {
1109
+ const { feedKey, seq, data } = msg;
1110
+ log8("processing message", {
1111
+ feedKey,
1112
+ seq
1113
+ }, {
1114
+ file: "data-pipeline.ts",
1115
+ line: 187,
996
1116
  scope: this,
997
1117
  callSite: (f, a) => f(...a)
998
1118
  });
1119
+ try {
1120
+ if (data.payload.data) {
1121
+ const feedInfo = this._params.feedInfoProvider(feedKey);
1122
+ if (!feedInfo) {
1123
+ log8.error("Could not find feed.", {
1124
+ feedKey
1125
+ }, {
1126
+ file: "data-pipeline.ts",
1127
+ line: 193,
1128
+ scope: this,
1129
+ callSite: (f, a) => f(...a)
1130
+ });
1131
+ continue;
1132
+ }
1133
+ await this.databaseHost.echoProcessor({
1134
+ batch: data.payload.data.batch,
1135
+ meta: {
1136
+ feedKey,
1137
+ seq,
1138
+ timeframe: data.timeframe,
1139
+ memberKey: feedInfo.assertion.identityKey
1140
+ }
1141
+ });
1142
+ await this._noteTargetStateIfNeeded(this._pipeline.state.pendingTimeframe);
1143
+ }
1144
+ } catch (err) {
1145
+ log8.catch(err, {}, {
1146
+ file: "data-pipeline.ts",
1147
+ line: 211,
1148
+ scope: this,
1149
+ callSite: (f, a) => f(...a)
1150
+ });
1151
+ }
999
1152
  }
1000
1153
  }
1001
- _createProtocolProvider(credentials) {
1002
- return (wireParams) => {
1003
- const session = new SpaceProtocolSession({
1004
- wireParams,
1005
- swarmIdentity: this._swarmIdentity,
1006
- onSessionAuth: this._onSessionAuth
1154
+ _createSnapshot() {
1155
+ assert8(this.databaseHost, "Database backend is not initialized.");
1156
+ return {
1157
+ spaceKey: this._params.spaceKey.asUint8Array(),
1158
+ timeframe: this._pipeline.state.timeframe,
1159
+ database: this.databaseHost.createSnapshot()
1160
+ };
1161
+ }
1162
+ async _saveTargetTimeframe(timeframe) {
1163
+ var _a;
1164
+ const newTimeframe = Timeframe3.merge((_a = this._targetTimeframe) != null ? _a : new Timeframe3(), timeframe);
1165
+ await this._params.metadataStore.setSpaceLatestTimeframe(this._params.spaceKey, newTimeframe);
1166
+ this._targetTimeframe = newTimeframe;
1167
+ }
1168
+ async _saveCache() {
1169
+ var _a;
1170
+ const cache = {};
1171
+ try {
1172
+ const propertiesItem = this.itemManager.items.find((item) => {
1173
+ var _a2, _b;
1174
+ return ((_a2 = item.modelMeta) == null ? void 0 : _a2.type) === "dxos:model/document" && ((_b = getStateMachineFromItem(item)) == null ? void 0 : _b.snapshot().type) === "dxos.sdk.client.Properties";
1007
1175
  });
1008
- this._sessions.set(wireParams.remotePeerId, session);
1009
- for (const feed of this._feeds) {
1010
- session.replicator.addFeed(feed);
1176
+ if (propertiesItem) {
1177
+ cache.properties = (_a = getStateMachineFromItem(propertiesItem)) == null ? void 0 : _a.snapshot();
1011
1178
  }
1012
- return session;
1013
- };
1179
+ } catch (err) {
1180
+ log8.warn("Failed to cache properties", err, {
1181
+ file: "data-pipeline.ts",
1182
+ line: 245,
1183
+ scope: this,
1184
+ callSite: (f, a) => f(...a)
1185
+ });
1186
+ }
1187
+ await this._params.metadataStore.setCache(this._params.spaceKey, cache);
1014
1188
  }
1015
- };
1016
- __decorate5([
1017
- logInfo2
1018
- ], SpaceProtocol.prototype, "_topic", void 0);
1019
- __decorate5([
1020
- logInfo2
1021
- ], SpaceProtocol.prototype, "_ownPeerKey", null);
1022
- var AuthStatus;
1023
- (function(AuthStatus2) {
1024
- AuthStatus2["INITIAL"] = "INITIAL";
1025
- AuthStatus2["SUCCESS"] = "SUCCESS";
1026
- AuthStatus2["FAILURE"] = "FAILURE";
1027
- })(AuthStatus || (AuthStatus = {}));
1028
- var SpaceProtocolSession = class {
1029
- // TODO(dmaretskyi): Allow to pass in extra extensions.
1030
- constructor({ wireParams, swarmIdentity, onSessionAuth }) {
1031
- // TODO(dmaretskyi): Start with upload=false when switching it on the fly works.
1032
- this.replicator = new ReplicatorExtension().setOptions({
1033
- upload: true
1034
- });
1035
- this._authStatus = AuthStatus.INITIAL;
1036
- this._wireParams = wireParams;
1037
- this._swarmIdentity = swarmIdentity;
1038
- this._onSessionAuth = onSessionAuth;
1039
- this._teleport = new Teleport(wireParams);
1189
+ async _noteTargetStateIfNeeded(timeframe) {
1190
+ if (Date.now() - this._lastTimeframeSaveTime > TIMEFRAME_SAVE_DEBOUNCE_INTERVAL) {
1191
+ this._lastTimeframeSaveTime = Date.now();
1192
+ await this._saveTargetTimeframe(timeframe);
1193
+ }
1194
+ if (Date.now() - this._lastSnapshotSaveTime > AUTOMATIC_SNAPSHOT_DEBOUNCE_INTERVAL && timeframe.totalMessages() - this._lastAutomaticSnapshotTimeframe.totalMessages() > MESSAGES_PER_SNAPSHOT) {
1195
+ await this._saveCache();
1196
+ }
1040
1197
  }
1041
- get authStatus() {
1042
- return this._authStatus;
1198
+ async _processEpoch(epoch) {
1199
+ assert8(this._isOpen);
1200
+ assert8(this._pipeline);
1201
+ if (epoch.snapshotCid) {
1202
+ const snapshot = await this._params.snapshotManager.load(epoch.snapshotCid);
1203
+ this.databaseHost._itemDemuxer.restoreFromSnapshot(snapshot.database);
1204
+ }
1205
+ await this._pipeline.pause();
1206
+ await this._pipeline.setCursor(epoch.timeframe);
1207
+ await this._pipeline.unpause();
1043
1208
  }
1044
- get stream() {
1045
- return this._teleport.stream;
1209
+ async waitUntilTimeframe(timeframe) {
1210
+ assert8(this._pipeline, "Pipeline is not initialized.");
1211
+ await this._pipeline.state.waitUntilTimeframe(timeframe);
1046
1212
  }
1047
- async initialize() {
1048
- await this._teleport.open();
1049
- this._teleport.addExtension("dxos.mesh.teleport.auth", new AuthExtension({
1050
- provider: this._swarmIdentity.credentialProvider,
1051
- verifier: this._swarmIdentity.credentialAuthenticator,
1052
- onAuthSuccess: () => {
1053
- var _a;
1054
- this._authStatus = AuthStatus.SUCCESS;
1055
- log8("Peer authenticated", {}, {
1056
- file: "space-protocol.ts",
1057
- line: 206,
1058
- scope: this,
1059
- callSite: (f, a) => f(...a)
1060
- });
1061
- (_a = this._onSessionAuth) == null ? void 0 : _a.call(this, this._teleport);
1062
- },
1063
- onAuthFailure: () => {
1064
- log8.warn("Auth failed", {}, {
1065
- file: "space-protocol.ts",
1066
- line: 211,
1067
- scope: this,
1068
- callSite: (f, a) => f(...a)
1069
- });
1070
- this._authStatus = AuthStatus.FAILURE;
1071
- }
1072
- }));
1073
- this._teleport.addExtension("dxos.mesh.teleport.replicator", this.replicator);
1213
+ async createEpoch() {
1214
+ assert8(this._pipeline);
1215
+ assert8(this.currentEpoch);
1216
+ await this._pipeline.pause();
1217
+ const snapshot = await this._createSnapshot();
1218
+ const snapshotCid = await this._params.snapshotManager.store(snapshot);
1219
+ const epoch = {
1220
+ previousId: this.currentEpoch.id,
1221
+ timeframe: this._pipeline.state.timeframe,
1222
+ number: this.currentEpoch.subject.assertion.number + 1,
1223
+ snapshotCid
1224
+ };
1225
+ await this._pipeline.unpause();
1226
+ return epoch;
1074
1227
  }
1075
- async destroy() {
1076
- await this._teleport.close();
1228
+ async ensureEpochInitialized() {
1229
+ await this.onNewEpoch.waitForCondition(() => !!this.currentEpoch);
1077
1230
  }
1078
1231
  };
1079
1232
  __decorate5([
1080
- logInfo2
1081
- ], SpaceProtocolSession.prototype, "_wireParams", void 0);
1233
+ synchronized3
1234
+ ], DataPipeline.prototype, "open", null);
1082
1235
  __decorate5([
1083
- logInfo2
1084
- ], SpaceProtocolSession.prototype, "authStatus", null);
1236
+ synchronized3
1237
+ ], DataPipeline.prototype, "close", null);
1238
+ __decorate5([
1239
+ synchronized3
1240
+ ], DataPipeline.prototype, "_processEpoch", null);
1241
+ __decorate5([
1242
+ synchronized3
1243
+ ], DataPipeline.prototype, "createEpoch", null);
1244
+ DataPipeline = __decorate5([
1245
+ trackLeaks2("open", "close")
1246
+ ], DataPipeline);
1085
1247
 
1086
- // packages/core/echo/echo-pipeline/src/space/space-manager.ts
1087
- import { synchronized as synchronized4, trackLeaks as trackLeaks2 } from "@dxos/async";
1088
- import { failUndefined } from "@dxos/debug";
1089
- import { PublicKey as PublicKey4 } from "@dxos/keys";
1248
+ // packages/core/echo/echo-pipeline/src/space/space.ts
1249
+ import assert9 from "@dxos/node-std/assert";
1250
+ import { Event as Event4, synchronized as synchronized4, trackLeaks as trackLeaks3, Lock } from "@dxos/async";
1251
+ import { log as log10, logInfo } from "@dxos/log";
1252
+ import { AdmittedFeed as AdmittedFeed2 } from "@dxos/protocols/proto/dxos/halo/credentials";
1253
+ import { Callback as Callback2 } from "@dxos/util";
1254
+
1255
+ // packages/core/echo/echo-pipeline/src/space/control-pipeline.ts
1256
+ import { SpaceStateMachine } from "@dxos/credentials";
1257
+ import { PublicKey as PublicKey3 } from "@dxos/keys";
1090
1258
  import { log as log9 } from "@dxos/log";
1091
- import { trace } from "@dxos/protocols";
1092
- import { ComplexMap as ComplexMap2 } from "@dxos/util";
1093
- var __decorate6 = function(decorators, target, key, desc) {
1094
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1095
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
1096
- r = Reflect.decorate(decorators, target, key, desc);
1097
- else
1098
- for (var i = decorators.length - 1; i >= 0; i--)
1099
- if (d = decorators[i])
1100
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1101
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1102
- };
1103
- var SpaceManager = class SpaceManager2 {
1104
- constructor({ feedStore, networkManager }) {
1105
- this._spaces = new ComplexMap2(PublicKey4.hash);
1106
- this._instanceId = PublicKey4.random().toHex();
1107
- this._feedStore = feedStore;
1108
- this._networkManager = networkManager;
1259
+ import { AdmittedFeed } from "@dxos/protocols/proto/dxos/halo/credentials";
1260
+ import { Callback } from "@dxos/util";
1261
+ var ControlPipeline = class {
1262
+ constructor({ spaceKey, genesisFeed, feedProvider }) {
1263
+ this.onFeedAdmitted = new Callback();
1264
+ this._pipeline = new Pipeline();
1265
+ void this._pipeline.addFeed(genesisFeed);
1266
+ this._spaceStateMachine = new SpaceStateMachine(spaceKey);
1267
+ this._spaceStateMachine.onFeedAdmitted.set(async (info) => {
1268
+ log9("feed admitted", {
1269
+ key: info.key
1270
+ }, {
1271
+ file: "control-pipeline.ts",
1272
+ line: 39,
1273
+ scope: this,
1274
+ callSite: (f, a) => f(...a)
1275
+ });
1276
+ if (info.assertion.designation === AdmittedFeed.Designation.CONTROL && !info.key.equals(genesisFeed.key)) {
1277
+ try {
1278
+ const feed = await feedProvider(info.key);
1279
+ await this._pipeline.addFeed(feed);
1280
+ } catch (err) {
1281
+ log9.catch(err, {}, {
1282
+ file: "control-pipeline.ts",
1283
+ line: 47,
1284
+ scope: this,
1285
+ callSite: (f, a) => f(...a)
1286
+ });
1287
+ }
1288
+ }
1289
+ await this.onFeedAdmitted.callIfSet(info);
1290
+ });
1291
+ this.onMemberAdmitted = this._spaceStateMachine.onMemberAdmitted;
1292
+ this.onCredentialProcessed = this._spaceStateMachine.onCredentialProcessed;
1109
1293
  }
1110
- // TODO(burdon): Remove.
1111
- get spaces() {
1112
- return this._spaces;
1294
+ get spaceState() {
1295
+ return this._spaceStateMachine;
1113
1296
  }
1114
- async open() {
1115
- log9.trace("dxos.echo.space-manager", trace.begin({
1116
- id: this._instanceId,
1117
- parentId: this._traceParent
1118
- }), {
1119
- file: "space-manager.ts",
1120
- line: 66,
1297
+ get pipeline() {
1298
+ return this._pipeline;
1299
+ }
1300
+ setWriteFeed(feed) {
1301
+ this._pipeline.setWriteFeed(feed);
1302
+ }
1303
+ async start() {
1304
+ log9("starting...", {}, {
1305
+ file: "control-pipeline.ts",
1306
+ line: 71,
1121
1307
  scope: this,
1122
1308
  callSite: (f, a) => f(...a)
1123
1309
  });
1124
- }
1125
- async close() {
1126
- await Promise.all([
1127
- ...this._spaces.values()
1128
- ].map((space) => space.close()));
1129
- log9.trace("dxos.echo.space-manager", trace.end({
1130
- id: this._instanceId
1131
- }), {
1132
- file: "space-manager.ts",
1133
- line: 72,
1310
+ setTimeout(async () => {
1311
+ for await (const msg of this._pipeline.consume()) {
1312
+ try {
1313
+ log9("processing", {
1314
+ key: msg.feedKey,
1315
+ seq: msg.seq
1316
+ }, {
1317
+ file: "control-pipeline.ts",
1318
+ line: 76,
1319
+ scope: this,
1320
+ callSite: (f, a) => f(...a)
1321
+ });
1322
+ if (msg.data.payload.credential) {
1323
+ const result = await this._spaceStateMachine.process(msg.data.payload.credential.credential, PublicKey3.from(msg.feedKey));
1324
+ if (!result) {
1325
+ log9.warn("processing failed", {
1326
+ msg
1327
+ }, {
1328
+ file: "control-pipeline.ts",
1329
+ line: 83,
1330
+ scope: this,
1331
+ callSite: (f, a) => f(...a)
1332
+ });
1333
+ }
1334
+ }
1335
+ } catch (err) {
1336
+ log9.catch(err, {}, {
1337
+ file: "control-pipeline.ts",
1338
+ line: 87,
1339
+ scope: this,
1340
+ callSite: (f, a) => f(...a)
1341
+ });
1342
+ }
1343
+ }
1344
+ });
1345
+ await this._pipeline.start();
1346
+ log9("started", {}, {
1347
+ file: "control-pipeline.ts",
1348
+ line: 93,
1134
1349
  scope: this,
1135
1350
  callSite: (f, a) => f(...a)
1136
1351
  });
1137
1352
  }
1138
- async constructSpace({ metadata, swarmIdentity, onNetworkConnection }) {
1139
- var _a;
1140
- log9("constructing space...", {
1141
- spaceKey: metadata.genesisFeedKey
1142
- }, {
1143
- file: "space-manager.ts",
1144
- line: 76,
1353
+ async stop() {
1354
+ log9("stopping...", {}, {
1355
+ file: "control-pipeline.ts",
1356
+ line: 97,
1145
1357
  scope: this,
1146
1358
  callSite: (f, a) => f(...a)
1147
1359
  });
1148
- const genesisFeed = await this._feedStore.openFeed((_a = metadata.genesisFeedKey) != null ? _a : failUndefined());
1149
- const spaceKey = metadata.key;
1150
- const protocol = new SpaceProtocol({
1151
- topic: spaceKey,
1152
- swarmIdentity,
1153
- networkManager: this._networkManager,
1154
- onSessionAuth: onNetworkConnection
1155
- });
1156
- const space = new Space({
1157
- spaceKey,
1158
- protocol,
1159
- genesisFeed,
1160
- feedProvider: (feedKey) => this._feedStore.openFeed(feedKey)
1360
+ await this._pipeline.stop();
1361
+ log9("stopped", {}, {
1362
+ file: "control-pipeline.ts",
1363
+ line: 99,
1364
+ scope: this,
1365
+ callSite: (f, a) => f(...a)
1161
1366
  });
1162
- this._spaces.set(space.key, space);
1163
- return space;
1164
1367
  }
1165
1368
  };
1166
- __decorate6([
1167
- synchronized4
1168
- ], SpaceManager.prototype, "open", null);
1169
- __decorate6([
1170
- synchronized4
1171
- ], SpaceManager.prototype, "close", null);
1172
- SpaceManager = __decorate6([
1173
- trackLeaks2("open", "close")
1174
- ], SpaceManager);
1175
1369
 
1176
- // packages/core/echo/echo-pipeline/src/space/genesis.ts
1177
- import { createCredential } from "@dxos/credentials";
1178
- import { failUndefined as failUndefined2 } from "@dxos/debug";
1179
- import { AdmittedFeed as AdmittedFeed3, SpaceMember } from "@dxos/protocols/proto/dxos/halo/credentials";
1180
- var spaceGenesis = async (keyring, signingContext, space) => {
1181
- var _a, _b, _c;
1182
- const credentials = [
1183
- await createCredential({
1184
- signer: keyring,
1185
- issuer: space.key,
1186
- subject: space.key,
1187
- assertion: {
1188
- "@type": "dxos.halo.credentials.SpaceGenesis",
1189
- spaceKey: space.key
1190
- }
1191
- }),
1192
- await createCredential({
1193
- signer: keyring,
1194
- issuer: space.key,
1195
- subject: signingContext.identityKey,
1196
- assertion: {
1197
- "@type": "dxos.halo.credentials.SpaceMember",
1198
- spaceKey: space.key,
1199
- role: SpaceMember.Role.ADMIN,
1200
- profile: signingContext.profile,
1201
- genesisFeedKey: (_a = space.controlFeedKey) != null ? _a : failUndefined2()
1202
- }
1203
- }),
1204
- await signingContext.credentialSigner.createCredential({
1205
- subject: (_b = space.controlFeedKey) != null ? _b : failUndefined2(),
1206
- assertion: {
1207
- "@type": "dxos.halo.credentials.AdmittedFeed",
1208
- spaceKey: space.key,
1209
- identityKey: signingContext.identityKey,
1210
- deviceKey: signingContext.deviceKey,
1211
- designation: AdmittedFeed3.Designation.CONTROL
1212
- }
1213
- }),
1214
- await signingContext.credentialSigner.createCredential({
1215
- subject: (_c = space.dataFeedKey) != null ? _c : failUndefined2(),
1216
- assertion: {
1217
- "@type": "dxos.halo.credentials.AdmittedFeed",
1218
- spaceKey: space.key,
1219
- identityKey: signingContext.identityKey,
1220
- deviceKey: signingContext.deviceKey,
1221
- designation: AdmittedFeed3.Designation.DATA
1370
+ // packages/core/echo/echo-pipeline/src/space/space.ts
1371
+ var __decorate6 = function(decorators, target, key, desc) {
1372
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1373
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
1374
+ r = Reflect.decorate(decorators, target, key, desc);
1375
+ else
1376
+ for (var i = decorators.length - 1; i >= 0; i--)
1377
+ if (d = decorators[i])
1378
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1379
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1380
+ };
1381
+ var Space = class Space2 {
1382
+ constructor(params) {
1383
+ this._addFeedLock = new Lock();
1384
+ this.onCredentialProcessed = new Callback2();
1385
+ this.stateUpdate = new Event4();
1386
+ // Processes epoch credentials.
1387
+ this._dataPipelineCredentialConsumer = void 0;
1388
+ this._isOpen = false;
1389
+ assert9(params.spaceKey && params.feedProvider);
1390
+ this._key = params.spaceKey;
1391
+ this._genesisFeedKey = params.genesisFeed.key;
1392
+ this._feedProvider = params.feedProvider;
1393
+ this._snapshotManager = params.snapshotManager;
1394
+ this._controlPipeline = new ControlPipeline({
1395
+ spaceKey: params.spaceKey,
1396
+ genesisFeed: params.genesisFeed,
1397
+ feedProvider: params.feedProvider
1398
+ });
1399
+ this._controlPipeline.onFeedAdmitted.set(async (info) => {
1400
+ if (info.assertion.designation === AdmittedFeed2.Designation.DATA) {
1401
+ await this._addFeedLock.executeSynchronized(async () => {
1402
+ if (this._dataPipeline.pipeline) {
1403
+ if (!this._dataPipeline.pipeline.hasFeed(info.key)) {
1404
+ return this._dataPipeline.pipeline.addFeed(await this._feedProvider(info.key));
1405
+ }
1406
+ }
1407
+ });
1222
1408
  }
1223
- })
1224
- ];
1225
- for (const credential of credentials) {
1226
- await space.controlPipeline.writer.write({
1227
- credential: {
1228
- credential
1409
+ if (!info.key.equals(params.genesisFeed.key)) {
1410
+ this.protocol.addFeed(await params.feedProvider(info.key));
1229
1411
  }
1230
1412
  });
1231
- }
1232
- return credentials;
1233
- };
1234
-
1235
- // packages/core/echo/echo-pipeline/src/dbhost/data-service-host.ts
1236
- import assert7 from "@dxos/node-std/assert";
1237
- import { Stream } from "@dxos/codec-protobuf";
1238
- import { tagMutationsInBatch, setMetadataOnObject } from "@dxos/echo-db";
1239
- import { log as log10 } from "@dxos/log";
1240
- import { ComplexMap as ComplexMap3 } from "@dxos/util";
1241
- var DataServiceHost = class {
1242
- constructor(_itemManager, _itemDemuxer, _writeStream) {
1243
- this._itemManager = _itemManager;
1244
- this._itemDemuxer = _itemDemuxer;
1245
- this._writeStream = _writeStream;
1246
- this._clientTagMap = new ComplexMap3(([feedKey, seq]) => `${feedKey.toHex()}:${seq}`);
1247
- }
1248
- /**
1249
- * Real-time subscription to data objects in a space.
1250
- */
1251
- subscribe() {
1252
- return new Stream(({ next, ctx }) => {
1253
- const objects = Array.from(this._itemManager.entities.values()).map((entity) => entity.createSnapshot());
1254
- next({
1255
- batch: {
1256
- objects
1257
- }
1258
- });
1259
- this._itemDemuxer.mutation.on(ctx, (message) => {
1260
- var _a;
1261
- const { batch, meta } = message;
1262
- log10("message", {
1263
- batch,
1264
- meta
1265
- }, {
1266
- file: "data-service-host.ts",
1267
- line: 49,
1268
- scope: this,
1269
- callSite: (f, a) => f(...a)
1270
- });
1271
- const clientTag = this._clientTagMap.get([
1272
- message.meta.feedKey,
1273
- message.meta.seq
1274
- ]);
1275
- (_a = batch.objects) == null ? void 0 : _a.forEach((object) => {
1276
- setMetadataOnObject(object, meta);
1277
- });
1278
- if (clientTag) {
1279
- tagMutationsInBatch(batch, clientTag);
1280
- }
1281
- next({
1282
- clientTag,
1283
- feedKey: message.meta.feedKey,
1284
- seq: message.meta.seq,
1285
- batch
1286
- });
1413
+ this._controlPipeline.onCredentialProcessed.set(async (credential) => {
1414
+ await this.onCredentialProcessed.callIfSet(credential);
1415
+ log10("onCredentialProcessed", {
1416
+ credential
1417
+ }, {
1418
+ file: "space.ts",
1419
+ line: 106,
1420
+ scope: this,
1421
+ callSite: (f, a) => f(...a)
1287
1422
  });
1423
+ this.stateUpdate.emit();
1288
1424
  });
1289
- }
1290
- async write(request) {
1291
- var _a, _b, _c;
1292
- assert7(this._writeStream, "Cannot write mutations in readonly mode");
1293
- log10("write", {
1294
- clientTag: request.clientTag,
1295
- objectCount: (_b = (_a = request.batch.objects) == null ? void 0 : _a.length) != null ? _b : 0
1296
- }, {
1297
- file: "data-service-host.ts",
1298
- line: 77,
1299
- scope: this,
1300
- callSite: (f, a) => f(...a)
1301
- });
1302
- const message = {
1303
- batch: {
1304
- objects: (_c = request.batch.objects) == null ? void 0 : _c.map((object) => {
1305
- var _a2;
1306
- return {
1307
- ...object,
1308
- mutations: (_a2 = object.mutations) == null ? void 0 : _a2.map((mutation) => ({
1309
- ...mutation,
1310
- meta: void 0
1311
- })),
1312
- meta: void 0
1313
- };
1314
- })
1315
- }
1316
- };
1317
- const receipt = await this._writeStream.write(message, {
1318
- afterWrite: async (receipt2) => {
1319
- if (request.clientTag) {
1320
- log10("tag", {
1321
- clientTag: request.clientTag,
1322
- feedKey: receipt2.feedKey,
1323
- seq: receipt2.seq
1324
- }, {
1325
- file: "data-service-host.ts",
1326
- line: 96,
1327
- scope: this,
1328
- callSite: (f, a) => f(...a)
1329
- });
1330
- this._clientTagMap.set([
1331
- receipt2.feedKey,
1332
- receipt2.seq
1333
- ], request.clientTag);
1425
+ this.protocol = params.protocol;
1426
+ this.protocol.addFeed(params.genesisFeed);
1427
+ this._dataPipeline = new DataPipeline({
1428
+ modelFactory: params.modelFactory,
1429
+ metadataStore: params.metadataStore,
1430
+ snapshotManager: params.snapshotManager,
1431
+ memberKey: params.memberKey,
1432
+ spaceKey: this._key,
1433
+ feedInfoProvider: (feedKey) => this._controlPipeline.spaceState.feeds.get(feedKey),
1434
+ snapshotId: params.snapshotId,
1435
+ onPipelineCreated: async (pipeline) => {
1436
+ if (this._dataFeed) {
1437
+ pipeline.setWriteFeed(this._dataFeed);
1334
1438
  }
1439
+ await this._addFeedLock.executeSynchronized(async () => {
1440
+ for (const feed of this._controlPipeline.spaceState.feeds.values()) {
1441
+ if (feed.assertion.designation === AdmittedFeed2.Designation.DATA && !pipeline.hasFeed(feed.key)) {
1442
+ await pipeline.addFeed(await this._feedProvider(feed.key));
1443
+ }
1444
+ }
1445
+ });
1335
1446
  }
1336
1447
  });
1337
- return receipt;
1338
1448
  }
1339
- };
1340
-
1341
- // packages/core/echo/echo-pipeline/src/dbhost/database-host.ts
1342
- import { ItemDemuxer } from "@dxos/echo-db";
1343
- var DatabaseHost = class {
1344
- constructor(_outboundStream, _snapshot) {
1345
- this._outboundStream = _outboundStream;
1346
- this._snapshot = _snapshot;
1449
+ get key() {
1450
+ return this._key;
1347
1451
  }
1348
- get isReadOnly() {
1349
- return !!this._outboundStream;
1452
+ get isOpen() {
1453
+ return this._isOpen;
1350
1454
  }
1351
- get echoProcessor() {
1352
- return this._echoProcessor;
1455
+ get genesisFeedKey() {
1456
+ return this._genesisFeedKey;
1353
1457
  }
1354
- async open(itemManager, modelFactory) {
1355
- this._itemManager = itemManager;
1356
- this._itemManager._debugLabel = "host";
1357
- this._itemDemuxer = new ItemDemuxer(itemManager, modelFactory);
1358
- this._echoProcessor = this._itemDemuxer.open();
1359
- if (this._snapshot) {
1360
- this._itemDemuxer.restoreFromSnapshot(this._snapshot);
1361
- }
1458
+ get controlFeedKey() {
1459
+ var _a;
1460
+ return (_a = this._controlFeed) == null ? void 0 : _a.key;
1461
+ }
1462
+ get dataFeedKey() {
1463
+ var _a;
1464
+ return (_a = this._dataFeed) == null ? void 0 : _a.key;
1465
+ }
1466
+ get spaceState() {
1467
+ return this._controlPipeline.spaceState;
1468
+ }
1469
+ /**
1470
+ * @test-only
1471
+ */
1472
+ get controlPipeline() {
1473
+ return this._controlPipeline.pipeline;
1474
+ }
1475
+ get dataPipeline() {
1476
+ return this._dataPipeline;
1477
+ }
1478
+ get snapshotManager() {
1479
+ return this._snapshotManager;
1480
+ }
1481
+ setControlFeed(feed) {
1482
+ assert9(!this._controlFeed, "Control feed already set.");
1483
+ this._controlFeed = feed;
1484
+ this._controlPipeline.setWriteFeed(feed);
1485
+ return this;
1362
1486
  }
1363
- async close() {
1487
+ setDataFeed(feed) {
1488
+ var _a;
1489
+ assert9(!this._dataFeed, "Data feed already set.");
1490
+ this._dataFeed = feed;
1491
+ (_a = this._dataPipeline.pipeline) == null ? void 0 : _a.setWriteFeed(feed);
1492
+ return this;
1364
1493
  }
1365
- getWriteStream() {
1366
- return this._outboundStream;
1494
+ /**
1495
+ * Use for diagnostics.
1496
+ */
1497
+ getControlFeeds() {
1498
+ return Array.from(this._controlPipeline.spaceState.feeds.values());
1367
1499
  }
1368
- createSnapshot() {
1369
- return this._itemDemuxer.createSnapshot();
1500
+ /**
1501
+ * Use for diagnostics.
1502
+ */
1503
+ // getDataFeeds(): FeedInfo[] {
1504
+ // return this._dataPipeline?.getFeeds();
1505
+ // }
1506
+ async open() {
1507
+ log10("opening...", {}, {
1508
+ file: "space.ts",
1509
+ line: 208,
1510
+ scope: this,
1511
+ callSite: (f, a) => f(...a)
1512
+ });
1513
+ if (this._isOpen) {
1514
+ return;
1515
+ }
1516
+ await this._controlPipeline.start();
1517
+ await this.protocol.start();
1518
+ this._dataPipelineCredentialConsumer = this._controlPipeline.spaceState.registerProcessor(this._dataPipeline.createCredentialProcessor());
1519
+ this._isOpen = true;
1520
+ log10("opened", {}, {
1521
+ file: "space.ts",
1522
+ line: 222,
1523
+ scope: this,
1524
+ callSite: (f, a) => f(...a)
1525
+ });
1370
1526
  }
1371
- createDataServiceHost() {
1527
+ async close() {
1372
1528
  var _a;
1373
- return new DataServiceHost(this._itemManager, this._itemDemuxer, (_a = this._outboundStream) != null ? _a : void 0);
1529
+ log10("closing...", {
1530
+ key: this._key
1531
+ }, {
1532
+ file: "space.ts",
1533
+ line: 227,
1534
+ scope: this,
1535
+ callSite: (f, a) => f(...a)
1536
+ });
1537
+ if (!this._isOpen) {
1538
+ return;
1539
+ }
1540
+ await ((_a = this._dataPipelineCredentialConsumer) == null ? void 0 : _a.close());
1541
+ this._dataPipelineCredentialConsumer = void 0;
1542
+ await this._dataPipeline.close();
1543
+ await this.protocol.stop();
1544
+ await this._controlPipeline.stop();
1545
+ this._isOpen = false;
1546
+ log10("closed", {}, {
1547
+ file: "space.ts",
1548
+ line: 241,
1549
+ scope: this,
1550
+ callSite: (f, a) => f(...a)
1551
+ });
1552
+ }
1553
+ async initializeDataPipeline() {
1554
+ log10.info("initializeDataPipeline", {}, {
1555
+ file: "space.ts",
1556
+ line: 245,
1557
+ scope: this,
1558
+ callSite: (f, a) => f(...a)
1559
+ });
1560
+ assert9(this._isOpen, "Space must be open to initialize data pipeline.");
1561
+ await this._dataPipeline.open();
1562
+ await this._dataPipelineCredentialConsumer.open();
1374
1563
  }
1375
1564
  };
1565
+ __decorate6([
1566
+ logInfo
1567
+ ], Space.prototype, "key", null);
1568
+ __decorate6([
1569
+ synchronized4
1570
+ ], Space.prototype, "open", null);
1571
+ __decorate6([
1572
+ synchronized4
1573
+ ], Space.prototype, "close", null);
1574
+ Space = __decorate6([
1575
+ trackLeaks3("open", "close")
1576
+ ], Space);
1376
1577
 
1377
- // packages/core/echo/echo-pipeline/src/dbhost/snapshot-manager.ts
1378
- import { trackLeaks as trackLeaks3 } from "@dxos/async";
1379
- import { schema as schema4 } from "@dxos/protocols";
1380
- import { ObjectSync } from "@dxos/teleport-extension-object-sync";
1578
+ // packages/core/echo/echo-pipeline/src/space/space-protocol.ts
1579
+ import { discoveryKey, sha256 } from "@dxos/crypto";
1580
+ import { PublicKey as PublicKey4 } from "@dxos/keys";
1581
+ import { log as log11, logInfo as logInfo2 } from "@dxos/log";
1582
+ import { MMSTTopology } from "@dxos/network-manager";
1583
+ import { Teleport } from "@dxos/teleport";
1584
+ import { ReplicatorExtension } from "@dxos/teleport-extension-replicator";
1585
+ import { ComplexMap as ComplexMap4 } from "@dxos/util";
1381
1586
  var __decorate7 = function(decorators, target, key, desc) {
1382
1587
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1383
1588
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
@@ -1388,154 +1593,180 @@ var __decorate7 = function(decorators, target, key, desc) {
1388
1593
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1389
1594
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1390
1595
  };
1391
- var SnapshotManager = class SnapshotManager2 {
1392
- // prettier-ignore
1393
- constructor(_snapshotStore) {
1394
- this._snapshotStore = _snapshotStore;
1395
- this._objectSync = new ObjectSync({
1396
- getObject: async (id) => {
1397
- const snapshot = await this._snapshotStore.loadSnapshot(id);
1398
- if (!snapshot) {
1399
- return void 0;
1400
- }
1401
- return {
1402
- id,
1403
- payload: schema4.getCodecForType("dxos.echo.snapshot.SpaceSnapshot").encodeAsAny(snapshot)
1404
- };
1405
- },
1406
- setObject: async (data) => {
1407
- const snapshot = schema4.getCodecForType("dxos.echo.snapshot.SpaceSnapshot").decode(data.payload);
1408
- await this._snapshotStore.saveSnapshot(snapshot);
1409
- }
1410
- });
1411
- }
1412
- get objectSync() {
1413
- return this._objectSync;
1414
- }
1415
- async open() {
1416
- await this._objectSync.open();
1417
- }
1418
- async close() {
1419
- await this._objectSync.close();
1596
+ var MOCK_AUTH_PROVIDER = async (nonce) => Buffer.from("mock");
1597
+ var MOCK_AUTH_VERIFIER = async (nonce, credential) => true;
1598
+ var SpaceProtocol = class {
1599
+ constructor({ topic, swarmIdentity, networkManager, onSessionAuth }) {
1600
+ this._feeds = /* @__PURE__ */ new Set();
1601
+ this._sessions = new ComplexMap4(PublicKey4.hash);
1602
+ this._networkManager = networkManager;
1603
+ this._swarmIdentity = swarmIdentity;
1604
+ this._onSessionAuth = onSessionAuth;
1605
+ this._topic = PublicKey4.from(discoveryKey(sha256(topic.toHex())));
1420
1606
  }
1421
- async load(id) {
1422
- const local = await this._snapshotStore.loadSnapshot(id);
1423
- if (local) {
1424
- return local;
1425
- }
1426
- const remote = await this._objectSync.download(id);
1427
- return schema4.getCodecForType("dxos.echo.snapshot.SpaceSnapshot").decode(remote.payload);
1607
+ get sessions() {
1608
+ return this._sessions;
1428
1609
  }
1429
- async store(snapshot) {
1430
- const id = await this._snapshotStore.saveSnapshot(snapshot);
1431
- return id;
1610
+ get feeds() {
1611
+ return this._feeds;
1432
1612
  }
1433
- };
1434
- SnapshotManager = __decorate7([
1435
- trackLeaks3("open", "close")
1436
- ], SnapshotManager);
1437
-
1438
- // packages/core/echo/echo-pipeline/src/dbhost/snapshot-store.ts
1439
- import { subtleCrypto } from "@dxos/crypto";
1440
- import { schema as schema5 } from "@dxos/protocols";
1441
- var SnapshotStore = class {
1442
- // prettier-ignore
1443
- constructor(_directory) {
1444
- this._directory = _directory;
1613
+ get _ownPeerKey() {
1614
+ return this._swarmIdentity.peerKey;
1445
1615
  }
1446
- async saveSnapshot(snapshot) {
1447
- const encoded = schema5.getCodecForType("dxos.echo.snapshot.SpaceSnapshot").encode(snapshot);
1448
- const key = await subtleCrypto.digest("SHA-256", encoded);
1449
- const keyString = Buffer.from(key).toString("hex");
1450
- const file = await this._directory.getOrCreateFile(keyString);
1451
- try {
1452
- await file.write(0, Buffer.from(encoded));
1453
- } finally {
1454
- await file.close();
1616
+ // TODO(burdon): Create abstraction for Space (e.g., add keys and have provider).
1617
+ addFeed(feed) {
1618
+ log11("addFeed", {
1619
+ key: feed.key
1620
+ }, {
1621
+ file: "space-protocol.ts",
1622
+ line: 84,
1623
+ scope: this,
1624
+ callSite: (f, a) => f(...a)
1625
+ });
1626
+ this._feeds.add(feed);
1627
+ for (const session of this._sessions.values()) {
1628
+ session.replicator.addFeed(feed);
1455
1629
  }
1456
- return keyString;
1457
1630
  }
1458
- async loadSnapshot(key) {
1459
- const file = await this._directory.getOrCreateFile(key);
1460
- try {
1461
- const { size } = await file.stat();
1462
- if (size === 0) {
1463
- return void 0;
1464
- }
1465
- const buffer = await file.read(0, size);
1466
- return schema5.getCodecForType("dxos.echo.snapshot.SpaceSnapshot").decode(buffer);
1467
- } finally {
1468
- await file.close();
1631
+ async start() {
1632
+ if (this._connection) {
1633
+ return;
1469
1634
  }
1470
- }
1471
- };
1472
-
1473
- // packages/core/echo/echo-pipeline/src/dbhost/data-service.ts
1474
- import assert8 from "@dxos/node-std/assert";
1475
- import { raise } from "@dxos/debug";
1476
- import { PublicKey as PublicKey5 } from "@dxos/keys";
1477
- import { log as log11 } from "@dxos/log";
1478
- import { ComplexMap as ComplexMap4 } from "@dxos/util";
1479
- var DataServiceSubscriptions = class {
1480
- constructor() {
1481
- this._spaces = new ComplexMap4(PublicKey5.hash);
1482
- }
1483
- clear() {
1484
- this._spaces.clear();
1485
- }
1486
- registerSpace(spaceKey, host) {
1487
- log11("Registering space", {
1488
- spaceKey
1489
- }, {
1490
- file: "data-service.ts",
1491
- line: 31,
1635
+ const credentials = await this._swarmIdentity.credentialProvider(Buffer.from(""));
1636
+ const topologyConfig = {
1637
+ originateConnections: 4,
1638
+ maxPeers: 10,
1639
+ sampleSize: 20
1640
+ };
1641
+ log11("starting...", {}, {
1642
+ file: "space-protocol.ts",
1643
+ line: 107,
1492
1644
  scope: this,
1493
1645
  callSite: (f, a) => f(...a)
1494
1646
  });
1495
- this._spaces.set(spaceKey, host);
1496
- }
1497
- unregisterSpace(spaceKey) {
1498
- log11("Unregistering space", {
1499
- spaceKey
1500
- }, {
1501
- file: "data-service.ts",
1502
- line: 36,
1647
+ this._connection = await this._networkManager.joinSwarm({
1648
+ protocolProvider: this._createProtocolProvider(credentials),
1649
+ peerId: this._swarmIdentity.peerKey,
1650
+ topic: this._topic,
1651
+ topology: new MMSTTopology(topologyConfig),
1652
+ label: `Protocol swarm: ${this._topic}`
1653
+ });
1654
+ log11("started", {}, {
1655
+ file: "space-protocol.ts",
1656
+ line: 116,
1503
1657
  scope: this,
1504
1658
  callSite: (f, a) => f(...a)
1505
1659
  });
1506
- this._spaces.delete(spaceKey);
1507
1660
  }
1508
- getDataService(spaceKey) {
1509
- return this._spaces.get(spaceKey);
1661
+ async stop() {
1662
+ if (this._connection) {
1663
+ log11("stopping...", {}, {
1664
+ file: "space-protocol.ts",
1665
+ line: 121,
1666
+ scope: this,
1667
+ callSite: (f, a) => f(...a)
1668
+ });
1669
+ await this._connection.close();
1670
+ log11("stopped", {}, {
1671
+ file: "space-protocol.ts",
1672
+ line: 123,
1673
+ scope: this,
1674
+ callSite: (f, a) => f(...a)
1675
+ });
1676
+ }
1677
+ }
1678
+ _createProtocolProvider(credentials) {
1679
+ return (wireParams) => {
1680
+ const session = new SpaceProtocolSession({
1681
+ wireParams,
1682
+ swarmIdentity: this._swarmIdentity,
1683
+ onSessionAuth: this._onSessionAuth
1684
+ });
1685
+ this._sessions.set(wireParams.remotePeerId, session);
1686
+ for (const feed of this._feeds) {
1687
+ session.replicator.addFeed(feed);
1688
+ }
1689
+ return session;
1690
+ };
1691
+ }
1692
+ };
1693
+ __decorate7([
1694
+ logInfo2
1695
+ ], SpaceProtocol.prototype, "_topic", void 0);
1696
+ __decorate7([
1697
+ logInfo2
1698
+ ], SpaceProtocol.prototype, "_ownPeerKey", null);
1699
+ var AuthStatus;
1700
+ (function(AuthStatus2) {
1701
+ AuthStatus2["INITIAL"] = "INITIAL";
1702
+ AuthStatus2["SUCCESS"] = "SUCCESS";
1703
+ AuthStatus2["FAILURE"] = "FAILURE";
1704
+ })(AuthStatus || (AuthStatus = {}));
1705
+ var SpaceProtocolSession = class {
1706
+ // TODO(dmaretskyi): Allow to pass in extra extensions.
1707
+ constructor({ wireParams, swarmIdentity, onSessionAuth }) {
1708
+ // TODO(dmaretskyi): Start with upload=false when switching it on the fly works.
1709
+ this.replicator = new ReplicatorExtension().setOptions({
1710
+ upload: true
1711
+ });
1712
+ this._authStatus = AuthStatus.INITIAL;
1713
+ this._wireParams = wireParams;
1714
+ this._swarmIdentity = swarmIdentity;
1715
+ this._onSessionAuth = onSessionAuth;
1716
+ this._teleport = new Teleport(wireParams);
1717
+ }
1718
+ get authStatus() {
1719
+ return this._authStatus;
1510
1720
  }
1511
- };
1512
- var DataServiceImpl = class {
1513
- constructor(_subscriptions) {
1514
- this._subscriptions = _subscriptions;
1721
+ get stream() {
1722
+ return this._teleport.stream;
1515
1723
  }
1516
- subscribe(request) {
1517
- var _a;
1518
- assert8(request.spaceKey);
1519
- const host = (_a = this._subscriptions.getDataService(request.spaceKey)) != null ? _a : raise(new Error(`space not found: ${request.spaceKey}`));
1520
- return host.subscribe();
1724
+ async initialize() {
1725
+ await this._teleport.open();
1726
+ this._teleport.addExtension("dxos.mesh.teleport.auth", new AuthExtension({
1727
+ provider: this._swarmIdentity.credentialProvider,
1728
+ verifier: this._swarmIdentity.credentialAuthenticator,
1729
+ onAuthSuccess: () => {
1730
+ var _a;
1731
+ this._authStatus = AuthStatus.SUCCESS;
1732
+ log11("Peer authenticated", {}, {
1733
+ file: "space-protocol.ts",
1734
+ line: 206,
1735
+ scope: this,
1736
+ callSite: (f, a) => f(...a)
1737
+ });
1738
+ (_a = this._onSessionAuth) == null ? void 0 : _a.call(this, this._teleport);
1739
+ },
1740
+ onAuthFailure: () => {
1741
+ log11.warn("Auth failed", {}, {
1742
+ file: "space-protocol.ts",
1743
+ line: 211,
1744
+ scope: this,
1745
+ callSite: (f, a) => f(...a)
1746
+ });
1747
+ this._authStatus = AuthStatus.FAILURE;
1748
+ }
1749
+ }));
1750
+ this._teleport.addExtension("dxos.mesh.teleport.replicator", this.replicator);
1521
1751
  }
1522
- write(request) {
1523
- var _a;
1524
- assert8(request.spaceKey);
1525
- assert8(request.batch);
1526
- const host = (_a = this._subscriptions.getDataService(request.spaceKey)) != null ? _a : raise(new Error(`space not found: ${request.spaceKey}`));
1527
- return host.write(request);
1752
+ async destroy() {
1753
+ await this._teleport.close();
1528
1754
  }
1529
1755
  };
1756
+ __decorate7([
1757
+ logInfo2
1758
+ ], SpaceProtocolSession.prototype, "_wireParams", void 0);
1759
+ __decorate7([
1760
+ logInfo2
1761
+ ], SpaceProtocolSession.prototype, "authStatus", null);
1530
1762
 
1531
- // packages/core/echo/echo-pipeline/src/space/data-pipeline.ts
1532
- import assert9 from "@dxos/node-std/assert";
1533
- import { scheduleTask as scheduleTask2, synchronized as synchronized5, trackLeaks as trackLeaks4 } from "@dxos/async";
1534
- import { Context as Context3 } from "@dxos/context";
1535
- import { failUndefined as failUndefined3 } from "@dxos/debug";
1536
- import { ItemManager } from "@dxos/echo-db";
1763
+ // packages/core/echo/echo-pipeline/src/space/space-manager.ts
1764
+ import { synchronized as synchronized5, trackLeaks as trackLeaks4 } from "@dxos/async";
1765
+ import { failUndefined as failUndefined2 } from "@dxos/debug";
1766
+ import { PublicKey as PublicKey5 } from "@dxos/keys";
1537
1767
  import { log as log12 } from "@dxos/log";
1538
- import { Timeframe as Timeframe3 } from "@dxos/timeframe";
1768
+ import { trace } from "@dxos/protocols";
1769
+ import { ComplexMap as ComplexMap5 } from "@dxos/util";
1539
1770
  var __decorate8 = function(decorators, target, key, desc) {
1540
1771
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1541
1772
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
@@ -1546,204 +1777,85 @@ var __decorate8 = function(decorators, target, key, desc) {
1546
1777
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1547
1778
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1548
1779
  };
1549
- var MESSAGES_PER_SNAPSHOT = 10;
1550
- var AUTOMATIC_SNAPSHOT_DEBOUNCE_INTERVAL = 5e3;
1551
- var TIMEFRAME_SAVE_DEBOUNCE_INTERVAL = 500;
1552
- var DISABLE_SNAPSHOT_CACHE = true;
1553
- var DataPipeline = class DataPipeline2 {
1554
- constructor(_params) {
1555
- this._params = _params;
1556
- this._ctx = new Context3();
1557
- this._lastAutomaticSnapshotTimeframe = new Timeframe3();
1558
- this._isOpen = false;
1559
- this._lastTimeframeSaveTime = 0;
1560
- this._lastSnapshotSaveTime = 0;
1561
- }
1562
- get isOpen() {
1563
- return this._isOpen;
1780
+ var SpaceManager = class SpaceManager2 {
1781
+ constructor({ feedStore, networkManager, modelFactory, metadataStore, snapshotStore }) {
1782
+ this._spaces = new ComplexMap5(PublicKey5.hash);
1783
+ this._instanceId = PublicKey5.random().toHex();
1784
+ this._feedStore = feedStore;
1785
+ this._networkManager = networkManager;
1786
+ this._modelFactory = modelFactory;
1787
+ this._metadataStore = metadataStore;
1788
+ this._snapshotStore = snapshotStore;
1564
1789
  }
1565
- get pipelineState() {
1566
- var _a;
1567
- return (_a = this._pipeline) == null ? void 0 : _a.state;
1790
+ // TODO(burdon): Remove.
1791
+ get spaces() {
1792
+ return this._spaces;
1568
1793
  }
1569
- get snapshotTimeframe() {
1570
- var _a;
1571
- return (_a = this._snapshot) == null ? void 0 : _a.timeframe;
1794
+ async open() {
1572
1795
  }
1573
- getStartTimeframe() {
1574
- var _a;
1575
- return snapshotTimeframeToStartingTimeframe((_a = this.snapshotTimeframe) != null ? _a : new Timeframe3());
1796
+ async close() {
1797
+ await Promise.all([
1798
+ ...this._spaces.values()
1799
+ ].map((space) => space.close()));
1576
1800
  }
1577
- setTargetTimeframe(timeframe) {
1801
+ async constructSpace({ metadata, swarmIdentity, onNetworkConnection, memberKey }) {
1578
1802
  var _a;
1579
- this._targetTimeframe = timeframe;
1580
- (_a = this._pipeline) == null ? void 0 : _a.state.setTargetTimeframe(timeframe);
1581
- }
1582
- async open(spaceContext) {
1583
- var _a, _b, _c, _d;
1584
- if (this._isOpen) {
1585
- return;
1586
- }
1587
- this._spaceContext = spaceContext;
1588
- if (this._params.snapshotId && !DISABLE_SNAPSHOT_CACHE) {
1589
- this._snapshot = await this._params.snapshotManager.load(this._params.snapshotId);
1590
- this._lastAutomaticSnapshotTimeframe = (_b = (_a = this._snapshot) == null ? void 0 : _a.timeframe) != null ? _b : new Timeframe3();
1591
- }
1592
- this._pipeline = await this._spaceContext.openPipeline(this.getStartTimeframe());
1593
- if (this._targetTimeframe) {
1594
- this._pipeline.state.setTargetTimeframe(this._targetTimeframe);
1595
- }
1596
- const feedWriter = createMappedFeedWriter((data) => ({
1597
- data
1598
- }), (_c = this._pipeline.writer) != null ? _c : failUndefined3());
1599
- this.databaseBackend = new DatabaseHost(feedWriter, (_d = this._snapshot) == null ? void 0 : _d.database);
1600
- this._itemManager = new ItemManager(this._params.modelFactory);
1601
- await this.databaseBackend.open(this._itemManager, this._params.modelFactory);
1602
- scheduleTask2(this._ctx, async () => {
1603
- await this._consumePipeline();
1803
+ log12.trace("dxos.echo.space-manager.construct-space", trace.begin({
1804
+ id: this._instanceId
1805
+ }), {
1806
+ file: "space-manager.ts",
1807
+ line: 74,
1808
+ scope: this,
1809
+ callSite: (f, a) => f(...a)
1604
1810
  });
1605
- this._isOpen = true;
1606
- }
1607
- async close() {
1608
- var _a, _b, _c;
1609
- if (!this._isOpen) {
1610
- return;
1611
- }
1612
- log12("close", {}, {
1613
- file: "data-pipeline.ts",
1614
- line: 145,
1811
+ log12("constructing space...", {
1812
+ spaceKey: metadata.genesisFeedKey
1813
+ }, {
1814
+ file: "space-manager.ts",
1815
+ line: 75,
1615
1816
  scope: this,
1616
1817
  callSite: (f, a) => f(...a)
1617
1818
  });
1618
- this._isOpen = false;
1619
- await this._ctx.dispose();
1620
- await ((_a = this._pipeline) == null ? void 0 : _a.stop());
1621
- try {
1622
- if (this._pipeline) {
1623
- await this._saveTargetTimeframe(this._pipeline.state.timeframe);
1624
- if (!DISABLE_SNAPSHOT_CACHE) {
1625
- await this._saveSnapshot(this._pipeline.state.timeframe);
1626
- }
1627
- }
1628
- } catch (err) {
1629
- log12.catch(err, {}, {
1630
- file: "data-pipeline.ts",
1631
- line: 160,
1632
- scope: this,
1633
- callSite: (f, a) => f(...a)
1634
- });
1635
- }
1636
- await ((_b = this.databaseBackend) == null ? void 0 : _b.close());
1637
- await ((_c = this._itemManager) == null ? void 0 : _c.destroy());
1638
- await this._params.snapshotManager.close();
1639
- }
1640
- async _consumePipeline() {
1641
- assert9(this._pipeline, "Pipeline is not initialized.");
1642
- for await (const msg of this._pipeline.consume()) {
1643
- const { feedKey, seq, data } = msg;
1644
- log12("processing message", {
1645
- msg
1646
- }, {
1647
- file: "data-pipeline.ts",
1648
- line: 172,
1649
- scope: this,
1650
- callSite: (f, a) => f(...a)
1651
- });
1652
- try {
1653
- if (data.payload.data) {
1654
- const feedInfo = this._params.feedInfoProvider(feedKey);
1655
- if (!feedInfo) {
1656
- log12.error("Could not find feed.", {
1657
- feedKey
1658
- }, {
1659
- file: "data-pipeline.ts",
1660
- line: 178,
1661
- scope: this,
1662
- callSite: (f, a) => f(...a)
1663
- });
1664
- continue;
1665
- }
1666
- await this.databaseBackend.echoProcessor({
1667
- batch: data.payload.data.batch,
1668
- meta: {
1669
- feedKey,
1670
- seq,
1671
- timeframe: data.timeframe,
1672
- memberKey: feedInfo.assertion.identityKey
1673
- }
1674
- });
1675
- await this._noteTargetStateIfNeeded(this._pipeline.state.pendingTimeframe);
1676
- }
1677
- } catch (err) {
1678
- log12.catch(err, {}, {
1679
- file: "data-pipeline.ts",
1680
- line: 196,
1681
- scope: this,
1682
- callSite: (f, a) => f(...a)
1683
- });
1684
- }
1685
- }
1686
- }
1687
- _createSnapshot(timeframe) {
1688
- assert9(this.databaseBackend, "Database backend is not initialized.");
1689
- return {
1690
- spaceKey: this._params.spaceKey.asUint8Array(),
1691
- timeframe,
1692
- database: this.databaseBackend.createSnapshot()
1693
- };
1694
- }
1695
- async _saveSnapshot(timeframe) {
1696
- const snapshot = await this._createSnapshot(timeframe);
1697
- const snapshotKey = await this._params.snapshotManager.store(snapshot);
1698
- await this._params.metadataStore.setSpaceSnapshot(this._params.spaceKey, snapshotKey);
1699
- return snapshot;
1700
- }
1701
- async _saveTargetTimeframe(timeframe) {
1702
- var _a;
1703
- const newTimeframe = Timeframe3.merge((_a = this._targetTimeframe) != null ? _a : new Timeframe3(), timeframe);
1704
- await this._params.metadataStore.setSpaceLatestTimeframe(this._params.spaceKey, newTimeframe);
1705
- this._targetTimeframe = newTimeframe;
1706
- }
1707
- async _noteTargetStateIfNeeded(timeframe) {
1708
- var _a;
1709
- if (Date.now() - this._lastTimeframeSaveTime > TIMEFRAME_SAVE_DEBOUNCE_INTERVAL) {
1710
- this._lastTimeframeSaveTime = Date.now();
1711
- await this._saveTargetTimeframe(timeframe);
1712
- }
1713
- if (!DISABLE_SNAPSHOT_CACHE && Date.now() - this._lastSnapshotSaveTime > AUTOMATIC_SNAPSHOT_DEBOUNCE_INTERVAL && timeframe.totalMessages() - this._lastAutomaticSnapshotTimeframe.totalMessages() > MESSAGES_PER_SNAPSHOT) {
1714
- this._lastSnapshotSaveTime = Date.now();
1715
- const snapshot = await this._saveSnapshot(timeframe);
1716
- this._lastAutomaticSnapshotTimeframe = (_a = snapshot.timeframe) != null ? _a : failUndefined3();
1717
- log12("save", {
1718
- snapshot
1719
- }, {
1720
- file: "data-pipeline.ts",
1721
- line: 239,
1722
- scope: this,
1723
- callSite: (f, a) => f(...a)
1724
- });
1725
- }
1726
- }
1727
- async waitUntilTimeframe(timeframe) {
1728
- assert9(this._pipeline, "Pipeline is not initialized.");
1729
- await this._pipeline.state.waitUntilTimeframe(timeframe);
1819
+ const genesisFeed = await this._feedStore.openFeed((_a = metadata.genesisFeedKey) != null ? _a : failUndefined2());
1820
+ const spaceKey = metadata.key;
1821
+ const protocol = new SpaceProtocol({
1822
+ topic: spaceKey,
1823
+ swarmIdentity,
1824
+ networkManager: this._networkManager,
1825
+ onSessionAuth: onNetworkConnection
1826
+ });
1827
+ const snapshotManager = new SnapshotManager(this._snapshotStore);
1828
+ const space = new Space({
1829
+ spaceKey,
1830
+ protocol,
1831
+ genesisFeed,
1832
+ feedProvider: (feedKey) => this._feedStore.openFeed(feedKey),
1833
+ modelFactory: this._modelFactory,
1834
+ metadataStore: this._metadataStore,
1835
+ snapshotManager,
1836
+ memberKey
1837
+ });
1838
+ this._spaces.set(space.key, space);
1839
+ log12.trace("dxos.echo.space-manager.construct-space", trace.end({
1840
+ id: this._instanceId
1841
+ }), {
1842
+ file: "space-manager.ts",
1843
+ line: 100,
1844
+ scope: this,
1845
+ callSite: (f, a) => f(...a)
1846
+ });
1847
+ return space;
1730
1848
  }
1731
1849
  };
1732
1850
  __decorate8([
1733
1851
  synchronized5
1734
- ], DataPipeline.prototype, "open", null);
1852
+ ], SpaceManager.prototype, "open", null);
1735
1853
  __decorate8([
1736
1854
  synchronized5
1737
- ], DataPipeline.prototype, "close", null);
1738
- DataPipeline = __decorate8([
1855
+ ], SpaceManager.prototype, "close", null);
1856
+ SpaceManager = __decorate8([
1739
1857
  trackLeaks4("open", "close")
1740
- ], DataPipeline);
1741
- var snapshotTimeframeToStartingTimeframe = (snapshotTimeframe) => {
1742
- return snapshotTimeframe.map(([key, seq]) => [
1743
- key,
1744
- seq + 1
1745
- ]);
1746
- };
1858
+ ], SpaceManager);
1747
1859
 
1748
1860
  export {
1749
1861
  STORAGE_VERSION,
@@ -1757,20 +1869,19 @@ export {
1757
1869
  TimeframeClock,
1758
1870
  Pipeline,
1759
1871
  AuthExtension,
1760
- Space,
1761
- MOCK_AUTH_PROVIDER,
1762
- MOCK_AUTH_VERIFIER,
1763
- SpaceProtocol,
1764
- AuthStatus,
1765
- SpaceProtocolSession,
1766
- SpaceManager,
1767
- spaceGenesis,
1768
1872
  DataServiceHost,
1769
1873
  DatabaseHost,
1770
1874
  SnapshotManager,
1771
1875
  SnapshotStore,
1772
1876
  DataServiceSubscriptions,
1773
1877
  DataServiceImpl,
1774
- DataPipeline
1878
+ DataPipeline,
1879
+ Space,
1880
+ MOCK_AUTH_PROVIDER,
1881
+ MOCK_AUTH_VERIFIER,
1882
+ SpaceProtocol,
1883
+ AuthStatus,
1884
+ SpaceProtocolSession,
1885
+ SpaceManager
1775
1886
  };
1776
- //# sourceMappingURL=chunk-4YAT2XJW.mjs.map
1887
+ //# sourceMappingURL=chunk-2NGLSBL4.mjs.map