@agentshouse/mdruntime 0.1.0-c95464765b116 → 0.1.0-c969b8dee6cc8
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 +238 -233
- 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/bootstrap.js +4 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +2 -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/kit.js +1 -1
- package/dist/chat/list.d.ts.map +1 -1
- package/dist/chat/list.js +39 -3
- package/dist/chat/provider.d.ts +1 -1
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +35 -27
- 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 +7 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -5
- package/dist/connection.d.ts +13 -3
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +417 -107
- package/dist/conversation.d.ts +7 -0
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +206 -48
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +1 -1
- package/dist/host.d.ts +8 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +58 -6
- 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 +6 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +20 -5
- package/dist/model.d.ts +3 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +1 -1
- 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 +1 -0
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +27 -3
- package/dist/query.d.ts +29 -2
- 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.map +1 -1
- package/dist/read-side.js +176 -48
- 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 +6 -0
- package/dist/replica.d.ts +5 -1
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +30 -5
- package/dist/room-draft.d.ts +13 -1
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +2 -2
- 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 +1 -0
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +17 -1
- package/dist/search.d.ts +28 -7
- 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/store.d.ts +20 -5
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +58 -18
- 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/connection.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
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
11
|
import { changeSet, entering, mergeChangeSets } from './change-set.js';
|
|
10
12
|
import { createConversationStore, } from './conversation.js';
|
|
@@ -13,12 +15,13 @@ 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, 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
27
|
import { demanded, narrower, WHOLE_DEMAND, wider } from './acquisition.js';
|
|
@@ -31,6 +34,15 @@ import { beneath, normalize } from './paths.js';
|
|
|
31
34
|
import { openRuntimeStore, } from './store.js';
|
|
32
35
|
import { unconfirmed } from './transfer.js';
|
|
33
36
|
import { governs, holds, narrowed, planReach, } from './reach.js';
|
|
37
|
+
function roomsAlone(plan) {
|
|
38
|
+
return (plan.reach === null &&
|
|
39
|
+
plan.root.types === null &&
|
|
40
|
+
plan.root.where === null &&
|
|
41
|
+
plan.root.pathPrefix === null &&
|
|
42
|
+
plan.paths === null &&
|
|
43
|
+
plan.hop === null &&
|
|
44
|
+
plan.backlinks === null);
|
|
45
|
+
}
|
|
34
46
|
function within(root, path) {
|
|
35
47
|
return root === '' || beneath(root, path);
|
|
36
48
|
}
|
|
@@ -48,6 +60,10 @@ const OPENING_LANE = 'opening';
|
|
|
48
60
|
const CONVERSATION_LANE = 'conversations';
|
|
49
61
|
const ROOM_READS = 256;
|
|
50
62
|
const ROOM_ATTEMPTS = 16;
|
|
63
|
+
function operationOf(input) {
|
|
64
|
+
const operation = input?.operation_id;
|
|
65
|
+
return typeof operation === 'string' ? operation : null;
|
|
66
|
+
}
|
|
51
67
|
const outside = new Map();
|
|
52
68
|
function outsideOf(roots) {
|
|
53
69
|
const key = JSON.stringify(roots);
|
|
@@ -87,6 +103,8 @@ export function composeRuntime(options) {
|
|
|
87
103
|
const replicas = new FencedMap(isFenced);
|
|
88
104
|
const queuesWithoutReplica = new FencedMap(isFenced);
|
|
89
105
|
const pendings = new Map();
|
|
106
|
+
const seeds = new Map();
|
|
107
|
+
const seedWatchers = new Map();
|
|
90
108
|
const roomDrafts = new FencedMap(isFenced);
|
|
91
109
|
const presentations = new Map();
|
|
92
110
|
let limits = [];
|
|
@@ -99,6 +117,7 @@ export function composeRuntime(options) {
|
|
|
99
117
|
const liveModels = new FencedMap(isModelFenced);
|
|
100
118
|
const invalidModels = new Set();
|
|
101
119
|
const discovered = new Map();
|
|
120
|
+
const reads = doorReads(options.adapter, (locator) => discovered.get(locator), () => generation);
|
|
102
121
|
let roomsListed = false;
|
|
103
122
|
const scopedRefusals = new Map();
|
|
104
123
|
const sourceRefusals = new Map();
|
|
@@ -135,6 +154,7 @@ export function composeRuntime(options) {
|
|
|
135
154
|
let handed = false;
|
|
136
155
|
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
137
156
|
const repairing = new Set();
|
|
157
|
+
const calling = new Set();
|
|
138
158
|
const repairable = new Map();
|
|
139
159
|
let outcomeRetention = null;
|
|
140
160
|
const answering = new Map();
|
|
@@ -255,6 +275,15 @@ export function composeRuntime(options) {
|
|
|
255
275
|
const value = presentations.get(app);
|
|
256
276
|
return value === undefined ? null : structuredClone(value);
|
|
257
277
|
}
|
|
278
|
+
function keepPresentation(app, next) {
|
|
279
|
+
return exclusive(async () => {
|
|
280
|
+
const local = await openLocal();
|
|
281
|
+
const stored = next(presentations.get(app));
|
|
282
|
+
await local.present({ app, value: stored });
|
|
283
|
+
presentations.set(app, stored);
|
|
284
|
+
publishChangeSet(changeSet('', null, null, { presentation: app }));
|
|
285
|
+
});
|
|
286
|
+
}
|
|
258
287
|
function emitPresentation(app) {
|
|
259
288
|
if (closed)
|
|
260
289
|
return;
|
|
@@ -484,7 +513,8 @@ export function composeRuntime(options) {
|
|
|
484
513
|
return [...new Set(named)].filter((locator) => reached === null || reached.has(locator)).sort();
|
|
485
514
|
}
|
|
486
515
|
function awaited(locator) {
|
|
487
|
-
return
|
|
516
|
+
return (!roomsListed ||
|
|
517
|
+
(rooms.has(locator) && discovered.get(locator)?.replica.admission === 'admitted'));
|
|
488
518
|
}
|
|
489
519
|
function covered(plan) {
|
|
490
520
|
const locators = reachedRooms(plan);
|
|
@@ -535,20 +565,28 @@ export function composeRuntime(options) {
|
|
|
535
565
|
.map((room) => room.locator)
|
|
536
566
|
.sort();
|
|
537
567
|
}
|
|
568
|
+
function heldLocators(acquired) {
|
|
569
|
+
return demand.whole ? acquired : [...new Set([...acquired, ...demand.rooms])].sort();
|
|
570
|
+
}
|
|
538
571
|
function heldDemand(acquired) {
|
|
539
572
|
if (demand.whole)
|
|
540
573
|
return null;
|
|
541
|
-
return {
|
|
574
|
+
return {
|
|
575
|
+
rooms: heldLocators(acquired.map((room) => room.locator)),
|
|
576
|
+
conversations: demand.conversations,
|
|
577
|
+
};
|
|
542
578
|
}
|
|
543
579
|
function applyDemand(next) {
|
|
544
|
-
const before = acquiredLocators();
|
|
545
|
-
const conversed = demand
|
|
580
|
+
const before = heldLocators(acquiredLocators());
|
|
581
|
+
const { whole, conversations: conversed } = demand;
|
|
546
582
|
demand = next;
|
|
547
|
-
const after = acquiredLocators();
|
|
583
|
+
const after = heldLocators(acquiredLocators());
|
|
548
584
|
queueReadiness();
|
|
549
585
|
if (!conversed && next.conversations)
|
|
550
586
|
scheduleCurrency();
|
|
551
|
-
if (JSON.stringify(before) === JSON.stringify(after) &&
|
|
587
|
+
if (JSON.stringify(before) === JSON.stringify(after) &&
|
|
588
|
+
whole === next.whole &&
|
|
589
|
+
conversed === next.conversations)
|
|
552
590
|
return;
|
|
553
591
|
journal.record(`acquisition ${next.whole ? 'whole' : after.length}`);
|
|
554
592
|
if (options.adapter.roomStream === undefined)
|
|
@@ -643,8 +681,7 @@ export function composeRuntime(options) {
|
|
|
643
681
|
}
|
|
644
682
|
function readinessFor(port, reach) {
|
|
645
683
|
const required = requiredRooms(reach);
|
|
646
|
-
const
|
|
647
|
-
const search = target?.search === true;
|
|
684
|
+
const search = port.search === 'required';
|
|
648
685
|
const coverage = required.rooms.map((room) => ({ room, state: roomState(room, search) }));
|
|
649
686
|
const building = (state) => coverage
|
|
650
687
|
.filter((room) => room.state === state && !unindexable.has(room.room))
|
|
@@ -672,15 +709,11 @@ export function composeRuntime(options) {
|
|
|
672
709
|
: port.capacity,
|
|
673
710
|
refused: refusedIn(required.rooms),
|
|
674
711
|
lost: storageLoss !== null,
|
|
675
|
-
established: target?.established === true,
|
|
712
|
+
established: targets[port.target]?.established === true,
|
|
676
713
|
});
|
|
677
714
|
}
|
|
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 };
|
|
715
|
+
function establish(name) {
|
|
716
|
+
targets = { ...targets, [name]: { established: true } };
|
|
684
717
|
queueReadiness();
|
|
685
718
|
void durably((local) => local.keepTargets(targets)).catch(() => {
|
|
686
719
|
journal.record(`target record failed ${name}`);
|
|
@@ -691,7 +724,7 @@ export function composeRuntime(options) {
|
|
|
691
724
|
return;
|
|
692
725
|
const readiness = readinessFor(watch.port, watch.reach);
|
|
693
726
|
if (readiness.ready && targets[watch.port.target]?.established !== true) {
|
|
694
|
-
|
|
727
|
+
establish(watch.port.target);
|
|
695
728
|
}
|
|
696
729
|
const shown = JSON.stringify(readiness);
|
|
697
730
|
if (shown === watch.last)
|
|
@@ -719,10 +752,6 @@ export function composeRuntime(options) {
|
|
|
719
752
|
readinessWatches.delete(watch);
|
|
720
753
|
};
|
|
721
754
|
}
|
|
722
|
-
function searching(target) {
|
|
723
|
-
if (targets[target]?.search !== true)
|
|
724
|
-
keepTarget(target, { search: true });
|
|
725
|
-
}
|
|
726
755
|
function layerBytes(layer) {
|
|
727
756
|
let bytes = 0;
|
|
728
757
|
for (const [path, text] of Object.entries(layer.documents))
|
|
@@ -810,18 +839,54 @@ export function composeRuntime(options) {
|
|
|
810
839
|
const available = await estimated().catch(() => null);
|
|
811
840
|
pause(locator, { cause: 'quota-exceeded', needed: bytes, available });
|
|
812
841
|
}
|
|
842
|
+
function seedRoomHeld(room, version) {
|
|
843
|
+
return prepared.has(room) && reachedBy(room, version);
|
|
844
|
+
}
|
|
845
|
+
function seedHeld(at) {
|
|
846
|
+
return Object.entries(at).every(([room, version]) => (roomsListed && !rooms.has(room)) || seedRoomHeld(room, version));
|
|
847
|
+
}
|
|
848
|
+
function seedApplies(plan) {
|
|
849
|
+
const seed = seeds.get(plan.key);
|
|
850
|
+
if (seed === undefined)
|
|
851
|
+
return null;
|
|
852
|
+
if (!seedHeld(seed.at))
|
|
853
|
+
return seed;
|
|
854
|
+
seeds.delete(plan.key);
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
function seedBehind(plan, cover) {
|
|
858
|
+
const seed = seedApplies(plan);
|
|
859
|
+
if (seed === null || 'pending' in cover)
|
|
860
|
+
return cover;
|
|
861
|
+
return {
|
|
862
|
+
pending: Object.entries(seed.at)
|
|
863
|
+
.filter(([room, version]) => !seedRoomHeld(room, version))
|
|
864
|
+
.map(([room]) => ({ room, awaits: 'acquisition' })),
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
function seed(entry) {
|
|
868
|
+
if (seedHeld(entry.at))
|
|
869
|
+
return;
|
|
870
|
+
for (const { query, rows } of entry.queries) {
|
|
871
|
+
const key = queryKey(query);
|
|
872
|
+
seeds.set(key, { rows, at: entry.at });
|
|
873
|
+
for (const shown of seedWatchers.get(key) ?? [])
|
|
874
|
+
shown();
|
|
875
|
+
}
|
|
876
|
+
}
|
|
813
877
|
async function rowsQuery(plan) {
|
|
814
878
|
const before = covered(plan);
|
|
815
879
|
awaitPreparation(before);
|
|
880
|
+
const seeded = seedApplies(plan);
|
|
881
|
+
if (seeded !== null)
|
|
882
|
+
return seededResult(seeded).result;
|
|
816
883
|
if ('pending' in before)
|
|
817
884
|
return before;
|
|
818
885
|
const { rows, at: cover } = await readSide.query(plan, () => covered(plan));
|
|
819
886
|
awaitPreparation(cover);
|
|
820
887
|
if ('pending' in cover)
|
|
821
888
|
return cover;
|
|
822
|
-
return
|
|
823
|
-
? { groups: rows.groups, coverage: cover.coverage }
|
|
824
|
-
: { documents: rows.documents, coverage: cover.coverage };
|
|
889
|
+
return { ...rows, coverage: cover.coverage };
|
|
825
890
|
}
|
|
826
891
|
async function indexConversation(conversation) {
|
|
827
892
|
searchIndex.indexMessages(conversation, [], false);
|
|
@@ -971,6 +1036,9 @@ export function composeRuntime(options) {
|
|
|
971
1036
|
if (fence !== undefined && fence.erased && fence.at < started)
|
|
972
1037
|
fences.delete(room.locator);
|
|
973
1038
|
}
|
|
1039
|
+
await loseRooms(found
|
|
1040
|
+
.filter((room) => room.replica.admission !== 'admitted' && heldLocally(room.locator))
|
|
1041
|
+
.map((room) => room.locator));
|
|
974
1042
|
const listed = found.filter((room) => !fences.has(room.locator));
|
|
975
1043
|
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
976
1044
|
for (const locator of listedLocators)
|
|
@@ -995,6 +1063,12 @@ export function composeRuntime(options) {
|
|
|
995
1063
|
emitRooms();
|
|
996
1064
|
return listed;
|
|
997
1065
|
}
|
|
1066
|
+
function heldLocally(locator) {
|
|
1067
|
+
return (replicas.has(locator) ||
|
|
1068
|
+
roomDrafts.has(locator) ||
|
|
1069
|
+
transfers.has(locator) ||
|
|
1070
|
+
queuesWithoutReplica.has(locator));
|
|
1071
|
+
}
|
|
998
1072
|
function unlistedHeld(listed) {
|
|
999
1073
|
return [
|
|
1000
1074
|
...new Set([
|
|
@@ -1230,8 +1304,9 @@ export function composeRuntime(options) {
|
|
|
1230
1304
|
}
|
|
1231
1305
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
1232
1306
|
const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
1233
|
-
const withdrawn =
|
|
1234
|
-
const
|
|
1307
|
+
const { withdrawn, kept } = standing(confirmed, queue, withdrawable, localSaves);
|
|
1308
|
+
const founded = kept.filter((batch) => !queue.includes(batch));
|
|
1309
|
+
const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
|
|
1235
1310
|
const unqueued = withdrawn.map((batch) => ({
|
|
1236
1311
|
batch,
|
|
1237
1312
|
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
@@ -1255,9 +1330,10 @@ export function composeRuntime(options) {
|
|
|
1255
1330
|
});
|
|
1256
1331
|
}
|
|
1257
1332
|
}
|
|
1258
|
-
await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
|
|
1333
|
+
await local.keepAuthored({ locator, queue: kept, founded, roomDrafts: [...written.values()] });
|
|
1334
|
+
const staying = new Set(kept.map((batch) => batch.id));
|
|
1259
1335
|
for (const batch of previousQueue) {
|
|
1260
|
-
if (!
|
|
1336
|
+
if (!staying.has(batch.id))
|
|
1261
1337
|
pendings.delete(batch.id);
|
|
1262
1338
|
}
|
|
1263
1339
|
for (const record of written.values())
|
|
@@ -1278,7 +1354,7 @@ export function composeRuntime(options) {
|
|
|
1278
1354
|
else
|
|
1279
1355
|
queuesWithoutReplica.delete(locator);
|
|
1280
1356
|
publishChangeSet(changeSet(locator, before ?? null, held, {
|
|
1281
|
-
queue:
|
|
1357
|
+
queue: queueChanged,
|
|
1282
1358
|
drafts: [...written.keys()],
|
|
1283
1359
|
}));
|
|
1284
1360
|
moveTo({ ...status, ...attention() });
|
|
@@ -1293,7 +1369,7 @@ export function composeRuntime(options) {
|
|
|
1293
1369
|
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
1294
1370
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
1295
1371
|
room: settling.room,
|
|
1296
|
-
queue:
|
|
1372
|
+
queue: queueChanged,
|
|
1297
1373
|
drafts: [...written.keys()],
|
|
1298
1374
|
});
|
|
1299
1375
|
};
|
|
@@ -1385,7 +1461,7 @@ export function composeRuntime(options) {
|
|
|
1385
1461
|
return true;
|
|
1386
1462
|
});
|
|
1387
1463
|
}
|
|
1388
|
-
async function
|
|
1464
|
+
async function savedRoom(locator) {
|
|
1389
1465
|
const local = await openLocal();
|
|
1390
1466
|
await referenced(locator);
|
|
1391
1467
|
const replica = replicas.get(locator);
|
|
@@ -1393,6 +1469,10 @@ export function composeRuntime(options) {
|
|
|
1393
1469
|
if (replica === undefined || room === undefined) {
|
|
1394
1470
|
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
1395
1471
|
}
|
|
1472
|
+
return { local, replica, room };
|
|
1473
|
+
}
|
|
1474
|
+
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
1475
|
+
const target = await savedRoom(locator);
|
|
1396
1476
|
const saved = [];
|
|
1397
1477
|
for (const { path, revision } of named) {
|
|
1398
1478
|
const record = roomDrafts.get(locator)?.get(path);
|
|
@@ -1402,6 +1482,38 @@ export function composeRuntime(options) {
|
|
|
1402
1482
|
if (!saved.some((draft) => draft.path === path))
|
|
1403
1483
|
saved.push({ path, parts: record.parts });
|
|
1404
1484
|
}
|
|
1485
|
+
return committed(locator, target, saved, new Set(saved.map(({ path }) => path)), writtenBy);
|
|
1486
|
+
}
|
|
1487
|
+
async function saveParts(locator, named, writtenBy) {
|
|
1488
|
+
const read = named.map(({ path, parts }) => ({ path, parts: readParts(parts) }));
|
|
1489
|
+
if (read.some(({ path }, at) => path === '' ||
|
|
1490
|
+
normalize(path) !== path ||
|
|
1491
|
+
read.findIndex((one) => one.path === path) !== at)) {
|
|
1492
|
+
throw runtimeError('room-draft-malformed');
|
|
1493
|
+
}
|
|
1494
|
+
const target = await savedRoom(locator);
|
|
1495
|
+
const saved = [];
|
|
1496
|
+
for (const { path, parts } of read) {
|
|
1497
|
+
const file = parts.file === undefined
|
|
1498
|
+
? undefined
|
|
1499
|
+
: await heldFile(parts.file.blob, binaryBase(target.replica.visible, path));
|
|
1500
|
+
saved.push({ path, parts: held(undefined, parts, file) });
|
|
1501
|
+
}
|
|
1502
|
+
return committed(locator, target, saved, new Set(), writtenBy);
|
|
1503
|
+
}
|
|
1504
|
+
function carriedDrafts(locator, saved, consumed) {
|
|
1505
|
+
const holding = roomDrafts.get(locator);
|
|
1506
|
+
return new Map(saved.flatMap(({ path, parts }) => {
|
|
1507
|
+
const record = holding?.get(path);
|
|
1508
|
+
if (parts.move === undefined || record === undefined || consumed.has(path))
|
|
1509
|
+
return [];
|
|
1510
|
+
const to = moveTarget(parts.move);
|
|
1511
|
+
if (to === path || holding?.has(to) === true)
|
|
1512
|
+
return [];
|
|
1513
|
+
return [[path, { ...record, path: to, revision: roomDraftRevision() }]];
|
|
1514
|
+
}));
|
|
1515
|
+
}
|
|
1516
|
+
async function committed(locator, { local, replica, room }, saved, consumed, writtenBy) {
|
|
1405
1517
|
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
1406
1518
|
if (changed.length > 0)
|
|
1407
1519
|
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
@@ -1434,6 +1546,7 @@ export function composeRuntime(options) {
|
|
|
1434
1546
|
visible: replica.visible,
|
|
1435
1547
|
changes,
|
|
1436
1548
|
bases: parts.flatMap(({ bases }) => bases),
|
|
1549
|
+
queued: queuedDocuments(replica.queue),
|
|
1437
1550
|
writtenBy,
|
|
1438
1551
|
clean: {
|
|
1439
1552
|
text: replica.confirmed.record.documents,
|
|
@@ -1441,7 +1554,9 @@ export function composeRuntime(options) {
|
|
|
1441
1554
|
composition: compositionOf(replica.confirmed.record),
|
|
1442
1555
|
},
|
|
1443
1556
|
};
|
|
1444
|
-
const checked = withdrawing
|
|
1557
|
+
const checked = withdrawing
|
|
1558
|
+
? buildBatch({ ...input, queued: new Set([...input.queued, ...requeue.queued]) })
|
|
1559
|
+
: null;
|
|
1445
1560
|
if (checked?.kind === 'refused')
|
|
1446
1561
|
return { outcome: 'refused', ...checked.refusal };
|
|
1447
1562
|
const owners = [...requeue.owners].flatMap((origin) => {
|
|
@@ -1464,6 +1579,7 @@ export function composeRuntime(options) {
|
|
|
1464
1579
|
...input,
|
|
1465
1580
|
visible: replicaOf(replica.confirmed, requeue.queue, replica).visible,
|
|
1466
1581
|
changes: requeue.changes,
|
|
1582
|
+
queued: queuedDocuments(requeue.queue),
|
|
1467
1583
|
});
|
|
1468
1584
|
if (built?.kind === 'refused')
|
|
1469
1585
|
return { outcome: 'refused', ...built.refusal };
|
|
@@ -1498,13 +1614,16 @@ export function composeRuntime(options) {
|
|
|
1498
1614
|
if (limited !== null)
|
|
1499
1615
|
return { outcome: 'refused', ...limited };
|
|
1500
1616
|
}
|
|
1501
|
-
const
|
|
1617
|
+
const carried = carriedDrafts(locator, saved, consumed);
|
|
1618
|
+
const left = [...consumed, ...carried.keys()];
|
|
1619
|
+
const arrived = [...carried.values()];
|
|
1502
1620
|
try {
|
|
1503
1621
|
await local.keepAuthored({
|
|
1504
1622
|
locator,
|
|
1505
1623
|
queue: batches,
|
|
1506
1624
|
saved: drafted,
|
|
1507
|
-
consumedRoomDrafts:
|
|
1625
|
+
consumedRoomDrafts: left,
|
|
1626
|
+
roomDrafts: arrived,
|
|
1508
1627
|
});
|
|
1509
1628
|
}
|
|
1510
1629
|
catch {
|
|
@@ -1519,12 +1638,14 @@ export function composeRuntime(options) {
|
|
|
1519
1638
|
}
|
|
1520
1639
|
if (!localSaves)
|
|
1521
1640
|
recorded = false;
|
|
1522
|
-
for (const path of
|
|
1641
|
+
for (const path of left)
|
|
1523
1642
|
forgetRoomDraft(locator, path);
|
|
1643
|
+
for (const record of arrived)
|
|
1644
|
+
rememberRoomDraft(record);
|
|
1524
1645
|
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
1525
1646
|
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
1526
1647
|
queue: true,
|
|
1527
|
-
drafts:
|
|
1648
|
+
drafts: [...left, ...arrived.map(({ path }) => path)],
|
|
1528
1649
|
}));
|
|
1529
1650
|
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
1530
1651
|
submitLater(locator);
|
|
@@ -1615,7 +1736,7 @@ export function composeRuntime(options) {
|
|
|
1615
1736
|
const scope = options.adapter.referrerScope(request.referrer);
|
|
1616
1737
|
return scope?.kind === 'room' ? scope.locator : null;
|
|
1617
1738
|
}
|
|
1618
|
-
async function
|
|
1739
|
+
async function transferBinary(request, transfer, answer) {
|
|
1619
1740
|
if (closed || refusal !== null)
|
|
1620
1741
|
return UNCERTAIN;
|
|
1621
1742
|
try {
|
|
@@ -1631,40 +1752,41 @@ export function composeRuntime(options) {
|
|
|
1631
1752
|
? `transfer binary ${request.locator} ${request.path}`
|
|
1632
1753
|
: `transfer binary ${request.fileRef}`);
|
|
1633
1754
|
try {
|
|
1634
|
-
const result = await
|
|
1635
|
-
|
|
1755
|
+
const result = await transfer(authorization);
|
|
1756
|
+
const settled = result;
|
|
1757
|
+
journal.record(`outcome binary ${settled.kind}`);
|
|
1636
1758
|
const locator = binaryRoom(request);
|
|
1637
1759
|
if (locator !== null && fencedSince(locator, since))
|
|
1638
1760
|
return failure({ kind: 'room-not-readable', locator });
|
|
1639
|
-
if (
|
|
1640
|
-
const room = replicas.get(
|
|
1761
|
+
if (settled.kind === 'scoped-refusal') {
|
|
1762
|
+
const room = replicas.get(settled.locator)?.confirmed.record;
|
|
1641
1763
|
if (room !== undefined)
|
|
1642
|
-
await scoped(
|
|
1764
|
+
await scoped(settled, room, await openLocal());
|
|
1643
1765
|
}
|
|
1644
|
-
if (
|
|
1766
|
+
if (settled.kind === 'revoked-authorization')
|
|
1645
1767
|
await refuse('revoked-authorization');
|
|
1646
|
-
return
|
|
1768
|
+
return answer(result);
|
|
1647
1769
|
}
|
|
1648
1770
|
catch {
|
|
1649
1771
|
journal.record('outcome binary transport-uncertainty');
|
|
1650
1772
|
return UNCERTAIN;
|
|
1651
1773
|
}
|
|
1652
1774
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1775
|
+
function readBinary(request) {
|
|
1776
|
+
return transferBinary(request, (held) => options.adapter.binary(held, request), binaryAnswer);
|
|
1777
|
+
}
|
|
1778
|
+
function binaryPath(locator, path, revision) {
|
|
1655
1779
|
if (revision !== undefined)
|
|
1656
|
-
return
|
|
1780
|
+
return { kind: 'path', locator, path, revision };
|
|
1657
1781
|
const visible = replicas.get(locator)?.visible;
|
|
1658
1782
|
const held = visible?.binaries[path];
|
|
1659
1783
|
if (visible === undefined || held === undefined)
|
|
1660
|
-
return
|
|
1661
|
-
if (held.state === 'pending')
|
|
1662
|
-
|
|
1663
|
-
return blob === null ? absentBinary(path) : { kind: 'binary', blob };
|
|
1664
|
-
}
|
|
1784
|
+
return null;
|
|
1785
|
+
if (held.state === 'pending')
|
|
1786
|
+
return { kind: 'pending', localId: held.localId };
|
|
1665
1787
|
const record = binaryRecord(path, held, visible.revisions[path]);
|
|
1666
1788
|
if (record.state !== 'accepted')
|
|
1667
|
-
return
|
|
1789
|
+
return null;
|
|
1668
1790
|
const replica = replicas.get(locator);
|
|
1669
1791
|
const queued = (replica?.queue ?? []).flatMap((batch) => batch.changes);
|
|
1670
1792
|
const holds = (at) => {
|
|
@@ -1679,13 +1801,31 @@ export function composeRuntime(options) {
|
|
|
1679
1801
|
if (change.kind === 'move' && change.to === confirmed)
|
|
1680
1802
|
confirmed = change.path;
|
|
1681
1803
|
}
|
|
1682
|
-
return
|
|
1804
|
+
return {
|
|
1683
1805
|
kind: 'path',
|
|
1684
1806
|
locator,
|
|
1685
1807
|
path: confirmed,
|
|
1686
1808
|
revision: record.revision,
|
|
1687
1809
|
fileRef: record.fileRef,
|
|
1688
|
-
}
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
async function readBinaryPath(locator, path, revision) {
|
|
1813
|
+
await ready;
|
|
1814
|
+
const asked = binaryPath(locator, path, revision);
|
|
1815
|
+
if (asked === null)
|
|
1816
|
+
return absentBinary(path);
|
|
1817
|
+
if (asked.kind === 'pending') {
|
|
1818
|
+
const blob = queuedBlob(locator, path, asked.localId);
|
|
1819
|
+
return blob === null ? absentBinary(path) : { kind: 'binary', blob };
|
|
1820
|
+
}
|
|
1821
|
+
return readBinary(asked);
|
|
1822
|
+
}
|
|
1823
|
+
async function linkBinaryPath(locator, path, revision) {
|
|
1824
|
+
await ready;
|
|
1825
|
+
const asked = binaryPath(locator, path, revision);
|
|
1826
|
+
if (asked === null || asked.kind === 'pending')
|
|
1827
|
+
return absentBinary(path);
|
|
1828
|
+
return transferBinary(asked, (held) => options.adapter.link(held, asked), binaryLinkAnswer);
|
|
1689
1829
|
}
|
|
1690
1830
|
async function readBinaryReference(reference) {
|
|
1691
1831
|
await ready;
|
|
@@ -1711,7 +1851,7 @@ export function composeRuntime(options) {
|
|
|
1711
1851
|
}
|
|
1712
1852
|
return undefined;
|
|
1713
1853
|
}
|
|
1714
|
-
async function invokeAction(name, input) {
|
|
1854
|
+
async function invokeAction(name, input, dispatching) {
|
|
1715
1855
|
await ready;
|
|
1716
1856
|
if (closed || refusal !== null)
|
|
1717
1857
|
return UNCERTAIN;
|
|
@@ -1725,6 +1865,7 @@ export function composeRuntime(options) {
|
|
|
1725
1865
|
}
|
|
1726
1866
|
if (closed || authorization === undefined || refusal !== null)
|
|
1727
1867
|
return UNCERTAIN;
|
|
1868
|
+
await dispatching?.();
|
|
1728
1869
|
journal.record(`transfer action ${name}`);
|
|
1729
1870
|
try {
|
|
1730
1871
|
const result = await options.adapter.action(authorization, { name, input });
|
|
@@ -1745,6 +1886,60 @@ export function composeRuntime(options) {
|
|
|
1745
1886
|
return UNCERTAIN;
|
|
1746
1887
|
}
|
|
1747
1888
|
}
|
|
1889
|
+
function unsettled(result) {
|
|
1890
|
+
return result.kind !== 'action' && result.retryable;
|
|
1891
|
+
}
|
|
1892
|
+
async function recoveredAction(name, input) {
|
|
1893
|
+
const operation = operationOf(input);
|
|
1894
|
+
if (operation === null)
|
|
1895
|
+
return invokeAction(name, input);
|
|
1896
|
+
const kept = async () => (await openLocal()).keepCall({ operation, name, input });
|
|
1897
|
+
calling.add(operation);
|
|
1898
|
+
try {
|
|
1899
|
+
let result = await invokeAction(name, input, kept);
|
|
1900
|
+
if (unsettled(result)) {
|
|
1901
|
+
await recovery.attempt(`action ${operation}`, async () => {
|
|
1902
|
+
if (closed || refusal !== null)
|
|
1903
|
+
return 'done';
|
|
1904
|
+
if (!socketOpen)
|
|
1905
|
+
return 'again';
|
|
1906
|
+
result = await invokeAction(name, input, kept);
|
|
1907
|
+
return unsettled(result) ? 'again' : 'done';
|
|
1908
|
+
}, { again: true });
|
|
1909
|
+
}
|
|
1910
|
+
if (!unsettled(result))
|
|
1911
|
+
await (await openLocal()).dropCall(operation);
|
|
1912
|
+
return result;
|
|
1913
|
+
}
|
|
1914
|
+
finally {
|
|
1915
|
+
calling.delete(operation);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
async function resendCalls(signal) {
|
|
1919
|
+
if (!socketOpen || closed || stopping)
|
|
1920
|
+
return 'done';
|
|
1921
|
+
const local = await openLocal();
|
|
1922
|
+
for (const held of await local.calls()) {
|
|
1923
|
+
if (calling.has(held.operation))
|
|
1924
|
+
continue;
|
|
1925
|
+
if (closed || signal.aborted)
|
|
1926
|
+
return 'done';
|
|
1927
|
+
if (pastHorizon(held.operation)) {
|
|
1928
|
+
await local.dropCall(held.operation);
|
|
1929
|
+
continue;
|
|
1930
|
+
}
|
|
1931
|
+
calling.add(held.operation);
|
|
1932
|
+
try {
|
|
1933
|
+
if (unsettled(await invokeAction(held.name, held.input)))
|
|
1934
|
+
return 'again';
|
|
1935
|
+
await local.dropCall(held.operation);
|
|
1936
|
+
}
|
|
1937
|
+
finally {
|
|
1938
|
+
calling.delete(held.operation);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
return 'done';
|
|
1942
|
+
}
|
|
1748
1943
|
function continuing() {
|
|
1749
1944
|
return !closed && !discoveryPaused;
|
|
1750
1945
|
}
|
|
@@ -1762,8 +1957,9 @@ export function composeRuntime(options) {
|
|
|
1762
1957
|
async function pullRefresh(authorized, confirmed, live) {
|
|
1763
1958
|
if (!live())
|
|
1764
1959
|
return unconfirmed;
|
|
1960
|
+
const turn = generation;
|
|
1765
1961
|
try {
|
|
1766
|
-
const result = await refreshRoom(
|
|
1962
|
+
const result = await refreshRoom(reads, authorized, confirmed, (models, held) => (live() ? remodel(confirmed.record.locator, models, held) : held), live);
|
|
1767
1963
|
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
1768
1964
|
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
1769
1965
|
return result;
|
|
@@ -1772,6 +1968,9 @@ export function composeRuntime(options) {
|
|
|
1772
1968
|
journal.record(`transfer refresh ${confirmed.record.locator} transport-uncertainty`);
|
|
1773
1969
|
throw error;
|
|
1774
1970
|
}
|
|
1971
|
+
finally {
|
|
1972
|
+
reads.finished(confirmed.record.locator, turn);
|
|
1973
|
+
}
|
|
1775
1974
|
}
|
|
1776
1975
|
function staging(local, live) {
|
|
1777
1976
|
return {
|
|
@@ -1814,12 +2013,13 @@ export function composeRuntime(options) {
|
|
|
1814
2013
|
async function pullBootstrap(authorized, room, local, live) {
|
|
1815
2014
|
if (!live())
|
|
1816
2015
|
return unconfirmed;
|
|
2016
|
+
const turn = generation;
|
|
1817
2017
|
if (replicas.has(room.locator)) {
|
|
1818
2018
|
catching.add(room.locator);
|
|
1819
2019
|
readSide.recover();
|
|
1820
2020
|
}
|
|
1821
2021
|
try {
|
|
1822
|
-
const result = await bootstrapRoom(
|
|
2022
|
+
const result = await bootstrapRoom(reads, authorized, room, {
|
|
1823
2023
|
collection: modelOf(room.locator),
|
|
1824
2024
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
1825
2025
|
}, (models, held) => (live() ? remodel(room.locator, models, held) : held), live, staging(local, live), reusable(room));
|
|
@@ -1834,12 +2034,28 @@ export function composeRuntime(options) {
|
|
|
1834
2034
|
caughtUpTo(room.locator);
|
|
1835
2035
|
throw error;
|
|
1836
2036
|
}
|
|
2037
|
+
finally {
|
|
2038
|
+
reads.finished(room.locator, turn);
|
|
2039
|
+
}
|
|
1837
2040
|
}
|
|
1838
2041
|
function caughtUpTo(locator) {
|
|
1839
2042
|
if (catching.delete(locator))
|
|
1840
2043
|
readSide.recover();
|
|
1841
2044
|
}
|
|
2045
|
+
function behind(room) {
|
|
2046
|
+
const held = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
2047
|
+
return (held !== undefined &&
|
|
2048
|
+
[announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later)));
|
|
2049
|
+
}
|
|
1842
2050
|
async function advance(room, local, authorized, live) {
|
|
2051
|
+
const carried = reads.carried(room.locator);
|
|
2052
|
+
const withheld = await advanceOnce(room, local, authorized, live);
|
|
2053
|
+
if (withheld !== null || reads.carried(room.locator) === carried || !behind(room)) {
|
|
2054
|
+
return withheld;
|
|
2055
|
+
}
|
|
2056
|
+
return advanceOnce(room, local, authorized, live);
|
|
2057
|
+
}
|
|
2058
|
+
async function advanceOnce(room, local, authorized, live) {
|
|
1843
2059
|
await referenced(room.locator);
|
|
1844
2060
|
if (!live() || paused.has(room.locator))
|
|
1845
2061
|
return unconfirmed;
|
|
@@ -2276,6 +2492,11 @@ export function composeRuntime(options) {
|
|
|
2276
2492
|
});
|
|
2277
2493
|
}
|
|
2278
2494
|
async function scoped(result, room, local) {
|
|
2495
|
+
if ((result.scope === 'content' || result.scope === 'sync') && !result.retryable) {
|
|
2496
|
+
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
2497
|
+
await loseRooms([room.locator]);
|
|
2498
|
+
return;
|
|
2499
|
+
}
|
|
2279
2500
|
scopedRefusals.set(room.locator, {
|
|
2280
2501
|
code: result.code,
|
|
2281
2502
|
fields: result.fields,
|
|
@@ -2359,19 +2580,10 @@ export function composeRuntime(options) {
|
|
|
2359
2580
|
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
2360
2581
|
if (discoveryPaused || authorization === undefined)
|
|
2361
2582
|
return false;
|
|
2362
|
-
|
|
2363
|
-
if (replicas.has(room.locator))
|
|
2364
|
-
progress.complete = false;
|
|
2365
|
-
return false;
|
|
2366
|
-
}
|
|
2367
|
-
return true;
|
|
2583
|
+
return room.replica.admission === 'admitted';
|
|
2368
2584
|
}
|
|
2369
2585
|
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)
|
|
2586
|
+
if (authorization === undefined || !live() || !behind(room))
|
|
2375
2587
|
return;
|
|
2376
2588
|
const withheld = await advance(room, local, authorization, live);
|
|
2377
2589
|
if (withheld !== null)
|
|
@@ -2599,9 +2811,43 @@ export function composeRuntime(options) {
|
|
|
2599
2811
|
options.adapter.roomStream?.reopen();
|
|
2600
2812
|
});
|
|
2601
2813
|
}
|
|
2602
|
-
function
|
|
2814
|
+
function rediscover() {
|
|
2815
|
+
return task(OPENING_LANE, async (live) => {
|
|
2816
|
+
if (!live() || !socketOpen)
|
|
2817
|
+
return;
|
|
2818
|
+
const progress = { complete: true, reachable: true };
|
|
2819
|
+
const gate = discover();
|
|
2820
|
+
moveTo(transferring(status));
|
|
2821
|
+
try {
|
|
2822
|
+
const local = await openLocal();
|
|
2823
|
+
if (!live())
|
|
2824
|
+
return;
|
|
2825
|
+
await ensureAuthorization();
|
|
2826
|
+
if (!live())
|
|
2827
|
+
return;
|
|
2828
|
+
await discoverRooms(local, progress, 'authorized', live);
|
|
2829
|
+
}
|
|
2830
|
+
catch {
|
|
2831
|
+
progress.complete = false;
|
|
2832
|
+
}
|
|
2833
|
+
finally {
|
|
2834
|
+
gate.release();
|
|
2835
|
+
notifyReady();
|
|
2836
|
+
}
|
|
2837
|
+
if (!live())
|
|
2838
|
+
return;
|
|
2839
|
+
if (!progress.complete)
|
|
2840
|
+
complete = false;
|
|
2841
|
+
await rest(progress, live);
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
function closedSocket(revoked, lost) {
|
|
2603
2845
|
const opened = socketOpen;
|
|
2604
2846
|
socketOpen = false;
|
|
2847
|
+
if (lost) {
|
|
2848
|
+
reads.lost();
|
|
2849
|
+
conversations.lost();
|
|
2850
|
+
}
|
|
2605
2851
|
if (revoked) {
|
|
2606
2852
|
void refuse('revoked-authorization');
|
|
2607
2853
|
return;
|
|
@@ -2788,6 +3034,7 @@ export function composeRuntime(options) {
|
|
|
2788
3034
|
progress.complete = false;
|
|
2789
3035
|
return;
|
|
2790
3036
|
}
|
|
3037
|
+
reads.applied();
|
|
2791
3038
|
submitAll();
|
|
2792
3039
|
for (const locator of stale)
|
|
2793
3040
|
if (repairable.has(locator))
|
|
@@ -2800,6 +3047,8 @@ export function composeRuntime(options) {
|
|
|
2800
3047
|
await conversations.catchUp(reading);
|
|
2801
3048
|
if (progress.reachable && reading.reachable)
|
|
2802
3049
|
await conversations.resend(reading);
|
|
3050
|
+
if (progress.reachable)
|
|
3051
|
+
void recovery.attempt('calls', resendCalls);
|
|
2803
3052
|
if (!reading.reachable) {
|
|
2804
3053
|
progress.reachable = false;
|
|
2805
3054
|
resendLater();
|
|
@@ -2912,14 +3161,17 @@ export function composeRuntime(options) {
|
|
|
2912
3161
|
if (event.kind === 'opened') {
|
|
2913
3162
|
socketOpen = true;
|
|
2914
3163
|
generation += 1;
|
|
3164
|
+
reads.opened();
|
|
2915
3165
|
conversations.reopened();
|
|
2916
3166
|
recovery.resume();
|
|
2917
3167
|
void sync('opened');
|
|
2918
3168
|
}
|
|
2919
3169
|
else if (event.kind === 'closed')
|
|
2920
|
-
closedSocket(event.revoked);
|
|
3170
|
+
closedSocket(event.revoked, event.lost);
|
|
2921
3171
|
else if (event.kind === 'opening')
|
|
2922
3172
|
void applyOpening(event.expired);
|
|
3173
|
+
else if (event.kind === 'authority-changed')
|
|
3174
|
+
void rediscover();
|
|
2923
3175
|
else
|
|
2924
3176
|
void applyRoom(event.locator, event.roomVersion);
|
|
2925
3177
|
}, receipts);
|
|
@@ -3041,56 +3293,110 @@ export function composeRuntime(options) {
|
|
|
3041
3293
|
if (options.adapter.roomStream === undefined)
|
|
3042
3294
|
void sync('unstreamed');
|
|
3043
3295
|
function planned(input, reach) {
|
|
3044
|
-
|
|
3296
|
+
const plan = planOf(input, roomFacts(reach), planReach(reach));
|
|
3297
|
+
const named = plan.paths !== null &&
|
|
3298
|
+
plan.rooms !== null &&
|
|
3299
|
+
plan.paths.length * plan.rooms.length <= MAXIMUM_WINDOW;
|
|
3300
|
+
return plan.limit === null && named ? plan : { ...plan, limit: capped(plan.limit) };
|
|
3045
3301
|
}
|
|
3046
3302
|
function watchPlan(plan, deliver) {
|
|
3047
|
-
|
|
3303
|
+
let seeded = false;
|
|
3304
|
+
let pending = true;
|
|
3305
|
+
const shown = () => {
|
|
3306
|
+
const seed = seeds.get(plan.key);
|
|
3307
|
+
if (seed === undefined || seeded || !pending)
|
|
3308
|
+
return;
|
|
3309
|
+
seeded = true;
|
|
3310
|
+
deliver(seededResult(seed));
|
|
3311
|
+
};
|
|
3312
|
+
const watchers = seedWatchers.get(plan.key) ?? new Set();
|
|
3313
|
+
seedWatchers.set(plan.key, watchers);
|
|
3314
|
+
watchers.add(shown);
|
|
3315
|
+
const stop = readSide.watch(plan, identityOf(plan), () => {
|
|
3048
3316
|
const cover = covered(plan);
|
|
3049
3317
|
awaitPreparation(cover);
|
|
3050
|
-
return cover;
|
|
3051
|
-
},
|
|
3318
|
+
return seedBehind(plan, cover);
|
|
3319
|
+
}, (message) => {
|
|
3320
|
+
const answered = message;
|
|
3321
|
+
pending = whole(answered) && 'pending' in answered.result;
|
|
3322
|
+
if (pending && seeds.has(plan.key)) {
|
|
3323
|
+
shown();
|
|
3324
|
+
return;
|
|
3325
|
+
}
|
|
3326
|
+
seeded = false;
|
|
3327
|
+
deliver(message);
|
|
3328
|
+
});
|
|
3329
|
+
return () => {
|
|
3330
|
+
watchers.delete(shown);
|
|
3331
|
+
if (watchers.size === 0)
|
|
3332
|
+
seedWatchers.delete(plan.key);
|
|
3333
|
+
stop();
|
|
3334
|
+
};
|
|
3052
3335
|
}
|
|
3053
3336
|
async function searched(input, reach) {
|
|
3054
|
-
const
|
|
3337
|
+
const asked = searchPlanOf(input);
|
|
3338
|
+
const plan = { ...asked, limit: capped(asked.limit) };
|
|
3055
3339
|
const filter = filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach));
|
|
3056
3340
|
for (;;) {
|
|
3057
3341
|
const cover = covered(filter);
|
|
3058
3342
|
awaitPreparation(cover);
|
|
3059
3343
|
const pending = 'pending' in cover ? [...cover.pending] : [];
|
|
3060
|
-
const
|
|
3344
|
+
const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
|
|
3345
|
+
const held = covered({
|
|
3346
|
+
...filter,
|
|
3347
|
+
rooms: reachedRooms(filter).filter((room) => !awaited.has(room)),
|
|
3348
|
+
});
|
|
3349
|
+
const covering = 'pending' in held ? [] : held.coverage.rooms;
|
|
3061
3350
|
const unindexed = covering
|
|
3062
3351
|
.filter(({ room }) => !searchIndex.rooms().has(room))
|
|
3063
3352
|
.map(({ room }) => room);
|
|
3064
3353
|
for (const room of unindexed)
|
|
3065
3354
|
pending.push({ room, awaits: 'index' });
|
|
3066
|
-
const
|
|
3355
|
+
const listed = conversations.searchable();
|
|
3067
3356
|
const named = reach !== null && !reach.conversations
|
|
3068
3357
|
? []
|
|
3069
|
-
: (plan.conversations ??
|
|
3358
|
+
: (plan.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
|
|
3070
3359
|
const waiting = named.filter((one) => !searchIndex.conversations().has(one));
|
|
3071
3360
|
for (const conversation of waiting)
|
|
3072
3361
|
pending.push({ conversation, awaits: 'index' });
|
|
3073
3362
|
if (unindexed.length > 0 || waiting.length > 0)
|
|
3074
3363
|
index(unindexed, waiting);
|
|
3075
|
-
|
|
3076
|
-
|
|
3364
|
+
const complete = pending.length === 0 && !('pending' in cover);
|
|
3365
|
+
const answering = complete
|
|
3366
|
+
? filter
|
|
3367
|
+
: {
|
|
3368
|
+
...filter,
|
|
3369
|
+
rooms: covering
|
|
3370
|
+
.filter(({ room }) => searchIndex.rooms().has(room))
|
|
3371
|
+
.map(({ room }) => room),
|
|
3372
|
+
};
|
|
3373
|
+
const answered = covered(answering);
|
|
3374
|
+
if ('pending' in answered)
|
|
3375
|
+
continue;
|
|
3376
|
+
const searchedConversations = named.filter((one) => searchIndex.conversations().has(one));
|
|
3077
3377
|
const seen = searchIndex.changes();
|
|
3078
3378
|
await searchIndex.caughtUp();
|
|
3079
|
-
const selected =
|
|
3379
|
+
const selected = roomsAlone(answering) ? null : await rowsQuery(answering);
|
|
3080
3380
|
if (selected !== null && 'pending' in selected)
|
|
3081
|
-
|
|
3381
|
+
continue;
|
|
3082
3382
|
if (searchIndex.changes() !== seen ||
|
|
3083
|
-
JSON.stringify(covered(
|
|
3383
|
+
JSON.stringify(covered(answering)) !== JSON.stringify(answered))
|
|
3084
3384
|
continue;
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3385
|
+
const hits = searchIndex.search(plan, selected === null ? null : selected.documents, searchedConversations, answering.rooms === null ? null : new Set(answering.rooms), plan.collection
|
|
3386
|
+
? new Map(answered.coverage.rooms.map(({ room }) => [
|
|
3387
|
+
room,
|
|
3388
|
+
replicas.get(room)?.confirmed.record.typedSelection?.roots ?? [],
|
|
3389
|
+
]))
|
|
3390
|
+
: null);
|
|
3391
|
+
const coverage = {
|
|
3392
|
+
rooms: answered.coverage.rooms,
|
|
3393
|
+
conversations: searchedConversations,
|
|
3394
|
+
index: searchIndex.generation(),
|
|
3395
|
+
overlay: answered.coverage.overlay,
|
|
3093
3396
|
};
|
|
3397
|
+
return complete
|
|
3398
|
+
? { ...hits, coverage }
|
|
3399
|
+
: { pending, documents: hits.documents, messages: hits.messages, coverage };
|
|
3094
3400
|
}
|
|
3095
3401
|
}
|
|
3096
3402
|
const watch = (topic, input, deliver) => {
|
|
@@ -3138,7 +3444,7 @@ export function composeRuntime(options) {
|
|
|
3138
3444
|
};
|
|
3139
3445
|
}
|
|
3140
3446
|
if (topic === 'readiness') {
|
|
3141
|
-
return watchReadiness({ target: '', code: 'stored', capacity: null }, null, deliver);
|
|
3447
|
+
return watchReadiness({ target: '', code: 'stored', capacity: null, search: 'unused' }, null, deliver);
|
|
3142
3448
|
}
|
|
3143
3449
|
if (topic === 'rooms') {
|
|
3144
3450
|
roomListeners.add(deliver);
|
|
@@ -3275,27 +3581,31 @@ export function composeRuntime(options) {
|
|
|
3275
3581
|
});
|
|
3276
3582
|
},
|
|
3277
3583
|
write(app, value) {
|
|
3278
|
-
return
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3584
|
+
return keepPresentation(app, () => structuredClone(value));
|
|
3585
|
+
},
|
|
3586
|
+
edit(app, members) {
|
|
3587
|
+
return keepPresentation(app, (held) => Object.fromEntries([
|
|
3588
|
+
...Object.entries(plain(held) ? held : {}).filter(([member]) => !Object.hasOwn(members, member)),
|
|
3589
|
+
...Object.entries(structuredClone(members)).filter(([, value]) => value !== null),
|
|
3590
|
+
]));
|
|
3285
3591
|
},
|
|
3286
3592
|
},
|
|
3287
3593
|
save(room, named, writtenBy) {
|
|
3288
3594
|
return exclusive(() => saveRoomDrafts(room, named, writtenBy));
|
|
3289
3595
|
},
|
|
3596
|
+
saveParts(room, parts, writtenBy) {
|
|
3597
|
+
return exclusive(() => saveParts(room, parts, writtenBy));
|
|
3598
|
+
},
|
|
3290
3599
|
history(locator, path, query) {
|
|
3291
3600
|
return readHistory(locator, path, query);
|
|
3292
3601
|
},
|
|
3293
3602
|
binaryFile: {
|
|
3294
3603
|
read: readBinaryPath,
|
|
3604
|
+
link: linkBinaryPath,
|
|
3295
3605
|
readReference: readBinaryReference,
|
|
3296
3606
|
},
|
|
3297
3607
|
call(name, input) {
|
|
3298
|
-
return
|
|
3608
|
+
return recoveredAction(name, input);
|
|
3299
3609
|
},
|
|
3300
3610
|
reached(locator, roomVersion, path) {
|
|
3301
3611
|
return path === undefined
|
|
@@ -3339,11 +3649,11 @@ export function composeRuntime(options) {
|
|
|
3339
3649
|
query: (plan) => rowsQuery(plan),
|
|
3340
3650
|
watchPlan,
|
|
3341
3651
|
search: searched,
|
|
3342
|
-
searching,
|
|
3343
3652
|
watchReadiness,
|
|
3344
3653
|
};
|
|
3345
3654
|
return {
|
|
3346
3655
|
md,
|
|
3656
|
+
seed,
|
|
3347
3657
|
stop,
|
|
3348
3658
|
lose: loseRooms,
|
|
3349
3659
|
watch,
|