@agentshouse/mdruntime 0.1.0-c03e7b79830dd → 0.1.0-c0bed82db8d73

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 (169) hide show
  1. package/AGENTS.md +65 -65
  2. package/README.md +8 -7
  3. package/dist/adapter.d.ts +10 -14
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/attached-model.d.ts +1 -1
  6. package/dist/attached-model.d.ts.map +1 -1
  7. package/dist/attached-model.js +2 -2
  8. package/dist/binary.d.ts +0 -5
  9. package/dist/binary.d.ts.map +1 -1
  10. package/dist/binary.js +1 -1
  11. package/dist/bootstrap.d.ts +13 -2
  12. package/dist/bootstrap.d.ts.map +1 -1
  13. package/dist/bootstrap.js +94 -57
  14. package/dist/change-set.d.ts +1 -7
  15. package/dist/change-set.d.ts.map +1 -1
  16. package/dist/change-set.js +1 -4
  17. package/dist/client.d.ts +2 -3
  18. package/dist/client.d.ts.map +1 -1
  19. package/dist/client.js +2 -2
  20. package/dist/compile.d.ts +6 -1
  21. package/dist/compile.d.ts.map +1 -1
  22. package/dist/compile.js +12 -3
  23. package/dist/connection.d.ts +4 -5
  24. package/dist/connection.d.ts.map +1 -1
  25. package/dist/connection.js +481 -448
  26. package/dist/conversation.d.ts +2 -0
  27. package/dist/conversation.d.ts.map +1 -1
  28. package/dist/conversation.js +40 -19
  29. package/dist/durability.d.ts +2 -5
  30. package/dist/durability.d.ts.map +1 -1
  31. package/dist/durability.js +5 -0
  32. package/dist/fulltext.d.ts +27 -0
  33. package/dist/fulltext.d.ts.map +1 -0
  34. package/dist/fulltext.js +368 -0
  35. package/dist/host.d.ts +4 -4
  36. package/dist/host.d.ts.map +1 -1
  37. package/dist/host.js +35 -15
  38. package/dist/identity.d.ts +0 -1
  39. package/dist/identity.d.ts.map +1 -1
  40. package/dist/identity.js +0 -3
  41. package/dist/index.d.ts +7 -7
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +2 -2
  44. package/dist/memory.d.ts.map +1 -1
  45. package/dist/memory.js +0 -11
  46. package/dist/messages.d.ts +28 -0
  47. package/dist/messages.d.ts.map +1 -0
  48. package/dist/messages.js +90 -0
  49. package/dist/outcome.d.ts +0 -1
  50. package/dist/outcome.d.ts.map +1 -1
  51. package/dist/outcome.js +0 -3
  52. package/dist/overlay.js +1 -1
  53. package/dist/query.d.ts +4 -1
  54. package/dist/query.d.ts.map +1 -1
  55. package/dist/query.js +12 -1
  56. package/dist/reach.d.ts +0 -2
  57. package/dist/reach.d.ts.map +1 -1
  58. package/dist/reach.js +17 -28
  59. package/dist/readiness.d.ts +20 -9
  60. package/dist/readiness.d.ts.map +1 -1
  61. package/dist/readiness.js +6 -6
  62. package/dist/reads.d.ts.map +1 -1
  63. package/dist/reads.js +3 -1
  64. package/dist/refresh.d.ts +6 -2
  65. package/dist/refresh.d.ts.map +1 -1
  66. package/dist/refresh.js +60 -22
  67. package/dist/replica.d.ts +3 -1
  68. package/dist/replica.d.ts.map +1 -1
  69. package/dist/room-draft.d.ts +1 -0
  70. package/dist/room-draft.d.ts.map +1 -1
  71. package/dist/room-draft.js +33 -0
  72. package/dist/save.js +2 -2
  73. package/dist/search.d.ts +11 -43
  74. package/dist/search.d.ts.map +1 -1
  75. package/dist/search.js +0 -296
  76. package/dist/store.d.ts +17 -36
  77. package/dist/store.d.ts.map +1 -1
  78. package/dist/store.js +28 -227
  79. package/dist/stored.d.ts +9 -7
  80. package/dist/stored.d.ts.map +1 -1
  81. package/dist/stored.js +46 -40
  82. package/dist/tables.d.ts +46 -12
  83. package/dist/tables.d.ts.map +1 -1
  84. package/dist/tables.js +94 -7
  85. package/dist/wire.d.ts +0 -4
  86. package/dist/wire.d.ts.map +1 -1
  87. package/dist/wire.js +0 -1
  88. package/dist/words.d.ts +19 -0
  89. package/dist/words.d.ts.map +1 -0
  90. package/dist/words.js +150 -0
  91. package/package.json +11 -18
  92. package/dist/chat/acts.d.ts +0 -33
  93. package/dist/chat/acts.d.ts.map +0 -1
  94. package/dist/chat/acts.js +0 -36
  95. package/dist/chat/context.d.ts +0 -3
  96. package/dist/chat/context.d.ts.map +0 -1
  97. package/dist/chat/context.js +0 -10
  98. package/dist/chat/earlier.d.ts +0 -10
  99. package/dist/chat/earlier.d.ts.map +0 -1
  100. package/dist/chat/earlier.js +0 -54
  101. package/dist/chat/house.d.ts +0 -25
  102. package/dist/chat/house.d.ts.map +0 -1
  103. package/dist/chat/house.js +0 -38
  104. package/dist/chat/input.d.ts +0 -11
  105. package/dist/chat/input.d.ts.map +0 -1
  106. package/dist/chat/input.js +0 -92
  107. package/dist/chat/kit.d.ts +0 -61
  108. package/dist/chat/kit.d.ts.map +0 -1
  109. package/dist/chat/kit.js +0 -123
  110. package/dist/chat/lasting.d.ts +0 -6
  111. package/dist/chat/lasting.d.ts.map +0 -1
  112. package/dist/chat/lasting.js +0 -19
  113. package/dist/chat/list.d.ts +0 -28
  114. package/dist/chat/list.d.ts.map +0 -1
  115. package/dist/chat/list.js +0 -103
  116. package/dist/chat/markdown.d.ts +0 -21
  117. package/dist/chat/markdown.d.ts.map +0 -1
  118. package/dist/chat/markdown.js +0 -246
  119. package/dist/chat/message.d.ts +0 -7
  120. package/dist/chat/message.d.ts.map +0 -1
  121. package/dist/chat/message.js +0 -244
  122. package/dist/chat/messages.d.ts +0 -30
  123. package/dist/chat/messages.d.ts.map +0 -1
  124. package/dist/chat/messages.js +0 -198
  125. package/dist/chat/name.d.ts +0 -7
  126. package/dist/chat/name.d.ts.map +0 -1
  127. package/dist/chat/name.js +0 -37
  128. package/dist/chat/opened.d.ts +0 -12
  129. package/dist/chat/opened.d.ts.map +0 -1
  130. package/dist/chat/opened.js +0 -19
  131. package/dist/chat/overlay.d.ts +0 -32
  132. package/dist/chat/overlay.d.ts.map +0 -1
  133. package/dist/chat/overlay.js +0 -103
  134. package/dist/chat/parts.d.ts +0 -58
  135. package/dist/chat/parts.d.ts.map +0 -1
  136. package/dist/chat/parts.js +0 -120
  137. package/dist/chat/pieces.d.ts +0 -62
  138. package/dist/chat/pieces.d.ts.map +0 -1
  139. package/dist/chat/pieces.js +0 -1110
  140. package/dist/chat/process.d.ts +0 -20
  141. package/dist/chat/process.d.ts.map +0 -1
  142. package/dist/chat/process.js +0 -32
  143. package/dist/chat/provider.d.ts +0 -70
  144. package/dist/chat/provider.d.ts.map +0 -1
  145. package/dist/chat/provider.js +0 -635
  146. package/dist/chat/question.d.ts +0 -9
  147. package/dist/chat/question.d.ts.map +0 -1
  148. package/dist/chat/question.js +0 -69
  149. package/dist/chat/questions.d.ts +0 -31
  150. package/dist/chat/questions.d.ts.map +0 -1
  151. package/dist/chat/questions.js +0 -111
  152. package/dist/chat/read.d.ts +0 -5
  153. package/dist/chat/read.d.ts.map +0 -1
  154. package/dist/chat/read.js +0 -13
  155. package/dist/chat/ready.d.ts +0 -37
  156. package/dist/chat/ready.d.ts.map +0 -1
  157. package/dist/chat/ready.js +0 -93
  158. package/dist/chat/room-file.d.ts +0 -8
  159. package/dist/chat/room-file.d.ts.map +0 -1
  160. package/dist/chat/room-file.js +0 -72
  161. package/dist/chat/steady.d.ts +0 -3
  162. package/dist/chat/steady.d.ts.map +0 -1
  163. package/dist/chat/steady.js +0 -26
  164. package/dist/chat/style.d.ts +0 -4
  165. package/dist/chat/style.d.ts.map +0 -1
  166. package/dist/chat/style.js +0 -306
  167. package/dist/chat.d.ts +0 -15
  168. package/dist/chat.d.ts.map +0 -1
  169. package/dist/chat.js +0 -8
@@ -6,7 +6,7 @@ import { filterOf, identityOf, planOf, plain, queryKey, } from './query.js';
6
6
  import { createLiveQueries } from './live.js';
7
7
  import { seededResult } from './seed.js';
8
8
  import { documentKey, roomOfKey } from './rows.js';
9
- import { createSearchIndex, searchPlanOf, } from './search.js';
9
+ import { searchPlanOf } from './search.js';
10
10
  import { capped, MAXIMUM_WINDOW } from './windows.js';
11
11
  import { bootstrapRoom } from './bootstrap.js';
12
12
  import { changeSet, mergeChangeSets } from './change-set.js';
@@ -17,18 +17,17 @@ import { runtimeError } from './failure.js';
17
17
  import { createLog } from './log.js';
18
18
  import { binaryRecord } from './binary.js';
19
19
  import { openedFiles } from './opened.js';
20
- import { ABSENT_TARGET, absentBinary, absentReference, binaryAnswer, binaryLinkAnswer, calledResult, failure, historyAnswer, LOCKED_UNREADABLE, lockedRoom, undeclared, UNCERTAIN, UNREADABLE, } from './outcome.js';
20
+ import { ABSENT_TARGET, absentBinary, binaryAnswer, binaryLinkAnswer, calledResult, failure, historyAnswer, LOCKED_UNREADABLE, lockedRoom, undeclared, UNCERTAIN, UNREADABLE, } from './outcome.js';
21
21
  import { createRecovery } from './recovery.js';
22
22
  import { doorReads } from './reads.js';
23
23
  import { advancement } from './refresh.js';
24
24
  import { buildBatch, buildMove, joined, limiting, queuedDocuments, requeuing, } from './save.js';
25
25
  import { compositionOf, entryAt, landed, movedBy, overlayOf, replaying, returning, returnedPaths, sameMembership, slicing, slicingWith, standing, storedRecord, touchedBy, } from './replica.js';
26
- import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, moveTarget, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
26
+ import { binaryBase, changedParts, changesOf, gatheredParts, held, heldFile, joinedDraft, moveTarget, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
27
27
  import { same, saving, STARTING, transferring, } from './status.js';
28
- import { detectStorageLoss, nextGeneration } from './durability.js';
29
- import { readinessOf, SETTLED_ROOMS, } from './readiness.js';
28
+ import { detectStorageLoss, nextGeneration, refusedForSpace, } from './durability.js';
29
+ import { readinessOf, } from './readiness.js';
30
30
  import { FencedMap } from './fence.js';
31
- import { fingerprint } from './stored.js';
32
31
  import { sameBinary } from './overlay.js';
33
32
  import { paced, ROW_WEIGHT, stepped, yielded } from './turn.js';
34
33
  import { identityTime, roomDraftRevision } from './identity.js';
@@ -37,15 +36,6 @@ import { openRuntimeStore, } from './store.js';
37
36
  import { changedCleanly, sameComposition, validatedCollection, } from './validation.js';
38
37
  import { unconfirmed } from './transfer.js';
39
38
  import { governs, holds, narrowed, planReach, } from './reach.js';
40
- function roomsAlone(plan) {
41
- return (plan.reach === null &&
42
- plan.root.types === null &&
43
- plan.root.where === null &&
44
- plan.root.pathPrefix === null &&
45
- plan.paths === null &&
46
- plan.hop === null &&
47
- plan.backlinks === null);
48
- }
49
39
  function within(root, path) {
50
40
  return root === '' || beneath(root, path);
51
41
  }
@@ -63,12 +53,23 @@ const OPENING_LANE = 'opening';
63
53
  const CONVERSATION_LANE = 'conversations';
64
54
  const ROOM_READS = 256;
65
55
  const ROOM_ATTEMPTS = 16;
66
- const SEARCH_BATCH = 256;
67
56
  const ERASE_BATCH = 512;
68
57
  const REDERIVE_BATCH = 256;
69
58
  const WALK_BYTES = 1 << 20;
70
59
  const QUERY_BYTES = 1 << 20;
71
60
  const STAGE_BYTES = 1 << 20;
61
+ const STAGE_FLOOR = 1 << 16;
62
+ // A batch aims at this much database work, so a Save or a query waits behind at most one short write.
63
+ const STAGE_AIM_MS = 50;
64
+ const PARTS_HELD = 4;
65
+ // Bytes still arriving are reported at most this often, well inside the 2 s progress bound, so readiness is not rebuilt for every chunk the network hands over.
66
+ const ARRIVAL_REPORT_MS = 250;
67
+ const INSTALL_STEPS = [
68
+ 'writing',
69
+ 'downloading',
70
+ 'replaying',
71
+ 'listing',
72
+ ];
72
73
  const CLEAN_CHANGES = 2_000;
73
74
  // A Room that locks offline stays readable this long after the connection drops, so a laptop waking or changing network keeps working while a disconnected device locks soon after.
74
75
  const LOCK_GRACE_MS = 120_000;
@@ -92,10 +93,9 @@ function pendingKey(locator, id) {
92
93
  }
93
94
  export function composeRuntime(options) {
94
95
  const localSaves = options.adapter.localSaves === true;
95
- const roomTypes = roomTypesOf(options.adapter.roomTypes);
96
+ const roomTypes = roomTypesOf(options.adapter.roomTypes, options.adapter.nameableRoomTypes);
96
97
  let authorization = options.authorization;
97
98
  let opening;
98
- let store;
99
99
  const fences = new Map();
100
100
  const isFenced = (locator) => fences.has(locator);
101
101
  let fencings = 0;
@@ -172,6 +172,7 @@ export function composeRuntime(options) {
172
172
  let roomReads = { generation, free: ROOM_READS, waiting: [] };
173
173
  const working = new Set();
174
174
  let mutations = Promise.resolve();
175
+ let gathering = null;
175
176
  const roomListeners = new Set();
176
177
  const refusalListeners = new Set();
177
178
  const storageLossListeners = new Set();
@@ -192,19 +193,39 @@ export function composeRuntime(options) {
192
193
  const serviceModeListeners = new Set();
193
194
  let member = null;
194
195
  const memberListeners = new Set();
195
- const prepared = new Set();
196
196
  const unpreparedDemand = new Set();
197
- const unindexable = new Set();
197
+ const unpreparable = new Set();
198
198
  let preparing = false;
199
199
  let targets = {};
200
200
  let persistence = options.shell.persistence;
201
- let capacity = null;
202
- const paused = new Set();
203
- let rechecking = null;
201
+ let full = false;
202
+ let probing = null;
203
+ let testing = null;
204
+ const database = {
205
+ async ask(request) {
206
+ try {
207
+ return await options.database.ask(request);
208
+ }
209
+ catch (error) {
210
+ if (refusedForSpace(error))
211
+ filled();
212
+ throw error;
213
+ }
214
+ },
215
+ };
204
216
  const readinessWatches = new Set();
205
217
  let readinessQueued = false;
206
218
  const catching = new Set();
207
219
  const transfers = new FencedMap(isFenced);
220
+ const installs = new Map();
221
+ const arrivals = new Map();
222
+ let arrivalReported = 0;
223
+ let onScreen = options.shell.onScreen;
224
+ const shownWaiters = [];
225
+ let partsFree = PARTS_HELD;
226
+ const partWaiters = [];
227
+ let stageBytes = STAGE_FLOOR * 4;
228
+ let installWrites = Promise.resolve();
208
229
  const conversations = createConversationStore({
209
230
  agents: declaredAgents,
210
231
  documents: (declaration, after) => rowsQuery(planOf({
@@ -225,36 +246,13 @@ export function composeRuntime(options) {
225
246
  stream: () => options.adapter.conversations,
226
247
  publish: publishChangeSet,
227
248
  online: () => connectionOf() !== 'unreachable' && refusal === null,
249
+ full: () => full,
228
250
  closed: () => closed,
229
251
  unanswered: resendLater,
230
252
  expired: (operation) => pastHorizon(operation),
231
253
  });
232
- const searchIndex = createSearchIndex({
233
- async *room(room) {
234
- const local = await openLocal();
235
- for (let after = null;;) {
236
- const key = replicas.get(room)?.record.key;
237
- if (key === undefined)
238
- return;
239
- const page = await local.searched(key, room, after, SEARCH_BATCH, null);
240
- yield page.documents;
241
- if (page.next === null)
242
- return;
243
- after = page.next;
244
- }
245
- },
246
- async documents(room, paths) {
247
- const key = replicas.get(room)?.record.key;
248
- if (key === undefined || paths.length === 0)
249
- return [];
250
- return (await (await openLocal()).searched(key, room, null, paths.length, paths)).documents;
251
- },
252
- });
253
- const roomsToIndex = new Set();
254
- const conversationsToIndex = new Set();
255
- let indexing = false;
256
254
  const journal = createLog();
257
- const keptFiles = openedFiles(options.database, isFenced);
255
+ const keptFiles = openedFiles(database, isFenced);
258
256
  let notifyReady = () => { };
259
257
  const ready = new Promise((resolve) => {
260
258
  notifyReady = resolve;
@@ -334,14 +332,38 @@ export function composeRuntime(options) {
334
332
  const value = presentations.get(app);
335
333
  return value === undefined ? null : structuredClone(value);
336
334
  }
337
- function keepPresentation(app, next) {
338
- return exclusive(async () => {
339
- const local = await openLocal();
340
- const stored = next(presentations.get(app));
341
- await local.present({ app, value: stored });
342
- presentations.set(app, stored);
343
- publishChangeSet(changeSet('', { presentation: app }));
344
- });
335
+ async function writtenRoomDraft(room, path, revision, input, next) {
336
+ const parts = readParts(input);
337
+ if (path === '' || normalize(path) !== path)
338
+ throw runtimeError('room-draft-malformed');
339
+ const local = await openLocal();
340
+ const current = roomDrafts.get(room)?.get(path);
341
+ if ((current?.revision ?? null) !== revision) {
342
+ return staleWrite(current === undefined ? null : ((await roomDraftsOf([current]))[0] ?? null));
343
+ }
344
+ const file = parts.file === undefined
345
+ ? undefined
346
+ : await heldFile(parts.file.blob, binaryBase(await visibleOf(room, [path]), path));
347
+ const record = {
348
+ locator: room,
349
+ path,
350
+ revision: next,
351
+ parts: held(current?.parts, parts, file),
352
+ returned: current?.returned ?? null,
353
+ };
354
+ await local.keepRoomDraft(record, parts.file === undefined || file === undefined
355
+ ? []
356
+ : [{ key: file.localId, blob: parts.file.blob }]);
357
+ rememberRoomDraft(record);
358
+ publishChangeSet(changeSet(room, { drafts: [path] }));
359
+ return { outcome: 'written', roomDraft: (await roomDraftsOf([record]))[0] ?? null };
360
+ }
361
+ async function keptPresentation(app, next) {
362
+ const local = await openLocal();
363
+ const stored = next(presentations.get(app));
364
+ await local.present({ app, value: stored });
365
+ presentations.set(app, stored);
366
+ publishChangeSet(changeSet('', { presentation: app }));
345
367
  }
346
368
  function emitPresentation(app) {
347
369
  if (closed)
@@ -532,23 +554,26 @@ export function composeRuntime(options) {
532
554
  }
533
555
  function applyChangeSet(set) {
534
556
  liveQueries.touched(set);
535
- searchIndex.changed(set);
536
557
  }
537
558
  function unpublished(error) {
538
559
  journal.record(`publication failed ${error instanceof Error ? error.name : 'unknown'}`);
539
560
  }
561
+ function indexed(locator) {
562
+ return replicas.get(locator)?.indexed === true;
563
+ }
540
564
  async function prepareReplica(locator) {
541
565
  for (;;) {
542
566
  const held = replicas.get(locator);
543
- if (held === undefined || closed || prepared.has(locator))
567
+ if (held === undefined || closed || held.indexed)
544
568
  return;
545
569
  const key = held.record.key;
546
570
  const done = await exclusive(async () => (await openLocal()).rederive(key, REDERIVE_BATCH));
547
- if (replicas.get(locator)?.record.key !== key)
571
+ const now = replicas.get(locator);
572
+ if (now?.record.key !== key)
548
573
  continue;
549
574
  if (!done)
550
575
  continue;
551
- prepared.add(locator);
576
+ replicas.set(locator, { ...now, indexed: true });
552
577
  publishChangeSet(changeSet(locator, { documents: null }));
553
578
  liveQueries.recover();
554
579
  queueReadiness();
@@ -556,11 +581,11 @@ export function composeRuntime(options) {
556
581
  }
557
582
  }
558
583
  function unprepared() {
559
- return [...unpreparedDemand, ...replicas.keys()].find((locator) => !prepared.has(locator) && !unindexable.has(locator));
584
+ return [...unpreparedDemand, ...replicas.keys()].find((locator) => !indexed(locator) && !unpreparable.has(locator));
560
585
  }
561
- function unbuilt(locator, error) {
562
- unindexable.add(locator);
563
- journal.record(`index failed ${locator} ${error instanceof Error ? error.name : 'unknown'}`);
586
+ function unpreparedBy(locator, error) {
587
+ unpreparable.add(locator);
588
+ journal.record(`preparation failed ${locator} ${error instanceof Error ? error.name : 'unknown'}`);
564
589
  queueReadiness();
565
590
  }
566
591
  function prepare(locators = []) {
@@ -576,7 +601,7 @@ export function composeRuntime(options) {
576
601
  if (closed)
577
602
  return;
578
603
  unpreparedDemand.delete(waiting);
579
- await prepareReplica(waiting).catch((error) => unbuilt(waiting, error));
604
+ await prepareReplica(waiting).catch((error) => unpreparedBy(waiting, error));
580
605
  }
581
606
  }
582
607
  finally {
@@ -617,7 +642,7 @@ export function composeRuntime(options) {
617
642
  pending.push({ room: locator, awaits: 'acquisition' });
618
643
  continue;
619
644
  }
620
- if (!prepared.has(locator)) {
645
+ if (!replica.indexed) {
621
646
  pending.push({ room: locator, awaits: 'preparation' });
622
647
  continue;
623
648
  }
@@ -650,7 +675,7 @@ export function composeRuntime(options) {
650
675
  return { listed: true, rooms: named.filter((room) => rooms.has(room)) };
651
676
  return { listed: named.every((room) => replicas.has(room)), rooms: named };
652
677
  }
653
- function roomState(locator, search) {
678
+ function roomState(locator) {
654
679
  if (sourceRefusals.has(locator))
655
680
  return 'refused';
656
681
  const listedRoom = discovered.get(locator);
@@ -661,27 +686,17 @@ export function composeRuntime(options) {
661
686
  return 'unadmitted';
662
687
  if (!replicas.has(locator))
663
688
  return 'acquiring';
664
- if (!prepared.has(locator))
689
+ if (!indexed(locator))
665
690
  return 'preparing';
666
- if (search && !searchIndex.rooms().has(locator))
667
- return 'indexing';
668
691
  return 'complete';
669
692
  }
670
- function conversationCoverage(reach, search) {
693
+ function conversationCoverage(reach) {
671
694
  const declared = reach === null || reach.conversations;
672
695
  if (!declared || options.adapter.conversations === undefined || declaredAgents() === undefined)
673
696
  return 'undeclared';
674
697
  if (!conversations.known() || !conversations.current())
675
698
  return 'acquiring';
676
- if (!search)
677
- return 'complete';
678
- const unindexed = conversations
679
- .searchable()
680
- .filter((one) => conversations.acquired(one) && !searchIndex.conversations().has(one));
681
- if (unindexed.length === 0)
682
- return 'complete';
683
- index([], unindexed);
684
- return 'indexing';
699
+ return 'complete';
685
700
  }
686
701
  function connectionOf() {
687
702
  if (socketOpen)
@@ -701,6 +716,7 @@ export function composeRuntime(options) {
701
716
  return [
702
717
  {
703
718
  room,
719
+ name: discovered.get(room)?.name ?? room,
704
720
  file: typeof file === 'string' ? file : '',
705
721
  rule: held.code,
706
722
  location: typeof location === 'string' ? location : null,
@@ -708,34 +724,73 @@ export function composeRuntime(options) {
708
724
  ];
709
725
  });
710
726
  }
727
+ function installationOf(coverage) {
728
+ const rooms = { done: 0, total: 0 };
729
+ const parts = { done: 0, total: 0 };
730
+ const bytes = { done: 0, total: 0 };
731
+ const received = { done: 0, total: 0 };
732
+ const steps = new Set();
733
+ let waiting = false;
734
+ for (const { room, state } of coverage) {
735
+ if (state === 'unadmitted')
736
+ continue;
737
+ rooms.total += 1;
738
+ if (state === 'complete')
739
+ rooms.done += 1;
740
+ const transfer = transfers.get(room);
741
+ const held = installs.get(room);
742
+ const inventory = transfer?.inventory ?? replicas.get(room)?.record.inventory ?? null;
743
+ if (inventory !== null) {
744
+ const done = transfer?.done ?? inventory;
745
+ const share = held?.share ?? null;
746
+ parts.total += inventory.parts;
747
+ bytes.total += inventory.bytes;
748
+ parts.done += done.parts;
749
+ bytes.done += done.bytes;
750
+ if (share !== null && share.entries > 0) {
751
+ bytes.done += Math.floor((share.bytes * share.written) / share.entries);
752
+ }
753
+ received.total += inventory.bytes;
754
+ received.done += done.bytes + (share?.bytes ?? 0);
755
+ for (const arrived of arrivals.get(room)?.values() ?? [])
756
+ received.done += arrived;
757
+ }
758
+ const installing = held !== undefined && state !== 'complete';
759
+ if (installing)
760
+ steps.add(held.step);
761
+ waiting ||= installing || state === 'acquiring';
762
+ }
763
+ return {
764
+ step: INSTALL_STEPS.find((step) => steps.has(step)) ?? null,
765
+ paused: waiting && !onScreen ? 'hidden' : null,
766
+ rooms,
767
+ parts,
768
+ bytes,
769
+ received,
770
+ };
771
+ }
711
772
  function readinessFor(port, reach) {
712
773
  const required = requiredRooms(reach);
713
774
  const search = port.search === 'required';
714
- const coverage = required.rooms.map((room) => ({ room, state: roomState(room, search) }));
715
- const building = (state) => coverage
716
- .filter((room) => room.state === state && !unindexable.has(room.room))
775
+ const coverage = required.rooms.map((room) => ({ room, state: roomState(room) }));
776
+ const preparing = coverage
777
+ .filter((room) => room.state === 'preparing' && !unpreparable.has(room.room))
717
778
  .map(({ room }) => room);
718
- const preparing = building('preparing');
719
779
  if (preparing.length > 0)
720
780
  prepare(preparing);
721
- const indexing = building('indexing');
722
- if (indexing.length > 0)
723
- index(indexing, []);
724
781
  return readinessOf({
725
782
  code: port.code,
726
783
  coverage: {
727
784
  listed: required.listed,
728
785
  rooms: coverage,
729
- conversations: conversationCoverage(reach, search),
786
+ conversations: conversationCoverage(reach),
730
787
  search: search ? 'required' : 'unused',
731
788
  },
732
789
  persistence,
733
790
  connection: connectionOf(),
734
791
  house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
735
- capacity: capacity !== null &&
736
- coverage.some(({ room, state }) => paused.has(room) || !SETTLED_ROOMS.has(state))
737
- ? capacity
738
- : port.capacity,
792
+ space: full ? 'full' : port.space,
793
+ installation: installationOf(coverage),
739
794
  refused: refusedIn(required.rooms),
740
795
  lost: storageLoss !== null,
741
796
  established: targets[port.target]?.established === true,
@@ -744,6 +799,7 @@ export function composeRuntime(options) {
744
799
  function establish(name) {
745
800
  targets = { ...targets, [name]: { established: true } };
746
801
  queueReadiness();
802
+ probeEachMinute();
747
803
  void durably((local) => local.fact('targets', targets)).catch(() => {
748
804
  journal.record(`target record failed ${name}`);
749
805
  });
@@ -781,87 +837,51 @@ export function composeRuntime(options) {
781
837
  readinessWatches.delete(watch);
782
838
  };
783
839
  }
784
- function pageBytes(page) {
785
- let bytes = 0;
786
- for (const { path, content } of page.entries)
787
- bytes += (path.length + (content?.length ?? 0)) * 2;
788
- return bytes;
789
- }
790
- function exceeded(error) {
791
- return (typeof error === 'object' &&
792
- error !== null &&
793
- error.name === 'QuotaExceededError');
794
- }
795
- async function estimated() {
796
- const estimate = options.shell.estimate === undefined ? null : await options.shell.estimate();
797
- return estimate === null ? null : Math.max(0, estimate.total - estimate.usage);
798
- }
799
- function pause(locator, cause) {
800
- paused.add(locator);
801
- capacity = cause;
802
- journal.record(`capacity ${cause.cause} ${locator} ${cause.needed} ${cause.available ?? '-'}`);
840
+ function filled() {
841
+ if (full || closed)
842
+ return;
843
+ full = true;
844
+ journal.record('space full');
803
845
  queueReadiness();
804
- if (rechecking !== null)
846
+ probeEachMinute();
847
+ }
848
+ function installedTarget() {
849
+ return Object.values(targets).some((target) => target.established);
850
+ }
851
+ function probeEachMinute() {
852
+ if (!full || probing !== null || closed || !installedTarget())
805
853
  return;
806
- // A paused preparation measures the browser's space again each minute, so space freed elsewhere resumes it without a retry.
807
- rechecking = setInterval(() => {
808
- void recheck();
854
+ // A device whose storage filled after its install tries a write each minute, so space freed elsewhere ends its read-only mode without a Retry.
855
+ probing = setInterval(() => {
856
+ void probed();
809
857
  }, 60_000);
810
858
  }
811
- async function recheck() {
812
- const held = capacity;
813
- if (held === null || closed)
814
- return;
815
- const available = await estimated().catch(() => null);
816
- if (available === null || available < (held.needed ?? 0))
817
- return;
818
- if (held.cause === 'storage-exceeded' &&
819
- (held.available === null || available <= held.available))
820
- return;
821
- resume();
859
+ function probed() {
860
+ testing ??= database.ask({ kind: 'space.probe' }).then(() => {
861
+ testing = null;
862
+ spaceFreed();
863
+ return true;
864
+ }, () => {
865
+ testing = null;
866
+ return false;
867
+ });
868
+ return testing;
822
869
  }
823
- function resume() {
824
- if (rechecking !== null)
825
- clearInterval(rechecking);
826
- rechecking = null;
827
- if (paused.size === 0 && capacity === null)
870
+ function spaceFreed() {
871
+ if (!full)
828
872
  return;
829
- paused.clear();
830
- capacity = null;
831
- journal.record('capacity resumed');
873
+ full = false;
874
+ if (probing !== null)
875
+ clearInterval(probing);
876
+ probing = null;
877
+ unpreparable.clear();
878
+ journal.record('space freed');
832
879
  queueReadiness();
833
880
  if (!closed && authorization !== undefined)
834
881
  void sync('unstreamed');
835
882
  }
836
- async function reclaimed(local) {
837
- const unreferenced = [...transfers.keys()].filter((locator) => !discovered.has(locator));
838
- for (const locator of unreferenced) {
839
- await local.abandon(locator);
840
- transfers.delete(locator);
841
- }
842
- if (unreferenced.length > 0)
843
- journal.record(`capacity reclaimed ${unreferenced.length}`);
844
- return unreferenced.length > 0;
845
- }
846
- async function admitted(local, locator, needed) {
847
- if (paused.has(locator))
848
- return false;
849
- let available = await estimated().catch(() => null);
850
- if (available === null || available >= needed)
851
- return true;
852
- if (await reclaimed(local))
853
- available = await estimated().catch(() => null);
854
- if (available === null || available >= needed)
855
- return true;
856
- pause(locator, { cause: 'insufficient-space', needed, available });
857
- return false;
858
- }
859
- async function exceededOn(locator, bytes) {
860
- const available = await estimated().catch(() => null);
861
- pause(locator, { cause: 'storage-exceeded', needed: bytes, available });
862
- }
863
883
  function seedRoomHeld(room, version) {
864
- return prepared.has(room) && reachedBy(room, version);
884
+ return indexed(room) && reachedBy(room, version);
865
885
  }
866
886
  function seedHeld(at) {
867
887
  return Object.entries(at).every(([room, version]) => (roomsListed && !rooms.has(room)) || seedRoomHeld(room, version));
@@ -982,81 +1002,6 @@ export function composeRuntime(options) {
982
1002
  const held = answered.cover;
983
1003
  return { ...answered.rows, coverage: held.coverage };
984
1004
  }
985
- async function everyRow(plan) {
986
- const window = { ...plan, limit: MAXIMUM_WINDOW, after: null, total: false };
987
- const first = await rowsQuery(window);
988
- if ('pending' in first || first.next === undefined)
989
- return first;
990
- const documents = 'documents' in first ? [...first.documents] : [];
991
- const groups = 'groups' in first ? [...first.groups] : [];
992
- for (let next = first.next; next !== undefined;) {
993
- const values = JSON.parse(next);
994
- const page = await rowsQuery({
995
- ...window,
996
- after: { text: next, values: values.length === 0 ? null : values },
997
- });
998
- if ('pending' in page)
999
- return page;
1000
- if ('documents' in page)
1001
- documents.push(...page.documents);
1002
- else
1003
- groups.push(...page.groups);
1004
- if (JSON.stringify(page.coverage) !== JSON.stringify(first.coverage))
1005
- return everyRow(plan);
1006
- next = page.next;
1007
- }
1008
- return 'documents' in first
1009
- ? { documents, coverage: first.coverage }
1010
- : { groups, coverage: first.coverage };
1011
- }
1012
- async function indexConversation(conversation) {
1013
- searchIndex.indexMessages(conversation, [], false);
1014
- let after = null;
1015
- for (;;) {
1016
- const batch = await durably((local) => local.messagesAfter(conversation, after));
1017
- if (closed)
1018
- return;
1019
- searchIndex.indexMessages(conversation, batch.map(({ identity, text }) => ({ conversation, identity, text })), batch.length === 0);
1020
- if (batch.length === 0)
1021
- return;
1022
- after = batch[batch.length - 1].identity;
1023
- }
1024
- }
1025
- function indexNext() {
1026
- const room = [...roomsToIndex].find((locator) => !searchIndex.rooms().has(locator) && !unindexable.has(locator));
1027
- if (room !== undefined) {
1028
- roomsToIndex.delete(room);
1029
- return () => prepared.has(room)
1030
- ? searchIndex.indexRoom(room).catch((error) => unbuilt(room, error))
1031
- : undefined;
1032
- }
1033
- const conversation = [...conversationsToIndex].find((one) => !searchIndex.conversations().has(one));
1034
- if (conversation === undefined)
1035
- return null;
1036
- conversationsToIndex.delete(conversation);
1037
- return () => indexConversation(conversation);
1038
- }
1039
- function index(rooms, named) {
1040
- for (const room of rooms)
1041
- roomsToIndex.add(room);
1042
- for (const conversation of named)
1043
- conversationsToIndex.add(conversation);
1044
- if (indexing)
1045
- return;
1046
- indexing = true;
1047
- void (async () => {
1048
- try {
1049
- for (let unit = indexNext(); unit !== null && !closed; unit = indexNext()) {
1050
- await yielded();
1051
- await unit();
1052
- queueReadiness();
1053
- }
1054
- }
1055
- finally {
1056
- indexing = false;
1057
- }
1058
- })().catch(() => { });
1059
- }
1060
1005
  function publishChangeSet(set) {
1061
1006
  applyChangeSet(set);
1062
1007
  if (set.rooms.length > 0 ||
@@ -1086,9 +1031,10 @@ export function composeRuntime(options) {
1086
1031
  held,
1087
1032
  record: { ...held.record, model: revoked, selectionModel: revoked },
1088
1033
  }));
1034
+ let answer = null;
1089
1035
  try {
1090
1036
  const local = await openLocal();
1091
- await local.commit({
1037
+ answer = await local.commit({
1092
1038
  rooms: records.map(({ locator, record }) => ({
1093
1039
  locator,
1094
1040
  key: record.key,
@@ -1114,8 +1060,8 @@ export function composeRuntime(options) {
1114
1060
  for (const { locator, held, record } of records) {
1115
1061
  if (replicas.get(locator) !== held)
1116
1062
  continue;
1117
- recomposed(locator, held.record, record);
1118
- replicas.set(locator, { ...held, record });
1063
+ const indexed = answer === null ? held.indexed : committedRoom(locator, answer).indexed;
1064
+ replicas.set(locator, { ...held, record, indexed });
1119
1065
  }
1120
1066
  for (const locator of [...rooms.keys()]) {
1121
1067
  const room = rooms.get(locator);
@@ -1255,9 +1201,7 @@ export function composeRuntime(options) {
1255
1201
  unreadable.delete(locator);
1256
1202
  stale.delete(locator);
1257
1203
  repairable.delete(locator);
1258
- unindexable.delete(locator);
1259
- paused.delete(locator);
1260
- prepared.delete(locator);
1204
+ unpreparable.delete(locator);
1261
1205
  scopedRefusals.delete(locator);
1262
1206
  sourceRefusals.delete(locator);
1263
1207
  declarationRefusals.delete(locator);
@@ -1271,7 +1215,6 @@ export function composeRuntime(options) {
1271
1215
  }
1272
1216
  if (fresh.length === 0)
1273
1217
  return fresh;
1274
- searchIndex.forget(fresh);
1275
1218
  publishChangeSet(mergeChangeSets(removed.map(({ locator, drafts }) => changeSet(locator, { documents: null, room: true, queue: true, drafts }))));
1276
1219
  liveQueries.recover();
1277
1220
  moveTo({ ...status, ...attention() });
@@ -1365,7 +1308,6 @@ export function composeRuntime(options) {
1365
1308
  }
1366
1309
  }
1367
1310
  replicas.clear();
1368
- prepared.clear();
1369
1311
  queuesWithoutReplica.clear();
1370
1312
  for (const [locator, queue] of queuedBatches) {
1371
1313
  if (!held.rooms.has(locator))
@@ -1383,9 +1325,8 @@ export function composeRuntime(options) {
1383
1325
  overlaid: held.overlaid.get(record.key) ?? new Set(),
1384
1326
  count: counts[index] ?? 0,
1385
1327
  confirmed: 0,
1328
+ indexed: !held.unindexed.has(record.key),
1386
1329
  });
1387
- if (!held.unprepared.has(record.key))
1388
- prepared.add(record.locator);
1389
1330
  const existing = rooms.get(record.locator);
1390
1331
  if (existing === undefined)
1391
1332
  list(record, false);
@@ -1431,8 +1372,7 @@ export function composeRuntime(options) {
1431
1372
  async function open() {
1432
1373
  if (closed)
1433
1374
  throw new Error('runtime closed');
1434
- const opened = await openRuntimeStore(isFenced, options.database);
1435
- store = opened;
1375
+ const opened = openRuntimeStore(isFenced, database);
1436
1376
  const interrupted = await opened.fences();
1437
1377
  for (const locator of interrupted) {
1438
1378
  fences.set(locator, { at: discoveries, erased: false });
@@ -1478,10 +1418,29 @@ export function composeRuntime(options) {
1478
1418
  return opening;
1479
1419
  }
1480
1420
  function exclusive(work) {
1421
+ gathering = null;
1481
1422
  const next = mutations.then(work, work);
1482
1423
  mutations = next.then(() => undefined, () => undefined);
1483
1424
  return next;
1484
1425
  }
1426
+ function gathered(key, input, merge, work) {
1427
+ const tail = gathering;
1428
+ if (tail !== null && tail.key === key) {
1429
+ const merged = merge(tail.input, input);
1430
+ if (merged !== null) {
1431
+ tail.input = merged;
1432
+ return tail.done;
1433
+ }
1434
+ }
1435
+ const entry = { key, input, done: Promise.resolve() };
1436
+ entry.done = exclusive(() => {
1437
+ if (gathering === entry)
1438
+ gathering = null;
1439
+ return work(entry.input);
1440
+ });
1441
+ gathering = entry;
1442
+ return entry.done;
1443
+ }
1485
1444
  function withdrawable(locator) {
1486
1445
  return (batch) => {
1487
1446
  const state = pendings.get(pendingKey(locator, batch.id));
@@ -1563,23 +1522,11 @@ export function composeRuntime(options) {
1563
1522
  },
1564
1523
  };
1565
1524
  }
1566
- function recomposed(locator, before, after) {
1567
- if (before !== undefined &&
1568
- before.key === after.key &&
1569
- fingerprint(compositionOf(before)) === fingerprint(compositionOf(after))) {
1570
- return;
1571
- }
1572
- if (before !== undefined && before.key === after.key)
1573
- prepared.delete(locator);
1574
- }
1575
- function committedRoom(locator, record, answer) {
1576
- if (answer.prepared[locator] === false)
1577
- prepared.delete(locator);
1578
- else if (answer.prepared[locator] === true)
1579
- prepared.add(locator);
1580
- if (!prepared.has(locator))
1525
+ function committedRoom(locator, answer) {
1526
+ const indexed = answer.indexed[locator] ?? replicas.get(locator)?.indexed ?? false;
1527
+ if (!indexed)
1581
1528
  prepare([locator]);
1582
- return { count: answer.counts[locator] ?? replicas.get(locator)?.count ?? 0 };
1529
+ return { count: answer.counts[locator] ?? replicas.get(locator)?.count ?? 0, indexed };
1583
1530
  }
1584
1531
  function replayed(record, confirmedLayer, queue) {
1585
1532
  return stepped(replaying(confirmedLayer, queue, record.locator, compositionOf(record)));
@@ -1729,14 +1676,14 @@ export function composeRuntime(options) {
1729
1676
  const confirmedChanged = settling.commit?.published !== undefined ||
1730
1677
  settling.commit?.refreshed === true ||
1731
1678
  overrides !== undefined;
1732
- recomposed(locator, before?.record, record);
1733
- const { count } = committedRoom(locator, record, answer);
1679
+ const { count, indexed } = committedRoom(locator, answer);
1734
1680
  replicas.set(locator, {
1735
1681
  record,
1736
1682
  queue: kept,
1737
1683
  overlaid: touched,
1738
1684
  count,
1739
1685
  confirmed: (before?.confirmed ?? 0) + (confirmedChanged ? 1 : 0),
1686
+ indexed,
1740
1687
  });
1741
1688
  return mergeChangeSets([
1742
1689
  changeSet(locator, {
@@ -1782,7 +1729,7 @@ export function composeRuntime(options) {
1782
1729
  };
1783
1730
  }
1784
1731
  function publish(local, room, record, landing, live) {
1785
- return exclusive(async () => {
1732
+ const landed = exclusive(async () => {
1786
1733
  if (!live()) {
1787
1734
  caughtUpTo(room.locator);
1788
1735
  return true;
@@ -1819,9 +1766,8 @@ export function composeRuntime(options) {
1819
1766
  });
1820
1767
  }
1821
1768
  catch (error) {
1822
- if (!exceeded(error))
1769
+ if (!refusedForSpace(error))
1823
1770
  throw error;
1824
- await exceededOn(room.locator, null);
1825
1771
  caughtUpTo(room.locator);
1826
1772
  return true;
1827
1773
  }
@@ -1833,7 +1779,7 @@ export function composeRuntime(options) {
1833
1779
  for (const batch of accepted)
1834
1780
  pendings.delete(pendingKey(room.locator, batch.id));
1835
1781
  stale.delete(room.locator);
1836
- unindexable.delete(room.locator);
1782
+ unpreparable.delete(room.locator);
1837
1783
  if (unreadable.delete(room.locator))
1838
1784
  scopedRefusals.delete(room.locator);
1839
1785
  sourceRefusals.delete(room.locator);
@@ -1848,6 +1794,7 @@ export function composeRuntime(options) {
1848
1794
  }
1849
1795
  return true;
1850
1796
  });
1797
+ return landing.kind === 'refreshed' ? landed : landed.finally(() => installed(room.locator));
1851
1798
  }
1852
1799
  async function savedRoom(locator) {
1853
1800
  const local = await openLocal();
@@ -2244,6 +2191,8 @@ export function composeRuntime(options) {
2244
2191
  submitLater(locator);
2245
2192
  }
2246
2193
  async function committed(locator, target, saved, consumed, writtenBy, files = []) {
2194
+ if (full)
2195
+ return { outcome: 'refused', code: 'durable-write', retryable: true };
2247
2196
  const into = movedInto(locator, saved);
2248
2197
  if (into !== null) {
2249
2198
  return committedMove(locator, into, target, saved, consumed, writtenBy, files);
@@ -2522,11 +2471,6 @@ export function composeRuntime(options) {
2522
2471
  return (await openLocal()).file(localId, change?.kind === 'binary' ? change.mediaType : '');
2523
2472
  };
2524
2473
  }
2525
- function binaryRoom(request) {
2526
- if (request.kind === 'path')
2527
- return request.locator;
2528
- return options.adapter.referrerScope(request.referrer)?.locator ?? null;
2529
- }
2530
2474
  async function transferBinary(request, transfer, answer) {
2531
2475
  if (closed || refusal !== null)
2532
2476
  return UNCERTAIN;
@@ -2539,16 +2483,13 @@ export function composeRuntime(options) {
2539
2483
  if (closed || authorization === undefined || refusal !== null)
2540
2484
  return UNCERTAIN;
2541
2485
  const since = fencings;
2542
- journal.record(request.kind === 'path'
2543
- ? `transfer binary ${request.locator} ${request.path}`
2544
- : `transfer binary ${request.fileRef}`);
2486
+ journal.record(`transfer binary ${request.locator} ${request.path}`);
2545
2487
  try {
2546
2488
  const result = await transfer(authorization);
2547
2489
  const settled = result;
2548
2490
  journal.record(`outcome binary ${settled.kind}`);
2549
- const locator = binaryRoom(request);
2550
- if (locator !== null && fencedSince(locator, since))
2551
- return failure({ kind: 'room-not-readable', locator });
2491
+ if (fencedSince(request.locator, since))
2492
+ return failure({ kind: 'room-not-readable', locator: request.locator });
2552
2493
  if (settled.kind === 'scoped-refusal') {
2553
2494
  const room = replicas.get(settled.locator)?.record;
2554
2495
  if (room !== undefined)
@@ -2595,7 +2536,7 @@ export function composeRuntime(options) {
2595
2536
  const holds = (at) => {
2596
2537
  const kept = confirmedLayer.binaries[at];
2597
2538
  return (kept?.state === 'accepted' &&
2598
- kept.fileRef === record.fileRef &&
2539
+ kept.sha256 === record.sha256 &&
2599
2540
  confirmedLayer.revisions[at] === record.revision);
2600
2541
  };
2601
2542
  let confirmedAt = path;
@@ -2609,7 +2550,6 @@ export function composeRuntime(options) {
2609
2550
  locator,
2610
2551
  path: confirmedAt,
2611
2552
  revision: record.revision,
2612
- fileRef: record.fileRef,
2613
2553
  };
2614
2554
  }
2615
2555
  async function readBinaryPath(locator, path, revision) {
@@ -2640,18 +2580,11 @@ export function composeRuntime(options) {
2640
2580
  return absentBinary(path);
2641
2581
  return transferBinary(asked, (held) => options.adapter.link(held, asked), binaryLinkAnswer);
2642
2582
  }
2643
- async function readBinaryReference(reference) {
2644
- await ready;
2645
- const scope = options.adapter.referrerScope(reference.referrer);
2646
- if (scope === null)
2647
- return absentReference(reference.fileRef);
2648
- return readBinary({ kind: 'reference', fileRef: reference.fileRef, referrer: reference.referrer }, { locator: scope.locator, path: scope.path ?? '', revision: reference.fileRef });
2649
- }
2650
2583
  function readPart(file, part) {
2651
- return options.database.ask({ kind: 'file.part', key: file, part });
2584
+ return database.ask({ kind: 'file.part', key: file, part });
2652
2585
  }
2653
2586
  async function cleanUp() {
2654
- const freed = await options.database.ask({ kind: 'opened.clean' });
2587
+ const freed = await database.ask({ kind: 'opened.clean' });
2655
2588
  return freed + ((await options.shell.cleanUp?.()) ?? 0);
2656
2589
  }
2657
2590
  function declaredAgents() {
@@ -2878,41 +2811,108 @@ export function composeRuntime(options) {
2878
2811
  return unreadableRoom(texts, walk, named);
2879
2812
  return null;
2880
2813
  }
2881
- function refreshingOf(local, key, liveness) {
2814
+ function refreshingOf(local, key, liveness, replaying = null) {
2815
+ const write = replaying === null ? exclusive : installWrite;
2882
2816
  return {
2817
+ async turn() {
2818
+ if (replaying === null)
2819
+ return;
2820
+ installing(replaying, 'replaying');
2821
+ await screened();
2822
+ },
2883
2823
  prior: async (paths) => new Map((await local.entries(key, 'refreshed', paths)).map((entry) => [entry.path, entry])),
2884
2824
  async stage(page) {
2885
- const batches = batched(page.entries);
2886
- for (const [index, entries] of batches.entries()) {
2887
- const last = index === batches.length - 1;
2888
- const staged = await exclusive(async () => {
2825
+ for (let written = 0;;) {
2826
+ if (replaying !== null)
2827
+ await screened();
2828
+ const end = batchEnd(page.entries, written);
2829
+ const last = end === page.entries.length;
2830
+ const entries = page.entries.slice(written, end);
2831
+ const staged = await write(async () => {
2889
2832
  if (!liveness())
2890
2833
  return false;
2891
- await local.refresh(key, last
2834
+ await timed(() => local.refresh(key, last
2892
2835
  ? { ...page, entries }
2893
- : { entries, removals: [], folders: [], folderRemovals: [] });
2836
+ : { entries, removals: [], folders: [], folderRemovals: [] }));
2894
2837
  return true;
2895
2838
  });
2896
2839
  if (!staged)
2897
2840
  return false;
2841
+ if (last)
2842
+ return true;
2843
+ written = end;
2898
2844
  }
2899
- return true;
2900
2845
  },
2901
2846
  };
2902
2847
  }
2903
- function batched(entries) {
2904
- const batches = [[]];
2848
+ function batchEnd(entries, from) {
2849
+ let end = from;
2905
2850
  let spent = 0;
2906
- for (const entry of entries) {
2907
- const size = (entry.content?.length ?? 0) + ROW_WEIGHT;
2908
- if (spent + size > STAGE_BYTES && batches.at(-1).length > 0) {
2909
- batches.push([]);
2910
- spent = 0;
2911
- }
2912
- batches.at(-1).push(entry);
2851
+ while (end < entries.length) {
2852
+ const size = (entries[end].content?.length ?? 0) + ROW_WEIGHT;
2853
+ if (spent + size > stageBytes && end > from)
2854
+ break;
2913
2855
  spent += size;
2856
+ end += 1;
2857
+ }
2858
+ return end;
2859
+ }
2860
+ async function timed(work) {
2861
+ const started = performance.now();
2862
+ const done = await work();
2863
+ const spent = performance.now() - started;
2864
+ if (spent < STAGE_AIM_MS / 2)
2865
+ stageBytes = Math.min(STAGE_BYTES, Math.round(stageBytes * 1.5));
2866
+ else if (spent > STAGE_AIM_MS * 2) {
2867
+ stageBytes = Math.max(STAGE_FLOOR, Math.round(stageBytes / 1.5));
2914
2868
  }
2915
- return batches;
2869
+ return done;
2870
+ }
2871
+ function installWrite(work) {
2872
+ const next = installWrites.then(() => exclusive(work), () => exclusive(work));
2873
+ installWrites = next.then(() => undefined, () => undefined);
2874
+ return next;
2875
+ }
2876
+ function screened() {
2877
+ if (onScreen || closed)
2878
+ return Promise.resolve();
2879
+ queueReadiness();
2880
+ return new Promise((resolve) => shownWaiters.push(resolve));
2881
+ }
2882
+ async function heldPart() {
2883
+ while (partsFree === 0)
2884
+ await new Promise((admit) => partWaiters.push(admit));
2885
+ partsFree -= 1;
2886
+ return freed();
2887
+ }
2888
+ function freed() {
2889
+ let released = false;
2890
+ return () => {
2891
+ if (released)
2892
+ return;
2893
+ released = true;
2894
+ partsFree += 1;
2895
+ partWaiters.shift()?.();
2896
+ };
2897
+ }
2898
+ function installing(locator, step, share = null) {
2899
+ installs.set(locator, { step, share });
2900
+ queueReadiness();
2901
+ }
2902
+ function installed(locator) {
2903
+ if (installs.delete(locator))
2904
+ queueReadiness();
2905
+ }
2906
+ function screen(value) {
2907
+ if (value === onScreen)
2908
+ return;
2909
+ onScreen = value;
2910
+ journal.record(`installation ${value ? 'shown' : 'hidden'}`);
2911
+ queueReadiness();
2912
+ if (!value)
2913
+ return;
2914
+ for (const resolve of shownWaiters.splice(0))
2915
+ resolve();
2916
2916
  }
2917
2917
  async function pullRefresh(authorized, held, liveness) {
2918
2918
  if (!liveness())
@@ -2940,6 +2940,8 @@ export function composeRuntime(options) {
2940
2940
  return { kind: 'advanced', record: result.record, base };
2941
2941
  }
2942
2942
  catch (error) {
2943
+ if (refusedForSpace(error))
2944
+ return unconfirmed;
2943
2945
  journal.record(`transfer refresh ${record.locator} transport-uncertainty`);
2944
2946
  throw error;
2945
2947
  }
@@ -2966,38 +2968,105 @@ export function composeRuntime(options) {
2966
2968
  function staging(local, liveness) {
2967
2969
  return {
2968
2970
  held: (locator) => transfers.get(locator) ?? null,
2969
- async stage(record, page, composition) {
2970
- const bytes = pageBytes(page);
2971
- const batches = batched(page.entries);
2972
- let key = record.key;
2973
- for (const [index, entries] of batches.entries()) {
2974
- const last = index === batches.length - 1;
2975
- const staged = await exclusive(async () => {
2976
- if (!liveness())
2977
- return null;
2978
- if (index === 0 && !(await admitted(local, record.locator, bytes)))
2979
- return null;
2980
- try {
2981
- return await local.stage(record, composition, { entries, folders: last ? page.folders : [] }, key, last);
2971
+ async turn(locator, step) {
2972
+ installing(locator, step);
2973
+ for (;;) {
2974
+ await screened();
2975
+ const release = step === 'downloading' ? await heldPart() : () => { };
2976
+ if (onScreen || closed)
2977
+ return release;
2978
+ release();
2979
+ }
2980
+ },
2981
+ spare() {
2982
+ if (!onScreen || closed || partsFree === 0)
2983
+ return null;
2984
+ partsFree -= 1;
2985
+ return freed();
2986
+ },
2987
+ arriving(locator) {
2988
+ let ended = false;
2989
+ return {
2990
+ at(token, bytes) {
2991
+ if (ended)
2992
+ return;
2993
+ const held = arrivals.get(locator) ?? new Map();
2994
+ arrivals.set(locator, held.set(token, bytes));
2995
+ const now = performance.now();
2996
+ if (now - arrivalReported < ARRIVAL_REPORT_MS)
2997
+ return;
2998
+ arrivalReported = now;
2999
+ queueReadiness();
3000
+ },
3001
+ end() {
3002
+ ended = true;
3003
+ if (arrivals.delete(locator))
3004
+ queueReadiness();
3005
+ },
3006
+ };
3007
+ },
3008
+ received: (locator, token) => local.transferPart(locator, token),
3009
+ keep: (locator, token, received) => installWrite(async () => {
3010
+ if (liveness())
3011
+ await local.keepPart(locator, token, received);
3012
+ }),
3013
+ announced(locator) {
3014
+ const later = announced.get(locator);
3015
+ return later === undefined ? [] : [later];
3016
+ },
3017
+ async stage(record, page, composition, received) {
3018
+ const { locator } = record;
3019
+ const whole = received?.byteLength ?? 0;
3020
+ const from = record.writing?.written ?? 0;
3021
+ const token = record.writing?.token ?? null;
3022
+ const entries = page.entries.length;
3023
+ let held = record;
3024
+ if (token !== null)
3025
+ arrivals.get(locator)?.delete(token);
3026
+ installing(locator, 'writing', { written: from, entries, bytes: whole });
3027
+ for (let written = from;;) {
3028
+ await screened();
3029
+ if (!liveness())
3030
+ return null;
3031
+ const end = batchEnd(page.entries, written);
3032
+ const last = end === entries;
3033
+ const next = last
3034
+ ? {
3035
+ ...held,
3036
+ writing: null,
3037
+ done: {
3038
+ parts: held.done.parts + (received === null ? 0 : 1),
3039
+ bytes: held.done.bytes + whole,
3040
+ },
2982
3041
  }
2983
- catch (error) {
2984
- if (!exceeded(error))
2985
- throw error;
2986
- await exceededOn(record.locator, bytes);
3042
+ : { ...held, writing: { token, written: end } };
3043
+ const key = await installWrite(async () => {
3044
+ if (!liveness())
2987
3045
  return null;
2988
- }
3046
+ return timed(() => local.stage(next, composition, {
3047
+ entries: page.entries.slice(written, end),
3048
+ folders: last ? page.folders : [],
3049
+ }, last && received !== null && token !== null ? token : undefined));
2989
3050
  });
2990
- if (staged === null)
3051
+ if (key === null)
2991
3052
  return null;
2992
- key = staged;
3053
+ const arrived = !transfers.has(locator);
3054
+ held = { ...next, key };
3055
+ transfers.set(locator, held);
3056
+ if (arrived)
3057
+ liveQueries.recover();
3058
+ if (last) {
3059
+ installing(locator, held.pageToken === null ? 'replaying' : 'downloading');
3060
+ liveQueries.recover();
3061
+ return held;
3062
+ }
3063
+ installing(locator, 'writing', { written: end, entries, bytes: whole });
3064
+ written = end;
2993
3065
  }
2994
- transfers.set(record.locator, { ...record, key });
2995
- liveQueries.recover();
2996
- return key;
2997
3066
  },
2998
- async refreshing(key) {
3067
+ async refreshing(locator, key) {
2999
3068
  await exclusive(() => local.unrefresh(key));
3000
- return refreshingOf(local, key, liveness);
3069
+ return refreshingOf(local, key, liveness, locator);
3001
3070
  },
3002
3071
  abandon: (locator) => exclusive(async () => {
3003
3072
  if (!liveness())
@@ -3043,13 +3112,18 @@ export function composeRuntime(options) {
3043
3112
  }, (models, held) => (liveness() ? remodel(room.locator, models, held) : held), liveness, staging(local, liveness), reusable(room, local));
3044
3113
  const detail = result.kind === 'staged' ? result.record.roomVersion : result.kind;
3045
3114
  journal.record(`transfer bootstrap ${room.locator} ${detail}`);
3046
- if (result.kind !== 'staged')
3115
+ if (result.kind !== 'staged') {
3047
3116
  caughtUpTo(room.locator);
3117
+ installed(room.locator);
3118
+ }
3048
3119
  return result;
3049
3120
  }
3050
3121
  catch (error) {
3051
- journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
3052
3122
  caughtUpTo(room.locator);
3123
+ installed(room.locator);
3124
+ if (refusedForSpace(error))
3125
+ return unconfirmed;
3126
+ journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
3053
3127
  throw error;
3054
3128
  }
3055
3129
  finally {
@@ -3073,8 +3147,9 @@ export function composeRuntime(options) {
3073
3147
  }
3074
3148
  return advanceOnce(room, local, authorized, live);
3075
3149
  }
3076
- async function advanceOnce(room, local, authorized, live) {
3077
- if (!live() || paused.has(room.locator))
3150
+ async function advanceOnce(room, local, authorized, running) {
3151
+ const live = () => running() && !full;
3152
+ if (!live())
3078
3153
  return unconfirmed;
3079
3154
  const held = replicas.get(room.locator);
3080
3155
  if (held !== undefined) {
@@ -3114,12 +3189,11 @@ export function composeRuntime(options) {
3114
3189
  return unconfirmed;
3115
3190
  }
3116
3191
  const bootstrapped = await pullBootstrap(authorized, room, local, live);
3117
- if (!live())
3118
- return unconfirmed;
3119
- if (bootstrapped.kind !== 'staged')
3120
- return bootstrapped;
3121
- await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed }, live);
3122
- return null;
3192
+ if (bootstrapped.kind === 'staged') {
3193
+ await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed }, live);
3194
+ return live() ? null : unconfirmed;
3195
+ }
3196
+ return live() ? bootstrapped : unconfirmed;
3123
3197
  }
3124
3198
  async function withhold(withheld, room, local, progress) {
3125
3199
  if (withheld.kind === 'unconfirmed')
@@ -3753,9 +3827,8 @@ export function composeRuntime(options) {
3753
3827
  },
3754
3828
  ],
3755
3829
  });
3756
- recomposed(room.locator, held.record, record);
3757
- const { count } = committedRoom(room.locator, record, answer);
3758
- replicas.set(room.locator, { ...held, record, overlaid: diff.touched, count });
3830
+ const { count, indexed } = committedRoom(room.locator, answer);
3831
+ replicas.set(room.locator, { ...held, record, overlaid: diff.touched, count, indexed });
3759
3832
  changed = changeSet(room.locator, {
3760
3833
  documents: answer.changed[room.locator] ?? [],
3761
3834
  room: true,
@@ -3892,10 +3965,9 @@ export function composeRuntime(options) {
3892
3965
  if (!live() || authorization === undefined)
3893
3966
  return;
3894
3967
  const bootstrapped = await pullBootstrap(authorization, room, local, live);
3895
- if (!live())
3896
- return;
3897
3968
  if (bootstrapped.kind !== 'staged') {
3898
- await withhold(bootstrapped, room, local, progress);
3969
+ if (live())
3970
+ await withhold(bootstrapped, room, local, progress);
3899
3971
  return;
3900
3972
  }
3901
3973
  await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed }, live);
@@ -4541,8 +4613,10 @@ export function composeRuntime(options) {
4541
4613
  if (closed)
4542
4614
  return;
4543
4615
  closed = true;
4544
- if (rechecking !== null)
4545
- clearInterval(rechecking);
4616
+ if (probing !== null)
4617
+ clearInterval(probing);
4618
+ for (const resolve of shownWaiters.splice(0))
4619
+ resolve();
4546
4620
  connectionLive();
4547
4621
  lockListeners.clear();
4548
4622
  readinessWatches.clear();
@@ -4553,10 +4627,6 @@ export function composeRuntime(options) {
4553
4627
  withdrawServiceMode?.();
4554
4628
  conversations.close();
4555
4629
  void recovery.retire();
4556
- store?.close();
4557
- void opening?.then((opened) => {
4558
- opened.close();
4559
- }, () => { });
4560
4630
  roomListeners.clear();
4561
4631
  refusalListeners.clear();
4562
4632
  storageLossListeners.clear();
@@ -4632,9 +4702,8 @@ export function composeRuntime(options) {
4632
4702
  }
4633
4703
  async function searched(input, reach, locked) {
4634
4704
  const asked = searchPlanOf(input);
4635
- const plan = { ...asked, limit: capped(asked.limit) };
4636
4705
  const filter = {
4637
- ...filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)),
4706
+ ...filterOf(asked.documents ?? {}, roomFacts(reach), planReach(reach)),
4638
4707
  locked,
4639
4708
  };
4640
4709
  const unlocking = (filter.rooms ?? [])
@@ -4643,65 +4712,41 @@ export function composeRuntime(options) {
4643
4712
  for (;;) {
4644
4713
  const cover = covered(filter);
4645
4714
  awaitPreparation(cover);
4646
- const pending = [
4647
- ...unlocking,
4648
- ...('pending' in cover ? cover.pending : []),
4649
- ];
4650
- const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
4651
- const held = covered({
4652
- ...filter,
4653
- rooms: reachedRooms(filter).filter((room) => !awaited.has(room)),
4654
- });
4655
- const covering = 'pending' in held ? [] : held.coverage.rooms;
4656
- const unindexed = covering
4657
- .filter(({ room }) => !searchIndex.rooms().has(room))
4658
- .map(({ room }) => room);
4659
- for (const room of unindexed)
4660
- pending.push({ room, awaits: 'index' });
4661
- const listed = conversations.searchable();
4662
- const named = reach !== null && !reach.conversations
4663
- ? []
4664
- : (plan.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
4665
- const waiting = named.filter((one) => !searchIndex.conversations().has(one));
4666
- for (const conversation of waiting)
4667
- pending.push({ conversation, awaits: 'index' });
4668
- if (unindexed.length > 0 || waiting.length > 0)
4669
- index(unindexed, waiting);
4715
+ const pending = [...unlocking, ...('pending' in cover ? cover.pending : [])];
4670
4716
  const complete = pending.length === 0 && !('pending' in cover);
4717
+ const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
4671
4718
  const answering = complete
4672
4719
  ? filter
4673
- : {
4674
- ...filter,
4675
- rooms: covering
4676
- .filter(({ room }) => searchIndex.rooms().has(room))
4677
- .map(({ room }) => room),
4678
- };
4720
+ : { ...filter, rooms: reachedRooms(filter).filter((room) => !awaited.has(room)) };
4679
4721
  const answered = covered(answering);
4680
4722
  if ('pending' in answered)
4681
4723
  continue;
4682
- const searchedConversations = named.filter((one) => searchIndex.conversations().has(one));
4683
- const seen = searchIndex.changes();
4684
- await searchIndex.caughtUp();
4685
- const selected = roomsAlone(answering) ? null : await everyRow(answering);
4686
- if (selected !== null && 'pending' in selected)
4687
- continue;
4688
- if (searchIndex.changes() !== seen ||
4689
- JSON.stringify(covered(answering)) !== JSON.stringify(answered))
4724
+ const listed = conversations.searchable();
4725
+ const named = reach !== null && !reach.conversations
4726
+ ? []
4727
+ : (asked.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
4728
+ const hits = await (await openLocal()).search({
4729
+ text: asked.text,
4730
+ limit: capped(asked.limit),
4731
+ after: asked.after,
4732
+ total: asked.total,
4733
+ documents: {
4734
+ plan: answering,
4735
+ rooms: answered.rooms,
4736
+ beneath: asked.collection
4737
+ ? answered.rooms.map(({ locator, key }) => ({
4738
+ key,
4739
+ roots: replicas.get(locator)?.record.typedSelection?.roots ?? [],
4740
+ }))
4741
+ : [],
4742
+ },
4743
+ conversations: named,
4744
+ });
4745
+ if (JSON.stringify(covered(answering)) !== JSON.stringify(answered))
4690
4746
  continue;
4691
- const hits = searchIndex.search(plan, selected === null ? null : selected.documents, searchedConversations, answering.rooms !== null
4692
- ? new Set(answering.rooms)
4693
- : locked.length === 0
4694
- ? null
4695
- : new Set(reachedRooms(answering)), plan.collection
4696
- ? new Map(answered.coverage.rooms.map(({ room }) => [
4697
- room,
4698
- replicas.get(room)?.record.typedSelection?.roots ?? [],
4699
- ]))
4700
- : null);
4701
4747
  const coverage = {
4702
4748
  rooms: answered.coverage.rooms,
4703
- conversations: searchedConversations,
4704
- index: searchIndex.generation(),
4749
+ conversations: named,
4705
4750
  overlay: answered.coverage.overlay,
4706
4751
  };
4707
4752
  return complete
@@ -4758,7 +4803,7 @@ export function composeRuntime(options) {
4758
4803
  };
4759
4804
  }
4760
4805
  if (topic === 'readiness') {
4761
- return watchReadiness({ target: '', code: 'stored', capacity: null, search: 'unused' }, null, deliver);
4806
+ return watchReadiness({ target: '', code: 'stored', space: null, search: 'unused' }, null, deliver);
4762
4807
  }
4763
4808
  if (topic === 'rooms') {
4764
4809
  roomListeners.add(deliver);
@@ -4844,33 +4889,17 @@ export function composeRuntime(options) {
4844
4889
  const record = roomDrafts.get(room)?.get(path);
4845
4890
  return record === undefined ? null : ((await roomDraftsOf([record]))[0] ?? null);
4846
4891
  },
4847
- write(room, path, revision, input) {
4848
- return exclusive(async () => {
4849
- const parts = readParts(input);
4850
- if (path === '' || normalize(path) !== path)
4851
- throw runtimeError('room-draft-malformed');
4852
- const local = await openLocal();
4853
- const current = roomDrafts.get(room)?.get(path);
4854
- if ((current?.revision ?? null) !== revision) {
4855
- return staleWrite(current === undefined ? null : ((await roomDraftsOf([current]))[0] ?? null));
4856
- }
4857
- const file = parts.file === undefined
4858
- ? undefined
4859
- : await heldFile(parts.file.blob, binaryBase(await visibleOf(room, [path]), path));
4860
- const record = {
4861
- locator: room,
4862
- path,
4863
- revision: roomDraftRevision(),
4864
- parts: held(current?.parts, parts, file),
4865
- returned: current?.returned ?? null,
4866
- };
4867
- await local.keepRoomDraft(record, parts.file === undefined || file === undefined
4868
- ? []
4869
- : [{ key: file.localId, blob: parts.file.blob }]);
4870
- rememberRoomDraft(record);
4871
- publishChangeSet(changeSet(room, { drafts: [path] }));
4872
- return { outcome: 'written', roomDraft: (await roomDraftsOf([record]))[0] ?? null };
4873
- });
4892
+ write(room, path, revision, input, next = roomDraftRevision()) {
4893
+ if (typeof next !== 'string' || next === '')
4894
+ return Promise.reject(runtimeError('room-draft-malformed'));
4895
+ return gathered(`room-draft\u0000${room}\u0000${path}`, { revision, input, next }, (earlier, later) => {
4896
+ if (later.revision !== earlier.next)
4897
+ return null;
4898
+ const parts = gatheredParts(earlier.input, later.input);
4899
+ return parts === null
4900
+ ? null
4901
+ : { revision: earlier.revision, input: parts, next: later.next };
4902
+ }, (written) => writtenRoomDraft(room, path, written.revision, written.input, written.next));
4874
4903
  },
4875
4904
  discard(room, path, revision) {
4876
4905
  return exclusive(async () => {
@@ -4897,13 +4926,13 @@ export function composeRuntime(options) {
4897
4926
  });
4898
4927
  },
4899
4928
  write(app, value) {
4900
- return keepPresentation(app, () => structuredClone(value));
4929
+ return exclusive(() => keptPresentation(app, () => structuredClone(value)));
4901
4930
  },
4902
4931
  edit(app, members) {
4903
- return keepPresentation(app, (held) => Object.fromEntries([
4904
- ...Object.entries(plain(held) ? held : {}).filter(([member]) => !Object.hasOwn(members, member)),
4905
- ...Object.entries(structuredClone(members)).filter(([, value]) => value !== null),
4906
- ]));
4932
+ return gathered(`presentation\u0000${app}`, structuredClone(members), (earlier, later) => ({ ...earlier, ...later }), (edited) => keptPresentation(app, (held) => Object.fromEntries([
4933
+ ...Object.entries(plain(held) ? held : {}).filter(([member]) => !Object.hasOwn(edited, member)),
4934
+ ...Object.entries(edited).filter(([, value]) => value !== null),
4935
+ ])));
4907
4936
  },
4908
4937
  },
4909
4938
  save(room, named, writtenBy) {
@@ -4922,7 +4951,6 @@ export function composeRuntime(options) {
4922
4951
  part: readPart,
4923
4952
  read: readBinaryPath,
4924
4953
  link: linkBinaryPath,
4925
- readReference: readBinaryReference,
4926
4954
  },
4927
4955
  async call(name, input) {
4928
4956
  await ready;
@@ -4943,9 +4971,10 @@ export function composeRuntime(options) {
4943
4971
  },
4944
4972
  async retry() {
4945
4973
  journal.record('retry');
4946
- unindexable.clear();
4974
+ unpreparable.clear();
4947
4975
  queueReadiness();
4948
- resume();
4976
+ if (full)
4977
+ await probed();
4949
4978
  },
4950
4979
  observe: observing(watch),
4951
4980
  async reauthorize() {
@@ -4967,7 +4996,6 @@ export function composeRuntime(options) {
4967
4996
  const reachable = {
4968
4997
  md,
4969
4998
  watch,
4970
- referrerScope: (referrer) => options.adapter.referrerScope(referrer),
4971
4999
  plan: planned,
4972
5000
  query: (plan) => rowsQuery(plan),
4973
5001
  watchPlan,
@@ -4991,6 +5019,11 @@ export function composeRuntime(options) {
4991
5019
  journal.record(`persistence ${answer}`);
4992
5020
  queueReadiness();
4993
5021
  },
4994
- within: (reach, port) => narrowed(reachable, reach, port),
5022
+ shown: screen,
5023
+ within: (reach, port) => {
5024
+ if (full)
5025
+ void probed();
5026
+ return narrowed(reachable, reach, port);
5027
+ },
4995
5028
  };
4996
5029
  }