@agentshouse/mdruntime 0.1.0-c97456d63baba → 0.1.0-c9b1e59e5baeb

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 (133) hide show
  1. package/AGENTS.md +221 -221
  2. package/README.md +8 -8
  3. package/dist/adapter.d.ts +35 -6
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/bootstrap.d.ts +2 -1
  6. package/dist/bootstrap.d.ts.map +1 -1
  7. package/dist/bootstrap.js +5 -1
  8. package/dist/change-set.d.ts +5 -2
  9. package/dist/change-set.d.ts.map +1 -1
  10. package/dist/change-set.js +8 -2
  11. package/dist/chat/earlier.d.ts +10 -0
  12. package/dist/chat/earlier.d.ts.map +1 -0
  13. package/dist/chat/earlier.js +54 -0
  14. package/dist/chat/kit.js +1 -1
  15. package/dist/chat/list.d.ts +2 -1
  16. package/dist/chat/list.d.ts.map +1 -1
  17. package/dist/chat/list.js +40 -4
  18. package/dist/chat/markdown.d.ts +1 -0
  19. package/dist/chat/markdown.d.ts.map +1 -1
  20. package/dist/chat/markdown.js +78 -20
  21. package/dist/chat/message.d.ts.map +1 -1
  22. package/dist/chat/message.js +9 -2
  23. package/dist/chat/messages.d.ts.map +1 -1
  24. package/dist/chat/messages.js +36 -16
  25. package/dist/chat/overlay.d.ts +1 -0
  26. package/dist/chat/overlay.d.ts.map +1 -1
  27. package/dist/chat/overlay.js +10 -0
  28. package/dist/chat/pieces.d.ts +62 -0
  29. package/dist/chat/pieces.d.ts.map +1 -0
  30. package/dist/chat/pieces.js +1110 -0
  31. package/dist/chat/provider.d.ts +1 -0
  32. package/dist/chat/provider.d.ts.map +1 -1
  33. package/dist/chat/provider.js +22 -14
  34. package/dist/chat/question.d.ts +1 -1
  35. package/dist/chat/question.d.ts.map +1 -1
  36. package/dist/chat/question.js +7 -18
  37. package/dist/chat/ready.d.ts +1 -1
  38. package/dist/chat/ready.d.ts.map +1 -1
  39. package/dist/chat/ready.js +1 -1
  40. package/dist/chat/steady.d.ts +3 -0
  41. package/dist/chat/steady.d.ts.map +1 -0
  42. package/dist/chat/steady.js +26 -0
  43. package/dist/chat/style.js +1 -1
  44. package/dist/chat.d.ts +2 -2
  45. package/dist/chat.d.ts.map +1 -1
  46. package/dist/chat.js +2 -2
  47. package/dist/client.d.ts +6 -2
  48. package/dist/client.d.ts.map +1 -1
  49. package/dist/client.js +16 -5
  50. package/dist/collection.d.ts +4 -3
  51. package/dist/collection.d.ts.map +1 -1
  52. package/dist/collection.js +128 -63
  53. package/dist/connection.d.ts +13 -5
  54. package/dist/connection.d.ts.map +1 -1
  55. package/dist/connection.js +506 -228
  56. package/dist/conversation.d.ts +11 -0
  57. package/dist/conversation.d.ts.map +1 -1
  58. package/dist/conversation.js +195 -41
  59. package/dist/entry.d.ts.map +1 -1
  60. package/dist/entry.js +3 -2
  61. package/dist/host.d.ts +9 -7
  62. package/dist/host.d.ts.map +1 -1
  63. package/dist/host.js +58 -9
  64. package/dist/index.d.ts +6 -5
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +1 -0
  67. package/dist/memory.d.ts +7 -3
  68. package/dist/memory.d.ts.map +1 -1
  69. package/dist/memory.js +24 -11
  70. package/dist/observation.d.ts +2 -0
  71. package/dist/observation.d.ts.map +1 -1
  72. package/dist/observation.js +9 -3
  73. package/dist/outcome.d.ts +8 -2
  74. package/dist/outcome.d.ts.map +1 -1
  75. package/dist/outcome.js +3 -0
  76. package/dist/overlay.d.ts +5 -4
  77. package/dist/overlay.d.ts.map +1 -1
  78. package/dist/overlay.js +197 -132
  79. package/dist/query.d.ts +27 -1
  80. package/dist/query.d.ts.map +1 -1
  81. package/dist/query.js +36 -7
  82. package/dist/reach.d.ts +1 -2
  83. package/dist/reach.d.ts.map +1 -1
  84. package/dist/reach.js +17 -4
  85. package/dist/react.d.ts +3 -3
  86. package/dist/react.d.ts.map +1 -1
  87. package/dist/react.js +7 -17
  88. package/dist/read-side.d.ts +3 -1
  89. package/dist/read-side.d.ts.map +1 -1
  90. package/dist/read-side.js +256 -69
  91. package/dist/reads.d.ts +11 -0
  92. package/dist/reads.d.ts.map +1 -0
  93. package/dist/reads.js +98 -0
  94. package/dist/refresh.d.ts +4 -4
  95. package/dist/refresh.d.ts.map +1 -1
  96. package/dist/refresh.js +39 -14
  97. package/dist/replica.d.ts +11 -5
  98. package/dist/replica.d.ts.map +1 -1
  99. package/dist/replica.js +79 -41
  100. package/dist/room-draft.d.ts +12 -0
  101. package/dist/room-draft.d.ts.map +1 -1
  102. package/dist/room-draft.js +11 -12
  103. package/dist/rows.d.ts +3 -1
  104. package/dist/rows.d.ts.map +1 -1
  105. package/dist/rows.js +8 -1
  106. package/dist/save.d.ts +4 -2
  107. package/dist/save.d.ts.map +1 -1
  108. package/dist/save.js +195 -111
  109. package/dist/search.d.ts +27 -6
  110. package/dist/search.d.ts.map +1 -1
  111. package/dist/search.js +89 -7
  112. package/dist/seed.d.ts +16 -0
  113. package/dist/seed.d.ts.map +1 -0
  114. package/dist/seed.js +75 -0
  115. package/dist/shared.d.ts +4 -0
  116. package/dist/shared.d.ts.map +1 -1
  117. package/dist/shared.js +63 -33
  118. package/dist/store.d.ts +20 -4
  119. package/dist/store.d.ts.map +1 -1
  120. package/dist/store.js +404 -317
  121. package/dist/turn.d.ts +5 -3
  122. package/dist/turn.d.ts.map +1 -1
  123. package/dist/turn.js +26 -7
  124. package/dist/windows.d.ts +26 -0
  125. package/dist/windows.d.ts.map +1 -0
  126. package/dist/windows.js +93 -0
  127. package/dist/wire.d.ts +16 -0
  128. package/dist/wire.d.ts.map +1 -1
  129. package/dist/wire.js +4 -0
  130. package/package.json +2 -2
  131. package/dist/acquisition.d.ts +0 -14
  132. package/dist/acquisition.d.ts.map +0 -1
  133. package/dist/acquisition.js +0 -24
@@ -1,36 +1,47 @@
1
1
  import { attachedModel } from './attached-model.js';
2
2
  import { cached, modelTypes, NO_MODEL, sameModel, } from './model.js';
3
- import { observing } from './observation.js';
4
- import { filterOf, identityOf, planOf, } from './query.js';
3
+ import { observing, whole } from './observation.js';
4
+ import { filterOf, identityOf, planOf, plain, queryKey, } from './query.js';
5
5
  import { createReadSide } from './read-side.js';
6
+ import { seededResult } from './seed.js';
6
7
  import { markdownPath, roomOfKey } from './rows.js';
7
8
  import { createSearchIndex, searchPlanOf, } from './search.js';
9
+ import { capped, MAXIMUM_WINDOW } from './windows.js';
8
10
  import { bootstrapRoom } from './bootstrap.js';
9
- import { changeSet, entering, mergeChangeSets } from './change-set.js';
11
+ import { changeSet, changing, entering, mergeChangeSets } from './change-set.js';
10
12
  import { createConversationStore, } from './conversation.js';
11
13
  import { queuedFile } from './change.js';
12
14
  import { indexReferrers, referencesIndexed, validRoot, } from './collection.js';
13
15
  import { runtimeError } from './failure.js';
14
16
  import { createLog } from './log.js';
15
17
  import { binaryRecord } from './binary.js';
16
- import { absentBinary, binaryAnswer, calledResult, failure, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
18
+ import { absentBinary, binaryAnswer, binaryLinkAnswer, calledResult, failure, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
17
19
  import { createRecovery } from './recovery.js';
20
+ import { doorReads } from './reads.js';
18
21
  import { refreshRoom } from './refresh.js';
19
- import { buildBatch, joined, limitedIn, requeued } from './save.js';
20
- import { applyReturned, compositionOf, confirmedOf, landed, readReclassified, replicaOf, sameMembership, withdrawals, } from './replica.js';
21
- import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
22
+ import { buildBatch, joined, limitedIn, queuedDocuments, requeued, } from './save.js';
23
+ import { applyReturned, compositionOf, confirmedOf, landed, readReclassified, replicaOf, replicating, sameMembership, standing, } from './replica.js';
24
+ import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, moveTarget, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
22
25
  import { same, saving, STARTING, transferring, } from './status.js';
23
26
  import { detectStorageLoss, nextGeneration } from './durability.js';
24
- import { demanded, narrower, WHOLE_DEMAND, wider } from './acquisition.js';
25
27
  import { readinessOf, SETTLED_ROOMS, } from './readiness.js';
26
28
  import { FencedMap } from './fence.js';
27
29
  import { tally } from './shared.js';
28
- import { inChunks, ROW_WEIGHT, yielded } from './turn.js';
30
+ import { inChunks, ROW_WEIGHT, stepped, yielded } from './turn.js';
29
31
  import { identityTime, roomDraftRevision } from './identity.js';
30
32
  import { beneath, normalize } from './paths.js';
31
33
  import { openRuntimeStore, } from './store.js';
32
34
  import { unconfirmed } from './transfer.js';
33
35
  import { governs, holds, narrowed, planReach, } from './reach.js';
36
+ function roomsAlone(plan) {
37
+ return (plan.reach === null &&
38
+ plan.root.types === null &&
39
+ plan.root.where === null &&
40
+ plan.root.pathPrefix === null &&
41
+ plan.paths === null &&
42
+ plan.hop === null &&
43
+ plan.backlinks === null);
44
+ }
34
45
  function within(root, path) {
35
46
  return root === '' || beneath(root, path);
36
47
  }
@@ -48,6 +59,10 @@ const OPENING_LANE = 'opening';
48
59
  const CONVERSATION_LANE = 'conversations';
49
60
  const ROOM_READS = 256;
50
61
  const ROOM_ATTEMPTS = 16;
62
+ function operationOf(input) {
63
+ const operation = input?.operation_id;
64
+ return typeof operation === 'string' ? operation : null;
65
+ }
51
66
  const outside = new Map();
52
67
  function outsideOf(roots) {
53
68
  const key = JSON.stringify(roots);
@@ -87,6 +102,8 @@ export function composeRuntime(options) {
87
102
  const replicas = new FencedMap(isFenced);
88
103
  const queuesWithoutReplica = new FencedMap(isFenced);
89
104
  const pendings = new Map();
105
+ const seeds = new Map();
106
+ const seedWatchers = new Map();
90
107
  const roomDrafts = new FencedMap(isFenced);
91
108
  const presentations = new Map();
92
109
  let limits = [];
@@ -99,6 +116,7 @@ export function composeRuntime(options) {
99
116
  const liveModels = new FencedMap(isModelFenced);
100
117
  const invalidModels = new Set();
101
118
  const discovered = new Map();
119
+ const reads = doorReads(options.adapter, (locator) => discovered.get(locator), () => generation);
102
120
  let roomsListed = false;
103
121
  const scopedRefusals = new Map();
104
122
  const sourceRefusals = new Map();
@@ -135,6 +153,7 @@ export function composeRuntime(options) {
135
153
  let handed = false;
136
154
  const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
137
155
  const repairing = new Set();
156
+ const calling = new Set();
138
157
  const repairable = new Map();
139
158
  let outcomeRetention = null;
140
159
  const answering = new Map();
@@ -145,6 +164,7 @@ export function composeRuntime(options) {
145
164
  let discovering = null;
146
165
  let opened = null;
147
166
  const owed = new Set();
167
+ const reloadsDue = new Map();
148
168
  const lanes = new Map();
149
169
  let roomReads = { generation, free: ROOM_READS, waiting: [] };
150
170
  const working = new Set();
@@ -171,8 +191,6 @@ export function composeRuntime(options) {
171
191
  const unpreparedDemand = new Set();
172
192
  const unindexable = new Set();
173
193
  let preparing = false;
174
- let demand = WHOLE_DEMAND;
175
- let narrowing = null;
176
194
  let targets = {};
177
195
  let persistence = options.shell.persistence;
178
196
  let capacity = null;
@@ -189,7 +207,7 @@ export function composeRuntime(options) {
189
207
  durably,
190
208
  stream: () => options.adapter.conversations,
191
209
  publish: publishChangeSet,
192
- online: () => !status.providerUnreachable && refusal === null,
210
+ online: () => connectionOf() !== 'unreachable' && refusal === null,
193
211
  closed: () => closed,
194
212
  unanswered: resendLater,
195
213
  expired: (operation) => pastHorizon(operation),
@@ -255,6 +273,15 @@ export function composeRuntime(options) {
255
273
  const value = presentations.get(app);
256
274
  return value === undefined ? null : structuredClone(value);
257
275
  }
276
+ function keepPresentation(app, next) {
277
+ return exclusive(async () => {
278
+ const local = await openLocal();
279
+ const stored = next(presentations.get(app));
280
+ await local.present({ app, value: stored });
281
+ presentations.set(app, stored);
282
+ publishChangeSet(changeSet('', null, null, { presentation: app }));
283
+ });
284
+ }
258
285
  function emitPresentation(app) {
259
286
  if (closed)
260
287
  return;
@@ -366,7 +393,7 @@ export function composeRuntime(options) {
366
393
  if (closed)
367
394
  return;
368
395
  queueReadiness();
369
- const next = status.state === 'synced' && demand.conversations && !conversations.current()
396
+ const next = status.state === 'synced' && !conversations.current()
370
397
  ? { ...status, state: 'syncing' }
371
398
  : status;
372
399
  if (same(shown, next))
@@ -399,8 +426,12 @@ export function composeRuntime(options) {
399
426
  ...set,
400
427
  documents: set.documents.filter(({ locator }) => prepared.has(locator) || projecting.has(locator)),
401
428
  };
402
- readSide.apply(held);
403
- searchIndex.changed(held);
429
+ const applied = readSide.apply(held, () => searchIndex.changed(held));
430
+ if (applied instanceof Promise)
431
+ void applied.catch(unpublished);
432
+ }
433
+ function unpublished(error) {
434
+ journal.record(`publication failed ${error instanceof Error ? error.name : 'unknown'}`);
404
435
  }
405
436
  async function referenced(locator) {
406
437
  for (;;) {
@@ -431,8 +462,8 @@ export function composeRuntime(options) {
431
462
  if (current === undefined || closed)
432
463
  return;
433
464
  const set = entering(locator, current, chunk);
434
- readSide.apply(set);
435
465
  entered.push(set);
466
+ return readSide.apply(set);
436
467
  });
437
468
  if (!replicas.has(locator) || closed)
438
469
  return;
@@ -474,9 +505,11 @@ export function composeRuntime(options) {
474
505
  }
475
506
  })();
476
507
  }
508
+ function roomsOfReach(reach) {
509
+ return new Set([...reach.rooms, ...reach.documents.map(roomOfKey)]);
510
+ }
477
511
  function reachedRooms(plan) {
478
- const reach = plan.reach;
479
- const reached = reach === null ? null : new Set([...reach.rooms, ...reach.documents.map(roomOfKey)]);
512
+ const reached = plan.reach === null ? null : roomsOfReach(plan.reach);
480
513
  const named = plan.rooms ??
481
514
  (reached === null
482
515
  ? [...new Set([...rooms.keys(), ...replicas.keys(), ...queuesWithoutReplica.keys()])]
@@ -484,7 +517,8 @@ export function composeRuntime(options) {
484
517
  return [...new Set(named)].filter((locator) => reached === null || reached.has(locator)).sort();
485
518
  }
486
519
  function awaited(locator) {
487
- return rooms.has(locator) || !roomsListed;
520
+ return (!roomsListed ||
521
+ (rooms.has(locator) && discovered.get(locator)?.replica.admission === 'admitted'));
488
522
  }
489
523
  function covered(plan) {
490
524
  const locators = reachedRooms(plan);
@@ -524,61 +558,11 @@ export function composeRuntime(options) {
524
558
  if (waiting.length > 0)
525
559
  prepare(waiting.map(({ room }) => room));
526
560
  }
527
- function acquires(room) {
528
- return (demanded(demand, room) ||
529
- (replicas.get(room.locator)?.queue.length ?? 0) > 0 ||
530
- queuesWithoutReplica.has(room.locator));
531
- }
532
- function acquiredLocators() {
533
- return [...discovered.values()]
534
- .filter(acquires)
535
- .map((room) => room.locator)
536
- .sort();
537
- }
538
- function heldDemand(acquired) {
539
- if (demand.whole)
540
- return null;
541
- return { rooms: acquired.map((room) => room.locator), conversations: demand.conversations };
542
- }
543
- function applyDemand(next) {
544
- const before = acquiredLocators();
545
- const conversed = demand.conversations;
546
- demand = next;
547
- const after = acquiredLocators();
548
- queueReadiness();
549
- if (!conversed && next.conversations)
550
- scheduleCurrency();
551
- if (JSON.stringify(before) === JSON.stringify(after) && conversed === next.conversations)
552
- return;
553
- journal.record(`acquisition ${next.whole ? 'whole' : after.length}`);
554
- if (options.adapter.roomStream === undefined)
555
- void sync('unstreamed');
556
- else if (socketOpen)
557
- options.adapter.roomStream.reopen();
558
- }
559
- function acquire(next) {
560
- if (closed)
561
- return;
562
- if (narrowing !== null)
563
- clearTimeout(narrowing);
564
- narrowing = null;
565
- const widening = wider(next, demand);
566
- if (!widening && !narrower(next, demand))
567
- return;
568
- if (widening || !roomsListed) {
569
- applyDemand(next);
570
- return;
571
- }
572
- // A narrower demand waits five seconds, so a tab that reloads returns before the stream reopens twice.
573
- narrowing = setTimeout(() => {
574
- narrowing = null;
575
- applyDemand(next);
576
- }, 5_000);
577
- }
578
- function requiredRooms(reach) {
561
+ function requiredRooms(given) {
562
+ const reach = planReach(given);
579
563
  if (reach === null)
580
564
  return { listed: roomsListed, rooms: [...rooms.keys()].sort() };
581
- const named = [...new Set(reach.rooms)].sort();
565
+ const named = [...roomsOfReach(reach)].sort();
582
566
  if (roomsListed)
583
567
  return { listed: true, rooms: named.filter((room) => rooms.has(room)) };
584
568
  return { listed: named.every((room) => replicas.has(room)), rooms: named };
@@ -643,8 +627,7 @@ export function composeRuntime(options) {
643
627
  }
644
628
  function readinessFor(port, reach) {
645
629
  const required = requiredRooms(reach);
646
- const target = targets[port.target];
647
- const search = target?.search === true;
630
+ const search = port.search === 'required';
648
631
  const coverage = required.rooms.map((room) => ({ room, state: roomState(room, search) }));
649
632
  const building = (state) => coverage
650
633
  .filter((room) => room.state === state && !unindexable.has(room.room))
@@ -672,15 +655,11 @@ export function composeRuntime(options) {
672
655
  : port.capacity,
673
656
  refused: refusedIn(required.rooms),
674
657
  lost: storageLoss !== null,
675
- established: target?.established === true,
658
+ established: targets[port.target]?.established === true,
676
659
  });
677
660
  }
678
- function keepTarget(name, change) {
679
- const held = targets[name] ?? { established: false, search: false };
680
- const next = { ...held, ...change };
681
- if (next.established === held.established && next.search === held.search)
682
- return;
683
- targets = { ...targets, [name]: next };
661
+ function establish(name) {
662
+ targets = { ...targets, [name]: { established: true } };
684
663
  queueReadiness();
685
664
  void durably((local) => local.keepTargets(targets)).catch(() => {
686
665
  journal.record(`target record failed ${name}`);
@@ -691,7 +670,7 @@ export function composeRuntime(options) {
691
670
  return;
692
671
  const readiness = readinessFor(watch.port, watch.reach);
693
672
  if (readiness.ready && targets[watch.port.target]?.established !== true) {
694
- keepTarget(watch.port.target, { established: true });
673
+ establish(watch.port.target);
695
674
  }
696
675
  const shown = JSON.stringify(readiness);
697
676
  if (shown === watch.last)
@@ -719,10 +698,6 @@ export function composeRuntime(options) {
719
698
  readinessWatches.delete(watch);
720
699
  };
721
700
  }
722
- function searching(target) {
723
- if (targets[target]?.search !== true)
724
- keepTarget(target, { search: true });
725
- }
726
701
  function layerBytes(layer) {
727
702
  let bytes = 0;
728
703
  for (const [path, text] of Object.entries(layer.documents))
@@ -781,10 +756,7 @@ export function composeRuntime(options) {
781
756
  void sync('unstreamed');
782
757
  }
783
758
  async function reclaimed(local) {
784
- const unreferenced = [...transfers.keys()].filter((locator) => {
785
- const room = discovered.get(locator);
786
- return room === undefined || !acquires(room);
787
- });
759
+ const unreferenced = [...transfers.keys()].filter((locator) => !discovered.has(locator));
788
760
  for (const locator of unreferenced) {
789
761
  await local.abandon(locator);
790
762
  transfers.delete(locator);
@@ -810,18 +782,54 @@ export function composeRuntime(options) {
810
782
  const available = await estimated().catch(() => null);
811
783
  pause(locator, { cause: 'quota-exceeded', needed: bytes, available });
812
784
  }
785
+ function seedRoomHeld(room, version) {
786
+ return prepared.has(room) && reachedBy(room, version);
787
+ }
788
+ function seedHeld(at) {
789
+ return Object.entries(at).every(([room, version]) => (roomsListed && !rooms.has(room)) || seedRoomHeld(room, version));
790
+ }
791
+ function seedApplies(plan) {
792
+ const seed = seeds.get(plan.key);
793
+ if (seed === undefined)
794
+ return null;
795
+ if (!seedHeld(seed.at))
796
+ return seed;
797
+ seeds.delete(plan.key);
798
+ return null;
799
+ }
800
+ function seedBehind(plan, cover) {
801
+ const seed = seedApplies(plan);
802
+ if (seed === null || 'pending' in cover)
803
+ return cover;
804
+ return {
805
+ pending: Object.entries(seed.at)
806
+ .filter(([room, version]) => !seedRoomHeld(room, version))
807
+ .map(([room]) => ({ room, awaits: 'acquisition' })),
808
+ };
809
+ }
810
+ function seed(entry) {
811
+ if (seedHeld(entry.at))
812
+ return;
813
+ for (const { query, rows } of entry.queries) {
814
+ const key = queryKey(query);
815
+ seeds.set(key, { rows, at: entry.at });
816
+ for (const shown of seedWatchers.get(key) ?? [])
817
+ shown();
818
+ }
819
+ }
813
820
  async function rowsQuery(plan) {
814
821
  const before = covered(plan);
815
822
  awaitPreparation(before);
823
+ const seeded = seedApplies(plan);
824
+ if (seeded !== null)
825
+ return seededResult(seeded).result;
816
826
  if ('pending' in before)
817
827
  return before;
818
828
  const { rows, at: cover } = await readSide.query(plan, () => covered(plan));
819
829
  awaitPreparation(cover);
820
830
  if ('pending' in cover)
821
831
  return cover;
822
- return 'groups' in rows
823
- ? { groups: rows.groups, coverage: cover.coverage }
824
- : { documents: rows.documents, coverage: cover.coverage };
832
+ return { ...rows, coverage: cover.coverage };
825
833
  }
826
834
  async function indexConversation(conversation) {
827
835
  searchIndex.indexMessages(conversation, [], false);
@@ -965,20 +973,27 @@ export function composeRuntime(options) {
965
973
  refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
966
974
  });
967
975
  }
968
- async function applyDiscovery(found, started) {
976
+ async function applyDiscovery(found, started, covered) {
969
977
  for (const room of found) {
970
978
  const fence = fences.get(room.locator);
971
979
  if (fence !== undefined && fence.erased && fence.at < started)
972
980
  fences.delete(room.locator);
973
981
  }
982
+ await loseRooms(found
983
+ .filter((room) => room.replica.admission !== 'admitted' && heldLocally(room.locator))
984
+ .map((room) => room.locator));
974
985
  const listed = found.filter((room) => !fences.has(room.locator));
975
986
  const listedLocators = new Set(listed.map((room) => room.locator));
976
987
  for (const locator of listedLocators)
977
988
  scopedRefusals.delete(locator);
978
- discovered.clear();
989
+ if (covered === undefined)
990
+ discovered.clear();
991
+ else
992
+ for (const locator of covered)
993
+ discovered.delete(locator);
979
994
  for (const room of listed)
980
995
  discovered.set(room.locator, room);
981
- const unlisted = unlistedHeld(new Set(found.map((room) => room.locator)));
996
+ const unlisted = unlistedHeld(new Set(found.map((room) => room.locator))).filter((locator) => covered === undefined || covered.includes(locator));
982
997
  if (options.adapter.exhaustiveDiscovery === true)
983
998
  await loseRooms(unlisted);
984
999
  else
@@ -995,6 +1010,12 @@ export function composeRuntime(options) {
995
1010
  emitRooms();
996
1011
  return listed;
997
1012
  }
1013
+ function heldLocally(locator) {
1014
+ return (replicas.has(locator) ||
1015
+ roomDrafts.has(locator) ||
1016
+ transfers.has(locator) ||
1017
+ queuesWithoutReplica.has(locator));
1018
+ }
998
1019
  function unlistedHeld(listed) {
999
1020
  return [
1000
1021
  ...new Set([
@@ -1230,8 +1251,9 @@ export function composeRuntime(options) {
1230
1251
  }
1231
1252
  async function settle(local, locator, before, confirmed, queue, settling = {}) {
1232
1253
  const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
1233
- const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
1234
- const kept = queue.filter((batch) => !withdrawn.includes(batch));
1254
+ const { withdrawn, kept } = await stepped(standing(confirmed, queue, withdrawable, localSaves));
1255
+ const founded = kept.filter((batch) => !queue.includes(batch));
1256
+ const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
1235
1257
  const unqueued = withdrawn.map((batch) => ({
1236
1258
  batch,
1237
1259
  returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
@@ -1255,9 +1277,10 @@ export function composeRuntime(options) {
1255
1277
  });
1256
1278
  }
1257
1279
  }
1258
- await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
1280
+ await local.keepAuthored({ locator, queue: kept, founded, roomDrafts: [...written.values()] });
1281
+ const staying = new Set(kept.map((batch) => batch.id));
1259
1282
  for (const batch of previousQueue) {
1260
- if (!kept.includes(batch))
1283
+ if (!staying.has(batch.id))
1261
1284
  pendings.delete(batch.id);
1262
1285
  }
1263
1286
  for (const record of written.values())
@@ -1270,34 +1293,38 @@ export function composeRuntime(options) {
1270
1293
  });
1271
1294
  }
1272
1295
  catch (error) {
1273
- const held = before === undefined ? null : replicaOf(before.confirmed, kept, before);
1296
+ const held = before === undefined ? null : await stepped(replicating(before.confirmed, kept, before));
1297
+ const set = await stepped(changing(locator, before ?? null, held, {
1298
+ queue: queueChanged,
1299
+ drafts: [...written.keys()],
1300
+ }));
1274
1301
  if (held !== null)
1275
1302
  replicas.set(locator, held);
1276
1303
  else if (kept.length > 0)
1277
1304
  queuesWithoutReplica.set(locator, kept);
1278
1305
  else
1279
1306
  queuesWithoutReplica.delete(locator);
1280
- publishChangeSet(changeSet(locator, before ?? null, held, {
1281
- queue: kept.length !== previousQueue.length,
1282
- drafts: [...written.keys()],
1283
- }));
1307
+ publishChangeSet(set);
1284
1308
  moveTo({ ...status, ...attention() });
1285
1309
  throw error;
1286
1310
  }
1311
+ const changed = { room: settling.room, queue: queueChanged, drafts: [...written.keys()] };
1312
+ const next = await stepped(replicating(confirmed, kept, before));
1313
+ const set = await stepped(changing(locator, before ?? null, next, changed));
1287
1314
  return () => {
1288
1315
  queuesWithoutReplica.delete(locator);
1289
1316
  for (const { batch, returned } of unqueued) {
1290
1317
  const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
1291
1318
  journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
1292
1319
  }
1293
- replicas.set(locator, replicaOf(confirmed, kept, before));
1294
- return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
1295
- room: settling.room,
1296
- queue: kept.length !== previousQueue.length,
1297
- drafts: [...written.keys()],
1298
- });
1320
+ replicas.set(locator, next);
1321
+ return heldChange(locator, before ?? null, next, set, changed);
1299
1322
  };
1300
1323
  }
1324
+ function heldChange(locator, before, next, set, changed) {
1325
+ const held = replicas.get(locator) ?? null;
1326
+ return held === next ? set : changeSet(locator, before, held, changed);
1327
+ }
1301
1328
  function adoptModels(locator, record) {
1302
1329
  for (const [attached, model] of [
1303
1330
  ['collection', record.model],
@@ -1385,7 +1412,7 @@ export function composeRuntime(options) {
1385
1412
  return true;
1386
1413
  });
1387
1414
  }
1388
- async function saveRoomDrafts(locator, named, writtenBy) {
1415
+ async function savedRoom(locator) {
1389
1416
  const local = await openLocal();
1390
1417
  await referenced(locator);
1391
1418
  const replica = replicas.get(locator);
@@ -1393,6 +1420,10 @@ export function composeRuntime(options) {
1393
1420
  if (replica === undefined || room === undefined) {
1394
1421
  throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
1395
1422
  }
1423
+ return { local, replica, room };
1424
+ }
1425
+ async function saveRoomDrafts(locator, named, writtenBy) {
1426
+ const target = await savedRoom(locator);
1396
1427
  const saved = [];
1397
1428
  for (const { path, revision } of named) {
1398
1429
  const record = roomDrafts.get(locator)?.get(path);
@@ -1402,6 +1433,38 @@ export function composeRuntime(options) {
1402
1433
  if (!saved.some((draft) => draft.path === path))
1403
1434
  saved.push({ path, parts: record.parts });
1404
1435
  }
1436
+ return committed(locator, target, saved, new Set(saved.map(({ path }) => path)), writtenBy);
1437
+ }
1438
+ async function saveParts(locator, named, writtenBy) {
1439
+ const read = named.map(({ path, parts }) => ({ path, parts: readParts(parts) }));
1440
+ if (read.some(({ path }, at) => path === '' ||
1441
+ normalize(path) !== path ||
1442
+ read.findIndex((one) => one.path === path) !== at)) {
1443
+ throw runtimeError('room-draft-malformed');
1444
+ }
1445
+ const target = await savedRoom(locator);
1446
+ const saved = [];
1447
+ for (const { path, parts } of read) {
1448
+ const file = parts.file === undefined
1449
+ ? undefined
1450
+ : await heldFile(parts.file.blob, binaryBase(target.replica.visible, path));
1451
+ saved.push({ path, parts: held(undefined, parts, file) });
1452
+ }
1453
+ return committed(locator, target, saved, new Set(), writtenBy);
1454
+ }
1455
+ function carriedDrafts(locator, saved, consumed) {
1456
+ const holding = roomDrafts.get(locator);
1457
+ return new Map(saved.flatMap(({ path, parts }) => {
1458
+ const record = holding?.get(path);
1459
+ if (parts.move === undefined || record === undefined || consumed.has(path))
1460
+ return [];
1461
+ const to = moveTarget(parts.move);
1462
+ if (to === path || holding?.has(to) === true)
1463
+ return [];
1464
+ return [[path, { ...record, path: to, revision: roomDraftRevision() }]];
1465
+ }));
1466
+ }
1467
+ async function committed(locator, { local, replica, room }, saved, consumed, writtenBy) {
1405
1468
  const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
1406
1469
  if (changed.length > 0)
1407
1470
  return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
@@ -1434,6 +1497,7 @@ export function composeRuntime(options) {
1434
1497
  visible: replica.visible,
1435
1498
  changes,
1436
1499
  bases: parts.flatMap(({ bases }) => bases),
1500
+ queued: queuedDocuments(replica.queue),
1437
1501
  writtenBy,
1438
1502
  clean: {
1439
1503
  text: replica.confirmed.record.documents,
@@ -1441,7 +1505,9 @@ export function composeRuntime(options) {
1441
1505
  composition: compositionOf(replica.confirmed.record),
1442
1506
  },
1443
1507
  };
1444
- const checked = withdrawing ? buildBatch({ ...input, queued: requeue.queued }) : null;
1508
+ const checked = withdrawing
1509
+ ? await buildBatch({ ...input, queued: new Set([...input.queued, ...requeue.queued]) })
1510
+ : null;
1445
1511
  if (checked?.kind === 'refused')
1446
1512
  return { outcome: 'refused', ...checked.refusal };
1447
1513
  const owners = [...requeue.owners].flatMap((origin) => {
@@ -1457,13 +1523,15 @@ export function composeRuntime(options) {
1457
1523
  };
1458
1524
  }
1459
1525
  const built = !withdrawing
1460
- ? buildBatch(input)
1526
+ ? await buildBatch(input)
1461
1527
  : requeue.changes.length === 0
1462
1528
  ? null
1463
- : buildBatch({
1529
+ : await buildBatch({
1464
1530
  ...input,
1465
- visible: replicaOf(replica.confirmed, requeue.queue, replica).visible,
1531
+ visible: (await stepped(replicating(replica.confirmed, requeue.queue, replica)))
1532
+ .visible,
1466
1533
  changes: requeue.changes,
1534
+ queued: queuedDocuments(requeue.queue),
1467
1535
  });
1468
1536
  if (built?.kind === 'refused')
1469
1537
  return { outcome: 'refused', ...built.refusal };
@@ -1498,18 +1566,26 @@ export function composeRuntime(options) {
1498
1566
  if (limited !== null)
1499
1567
  return { outcome: 'refused', ...limited };
1500
1568
  }
1501
- const consumed = saved.map(({ path }) => path);
1569
+ const carried = carriedDrafts(locator, saved, consumed);
1570
+ const left = [...consumed, ...carried.keys()];
1571
+ const arrived = [...carried.values()];
1502
1572
  try {
1503
1573
  await local.keepAuthored({
1504
1574
  locator,
1505
1575
  queue: batches,
1506
1576
  saved: drafted,
1507
- consumedRoomDrafts: consumed,
1577
+ consumedRoomDrafts: left,
1578
+ roomDrafts: arrived,
1508
1579
  });
1509
1580
  }
1510
1581
  catch {
1511
1582
  return { outcome: 'refused', code: 'durable-write', retryable: true };
1512
1583
  }
1584
+ const next = await stepped(replicating(replica.confirmed, batches, replica));
1585
+ const set = await stepped(changing(locator, replica, next, {
1586
+ queue: true,
1587
+ drafts: [...left, ...arrived.map(({ path }) => path)],
1588
+ }));
1513
1589
  if (fences.has(locator))
1514
1590
  throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
1515
1591
  for (const origin of withdrawn.keys())
@@ -1519,13 +1595,12 @@ export function composeRuntime(options) {
1519
1595
  }
1520
1596
  if (!localSaves)
1521
1597
  recorded = false;
1522
- for (const path of consumed)
1598
+ for (const path of left)
1523
1599
  forgetRoomDraft(locator, path);
1524
- replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
1525
- publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
1526
- queue: true,
1527
- drafts: consumed,
1528
- }));
1600
+ for (const record of arrived)
1601
+ rememberRoomDraft(record);
1602
+ replicas.set(locator, next);
1603
+ publishChangeSet(set);
1529
1604
  moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
1530
1605
  submitLater(locator);
1531
1606
  const identifier = join?.batch.id ??
@@ -1615,7 +1690,7 @@ export function composeRuntime(options) {
1615
1690
  const scope = options.adapter.referrerScope(request.referrer);
1616
1691
  return scope?.kind === 'room' ? scope.locator : null;
1617
1692
  }
1618
- async function readBinary(request) {
1693
+ async function transferBinary(request, transfer, answer) {
1619
1694
  if (closed || refusal !== null)
1620
1695
  return UNCERTAIN;
1621
1696
  try {
@@ -1631,40 +1706,41 @@ export function composeRuntime(options) {
1631
1706
  ? `transfer binary ${request.locator} ${request.path}`
1632
1707
  : `transfer binary ${request.fileRef}`);
1633
1708
  try {
1634
- const result = await options.adapter.binary(authorization, request);
1635
- journal.record(`outcome binary ${result.kind}`);
1709
+ const result = await transfer(authorization);
1710
+ const settled = result;
1711
+ journal.record(`outcome binary ${settled.kind}`);
1636
1712
  const locator = binaryRoom(request);
1637
1713
  if (locator !== null && fencedSince(locator, since))
1638
1714
  return failure({ kind: 'room-not-readable', locator });
1639
- if (result.kind === 'scoped-refusal') {
1640
- const room = replicas.get(result.locator)?.confirmed.record;
1715
+ if (settled.kind === 'scoped-refusal') {
1716
+ const room = replicas.get(settled.locator)?.confirmed.record;
1641
1717
  if (room !== undefined)
1642
- await scoped(result, room, await openLocal());
1718
+ await scoped(settled, room, await openLocal());
1643
1719
  }
1644
- if (result.kind === 'revoked-authorization')
1720
+ if (settled.kind === 'revoked-authorization')
1645
1721
  await refuse('revoked-authorization');
1646
- return binaryAnswer(result);
1722
+ return answer(result);
1647
1723
  }
1648
1724
  catch {
1649
1725
  journal.record('outcome binary transport-uncertainty');
1650
1726
  return UNCERTAIN;
1651
1727
  }
1652
1728
  }
1653
- async function readBinaryPath(locator, path, revision) {
1654
- await ready;
1729
+ function readBinary(request) {
1730
+ return transferBinary(request, (held) => options.adapter.binary(held, request), binaryAnswer);
1731
+ }
1732
+ function binaryPath(locator, path, revision) {
1655
1733
  if (revision !== undefined)
1656
- return readBinary({ kind: 'path', locator, path, revision });
1734
+ return { kind: 'path', locator, path, revision };
1657
1735
  const visible = replicas.get(locator)?.visible;
1658
1736
  const held = visible?.binaries[path];
1659
1737
  if (visible === undefined || held === undefined)
1660
- return absentBinary(path);
1661
- if (held.state === 'pending') {
1662
- const blob = queuedBlob(locator, path, held.localId);
1663
- return blob === null ? absentBinary(path) : { kind: 'binary', blob };
1664
- }
1738
+ return null;
1739
+ if (held.state === 'pending')
1740
+ return { kind: 'pending', localId: held.localId };
1665
1741
  const record = binaryRecord(path, held, visible.revisions[path]);
1666
1742
  if (record.state !== 'accepted')
1667
- return absentBinary(path);
1743
+ return null;
1668
1744
  const replica = replicas.get(locator);
1669
1745
  const queued = (replica?.queue ?? []).flatMap((batch) => batch.changes);
1670
1746
  const holds = (at) => {
@@ -1679,13 +1755,31 @@ export function composeRuntime(options) {
1679
1755
  if (change.kind === 'move' && change.to === confirmed)
1680
1756
  confirmed = change.path;
1681
1757
  }
1682
- return readBinary({
1758
+ return {
1683
1759
  kind: 'path',
1684
1760
  locator,
1685
1761
  path: confirmed,
1686
1762
  revision: record.revision,
1687
1763
  fileRef: record.fileRef,
1688
- });
1764
+ };
1765
+ }
1766
+ async function readBinaryPath(locator, path, revision) {
1767
+ await ready;
1768
+ const asked = binaryPath(locator, path, revision);
1769
+ if (asked === null)
1770
+ return absentBinary(path);
1771
+ if (asked.kind === 'pending') {
1772
+ const blob = queuedBlob(locator, path, asked.localId);
1773
+ return blob === null ? absentBinary(path) : { kind: 'binary', blob };
1774
+ }
1775
+ return readBinary(asked);
1776
+ }
1777
+ async function linkBinaryPath(locator, path, revision) {
1778
+ await ready;
1779
+ const asked = binaryPath(locator, path, revision);
1780
+ if (asked === null || asked.kind === 'pending')
1781
+ return absentBinary(path);
1782
+ return transferBinary(asked, (held) => options.adapter.link(held, asked), binaryLinkAnswer);
1689
1783
  }
1690
1784
  async function readBinaryReference(reference) {
1691
1785
  await ready;
@@ -1711,7 +1805,7 @@ export function composeRuntime(options) {
1711
1805
  }
1712
1806
  return undefined;
1713
1807
  }
1714
- async function invokeAction(name, input) {
1808
+ async function invokeAction(name, input, dispatching) {
1715
1809
  await ready;
1716
1810
  if (closed || refusal !== null)
1717
1811
  return UNCERTAIN;
@@ -1725,6 +1819,7 @@ export function composeRuntime(options) {
1725
1819
  }
1726
1820
  if (closed || authorization === undefined || refusal !== null)
1727
1821
  return UNCERTAIN;
1822
+ await dispatching?.();
1728
1823
  journal.record(`transfer action ${name}`);
1729
1824
  try {
1730
1825
  const result = await options.adapter.action(authorization, { name, input });
@@ -1745,6 +1840,60 @@ export function composeRuntime(options) {
1745
1840
  return UNCERTAIN;
1746
1841
  }
1747
1842
  }
1843
+ function unsettled(result) {
1844
+ return result.kind !== 'action' && result.retryable;
1845
+ }
1846
+ async function recoveredAction(name, input) {
1847
+ const operation = operationOf(input);
1848
+ if (operation === null)
1849
+ return invokeAction(name, input);
1850
+ const kept = async () => (await openLocal()).keepCall({ operation, name, input });
1851
+ calling.add(operation);
1852
+ try {
1853
+ let result = await invokeAction(name, input, kept);
1854
+ if (unsettled(result)) {
1855
+ await recovery.attempt(`action ${operation}`, async () => {
1856
+ if (closed || refusal !== null)
1857
+ return 'done';
1858
+ if (!socketOpen)
1859
+ return 'again';
1860
+ result = await invokeAction(name, input, kept);
1861
+ return unsettled(result) ? 'again' : 'done';
1862
+ }, { again: true });
1863
+ }
1864
+ if (!unsettled(result))
1865
+ await (await openLocal()).dropCall(operation);
1866
+ return result;
1867
+ }
1868
+ finally {
1869
+ calling.delete(operation);
1870
+ }
1871
+ }
1872
+ async function resendCalls(signal) {
1873
+ if (!socketOpen || closed || stopping)
1874
+ return 'done';
1875
+ const local = await openLocal();
1876
+ for (const held of await local.calls()) {
1877
+ if (calling.has(held.operation))
1878
+ continue;
1879
+ if (closed || signal.aborted)
1880
+ return 'done';
1881
+ if (pastHorizon(held.operation)) {
1882
+ await local.dropCall(held.operation);
1883
+ continue;
1884
+ }
1885
+ calling.add(held.operation);
1886
+ try {
1887
+ if (unsettled(await invokeAction(held.name, held.input)))
1888
+ return 'again';
1889
+ await local.dropCall(held.operation);
1890
+ }
1891
+ finally {
1892
+ calling.delete(held.operation);
1893
+ }
1894
+ }
1895
+ return 'done';
1896
+ }
1748
1897
  function continuing() {
1749
1898
  return !closed && !discoveryPaused;
1750
1899
  }
@@ -1762,8 +1911,9 @@ export function composeRuntime(options) {
1762
1911
  async function pullRefresh(authorized, confirmed, live) {
1763
1912
  if (!live())
1764
1913
  return unconfirmed;
1914
+ const turn = generation;
1765
1915
  try {
1766
- const result = await refreshRoom(options.adapter, authorized, confirmed, (models, held) => (live() ? remodel(confirmed.record.locator, models, held) : held), live);
1916
+ const result = await refreshRoom(reads, authorized, confirmed, (models, held) => (live() ? remodel(confirmed.record.locator, models, held) : held), live);
1767
1917
  const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
1768
1918
  journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
1769
1919
  return result;
@@ -1772,6 +1922,9 @@ export function composeRuntime(options) {
1772
1922
  journal.record(`transfer refresh ${confirmed.record.locator} transport-uncertainty`);
1773
1923
  throw error;
1774
1924
  }
1925
+ finally {
1926
+ reads.finished(confirmed.record.locator, turn);
1927
+ }
1775
1928
  }
1776
1929
  function staging(local, live) {
1777
1930
  return {
@@ -1814,12 +1967,13 @@ export function composeRuntime(options) {
1814
1967
  async function pullBootstrap(authorized, room, local, live) {
1815
1968
  if (!live())
1816
1969
  return unconfirmed;
1970
+ const turn = generation;
1817
1971
  if (replicas.has(room.locator)) {
1818
1972
  catching.add(room.locator);
1819
1973
  readSide.recover();
1820
1974
  }
1821
1975
  try {
1822
- const result = await bootstrapRoom(options.adapter, authorized, room, {
1976
+ const result = await bootstrapRoom(reads, authorized, room, {
1823
1977
  collection: modelOf(room.locator),
1824
1978
  selection: modelOf(room.locator, 'typed-selection'),
1825
1979
  }, (models, held) => (live() ? remodel(room.locator, models, held) : held), live, staging(local, live), reusable(room));
@@ -1834,12 +1988,28 @@ export function composeRuntime(options) {
1834
1988
  caughtUpTo(room.locator);
1835
1989
  throw error;
1836
1990
  }
1991
+ finally {
1992
+ reads.finished(room.locator, turn);
1993
+ }
1837
1994
  }
1838
1995
  function caughtUpTo(locator) {
1839
1996
  if (catching.delete(locator))
1840
1997
  readSide.recover();
1841
1998
  }
1999
+ function behind(room) {
2000
+ const held = replicas.get(room.locator)?.confirmed.record.roomVersion;
2001
+ return (held !== undefined &&
2002
+ [announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later)));
2003
+ }
1842
2004
  async function advance(room, local, authorized, live) {
2005
+ const carried = reads.carried(room.locator);
2006
+ const withheld = await advanceOnce(room, local, authorized, live);
2007
+ if (withheld !== null || reads.carried(room.locator) === carried || !behind(room)) {
2008
+ return withheld;
2009
+ }
2010
+ return advanceOnce(room, local, authorized, live);
2011
+ }
2012
+ async function advanceOnce(room, local, authorized, live) {
1843
2013
  await referenced(room.locator);
1844
2014
  if (!live() || paused.has(room.locator))
1845
2015
  return unconfirmed;
@@ -1876,6 +2046,8 @@ export function composeRuntime(options) {
1876
2046
  readSide.recover();
1877
2047
  return unconfirmed;
1878
2048
  }
2049
+ if (reloadsDue.get(room.locator) === generation)
2050
+ return unconfirmed;
1879
2051
  }
1880
2052
  const bootstrapped = await pullBootstrap(authorized, room, local, live);
1881
2053
  if (!live())
@@ -1967,7 +2139,7 @@ export function composeRuntime(options) {
1967
2139
  logAccepted(room.locator, submitted, accepted);
1968
2140
  const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
1969
2141
  ? { kind: 'staged', confirmed: current.confirmed }
1970
- : applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
2142
+ : await applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
1971
2143
  ? accepted.roomVersion
1972
2144
  : held, accepted.roomVersion);
1973
2145
  if (applied.kind !== 'staged') {
@@ -2229,6 +2401,7 @@ export function composeRuntime(options) {
2229
2401
  if (!continuing())
2230
2402
  return;
2231
2403
  const held = replicas.get(room.locator);
2404
+ let changed = null;
2232
2405
  if (held !== undefined) {
2233
2406
  const declared = {
2234
2407
  ...held.confirmed.record,
@@ -2239,7 +2412,7 @@ export function composeRuntime(options) {
2239
2412
  };
2240
2413
  const reclassified = sameMembership(declared, held.confirmed.record)
2241
2414
  ? { kind: 'staged', confirmed: confirmedOf(declared, held.confirmed) }
2242
- : readReclassified(declared, held.confirmed);
2415
+ : await readReclassified(declared, held.confirmed);
2243
2416
  const record = reclassified.kind === 'staged'
2244
2417
  ? declared
2245
2418
  : {
@@ -2250,15 +2423,19 @@ export function composeRuntime(options) {
2250
2423
  ? { kind: 'staged', confirmed: confirmedOf(record, held.confirmed) }
2251
2424
  : reclassified;
2252
2425
  await local.write({ next: record, previous: held.confirmed.record });
2426
+ const replica = read.kind === 'staged'
2427
+ ? await stepped(replicating(read.confirmed, held.queue, held))
2428
+ : { ...held, confirmed: { ...held.confirmed, record } };
2429
+ const set = await stepped(changing(room.locator, held, replica, { room: true }));
2430
+ replicas.set(room.locator, replica);
2431
+ changed = heldChange(room.locator, held, replica, set, { room: true });
2253
2432
  if (read.kind === 'staged') {
2254
- replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
2255
2433
  if (declarationRefusals.delete(room.locator)) {
2256
2434
  sourceRefusals.delete(room.locator);
2257
2435
  unreadable.delete(room.locator);
2258
2436
  }
2259
2437
  }
2260
- else if (read.kind === 'refused') {
2261
- replicas.set(room.locator, { ...held, confirmed: { ...held.confirmed, record } });
2438
+ else {
2262
2439
  refusedSource(room, read);
2263
2440
  declarationRefusals.add(room.locator);
2264
2441
  }
@@ -2269,13 +2446,18 @@ export function composeRuntime(options) {
2269
2446
  invalidModels.delete(key);
2270
2447
  }
2271
2448
  list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
2272
- if (held === undefined)
2449
+ if (changed === null)
2273
2450
  emitRooms();
2274
2451
  else
2275
- publishChangeSet(changeSet(room.locator, held, replicas.get(room.locator) ?? null, { room: true }));
2452
+ publishChangeSet(changed);
2276
2453
  });
2277
2454
  }
2278
2455
  async function scoped(result, room, local) {
2456
+ if ((result.scope === 'content' || result.scope === 'sync') && !result.retryable) {
2457
+ journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
2458
+ await loseRooms([room.locator]);
2459
+ return;
2460
+ }
2279
2461
  scopedRefusals.set(room.locator, {
2280
2462
  code: result.code,
2281
2463
  fields: result.fields,
@@ -2322,16 +2504,15 @@ export function composeRuntime(options) {
2322
2504
  });
2323
2505
  }
2324
2506
  async function synchronize(listed, local, progress, attempt, live) {
2325
- const acquired = listed.filter(acquires);
2326
- const prepared = await Promise.allSettled(acquired.map((room) => prepareRoom(room, local, progress)));
2507
+ const prepared = await Promise.allSettled(listed.map((room) => prepareRoom(room, local, progress)));
2327
2508
  if (!live())
2328
2509
  return;
2329
- const ready = acquired.filter((_, index) => {
2510
+ const ready = listed.filter((_, index) => {
2330
2511
  const outcome = prepared[index];
2331
2512
  return outcome?.status === 'fulfilled' && outcome.value;
2332
2513
  });
2333
2514
  if (attempt === 'opened') {
2334
- options.adapter.roomStream?.hold(heldRooms(ready), heldDemand(acquired));
2515
+ options.adapter.roomStream?.hold(heldRooms(ready));
2335
2516
  handed = true;
2336
2517
  }
2337
2518
  discovering?.release();
@@ -2359,19 +2540,10 @@ export function composeRuntime(options) {
2359
2540
  await retainModel(local, room, 'collection', modelOf(room.locator));
2360
2541
  if (discoveryPaused || authorization === undefined)
2361
2542
  return false;
2362
- if (room.replica.admission !== 'admitted') {
2363
- if (replicas.has(room.locator))
2364
- progress.complete = false;
2365
- return false;
2366
- }
2367
- return true;
2543
+ return room.replica.admission === 'admitted';
2368
2544
  }
2369
2545
  async function caughtUp(room, local, progress, live) {
2370
- const held = replicas.get(room.locator)?.confirmed.record.roomVersion;
2371
- if (held === undefined || authorization === undefined || !live())
2372
- return;
2373
- const behind = [announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later));
2374
- if (!behind)
2546
+ if (authorization === undefined || !live() || !behind(room))
2375
2547
  return;
2376
2548
  const withheld = await advance(room, local, authorization, live);
2377
2549
  if (withheld !== null)
@@ -2399,6 +2571,8 @@ export function composeRuntime(options) {
2399
2571
  }
2400
2572
  async function reload(room, local, progress, live) {
2401
2573
  try {
2574
+ if (live())
2575
+ reloadsDue.delete(room.locator);
2402
2576
  if (!live() || authorization === undefined)
2403
2577
  return;
2404
2578
  const bootstrapped = await pullBootstrap(authorization, room, local, live);
@@ -2415,7 +2589,7 @@ export function composeRuntime(options) {
2415
2589
  caughtUpTo(room.locator);
2416
2590
  }
2417
2591
  }
2418
- async function discoverRooms(local, progress, attempt, live) {
2592
+ async function discoverRooms(local, progress, attempt, live, scope) {
2419
2593
  if (closed || authorization === undefined) {
2420
2594
  notifyReady();
2421
2595
  return;
@@ -2425,7 +2599,7 @@ export function composeRuntime(options) {
2425
2599
  const started = discoveries;
2426
2600
  let result;
2427
2601
  try {
2428
- result = await options.adapter.discover(authorization);
2602
+ result = await options.adapter.discover(authorization, scope);
2429
2603
  }
2430
2604
  catch {
2431
2605
  result = { kind: 'transport-uncertainty' };
@@ -2502,7 +2676,7 @@ export function composeRuntime(options) {
2502
2676
  member = signedIn;
2503
2677
  emitMember();
2504
2678
  }
2505
- await synchronize(await applyDiscovery(result.rooms, started), local, progress, attempt, live);
2679
+ await synchronize(await applyDiscovery(result.rooms, started, result.covered), local, progress, attempt, live);
2506
2680
  }
2507
2681
  async function ensureAuthorization() {
2508
2682
  if (authorization !== undefined)
@@ -2599,9 +2773,43 @@ export function composeRuntime(options) {
2599
2773
  options.adapter.roomStream?.reopen();
2600
2774
  });
2601
2775
  }
2602
- function closedSocket(revoked) {
2776
+ function rediscover(scope) {
2777
+ return task(OPENING_LANE, async (live) => {
2778
+ if (!live() || !socketOpen)
2779
+ return;
2780
+ const progress = { complete: true, reachable: true };
2781
+ const gate = discover();
2782
+ moveTo(transferring(status));
2783
+ try {
2784
+ const local = await openLocal();
2785
+ if (!live())
2786
+ return;
2787
+ await ensureAuthorization();
2788
+ if (!live())
2789
+ return;
2790
+ await discoverRooms(local, progress, 'authorized', live, scope);
2791
+ }
2792
+ catch {
2793
+ progress.complete = false;
2794
+ }
2795
+ finally {
2796
+ gate.release();
2797
+ notifyReady();
2798
+ }
2799
+ if (!live())
2800
+ return;
2801
+ if (!progress.complete)
2802
+ complete = false;
2803
+ await rest(progress, live);
2804
+ });
2805
+ }
2806
+ function closedSocket(revoked, lost) {
2603
2807
  const opened = socketOpen;
2604
2808
  socketOpen = false;
2809
+ if (lost) {
2810
+ reads.lost();
2811
+ conversations.lost();
2812
+ }
2605
2813
  if (revoked) {
2606
2814
  void refuse('revoked-authorization');
2607
2815
  return;
@@ -2618,9 +2826,10 @@ export function composeRuntime(options) {
2618
2826
  function applyOpening(expired) {
2619
2827
  const progress = underway?.generation === generation ? underway.progress : null;
2620
2828
  const cycle = progress ?? { complete: true, reachable: true };
2621
- const replacing = reloadNow((room) => expired.includes(room.locator), cycle);
2622
2829
  if (opened === generation)
2623
2830
  opened = null;
2831
+ const reloading = progress === null ? [] : owedReloads(expired);
2832
+ const replacing = reloadNow((room) => expired.includes(room.locator) || reloading.includes(room.locator), cycle);
2624
2833
  return task(OPENING_LANE, async (live) => {
2625
2834
  underway = null;
2626
2835
  if (!live())
@@ -2630,24 +2839,17 @@ export function composeRuntime(options) {
2630
2839
  await rest(cycle, live);
2631
2840
  return;
2632
2841
  }
2633
- await roomsCarried(expired);
2842
+ await roomsCarried([...expired, ...reloading]);
2634
2843
  if (!live())
2635
2844
  return;
2636
- const replaced = new Set(owed);
2637
- owed.clear();
2638
2845
  try {
2639
2846
  const local = await openLocal();
2640
- const held = [...discovered.values()].filter((room) => replicas.has(room.locator));
2641
- const reloading = held.filter((room) => !expired.includes(room.locator) &&
2642
- (replaced.has(room.locator) || revoked(room.locator)));
2643
- for (const room of reloading)
2644
- catching.add(room.locator);
2645
- readSide.recover();
2646
- const reloads = Promise.allSettled(reloading.map((room) => roomTask(room.locator, (current) => reload(room, local, progress, current))));
2647
- const reloaded = [...(await reloads), ...(await replacing)];
2847
+ const reloaded = await replacing;
2648
2848
  if (progress.reachable) {
2649
- for (const room of held) {
2650
- if (!expired.includes(room.locator) && !reloading.includes(room)) {
2849
+ for (const room of discovered.values()) {
2850
+ if (replicas.has(room.locator) &&
2851
+ !expired.includes(room.locator) &&
2852
+ !reloading.includes(room.locator)) {
2651
2853
  await confirmModels(room, local);
2652
2854
  }
2653
2855
  }
@@ -2664,14 +2866,29 @@ export function composeRuntime(options) {
2664
2866
  await finishOpening(progress, live);
2665
2867
  });
2666
2868
  }
2869
+ function owedReloads(expired) {
2870
+ const reloading = [...discovered.values()]
2871
+ .filter((room) => replicas.has(room.locator) &&
2872
+ !expired.includes(room.locator) &&
2873
+ (owed.has(room.locator) || revoked(room.locator)))
2874
+ .map((room) => room.locator);
2875
+ owed.clear();
2876
+ for (const locator of reloading)
2877
+ catching.add(locator);
2878
+ readSide.recover();
2879
+ return reloading;
2880
+ }
2667
2881
  async function reloadNow(replaced, progress) {
2882
+ const reloaded = [...discovered.values()].filter((room) => replicas.has(room.locator) && replaced(room));
2883
+ for (const room of reloaded)
2884
+ reloadsDue.set(room.locator, generation);
2668
2885
  try {
2669
2886
  const local = await openLocal();
2670
- return await Promise.allSettled([...discovered.values()]
2671
- .filter((room) => replicas.has(room.locator) && replaced(room))
2672
- .map((room) => roomTask(room.locator, (live) => reload(room, local, progress, live))));
2887
+ return await Promise.allSettled(reloaded.map((room) => roomTask(room.locator, (live) => reload(room, local, progress, live))));
2673
2888
  }
2674
2889
  catch {
2890
+ for (const room of reloaded)
2891
+ reloadsDue.delete(room.locator);
2675
2892
  progress.reachable = false;
2676
2893
  return [];
2677
2894
  }
@@ -2788,6 +3005,7 @@ export function composeRuntime(options) {
2788
3005
  progress.complete = false;
2789
3006
  return;
2790
3007
  }
3008
+ reads.applied();
2791
3009
  submitAll();
2792
3010
  for (const locator of stale)
2793
3011
  if (repairable.has(locator))
@@ -2796,10 +3014,11 @@ export function composeRuntime(options) {
2796
3014
  if (status.providerUnreachable)
2797
3015
  conversations.reconnected();
2798
3016
  const reading = { complete: true, reachable: true };
2799
- if (demand.conversations)
2800
- await conversations.catchUp(reading);
3017
+ await conversations.catchUp(reading);
2801
3018
  if (progress.reachable && reading.reachable)
2802
3019
  await conversations.resend(reading);
3020
+ if (progress.reachable)
3021
+ void recovery.attempt('calls', resendCalls);
2803
3022
  if (!reading.reachable) {
2804
3023
  progress.reachable = false;
2805
3024
  resendLater();
@@ -2860,7 +3079,7 @@ export function composeRuntime(options) {
2860
3079
  return;
2861
3080
  const room = discovered.get(locator);
2862
3081
  const held = replicas.get(locator);
2863
- if (room === undefined || room.replica.admission !== 'admitted' || !acquires(room))
3082
+ if (room === undefined || room.replica.admission !== 'admitted')
2864
3083
  return;
2865
3084
  if (held !== undefined &&
2866
3085
  (held.confirmed.record.replica.selection !== room.replica.selection ||
@@ -2912,14 +3131,17 @@ export function composeRuntime(options) {
2912
3131
  if (event.kind === 'opened') {
2913
3132
  socketOpen = true;
2914
3133
  generation += 1;
3134
+ reads.opened();
2915
3135
  conversations.reopened();
2916
3136
  recovery.resume();
2917
3137
  void sync('opened');
2918
3138
  }
2919
3139
  else if (event.kind === 'closed')
2920
- closedSocket(event.revoked);
3140
+ closedSocket(event.revoked, event.lost);
2921
3141
  else if (event.kind === 'opening')
2922
3142
  void applyOpening(event.expired);
3143
+ else if (event.kind === 'authority-changed')
3144
+ void rediscover(event.scope);
2923
3145
  else
2924
3146
  void applyRoom(event.locator, event.roomVersion);
2925
3147
  }, receipts);
@@ -2974,7 +3196,7 @@ export function composeRuntime(options) {
2974
3196
  if (!live())
2975
3197
  return;
2976
3198
  await conversations.reconcile();
2977
- if (conversations.current() || !demand.conversations) {
3199
+ if (conversations.current()) {
2978
3200
  show();
2979
3201
  return;
2980
3202
  }
@@ -2992,8 +3214,6 @@ export function composeRuntime(options) {
2992
3214
  if (closed)
2993
3215
  return;
2994
3216
  closed = true;
2995
- if (narrowing !== null)
2996
- clearTimeout(narrowing);
2997
3217
  if (rechecking !== null)
2998
3218
  clearInterval(rechecking);
2999
3219
  readinessWatches.clear();
@@ -3041,56 +3261,111 @@ export function composeRuntime(options) {
3041
3261
  if (options.adapter.roomStream === undefined)
3042
3262
  void sync('unstreamed');
3043
3263
  function planned(input, reach) {
3044
- return planOf(input, roomFacts(reach), planReach(reach));
3264
+ const plan = planOf(input, roomFacts(reach), planReach(reach));
3265
+ const named = plan.paths !== null &&
3266
+ plan.rooms !== null &&
3267
+ plan.paths.length * plan.rooms.length <= MAXIMUM_WINDOW;
3268
+ return plan.limit === null && named ? plan : { ...plan, limit: capped(plan.limit) };
3045
3269
  }
3046
3270
  function watchPlan(plan, deliver) {
3047
- return readSide.watch(plan, identityOf(plan), () => {
3271
+ let seeded = false;
3272
+ let pending = true;
3273
+ const shown = () => {
3274
+ const seed = seeds.get(plan.key);
3275
+ if (seed === undefined || seeded || !pending)
3276
+ return;
3277
+ seeded = true;
3278
+ deliver(seededResult(seed));
3279
+ };
3280
+ const watchers = seedWatchers.get(plan.key) ?? new Set();
3281
+ seedWatchers.set(plan.key, watchers);
3282
+ watchers.add(shown);
3283
+ const stop = readSide.watch(plan, identityOf(plan), () => {
3048
3284
  const cover = covered(plan);
3049
3285
  awaitPreparation(cover);
3050
- return cover;
3051
- }, deliver);
3286
+ return seedBehind(plan, cover);
3287
+ }, (message) => {
3288
+ const answered = message;
3289
+ pending = whole(answered) && 'pending' in answered.result;
3290
+ if (pending && seeds.has(plan.key)) {
3291
+ shown();
3292
+ return;
3293
+ }
3294
+ seeded = false;
3295
+ deliver(message);
3296
+ });
3297
+ return () => {
3298
+ watchers.delete(shown);
3299
+ if (watchers.size === 0)
3300
+ seedWatchers.delete(plan.key);
3301
+ stop();
3302
+ };
3052
3303
  }
3053
3304
  async function searched(input, reach) {
3054
- const plan = searchPlanOf(input);
3305
+ const asked = searchPlanOf(input);
3306
+ const plan = { ...asked, limit: capped(asked.limit) };
3055
3307
  const filter = filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach));
3056
3308
  for (;;) {
3057
3309
  const cover = covered(filter);
3058
3310
  awaitPreparation(cover);
3059
3311
  const pending = 'pending' in cover ? [...cover.pending] : [];
3060
- const covering = 'pending' in cover ? [] : cover.coverage.rooms;
3312
+ const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
3313
+ const held = covered({
3314
+ ...filter,
3315
+ rooms: reachedRooms(filter).filter((room) => !awaited.has(room)),
3316
+ });
3317
+ const covering = 'pending' in held ? [] : held.coverage.rooms;
3061
3318
  const unindexed = covering
3062
3319
  .filter(({ room }) => !searchIndex.rooms().has(room))
3063
3320
  .map(({ room }) => room);
3064
3321
  for (const room of unindexed)
3065
3322
  pending.push({ room, awaits: 'index' });
3066
- const held = conversations.searchable();
3323
+ const listed = conversations.searchable();
3067
3324
  const named = reach !== null && !reach.conversations
3068
3325
  ? []
3069
- : (plan.conversations ?? held).filter((one) => held.includes(one) && conversations.acquired(one));
3326
+ : (plan.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
3070
3327
  const waiting = named.filter((one) => !searchIndex.conversations().has(one));
3071
3328
  for (const conversation of waiting)
3072
3329
  pending.push({ conversation, awaits: 'index' });
3073
3330
  if (unindexed.length > 0 || waiting.length > 0)
3074
3331
  index(unindexed, waiting);
3075
- if (pending.length > 0 || 'pending' in cover)
3076
- return { pending };
3332
+ const complete = pending.length === 0 && !('pending' in cover);
3333
+ const answering = complete
3334
+ ? filter
3335
+ : {
3336
+ ...filter,
3337
+ rooms: covering
3338
+ .filter(({ room }) => searchIndex.rooms().has(room))
3339
+ .map(({ room }) => room),
3340
+ };
3341
+ const answered = covered(answering);
3342
+ if ('pending' in answered)
3343
+ continue;
3344
+ const searchedConversations = named.filter((one) => searchIndex.conversations().has(one));
3345
+ await readSide.settled();
3077
3346
  const seen = searchIndex.changes();
3078
3347
  await searchIndex.caughtUp();
3079
- const selected = plan.documents === undefined && reach === null ? null : await rowsQuery(filter);
3348
+ const selected = roomsAlone(answering) ? null : await rowsQuery(answering);
3080
3349
  if (selected !== null && 'pending' in selected)
3081
- return selected;
3350
+ continue;
3082
3351
  if (searchIndex.changes() !== seen ||
3083
- JSON.stringify(covered(filter)) !== JSON.stringify(cover))
3352
+ JSON.stringify(covered(answering)) !== JSON.stringify(answered))
3084
3353
  continue;
3085
- return {
3086
- ...searchIndex.search(plan, selected === null ? null : selected.documents, named),
3087
- coverage: {
3088
- rooms: covering,
3089
- conversations: named,
3090
- index: searchIndex.generation(),
3091
- overlay: cover.coverage.overlay,
3092
- },
3354
+ const hits = searchIndex.search(plan, selected === null ? null : selected.documents, searchedConversations, answering.rooms === null ? null : new Set(answering.rooms), plan.collection
3355
+ ? new Map(answered.coverage.rooms.map(({ room }) => [
3356
+ room,
3357
+ replicas.get(room)?.confirmed.record.typedSelection?.roots ?? [],
3358
+ ]))
3359
+ : null);
3360
+ const coverage = {
3361
+ rooms: answered.coverage.rooms,
3362
+ conversations: searchedConversations,
3363
+ index: searchIndex.generation(),
3364
+ overlay: answered.coverage.overlay,
3093
3365
  };
3366
+ return complete
3367
+ ? { ...hits, coverage }
3368
+ : { pending, documents: hits.documents, messages: hits.messages, coverage };
3094
3369
  }
3095
3370
  }
3096
3371
  const watch = (topic, input, deliver) => {
@@ -3138,7 +3413,7 @@ export function composeRuntime(options) {
3138
3413
  };
3139
3414
  }
3140
3415
  if (topic === 'readiness') {
3141
- return watchReadiness({ target: '', code: 'stored', capacity: null }, null, deliver);
3416
+ return watchReadiness({ target: '', code: 'stored', capacity: null, search: 'unused' }, null, deliver);
3142
3417
  }
3143
3418
  if (topic === 'rooms') {
3144
3419
  roomListeners.add(deliver);
@@ -3275,27 +3550,31 @@ export function composeRuntime(options) {
3275
3550
  });
3276
3551
  },
3277
3552
  write(app, value) {
3278
- return exclusive(async () => {
3279
- const local = await openLocal();
3280
- const stored = structuredClone(value);
3281
- await local.present({ app, value: stored });
3282
- presentations.set(app, stored);
3283
- publishChangeSet(changeSet('', null, null, { presentation: app }));
3284
- });
3553
+ return keepPresentation(app, () => structuredClone(value));
3554
+ },
3555
+ edit(app, members) {
3556
+ return keepPresentation(app, (held) => Object.fromEntries([
3557
+ ...Object.entries(plain(held) ? held : {}).filter(([member]) => !Object.hasOwn(members, member)),
3558
+ ...Object.entries(structuredClone(members)).filter(([, value]) => value !== null),
3559
+ ]));
3285
3560
  },
3286
3561
  },
3287
3562
  save(room, named, writtenBy) {
3288
3563
  return exclusive(() => saveRoomDrafts(room, named, writtenBy));
3289
3564
  },
3565
+ saveParts(room, parts, writtenBy) {
3566
+ return exclusive(() => saveParts(room, parts, writtenBy));
3567
+ },
3290
3568
  history(locator, path, query) {
3291
3569
  return readHistory(locator, path, query);
3292
3570
  },
3293
3571
  binaryFile: {
3294
3572
  read: readBinaryPath,
3573
+ link: linkBinaryPath,
3295
3574
  readReference: readBinaryReference,
3296
3575
  },
3297
3576
  call(name, input) {
3298
- return invokeAction(name, input);
3577
+ return recoveredAction(name, input);
3299
3578
  },
3300
3579
  reached(locator, roomVersion, path) {
3301
3580
  return path === undefined
@@ -3339,15 +3618,14 @@ export function composeRuntime(options) {
3339
3618
  query: (plan) => rowsQuery(plan),
3340
3619
  watchPlan,
3341
3620
  search: searched,
3342
- searching,
3343
3621
  watchReadiness,
3344
3622
  };
3345
3623
  return {
3346
3624
  md,
3625
+ seed,
3347
3626
  stop,
3348
3627
  lose: loseRooms,
3349
3628
  watch,
3350
- acquire,
3351
3629
  persisted(answer) {
3352
3630
  if (answer === persistence)
3353
3631
  return;