@agentshouse/mdruntime 0.1.0-c93103f5357ff → 0.1.0-c94cfdbdb6f68
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 +194 -194
- package/README.md +8 -8
- package/dist/adapter.d.ts +20 -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/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/list.d.ts.map +1 -1
- package/dist/chat/list.js +39 -3
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +11 -12
- 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/style.js +1 -1
- package/dist/client.d.ts +6 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -3
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +64 -34
- package/dist/connection.d.ts +9 -4
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +312 -154
- package/dist/conversation.d.ts +6 -0
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +161 -41
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +2 -1
- package/dist/host.d.ts +0 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +8 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/memory.d.ts +6 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +17 -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/query.d.ts +21 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +28 -5
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +13 -0
- package/dist/react.d.ts +1 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +2 -14
- package/dist/read-side.d.ts +3 -1
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +251 -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/replica.d.ts +5 -1
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +19 -3
- package/dist/rows.d.ts +1 -0
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +5 -0
- package/dist/save.d.ts +1 -0
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +22 -8
- package/dist/search.d.ts +15 -2
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +76 -17
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +18 -4
- package/dist/store.d.ts +16 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +48 -14
- package/dist/turn.d.ts +3 -3
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +11 -5
- 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,11 +1,12 @@
|
|
|
1
1
|
import { attachedModel } from './attached-model.js';
|
|
2
2
|
import { cached, modelTypes, NO_MODEL, sameModel, } from './model.js';
|
|
3
3
|
import { observing, whole } from './observation.js';
|
|
4
|
-
import { filterOf, identityOf, planOf, queryKey, } from './query.js';
|
|
4
|
+
import { filterOf, identityOf, planOf, plain, queryKey, } from './query.js';
|
|
5
5
|
import { createReadSide } from './read-side.js';
|
|
6
6
|
import { seededResult } from './seed.js';
|
|
7
7
|
import { markdownPath, roomOfKey } from './rows.js';
|
|
8
8
|
import { createSearchIndex, searchPlanOf, } from './search.js';
|
|
9
|
+
import { capped, MAXIMUM_WINDOW } from './windows.js';
|
|
9
10
|
import { bootstrapRoom } from './bootstrap.js';
|
|
10
11
|
import { changeSet, entering, mergeChangeSets } from './change-set.js';
|
|
11
12
|
import { createConversationStore, } from './conversation.js';
|
|
@@ -14,15 +15,15 @@ import { indexReferrers, referencesIndexed, validRoot, } from './collection.js';
|
|
|
14
15
|
import { runtimeError } from './failure.js';
|
|
15
16
|
import { createLog } from './log.js';
|
|
16
17
|
import { binaryRecord } from './binary.js';
|
|
17
|
-
import { absentBinary, binaryAnswer, calledResult, failure, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
|
|
18
|
+
import { absentBinary, binaryAnswer, binaryLinkAnswer, calledResult, failure, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
|
|
18
19
|
import { createRecovery } from './recovery.js';
|
|
20
|
+
import { doorReads } from './reads.js';
|
|
19
21
|
import { refreshRoom } from './refresh.js';
|
|
20
|
-
import { buildBatch, joined, limitedIn, requeued } from './save.js';
|
|
21
|
-
import { applyReturned, compositionOf, confirmedOf, landed, readReclassified, replicaOf, sameMembership,
|
|
22
|
-
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, sameMembership, standing, } from './replica.js';
|
|
24
|
+
import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, moveTarget, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
|
|
23
25
|
import { same, saving, STARTING, transferring, } from './status.js';
|
|
24
26
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
25
|
-
import { demanded, narrower, WHOLE_DEMAND, wider } from './acquisition.js';
|
|
26
27
|
import { readinessOf, SETTLED_ROOMS, } from './readiness.js';
|
|
27
28
|
import { FencedMap } from './fence.js';
|
|
28
29
|
import { tally } from './shared.js';
|
|
@@ -37,6 +38,7 @@ function roomsAlone(plan) {
|
|
|
37
38
|
plan.root.types === null &&
|
|
38
39
|
plan.root.where === null &&
|
|
39
40
|
plan.root.pathPrefix === null &&
|
|
41
|
+
plan.paths === null &&
|
|
40
42
|
plan.hop === null &&
|
|
41
43
|
plan.backlinks === null);
|
|
42
44
|
}
|
|
@@ -57,6 +59,10 @@ const OPENING_LANE = 'opening';
|
|
|
57
59
|
const CONVERSATION_LANE = 'conversations';
|
|
58
60
|
const ROOM_READS = 256;
|
|
59
61
|
const ROOM_ATTEMPTS = 16;
|
|
62
|
+
function operationOf(input) {
|
|
63
|
+
const operation = input?.operation_id;
|
|
64
|
+
return typeof operation === 'string' ? operation : null;
|
|
65
|
+
}
|
|
60
66
|
const outside = new Map();
|
|
61
67
|
function outsideOf(roots) {
|
|
62
68
|
const key = JSON.stringify(roots);
|
|
@@ -110,6 +116,7 @@ export function composeRuntime(options) {
|
|
|
110
116
|
const liveModels = new FencedMap(isModelFenced);
|
|
111
117
|
const invalidModels = new Set();
|
|
112
118
|
const discovered = new Map();
|
|
119
|
+
const reads = doorReads(options.adapter, (locator) => discovered.get(locator), () => generation);
|
|
113
120
|
let roomsListed = false;
|
|
114
121
|
const scopedRefusals = new Map();
|
|
115
122
|
const sourceRefusals = new Map();
|
|
@@ -146,6 +153,7 @@ export function composeRuntime(options) {
|
|
|
146
153
|
let handed = false;
|
|
147
154
|
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
148
155
|
const repairing = new Set();
|
|
156
|
+
const calling = new Set();
|
|
149
157
|
const repairable = new Map();
|
|
150
158
|
let outcomeRetention = null;
|
|
151
159
|
const answering = new Map();
|
|
@@ -182,8 +190,6 @@ export function composeRuntime(options) {
|
|
|
182
190
|
const unpreparedDemand = new Set();
|
|
183
191
|
const unindexable = new Set();
|
|
184
192
|
let preparing = false;
|
|
185
|
-
let demand = WHOLE_DEMAND;
|
|
186
|
-
let narrowing = null;
|
|
187
193
|
let targets = {};
|
|
188
194
|
let persistence = options.shell.persistence;
|
|
189
195
|
let capacity = null;
|
|
@@ -200,7 +206,7 @@ export function composeRuntime(options) {
|
|
|
200
206
|
durably,
|
|
201
207
|
stream: () => options.adapter.conversations,
|
|
202
208
|
publish: publishChangeSet,
|
|
203
|
-
online: () =>
|
|
209
|
+
online: () => connectionOf() !== 'unreachable' && refusal === null,
|
|
204
210
|
closed: () => closed,
|
|
205
211
|
unanswered: resendLater,
|
|
206
212
|
expired: (operation) => pastHorizon(operation),
|
|
@@ -266,6 +272,15 @@ export function composeRuntime(options) {
|
|
|
266
272
|
const value = presentations.get(app);
|
|
267
273
|
return value === undefined ? null : structuredClone(value);
|
|
268
274
|
}
|
|
275
|
+
function keepPresentation(app, next) {
|
|
276
|
+
return exclusive(async () => {
|
|
277
|
+
const local = await openLocal();
|
|
278
|
+
const stored = next(presentations.get(app));
|
|
279
|
+
await local.present({ app, value: stored });
|
|
280
|
+
presentations.set(app, stored);
|
|
281
|
+
publishChangeSet(changeSet('', null, null, { presentation: app }));
|
|
282
|
+
});
|
|
283
|
+
}
|
|
269
284
|
function emitPresentation(app) {
|
|
270
285
|
if (closed)
|
|
271
286
|
return;
|
|
@@ -377,7 +392,7 @@ export function composeRuntime(options) {
|
|
|
377
392
|
if (closed)
|
|
378
393
|
return;
|
|
379
394
|
queueReadiness();
|
|
380
|
-
const next = status.state === 'synced' &&
|
|
395
|
+
const next = status.state === 'synced' && !conversations.current()
|
|
381
396
|
? { ...status, state: 'syncing' }
|
|
382
397
|
: status;
|
|
383
398
|
if (same(shown, next))
|
|
@@ -410,8 +425,12 @@ export function composeRuntime(options) {
|
|
|
410
425
|
...set,
|
|
411
426
|
documents: set.documents.filter(({ locator }) => prepared.has(locator) || projecting.has(locator)),
|
|
412
427
|
};
|
|
413
|
-
readSide.apply(held);
|
|
414
|
-
|
|
428
|
+
const applied = readSide.apply(held, () => searchIndex.changed(held));
|
|
429
|
+
if (applied instanceof Promise)
|
|
430
|
+
void applied.catch(unpublished);
|
|
431
|
+
}
|
|
432
|
+
function unpublished(error) {
|
|
433
|
+
journal.record(`publication failed ${error instanceof Error ? error.name : 'unknown'}`);
|
|
415
434
|
}
|
|
416
435
|
async function referenced(locator) {
|
|
417
436
|
for (;;) {
|
|
@@ -442,8 +461,8 @@ export function composeRuntime(options) {
|
|
|
442
461
|
if (current === undefined || closed)
|
|
443
462
|
return;
|
|
444
463
|
const set = entering(locator, current, chunk);
|
|
445
|
-
readSide.apply(set);
|
|
446
464
|
entered.push(set);
|
|
465
|
+
return readSide.apply(set);
|
|
447
466
|
});
|
|
448
467
|
if (!replicas.has(locator) || closed)
|
|
449
468
|
return;
|
|
@@ -536,57 +555,6 @@ export function composeRuntime(options) {
|
|
|
536
555
|
if (waiting.length > 0)
|
|
537
556
|
prepare(waiting.map(({ room }) => room));
|
|
538
557
|
}
|
|
539
|
-
function acquires(room) {
|
|
540
|
-
return (demanded(demand, room) ||
|
|
541
|
-
(replicas.get(room.locator)?.queue.length ?? 0) > 0 ||
|
|
542
|
-
queuesWithoutReplica.has(room.locator));
|
|
543
|
-
}
|
|
544
|
-
function acquiredLocators() {
|
|
545
|
-
return [...discovered.values()]
|
|
546
|
-
.filter(acquires)
|
|
547
|
-
.map((room) => room.locator)
|
|
548
|
-
.sort();
|
|
549
|
-
}
|
|
550
|
-
function heldDemand(acquired) {
|
|
551
|
-
if (demand.whole)
|
|
552
|
-
return null;
|
|
553
|
-
return { rooms: acquired.map((room) => room.locator), conversations: demand.conversations };
|
|
554
|
-
}
|
|
555
|
-
function applyDemand(next) {
|
|
556
|
-
const before = acquiredLocators();
|
|
557
|
-
const conversed = demand.conversations;
|
|
558
|
-
demand = next;
|
|
559
|
-
const after = acquiredLocators();
|
|
560
|
-
queueReadiness();
|
|
561
|
-
if (!conversed && next.conversations)
|
|
562
|
-
scheduleCurrency();
|
|
563
|
-
if (JSON.stringify(before) === JSON.stringify(after) && conversed === next.conversations)
|
|
564
|
-
return;
|
|
565
|
-
journal.record(`acquisition ${next.whole ? 'whole' : after.length}`);
|
|
566
|
-
if (options.adapter.roomStream === undefined)
|
|
567
|
-
void sync('unstreamed');
|
|
568
|
-
else if (socketOpen)
|
|
569
|
-
options.adapter.roomStream.reopen();
|
|
570
|
-
}
|
|
571
|
-
function acquire(next) {
|
|
572
|
-
if (closed)
|
|
573
|
-
return;
|
|
574
|
-
if (narrowing !== null)
|
|
575
|
-
clearTimeout(narrowing);
|
|
576
|
-
narrowing = null;
|
|
577
|
-
const widening = wider(next, demand);
|
|
578
|
-
if (!widening && !narrower(next, demand))
|
|
579
|
-
return;
|
|
580
|
-
if (widening || !roomsListed) {
|
|
581
|
-
applyDemand(next);
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
// A narrower demand waits five seconds, so a tab that reloads returns before the stream reopens twice.
|
|
585
|
-
narrowing = setTimeout(() => {
|
|
586
|
-
narrowing = null;
|
|
587
|
-
applyDemand(next);
|
|
588
|
-
}, 5_000);
|
|
589
|
-
}
|
|
590
558
|
function requiredRooms(reach) {
|
|
591
559
|
if (reach === null)
|
|
592
560
|
return { listed: roomsListed, rooms: [...rooms.keys()].sort() };
|
|
@@ -784,10 +752,7 @@ export function composeRuntime(options) {
|
|
|
784
752
|
void sync('unstreamed');
|
|
785
753
|
}
|
|
786
754
|
async function reclaimed(local) {
|
|
787
|
-
const unreferenced = [...transfers.keys()].filter((locator) =>
|
|
788
|
-
const room = discovered.get(locator);
|
|
789
|
-
return room === undefined || !acquires(room);
|
|
790
|
-
});
|
|
755
|
+
const unreferenced = [...transfers.keys()].filter((locator) => !discovered.has(locator));
|
|
791
756
|
for (const locator of unreferenced) {
|
|
792
757
|
await local.abandon(locator);
|
|
793
758
|
transfers.delete(locator);
|
|
@@ -860,9 +825,7 @@ export function composeRuntime(options) {
|
|
|
860
825
|
awaitPreparation(cover);
|
|
861
826
|
if ('pending' in cover)
|
|
862
827
|
return cover;
|
|
863
|
-
return
|
|
864
|
-
? { groups: rows.groups, coverage: cover.coverage }
|
|
865
|
-
: { documents: rows.documents, coverage: cover.coverage };
|
|
828
|
+
return { ...rows, coverage: cover.coverage };
|
|
866
829
|
}
|
|
867
830
|
async function indexConversation(conversation) {
|
|
868
831
|
searchIndex.indexMessages(conversation, [], false);
|
|
@@ -1006,7 +969,7 @@ export function composeRuntime(options) {
|
|
|
1006
969
|
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
1007
970
|
});
|
|
1008
971
|
}
|
|
1009
|
-
async function applyDiscovery(found, started) {
|
|
972
|
+
async function applyDiscovery(found, started, covered) {
|
|
1010
973
|
for (const room of found) {
|
|
1011
974
|
const fence = fences.get(room.locator);
|
|
1012
975
|
if (fence !== undefined && fence.erased && fence.at < started)
|
|
@@ -1019,10 +982,14 @@ export function composeRuntime(options) {
|
|
|
1019
982
|
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
1020
983
|
for (const locator of listedLocators)
|
|
1021
984
|
scopedRefusals.delete(locator);
|
|
1022
|
-
|
|
985
|
+
if (covered === undefined)
|
|
986
|
+
discovered.clear();
|
|
987
|
+
else
|
|
988
|
+
for (const locator of covered)
|
|
989
|
+
discovered.delete(locator);
|
|
1023
990
|
for (const room of listed)
|
|
1024
991
|
discovered.set(room.locator, room);
|
|
1025
|
-
const unlisted = unlistedHeld(new Set(found.map((room) => room.locator)));
|
|
992
|
+
const unlisted = unlistedHeld(new Set(found.map((room) => room.locator))).filter((locator) => covered === undefined || covered.includes(locator));
|
|
1026
993
|
if (options.adapter.exhaustiveDiscovery === true)
|
|
1027
994
|
await loseRooms(unlisted);
|
|
1028
995
|
else
|
|
@@ -1280,8 +1247,9 @@ export function composeRuntime(options) {
|
|
|
1280
1247
|
}
|
|
1281
1248
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
1282
1249
|
const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
1283
|
-
const withdrawn =
|
|
1284
|
-
const
|
|
1250
|
+
const { withdrawn, kept } = standing(confirmed, queue, withdrawable, localSaves);
|
|
1251
|
+
const founded = kept.filter((batch) => !queue.includes(batch));
|
|
1252
|
+
const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
|
|
1285
1253
|
const unqueued = withdrawn.map((batch) => ({
|
|
1286
1254
|
batch,
|
|
1287
1255
|
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
@@ -1305,9 +1273,10 @@ export function composeRuntime(options) {
|
|
|
1305
1273
|
});
|
|
1306
1274
|
}
|
|
1307
1275
|
}
|
|
1308
|
-
await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
|
|
1276
|
+
await local.keepAuthored({ locator, queue: kept, founded, roomDrafts: [...written.values()] });
|
|
1277
|
+
const staying = new Set(kept.map((batch) => batch.id));
|
|
1309
1278
|
for (const batch of previousQueue) {
|
|
1310
|
-
if (!
|
|
1279
|
+
if (!staying.has(batch.id))
|
|
1311
1280
|
pendings.delete(batch.id);
|
|
1312
1281
|
}
|
|
1313
1282
|
for (const record of written.values())
|
|
@@ -1328,7 +1297,7 @@ export function composeRuntime(options) {
|
|
|
1328
1297
|
else
|
|
1329
1298
|
queuesWithoutReplica.delete(locator);
|
|
1330
1299
|
publishChangeSet(changeSet(locator, before ?? null, held, {
|
|
1331
|
-
queue:
|
|
1300
|
+
queue: queueChanged,
|
|
1332
1301
|
drafts: [...written.keys()],
|
|
1333
1302
|
}));
|
|
1334
1303
|
moveTo({ ...status, ...attention() });
|
|
@@ -1343,7 +1312,7 @@ export function composeRuntime(options) {
|
|
|
1343
1312
|
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
1344
1313
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
1345
1314
|
room: settling.room,
|
|
1346
|
-
queue:
|
|
1315
|
+
queue: queueChanged,
|
|
1347
1316
|
drafts: [...written.keys()],
|
|
1348
1317
|
});
|
|
1349
1318
|
};
|
|
@@ -1435,7 +1404,7 @@ export function composeRuntime(options) {
|
|
|
1435
1404
|
return true;
|
|
1436
1405
|
});
|
|
1437
1406
|
}
|
|
1438
|
-
async function
|
|
1407
|
+
async function savedRoom(locator) {
|
|
1439
1408
|
const local = await openLocal();
|
|
1440
1409
|
await referenced(locator);
|
|
1441
1410
|
const replica = replicas.get(locator);
|
|
@@ -1443,6 +1412,10 @@ export function composeRuntime(options) {
|
|
|
1443
1412
|
if (replica === undefined || room === undefined) {
|
|
1444
1413
|
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
1445
1414
|
}
|
|
1415
|
+
return { local, replica, room };
|
|
1416
|
+
}
|
|
1417
|
+
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
1418
|
+
const target = await savedRoom(locator);
|
|
1446
1419
|
const saved = [];
|
|
1447
1420
|
for (const { path, revision } of named) {
|
|
1448
1421
|
const record = roomDrafts.get(locator)?.get(path);
|
|
@@ -1452,6 +1425,38 @@ export function composeRuntime(options) {
|
|
|
1452
1425
|
if (!saved.some((draft) => draft.path === path))
|
|
1453
1426
|
saved.push({ path, parts: record.parts });
|
|
1454
1427
|
}
|
|
1428
|
+
return committed(locator, target, saved, new Set(saved.map(({ path }) => path)), writtenBy);
|
|
1429
|
+
}
|
|
1430
|
+
async function saveParts(locator, named, writtenBy) {
|
|
1431
|
+
const read = named.map(({ path, parts }) => ({ path, parts: readParts(parts) }));
|
|
1432
|
+
if (read.some(({ path }, at) => path === '' ||
|
|
1433
|
+
normalize(path) !== path ||
|
|
1434
|
+
read.findIndex((one) => one.path === path) !== at)) {
|
|
1435
|
+
throw runtimeError('room-draft-malformed');
|
|
1436
|
+
}
|
|
1437
|
+
const target = await savedRoom(locator);
|
|
1438
|
+
const saved = [];
|
|
1439
|
+
for (const { path, parts } of read) {
|
|
1440
|
+
const file = parts.file === undefined
|
|
1441
|
+
? undefined
|
|
1442
|
+
: await heldFile(parts.file.blob, binaryBase(target.replica.visible, path));
|
|
1443
|
+
saved.push({ path, parts: held(undefined, parts, file) });
|
|
1444
|
+
}
|
|
1445
|
+
return committed(locator, target, saved, new Set(), writtenBy);
|
|
1446
|
+
}
|
|
1447
|
+
function carriedDrafts(locator, saved, consumed) {
|
|
1448
|
+
const holding = roomDrafts.get(locator);
|
|
1449
|
+
return new Map(saved.flatMap(({ path, parts }) => {
|
|
1450
|
+
const record = holding?.get(path);
|
|
1451
|
+
if (parts.move === undefined || record === undefined || consumed.has(path))
|
|
1452
|
+
return [];
|
|
1453
|
+
const to = moveTarget(parts.move);
|
|
1454
|
+
if (to === path || holding?.has(to) === true)
|
|
1455
|
+
return [];
|
|
1456
|
+
return [[path, { ...record, path: to, revision: roomDraftRevision() }]];
|
|
1457
|
+
}));
|
|
1458
|
+
}
|
|
1459
|
+
async function committed(locator, { local, replica, room }, saved, consumed, writtenBy) {
|
|
1455
1460
|
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
1456
1461
|
if (changed.length > 0)
|
|
1457
1462
|
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
@@ -1484,6 +1489,7 @@ export function composeRuntime(options) {
|
|
|
1484
1489
|
visible: replica.visible,
|
|
1485
1490
|
changes,
|
|
1486
1491
|
bases: parts.flatMap(({ bases }) => bases),
|
|
1492
|
+
queued: queuedDocuments(replica.queue),
|
|
1487
1493
|
writtenBy,
|
|
1488
1494
|
clean: {
|
|
1489
1495
|
text: replica.confirmed.record.documents,
|
|
@@ -1491,7 +1497,9 @@ export function composeRuntime(options) {
|
|
|
1491
1497
|
composition: compositionOf(replica.confirmed.record),
|
|
1492
1498
|
},
|
|
1493
1499
|
};
|
|
1494
|
-
const checked = withdrawing
|
|
1500
|
+
const checked = withdrawing
|
|
1501
|
+
? buildBatch({ ...input, queued: new Set([...input.queued, ...requeue.queued]) })
|
|
1502
|
+
: null;
|
|
1495
1503
|
if (checked?.kind === 'refused')
|
|
1496
1504
|
return { outcome: 'refused', ...checked.refusal };
|
|
1497
1505
|
const owners = [...requeue.owners].flatMap((origin) => {
|
|
@@ -1514,6 +1522,7 @@ export function composeRuntime(options) {
|
|
|
1514
1522
|
...input,
|
|
1515
1523
|
visible: replicaOf(replica.confirmed, requeue.queue, replica).visible,
|
|
1516
1524
|
changes: requeue.changes,
|
|
1525
|
+
queued: queuedDocuments(requeue.queue),
|
|
1517
1526
|
});
|
|
1518
1527
|
if (built?.kind === 'refused')
|
|
1519
1528
|
return { outcome: 'refused', ...built.refusal };
|
|
@@ -1548,13 +1557,16 @@ export function composeRuntime(options) {
|
|
|
1548
1557
|
if (limited !== null)
|
|
1549
1558
|
return { outcome: 'refused', ...limited };
|
|
1550
1559
|
}
|
|
1551
|
-
const
|
|
1560
|
+
const carried = carriedDrafts(locator, saved, consumed);
|
|
1561
|
+
const left = [...consumed, ...carried.keys()];
|
|
1562
|
+
const arrived = [...carried.values()];
|
|
1552
1563
|
try {
|
|
1553
1564
|
await local.keepAuthored({
|
|
1554
1565
|
locator,
|
|
1555
1566
|
queue: batches,
|
|
1556
1567
|
saved: drafted,
|
|
1557
|
-
consumedRoomDrafts:
|
|
1568
|
+
consumedRoomDrafts: left,
|
|
1569
|
+
roomDrafts: arrived,
|
|
1558
1570
|
});
|
|
1559
1571
|
}
|
|
1560
1572
|
catch {
|
|
@@ -1569,12 +1581,14 @@ export function composeRuntime(options) {
|
|
|
1569
1581
|
}
|
|
1570
1582
|
if (!localSaves)
|
|
1571
1583
|
recorded = false;
|
|
1572
|
-
for (const path of
|
|
1584
|
+
for (const path of left)
|
|
1573
1585
|
forgetRoomDraft(locator, path);
|
|
1586
|
+
for (const record of arrived)
|
|
1587
|
+
rememberRoomDraft(record);
|
|
1574
1588
|
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
1575
1589
|
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
1576
1590
|
queue: true,
|
|
1577
|
-
drafts:
|
|
1591
|
+
drafts: [...left, ...arrived.map(({ path }) => path)],
|
|
1578
1592
|
}));
|
|
1579
1593
|
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
1580
1594
|
submitLater(locator);
|
|
@@ -1665,7 +1679,7 @@ export function composeRuntime(options) {
|
|
|
1665
1679
|
const scope = options.adapter.referrerScope(request.referrer);
|
|
1666
1680
|
return scope?.kind === 'room' ? scope.locator : null;
|
|
1667
1681
|
}
|
|
1668
|
-
async function
|
|
1682
|
+
async function transferBinary(request, transfer, answer) {
|
|
1669
1683
|
if (closed || refusal !== null)
|
|
1670
1684
|
return UNCERTAIN;
|
|
1671
1685
|
try {
|
|
@@ -1681,40 +1695,41 @@ export function composeRuntime(options) {
|
|
|
1681
1695
|
? `transfer binary ${request.locator} ${request.path}`
|
|
1682
1696
|
: `transfer binary ${request.fileRef}`);
|
|
1683
1697
|
try {
|
|
1684
|
-
const result = await
|
|
1685
|
-
|
|
1698
|
+
const result = await transfer(authorization);
|
|
1699
|
+
const settled = result;
|
|
1700
|
+
journal.record(`outcome binary ${settled.kind}`);
|
|
1686
1701
|
const locator = binaryRoom(request);
|
|
1687
1702
|
if (locator !== null && fencedSince(locator, since))
|
|
1688
1703
|
return failure({ kind: 'room-not-readable', locator });
|
|
1689
|
-
if (
|
|
1690
|
-
const room = replicas.get(
|
|
1704
|
+
if (settled.kind === 'scoped-refusal') {
|
|
1705
|
+
const room = replicas.get(settled.locator)?.confirmed.record;
|
|
1691
1706
|
if (room !== undefined)
|
|
1692
|
-
await scoped(
|
|
1707
|
+
await scoped(settled, room, await openLocal());
|
|
1693
1708
|
}
|
|
1694
|
-
if (
|
|
1709
|
+
if (settled.kind === 'revoked-authorization')
|
|
1695
1710
|
await refuse('revoked-authorization');
|
|
1696
|
-
return
|
|
1711
|
+
return answer(result);
|
|
1697
1712
|
}
|
|
1698
1713
|
catch {
|
|
1699
1714
|
journal.record('outcome binary transport-uncertainty');
|
|
1700
1715
|
return UNCERTAIN;
|
|
1701
1716
|
}
|
|
1702
1717
|
}
|
|
1703
|
-
|
|
1704
|
-
|
|
1718
|
+
function readBinary(request) {
|
|
1719
|
+
return transferBinary(request, (held) => options.adapter.binary(held, request), binaryAnswer);
|
|
1720
|
+
}
|
|
1721
|
+
function binaryPath(locator, path, revision) {
|
|
1705
1722
|
if (revision !== undefined)
|
|
1706
|
-
return
|
|
1723
|
+
return { kind: 'path', locator, path, revision };
|
|
1707
1724
|
const visible = replicas.get(locator)?.visible;
|
|
1708
1725
|
const held = visible?.binaries[path];
|
|
1709
1726
|
if (visible === undefined || held === undefined)
|
|
1710
|
-
return
|
|
1711
|
-
if (held.state === 'pending')
|
|
1712
|
-
|
|
1713
|
-
return blob === null ? absentBinary(path) : { kind: 'binary', blob };
|
|
1714
|
-
}
|
|
1727
|
+
return null;
|
|
1728
|
+
if (held.state === 'pending')
|
|
1729
|
+
return { kind: 'pending', localId: held.localId };
|
|
1715
1730
|
const record = binaryRecord(path, held, visible.revisions[path]);
|
|
1716
1731
|
if (record.state !== 'accepted')
|
|
1717
|
-
return
|
|
1732
|
+
return null;
|
|
1718
1733
|
const replica = replicas.get(locator);
|
|
1719
1734
|
const queued = (replica?.queue ?? []).flatMap((batch) => batch.changes);
|
|
1720
1735
|
const holds = (at) => {
|
|
@@ -1729,13 +1744,31 @@ export function composeRuntime(options) {
|
|
|
1729
1744
|
if (change.kind === 'move' && change.to === confirmed)
|
|
1730
1745
|
confirmed = change.path;
|
|
1731
1746
|
}
|
|
1732
|
-
return
|
|
1747
|
+
return {
|
|
1733
1748
|
kind: 'path',
|
|
1734
1749
|
locator,
|
|
1735
1750
|
path: confirmed,
|
|
1736
1751
|
revision: record.revision,
|
|
1737
1752
|
fileRef: record.fileRef,
|
|
1738
|
-
}
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
async function readBinaryPath(locator, path, revision) {
|
|
1756
|
+
await ready;
|
|
1757
|
+
const asked = binaryPath(locator, path, revision);
|
|
1758
|
+
if (asked === null)
|
|
1759
|
+
return absentBinary(path);
|
|
1760
|
+
if (asked.kind === 'pending') {
|
|
1761
|
+
const blob = queuedBlob(locator, path, asked.localId);
|
|
1762
|
+
return blob === null ? absentBinary(path) : { kind: 'binary', blob };
|
|
1763
|
+
}
|
|
1764
|
+
return readBinary(asked);
|
|
1765
|
+
}
|
|
1766
|
+
async function linkBinaryPath(locator, path, revision) {
|
|
1767
|
+
await ready;
|
|
1768
|
+
const asked = binaryPath(locator, path, revision);
|
|
1769
|
+
if (asked === null || asked.kind === 'pending')
|
|
1770
|
+
return absentBinary(path);
|
|
1771
|
+
return transferBinary(asked, (held) => options.adapter.link(held, asked), binaryLinkAnswer);
|
|
1739
1772
|
}
|
|
1740
1773
|
async function readBinaryReference(reference) {
|
|
1741
1774
|
await ready;
|
|
@@ -1761,7 +1794,7 @@ export function composeRuntime(options) {
|
|
|
1761
1794
|
}
|
|
1762
1795
|
return undefined;
|
|
1763
1796
|
}
|
|
1764
|
-
async function invokeAction(name, input) {
|
|
1797
|
+
async function invokeAction(name, input, dispatching) {
|
|
1765
1798
|
await ready;
|
|
1766
1799
|
if (closed || refusal !== null)
|
|
1767
1800
|
return UNCERTAIN;
|
|
@@ -1775,6 +1808,7 @@ export function composeRuntime(options) {
|
|
|
1775
1808
|
}
|
|
1776
1809
|
if (closed || authorization === undefined || refusal !== null)
|
|
1777
1810
|
return UNCERTAIN;
|
|
1811
|
+
await dispatching?.();
|
|
1778
1812
|
journal.record(`transfer action ${name}`);
|
|
1779
1813
|
try {
|
|
1780
1814
|
const result = await options.adapter.action(authorization, { name, input });
|
|
@@ -1795,6 +1829,60 @@ export function composeRuntime(options) {
|
|
|
1795
1829
|
return UNCERTAIN;
|
|
1796
1830
|
}
|
|
1797
1831
|
}
|
|
1832
|
+
function unsettled(result) {
|
|
1833
|
+
return result.kind !== 'action' && result.retryable;
|
|
1834
|
+
}
|
|
1835
|
+
async function recoveredAction(name, input) {
|
|
1836
|
+
const operation = operationOf(input);
|
|
1837
|
+
if (operation === null)
|
|
1838
|
+
return invokeAction(name, input);
|
|
1839
|
+
const kept = async () => (await openLocal()).keepCall({ operation, name, input });
|
|
1840
|
+
calling.add(operation);
|
|
1841
|
+
try {
|
|
1842
|
+
let result = await invokeAction(name, input, kept);
|
|
1843
|
+
if (unsettled(result)) {
|
|
1844
|
+
await recovery.attempt(`action ${operation}`, async () => {
|
|
1845
|
+
if (closed || refusal !== null)
|
|
1846
|
+
return 'done';
|
|
1847
|
+
if (!socketOpen)
|
|
1848
|
+
return 'again';
|
|
1849
|
+
result = await invokeAction(name, input, kept);
|
|
1850
|
+
return unsettled(result) ? 'again' : 'done';
|
|
1851
|
+
}, { again: true });
|
|
1852
|
+
}
|
|
1853
|
+
if (!unsettled(result))
|
|
1854
|
+
await (await openLocal()).dropCall(operation);
|
|
1855
|
+
return result;
|
|
1856
|
+
}
|
|
1857
|
+
finally {
|
|
1858
|
+
calling.delete(operation);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
async function resendCalls(signal) {
|
|
1862
|
+
if (!socketOpen || closed || stopping)
|
|
1863
|
+
return 'done';
|
|
1864
|
+
const local = await openLocal();
|
|
1865
|
+
for (const held of await local.calls()) {
|
|
1866
|
+
if (calling.has(held.operation))
|
|
1867
|
+
continue;
|
|
1868
|
+
if (closed || signal.aborted)
|
|
1869
|
+
return 'done';
|
|
1870
|
+
if (pastHorizon(held.operation)) {
|
|
1871
|
+
await local.dropCall(held.operation);
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
calling.add(held.operation);
|
|
1875
|
+
try {
|
|
1876
|
+
if (unsettled(await invokeAction(held.name, held.input)))
|
|
1877
|
+
return 'again';
|
|
1878
|
+
await local.dropCall(held.operation);
|
|
1879
|
+
}
|
|
1880
|
+
finally {
|
|
1881
|
+
calling.delete(held.operation);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
return 'done';
|
|
1885
|
+
}
|
|
1798
1886
|
function continuing() {
|
|
1799
1887
|
return !closed && !discoveryPaused;
|
|
1800
1888
|
}
|
|
@@ -1812,8 +1900,9 @@ export function composeRuntime(options) {
|
|
|
1812
1900
|
async function pullRefresh(authorized, confirmed, live) {
|
|
1813
1901
|
if (!live())
|
|
1814
1902
|
return unconfirmed;
|
|
1903
|
+
const turn = generation;
|
|
1815
1904
|
try {
|
|
1816
|
-
const result = await refreshRoom(
|
|
1905
|
+
const result = await refreshRoom(reads, authorized, confirmed, (models, held) => (live() ? remodel(confirmed.record.locator, models, held) : held), live);
|
|
1817
1906
|
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
1818
1907
|
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
1819
1908
|
return result;
|
|
@@ -1822,6 +1911,9 @@ export function composeRuntime(options) {
|
|
|
1822
1911
|
journal.record(`transfer refresh ${confirmed.record.locator} transport-uncertainty`);
|
|
1823
1912
|
throw error;
|
|
1824
1913
|
}
|
|
1914
|
+
finally {
|
|
1915
|
+
reads.finished(confirmed.record.locator, turn);
|
|
1916
|
+
}
|
|
1825
1917
|
}
|
|
1826
1918
|
function staging(local, live) {
|
|
1827
1919
|
return {
|
|
@@ -1864,12 +1956,13 @@ export function composeRuntime(options) {
|
|
|
1864
1956
|
async function pullBootstrap(authorized, room, local, live) {
|
|
1865
1957
|
if (!live())
|
|
1866
1958
|
return unconfirmed;
|
|
1959
|
+
const turn = generation;
|
|
1867
1960
|
if (replicas.has(room.locator)) {
|
|
1868
1961
|
catching.add(room.locator);
|
|
1869
1962
|
readSide.recover();
|
|
1870
1963
|
}
|
|
1871
1964
|
try {
|
|
1872
|
-
const result = await bootstrapRoom(
|
|
1965
|
+
const result = await bootstrapRoom(reads, authorized, room, {
|
|
1873
1966
|
collection: modelOf(room.locator),
|
|
1874
1967
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
1875
1968
|
}, (models, held) => (live() ? remodel(room.locator, models, held) : held), live, staging(local, live), reusable(room));
|
|
@@ -1884,12 +1977,28 @@ export function composeRuntime(options) {
|
|
|
1884
1977
|
caughtUpTo(room.locator);
|
|
1885
1978
|
throw error;
|
|
1886
1979
|
}
|
|
1980
|
+
finally {
|
|
1981
|
+
reads.finished(room.locator, turn);
|
|
1982
|
+
}
|
|
1887
1983
|
}
|
|
1888
1984
|
function caughtUpTo(locator) {
|
|
1889
1985
|
if (catching.delete(locator))
|
|
1890
1986
|
readSide.recover();
|
|
1891
1987
|
}
|
|
1988
|
+
function behind(room) {
|
|
1989
|
+
const held = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
1990
|
+
return (held !== undefined &&
|
|
1991
|
+
[announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later)));
|
|
1992
|
+
}
|
|
1892
1993
|
async function advance(room, local, authorized, live) {
|
|
1994
|
+
const carried = reads.carried(room.locator);
|
|
1995
|
+
const withheld = await advanceOnce(room, local, authorized, live);
|
|
1996
|
+
if (withheld !== null || reads.carried(room.locator) === carried || !behind(room)) {
|
|
1997
|
+
return withheld;
|
|
1998
|
+
}
|
|
1999
|
+
return advanceOnce(room, local, authorized, live);
|
|
2000
|
+
}
|
|
2001
|
+
async function advanceOnce(room, local, authorized, live) {
|
|
1893
2002
|
await referenced(room.locator);
|
|
1894
2003
|
if (!live() || paused.has(room.locator))
|
|
1895
2004
|
return unconfirmed;
|
|
@@ -2377,16 +2486,15 @@ export function composeRuntime(options) {
|
|
|
2377
2486
|
});
|
|
2378
2487
|
}
|
|
2379
2488
|
async function synchronize(listed, local, progress, attempt, live) {
|
|
2380
|
-
const
|
|
2381
|
-
const prepared = await Promise.allSettled(acquired.map((room) => prepareRoom(room, local, progress)));
|
|
2489
|
+
const prepared = await Promise.allSettled(listed.map((room) => prepareRoom(room, local, progress)));
|
|
2382
2490
|
if (!live())
|
|
2383
2491
|
return;
|
|
2384
|
-
const ready =
|
|
2492
|
+
const ready = listed.filter((_, index) => {
|
|
2385
2493
|
const outcome = prepared[index];
|
|
2386
2494
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
2387
2495
|
});
|
|
2388
2496
|
if (attempt === 'opened') {
|
|
2389
|
-
options.adapter.roomStream?.hold(heldRooms(ready)
|
|
2497
|
+
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
2390
2498
|
handed = true;
|
|
2391
2499
|
}
|
|
2392
2500
|
discovering?.release();
|
|
@@ -2417,11 +2525,7 @@ export function composeRuntime(options) {
|
|
|
2417
2525
|
return room.replica.admission === 'admitted';
|
|
2418
2526
|
}
|
|
2419
2527
|
async function caughtUp(room, local, progress, live) {
|
|
2420
|
-
|
|
2421
|
-
if (held === undefined || authorization === undefined || !live())
|
|
2422
|
-
return;
|
|
2423
|
-
const behind = [announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later));
|
|
2424
|
-
if (!behind)
|
|
2528
|
+
if (authorization === undefined || !live() || !behind(room))
|
|
2425
2529
|
return;
|
|
2426
2530
|
const withheld = await advance(room, local, authorization, live);
|
|
2427
2531
|
if (withheld !== null)
|
|
@@ -2465,7 +2569,7 @@ export function composeRuntime(options) {
|
|
|
2465
2569
|
caughtUpTo(room.locator);
|
|
2466
2570
|
}
|
|
2467
2571
|
}
|
|
2468
|
-
async function discoverRooms(local, progress, attempt, live) {
|
|
2572
|
+
async function discoverRooms(local, progress, attempt, live, scope) {
|
|
2469
2573
|
if (closed || authorization === undefined) {
|
|
2470
2574
|
notifyReady();
|
|
2471
2575
|
return;
|
|
@@ -2475,7 +2579,7 @@ export function composeRuntime(options) {
|
|
|
2475
2579
|
const started = discoveries;
|
|
2476
2580
|
let result;
|
|
2477
2581
|
try {
|
|
2478
|
-
result = await options.adapter.discover(authorization);
|
|
2582
|
+
result = await options.adapter.discover(authorization, scope);
|
|
2479
2583
|
}
|
|
2480
2584
|
catch {
|
|
2481
2585
|
result = { kind: 'transport-uncertainty' };
|
|
@@ -2552,7 +2656,7 @@ export function composeRuntime(options) {
|
|
|
2552
2656
|
member = signedIn;
|
|
2553
2657
|
emitMember();
|
|
2554
2658
|
}
|
|
2555
|
-
await synchronize(await applyDiscovery(result.rooms, started), local, progress, attempt, live);
|
|
2659
|
+
await synchronize(await applyDiscovery(result.rooms, started, result.covered), local, progress, attempt, live);
|
|
2556
2660
|
}
|
|
2557
2661
|
async function ensureAuthorization() {
|
|
2558
2662
|
if (authorization !== undefined)
|
|
@@ -2649,9 +2753,43 @@ export function composeRuntime(options) {
|
|
|
2649
2753
|
options.adapter.roomStream?.reopen();
|
|
2650
2754
|
});
|
|
2651
2755
|
}
|
|
2652
|
-
function
|
|
2756
|
+
function rediscover(scope) {
|
|
2757
|
+
return task(OPENING_LANE, async (live) => {
|
|
2758
|
+
if (!live() || !socketOpen)
|
|
2759
|
+
return;
|
|
2760
|
+
const progress = { complete: true, reachable: true };
|
|
2761
|
+
const gate = discover();
|
|
2762
|
+
moveTo(transferring(status));
|
|
2763
|
+
try {
|
|
2764
|
+
const local = await openLocal();
|
|
2765
|
+
if (!live())
|
|
2766
|
+
return;
|
|
2767
|
+
await ensureAuthorization();
|
|
2768
|
+
if (!live())
|
|
2769
|
+
return;
|
|
2770
|
+
await discoverRooms(local, progress, 'authorized', live, scope);
|
|
2771
|
+
}
|
|
2772
|
+
catch {
|
|
2773
|
+
progress.complete = false;
|
|
2774
|
+
}
|
|
2775
|
+
finally {
|
|
2776
|
+
gate.release();
|
|
2777
|
+
notifyReady();
|
|
2778
|
+
}
|
|
2779
|
+
if (!live())
|
|
2780
|
+
return;
|
|
2781
|
+
if (!progress.complete)
|
|
2782
|
+
complete = false;
|
|
2783
|
+
await rest(progress, live);
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
function closedSocket(revoked, lost) {
|
|
2653
2787
|
const opened = socketOpen;
|
|
2654
2788
|
socketOpen = false;
|
|
2789
|
+
if (lost) {
|
|
2790
|
+
reads.lost();
|
|
2791
|
+
conversations.lost();
|
|
2792
|
+
}
|
|
2655
2793
|
if (revoked) {
|
|
2656
2794
|
void refuse('revoked-authorization');
|
|
2657
2795
|
return;
|
|
@@ -2668,9 +2806,10 @@ export function composeRuntime(options) {
|
|
|
2668
2806
|
function applyOpening(expired) {
|
|
2669
2807
|
const progress = underway?.generation === generation ? underway.progress : null;
|
|
2670
2808
|
const cycle = progress ?? { complete: true, reachable: true };
|
|
2671
|
-
const replacing = reloadNow((room) => expired.includes(room.locator), cycle);
|
|
2672
2809
|
if (opened === generation)
|
|
2673
2810
|
opened = null;
|
|
2811
|
+
const reloading = progress === null ? [] : owedReloads(expired);
|
|
2812
|
+
const replacing = reloadNow((room) => expired.includes(room.locator) || reloading.includes(room.locator), cycle);
|
|
2674
2813
|
return task(OPENING_LANE, async (live) => {
|
|
2675
2814
|
underway = null;
|
|
2676
2815
|
if (!live())
|
|
@@ -2680,24 +2819,17 @@ export function composeRuntime(options) {
|
|
|
2680
2819
|
await rest(cycle, live);
|
|
2681
2820
|
return;
|
|
2682
2821
|
}
|
|
2683
|
-
await roomsCarried(expired);
|
|
2822
|
+
await roomsCarried([...expired, ...reloading]);
|
|
2684
2823
|
if (!live())
|
|
2685
2824
|
return;
|
|
2686
|
-
const replaced = new Set(owed);
|
|
2687
|
-
owed.clear();
|
|
2688
2825
|
try {
|
|
2689
2826
|
const local = await openLocal();
|
|
2690
|
-
const
|
|
2691
|
-
const reloading = held.filter((room) => !expired.includes(room.locator) &&
|
|
2692
|
-
(replaced.has(room.locator) || revoked(room.locator)));
|
|
2693
|
-
for (const room of reloading)
|
|
2694
|
-
catching.add(room.locator);
|
|
2695
|
-
readSide.recover();
|
|
2696
|
-
const reloads = Promise.allSettled(reloading.map((room) => roomTask(room.locator, (current) => reload(room, local, progress, current))));
|
|
2697
|
-
const reloaded = [...(await reloads), ...(await replacing)];
|
|
2827
|
+
const reloaded = await replacing;
|
|
2698
2828
|
if (progress.reachable) {
|
|
2699
|
-
for (const room of
|
|
2700
|
-
if (
|
|
2829
|
+
for (const room of discovered.values()) {
|
|
2830
|
+
if (replicas.has(room.locator) &&
|
|
2831
|
+
!expired.includes(room.locator) &&
|
|
2832
|
+
!reloading.includes(room.locator)) {
|
|
2701
2833
|
await confirmModels(room, local);
|
|
2702
2834
|
}
|
|
2703
2835
|
}
|
|
@@ -2714,6 +2846,18 @@ export function composeRuntime(options) {
|
|
|
2714
2846
|
await finishOpening(progress, live);
|
|
2715
2847
|
});
|
|
2716
2848
|
}
|
|
2849
|
+
function owedReloads(expired) {
|
|
2850
|
+
const reloading = [...discovered.values()]
|
|
2851
|
+
.filter((room) => replicas.has(room.locator) &&
|
|
2852
|
+
!expired.includes(room.locator) &&
|
|
2853
|
+
(owed.has(room.locator) || revoked(room.locator)))
|
|
2854
|
+
.map((room) => room.locator);
|
|
2855
|
+
owed.clear();
|
|
2856
|
+
for (const locator of reloading)
|
|
2857
|
+
catching.add(locator);
|
|
2858
|
+
readSide.recover();
|
|
2859
|
+
return reloading;
|
|
2860
|
+
}
|
|
2717
2861
|
async function reloadNow(replaced, progress) {
|
|
2718
2862
|
try {
|
|
2719
2863
|
const local = await openLocal();
|
|
@@ -2838,6 +2982,7 @@ export function composeRuntime(options) {
|
|
|
2838
2982
|
progress.complete = false;
|
|
2839
2983
|
return;
|
|
2840
2984
|
}
|
|
2985
|
+
reads.applied();
|
|
2841
2986
|
submitAll();
|
|
2842
2987
|
for (const locator of stale)
|
|
2843
2988
|
if (repairable.has(locator))
|
|
@@ -2846,10 +2991,11 @@ export function composeRuntime(options) {
|
|
|
2846
2991
|
if (status.providerUnreachable)
|
|
2847
2992
|
conversations.reconnected();
|
|
2848
2993
|
const reading = { complete: true, reachable: true };
|
|
2849
|
-
|
|
2850
|
-
await conversations.catchUp(reading);
|
|
2994
|
+
await conversations.catchUp(reading);
|
|
2851
2995
|
if (progress.reachable && reading.reachable)
|
|
2852
2996
|
await conversations.resend(reading);
|
|
2997
|
+
if (progress.reachable)
|
|
2998
|
+
void recovery.attempt('calls', resendCalls);
|
|
2853
2999
|
if (!reading.reachable) {
|
|
2854
3000
|
progress.reachable = false;
|
|
2855
3001
|
resendLater();
|
|
@@ -2910,7 +3056,7 @@ export function composeRuntime(options) {
|
|
|
2910
3056
|
return;
|
|
2911
3057
|
const room = discovered.get(locator);
|
|
2912
3058
|
const held = replicas.get(locator);
|
|
2913
|
-
if (room === undefined || room.replica.admission !== 'admitted'
|
|
3059
|
+
if (room === undefined || room.replica.admission !== 'admitted')
|
|
2914
3060
|
return;
|
|
2915
3061
|
if (held !== undefined &&
|
|
2916
3062
|
(held.confirmed.record.replica.selection !== room.replica.selection ||
|
|
@@ -2962,14 +3108,17 @@ export function composeRuntime(options) {
|
|
|
2962
3108
|
if (event.kind === 'opened') {
|
|
2963
3109
|
socketOpen = true;
|
|
2964
3110
|
generation += 1;
|
|
3111
|
+
reads.opened();
|
|
2965
3112
|
conversations.reopened();
|
|
2966
3113
|
recovery.resume();
|
|
2967
3114
|
void sync('opened');
|
|
2968
3115
|
}
|
|
2969
3116
|
else if (event.kind === 'closed')
|
|
2970
|
-
closedSocket(event.revoked);
|
|
3117
|
+
closedSocket(event.revoked, event.lost);
|
|
2971
3118
|
else if (event.kind === 'opening')
|
|
2972
3119
|
void applyOpening(event.expired);
|
|
3120
|
+
else if (event.kind === 'authority-changed')
|
|
3121
|
+
void rediscover(event.scope);
|
|
2973
3122
|
else
|
|
2974
3123
|
void applyRoom(event.locator, event.roomVersion);
|
|
2975
3124
|
}, receipts);
|
|
@@ -3024,7 +3173,7 @@ export function composeRuntime(options) {
|
|
|
3024
3173
|
if (!live())
|
|
3025
3174
|
return;
|
|
3026
3175
|
await conversations.reconcile();
|
|
3027
|
-
if (conversations.current()
|
|
3176
|
+
if (conversations.current()) {
|
|
3028
3177
|
show();
|
|
3029
3178
|
return;
|
|
3030
3179
|
}
|
|
@@ -3042,8 +3191,6 @@ export function composeRuntime(options) {
|
|
|
3042
3191
|
if (closed)
|
|
3043
3192
|
return;
|
|
3044
3193
|
closed = true;
|
|
3045
|
-
if (narrowing !== null)
|
|
3046
|
-
clearTimeout(narrowing);
|
|
3047
3194
|
if (rechecking !== null)
|
|
3048
3195
|
clearInterval(rechecking);
|
|
3049
3196
|
readinessWatches.clear();
|
|
@@ -3091,7 +3238,11 @@ export function composeRuntime(options) {
|
|
|
3091
3238
|
if (options.adapter.roomStream === undefined)
|
|
3092
3239
|
void sync('unstreamed');
|
|
3093
3240
|
function planned(input, reach) {
|
|
3094
|
-
|
|
3241
|
+
const plan = planOf(input, roomFacts(reach), planReach(reach));
|
|
3242
|
+
const named = plan.paths !== null &&
|
|
3243
|
+
plan.rooms !== null &&
|
|
3244
|
+
plan.paths.length * plan.rooms.length <= MAXIMUM_WINDOW;
|
|
3245
|
+
return plan.limit === null && named ? plan : { ...plan, limit: capped(plan.limit) };
|
|
3095
3246
|
}
|
|
3096
3247
|
function watchPlan(plan, deliver) {
|
|
3097
3248
|
let seeded = false;
|
|
@@ -3128,7 +3279,8 @@ export function composeRuntime(options) {
|
|
|
3128
3279
|
};
|
|
3129
3280
|
}
|
|
3130
3281
|
async function searched(input, reach) {
|
|
3131
|
-
const
|
|
3282
|
+
const asked = searchPlanOf(input);
|
|
3283
|
+
const plan = { ...asked, limit: capped(asked.limit) };
|
|
3132
3284
|
const filter = filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach));
|
|
3133
3285
|
for (;;) {
|
|
3134
3286
|
const cover = covered(filter);
|
|
@@ -3167,6 +3319,7 @@ export function composeRuntime(options) {
|
|
|
3167
3319
|
if ('pending' in answered)
|
|
3168
3320
|
continue;
|
|
3169
3321
|
const searchedConversations = named.filter((one) => searchIndex.conversations().has(one));
|
|
3322
|
+
await readSide.settled();
|
|
3170
3323
|
const seen = searchIndex.changes();
|
|
3171
3324
|
await searchIndex.caughtUp();
|
|
3172
3325
|
const selected = roomsAlone(answering) ? null : await rowsQuery(answering);
|
|
@@ -3187,7 +3340,9 @@ export function composeRuntime(options) {
|
|
|
3187
3340
|
index: searchIndex.generation(),
|
|
3188
3341
|
overlay: answered.coverage.overlay,
|
|
3189
3342
|
};
|
|
3190
|
-
return complete
|
|
3343
|
+
return complete
|
|
3344
|
+
? { ...hits, coverage }
|
|
3345
|
+
: { pending, documents: hits.documents, messages: hits.messages, coverage };
|
|
3191
3346
|
}
|
|
3192
3347
|
}
|
|
3193
3348
|
const watch = (topic, input, deliver) => {
|
|
@@ -3372,27 +3527,31 @@ export function composeRuntime(options) {
|
|
|
3372
3527
|
});
|
|
3373
3528
|
},
|
|
3374
3529
|
write(app, value) {
|
|
3375
|
-
return
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3530
|
+
return keepPresentation(app, () => structuredClone(value));
|
|
3531
|
+
},
|
|
3532
|
+
edit(app, members) {
|
|
3533
|
+
return keepPresentation(app, (held) => Object.fromEntries([
|
|
3534
|
+
...Object.entries(plain(held) ? held : {}).filter(([member]) => !Object.hasOwn(members, member)),
|
|
3535
|
+
...Object.entries(structuredClone(members)).filter(([, value]) => value !== null),
|
|
3536
|
+
]));
|
|
3382
3537
|
},
|
|
3383
3538
|
},
|
|
3384
3539
|
save(room, named, writtenBy) {
|
|
3385
3540
|
return exclusive(() => saveRoomDrafts(room, named, writtenBy));
|
|
3386
3541
|
},
|
|
3542
|
+
saveParts(room, parts, writtenBy) {
|
|
3543
|
+
return exclusive(() => saveParts(room, parts, writtenBy));
|
|
3544
|
+
},
|
|
3387
3545
|
history(locator, path, query) {
|
|
3388
3546
|
return readHistory(locator, path, query);
|
|
3389
3547
|
},
|
|
3390
3548
|
binaryFile: {
|
|
3391
3549
|
read: readBinaryPath,
|
|
3550
|
+
link: linkBinaryPath,
|
|
3392
3551
|
readReference: readBinaryReference,
|
|
3393
3552
|
},
|
|
3394
3553
|
call(name, input) {
|
|
3395
|
-
return
|
|
3554
|
+
return recoveredAction(name, input);
|
|
3396
3555
|
},
|
|
3397
3556
|
reached(locator, roomVersion, path) {
|
|
3398
3557
|
return path === undefined
|
|
@@ -3444,7 +3603,6 @@ export function composeRuntime(options) {
|
|
|
3444
3603
|
stop,
|
|
3445
3604
|
lose: loseRooms,
|
|
3446
3605
|
watch,
|
|
3447
|
-
acquire,
|
|
3448
3606
|
persisted(answer) {
|
|
3449
3607
|
if (answer === persistence)
|
|
3450
3608
|
return;
|