@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.
- package/AGENTS.md +221 -221
- package/README.md +8 -8
- package/dist/adapter.d.ts +35 -6
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +2 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +5 -1
- package/dist/change-set.d.ts +5 -2
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +8 -2
- package/dist/chat/earlier.d.ts +10 -0
- package/dist/chat/earlier.d.ts.map +1 -0
- package/dist/chat/earlier.js +54 -0
- package/dist/chat/kit.js +1 -1
- package/dist/chat/list.d.ts +2 -1
- package/dist/chat/list.d.ts.map +1 -1
- package/dist/chat/list.js +40 -4
- package/dist/chat/markdown.d.ts +1 -0
- package/dist/chat/markdown.d.ts.map +1 -1
- package/dist/chat/markdown.js +78 -20
- package/dist/chat/message.d.ts.map +1 -1
- package/dist/chat/message.js +9 -2
- package/dist/chat/messages.d.ts.map +1 -1
- package/dist/chat/messages.js +36 -16
- package/dist/chat/overlay.d.ts +1 -0
- package/dist/chat/overlay.d.ts.map +1 -1
- package/dist/chat/overlay.js +10 -0
- package/dist/chat/pieces.d.ts +62 -0
- package/dist/chat/pieces.d.ts.map +1 -0
- package/dist/chat/pieces.js +1110 -0
- package/dist/chat/provider.d.ts +1 -0
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +22 -14
- package/dist/chat/question.d.ts +1 -1
- package/dist/chat/question.d.ts.map +1 -1
- package/dist/chat/question.js +7 -18
- package/dist/chat/ready.d.ts +1 -1
- package/dist/chat/ready.d.ts.map +1 -1
- package/dist/chat/ready.js +1 -1
- package/dist/chat/steady.d.ts +3 -0
- package/dist/chat/steady.d.ts.map +1 -0
- package/dist/chat/steady.js +26 -0
- package/dist/chat/style.js +1 -1
- package/dist/chat.d.ts +2 -2
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/client.d.ts +6 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -5
- package/dist/collection.d.ts +4 -3
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +128 -63
- package/dist/connection.d.ts +13 -5
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +506 -228
- package/dist/conversation.d.ts +11 -0
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +195 -41
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +3 -2
- package/dist/host.d.ts +9 -7
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +58 -9
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/memory.d.ts +7 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +24 -11
- package/dist/observation.d.ts +2 -0
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +9 -3
- package/dist/outcome.d.ts +8 -2
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +3 -0
- package/dist/overlay.d.ts +5 -4
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +197 -132
- package/dist/query.d.ts +27 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +36 -7
- package/dist/reach.d.ts +1 -2
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +17 -4
- package/dist/react.d.ts +3 -3
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +7 -17
- package/dist/read-side.d.ts +3 -1
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +256 -69
- package/dist/reads.d.ts +11 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +98 -0
- package/dist/refresh.d.ts +4 -4
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +39 -14
- package/dist/replica.d.ts +11 -5
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +79 -41
- package/dist/room-draft.d.ts +12 -0
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +11 -12
- package/dist/rows.d.ts +3 -1
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +8 -1
- package/dist/save.d.ts +4 -2
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +195 -111
- package/dist/search.d.ts +27 -6
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +89 -7
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +75 -0
- package/dist/shared.d.ts +4 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +63 -33
- package/dist/store.d.ts +20 -4
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +404 -317
- package/dist/turn.d.ts +5 -3
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +26 -7
- package/dist/windows.d.ts +26 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +93 -0
- package/dist/wire.d.ts +16 -0
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +4 -0
- package/package.json +2 -2
- package/dist/acquisition.d.ts +0 -14
- package/dist/acquisition.d.ts.map +0 -1
- package/dist/acquisition.js +0 -24
package/dist/connection.js
CHANGED
|
@@ -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,
|
|
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: () =>
|
|
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' &&
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
528
|
-
|
|
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 = [...
|
|
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
|
|
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
|
|
679
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
1234
|
-
const
|
|
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 (!
|
|
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 :
|
|
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(
|
|
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,
|
|
1294
|
-
return
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
1598
|
+
for (const path of left)
|
|
1523
1599
|
forgetRoomDraft(locator, path);
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
|
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
|
|
1635
|
-
|
|
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 (
|
|
1640
|
-
const room = replicas.get(
|
|
1715
|
+
if (settled.kind === 'scoped-refusal') {
|
|
1716
|
+
const room = replicas.get(settled.locator)?.confirmed.record;
|
|
1641
1717
|
if (room !== undefined)
|
|
1642
|
-
await scoped(
|
|
1718
|
+
await scoped(settled, room, await openLocal());
|
|
1643
1719
|
}
|
|
1644
|
-
if (
|
|
1720
|
+
if (settled.kind === 'revoked-authorization')
|
|
1645
1721
|
await refuse('revoked-authorization');
|
|
1646
|
-
return
|
|
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
|
-
|
|
1654
|
-
|
|
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
|
|
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
|
|
1661
|
-
if (held.state === 'pending')
|
|
1662
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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 (
|
|
2449
|
+
if (changed === null)
|
|
2273
2450
|
emitRooms();
|
|
2274
2451
|
else
|
|
2275
|
-
publishChangeSet(
|
|
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
|
|
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 =
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
2650
|
-
if (
|
|
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(
|
|
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
|
-
|
|
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'
|
|
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()
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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
|
|
3323
|
+
const listed = conversations.searchable();
|
|
3067
3324
|
const named = reach !== null && !reach.conversations
|
|
3068
3325
|
? []
|
|
3069
|
-
: (plan.conversations ??
|
|
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
|
-
|
|
3076
|
-
|
|
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 =
|
|
3348
|
+
const selected = roomsAlone(answering) ? null : await rowsQuery(answering);
|
|
3080
3349
|
if (selected !== null && 'pending' in selected)
|
|
3081
|
-
|
|
3350
|
+
continue;
|
|
3082
3351
|
if (searchIndex.changes() !== seen ||
|
|
3083
|
-
JSON.stringify(covered(
|
|
3352
|
+
JSON.stringify(covered(answering)) !== JSON.stringify(answered))
|
|
3084
3353
|
continue;
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
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
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
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
|
|
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;
|