@agentshouse/mdruntime 0.1.0-c069136dcb4e8 → 0.1.0-c09bf842921ff
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/README.md +1 -1
- package/dist/adapter.d.ts +51 -22
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +3 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +21 -56
- package/dist/bootstrap.d.ts +2 -2
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +9 -8
- package/dist/chain.d.ts +2 -1
- package/dist/chain.d.ts.map +1 -1
- package/dist/client.d.ts +9 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -1
- package/dist/collection.d.ts +2 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/compile.d.ts +1 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +90 -277
- package/dist/connection.d.ts +8 -7
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +362 -313
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +7 -0
- package/dist/conversation.d.ts +14 -8
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +85 -67
- package/dist/durability.d.ts +6 -0
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +2 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +16 -7
- package/dist/host.d.ts +3 -6
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +16 -23
- package/dist/identity.d.ts +0 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -3
- package/dist/index.d.ts +10 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/job.d.ts +14 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +28 -0
- package/dist/live.d.ts +2 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +10 -7
- package/dist/memory.d.ts +8 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +116 -66
- package/dist/messages.d.ts +2 -2
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +15 -16
- package/dist/model.d.ts +2 -7
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -3
- package/dist/observation.d.ts +2 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +7 -2
- package/dist/query.d.ts +4 -209
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -505
- package/dist/reach.d.ts +7 -7
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +33 -44
- package/dist/react.d.ts +3 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/readiness.d.ts +0 -2
- package/dist/readiness.d.ts.map +1 -1
- package/dist/refresh.d.ts +2 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +25 -1
- package/dist/replica.d.ts +8 -5
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +27 -9
- package/dist/room-draft.d.ts +4 -3
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +65 -52
- package/dist/rows.d.ts +14 -21
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +48 -184
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +1 -1
- package/dist/search.d.ts +12 -63
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +9 -52
- package/dist/store.d.ts +5 -7
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +11 -15
- package/dist/stored.d.ts +3 -4
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +90 -126
- package/dist/tables.d.ts +8 -6
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +22 -13
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +7 -4
- package/dist/windows.d.ts +1 -12
- package/dist/windows.d.ts.map +1 -1
- package/dist/windows.js +2 -75
- package/dist/wire.d.ts +9 -4
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +3 -1
- package/package.json +2 -2
- package/dist/fulltext.d.ts +0 -27
- package/dist/fulltext.d.ts.map +0 -1
- package/dist/fulltext.js +0 -370
- package/dist/words.d.ts +0 -19
- package/dist/words.d.ts.map +0 -1
- package/dist/words.js +0 -156
package/dist/connection.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { parseFrontmatter, roomTarget, target } from '@agentshouse/mdmodel';
|
|
2
|
-
import { attachedModel, builtInModel, roomTypesOf } from './attached-model.js';
|
|
1
|
+
import { identityOf, parseFrontmatter, plain, queryKey, roomTarget, target, } from '@agentshouse/mdmodel';
|
|
2
|
+
import { attachedModel, builtInModel, roomTypesOf, schemaTypes } from './attached-model.js';
|
|
3
3
|
import { cached, modelTypes, NO_MODEL, sameModel, } from './model.js';
|
|
4
4
|
import { observing, whole } from './observation.js';
|
|
5
|
-
import {
|
|
5
|
+
import { planWithin, } from './query.js';
|
|
6
6
|
import { createLiveQueries } from './live.js';
|
|
7
7
|
import { seededResult } from './seed.js';
|
|
8
8
|
import { documentKey, roomOfKey } from './rows.js';
|
|
@@ -30,7 +30,7 @@ import { readinessOf, } from './readiness.js';
|
|
|
30
30
|
import { FencedMap } from './fence.js';
|
|
31
31
|
import { sameBinary } from './overlay.js';
|
|
32
32
|
import { paced, ROW_WEIGHT, stepped, yielded } from './turn.js';
|
|
33
|
-
import {
|
|
33
|
+
import { roomDraftRevision } from './identity.js';
|
|
34
34
|
import { ancestors, beneath, normalize } from './paths.js';
|
|
35
35
|
import { openRuntimeStore, } from './store.js';
|
|
36
36
|
import { validatedNeighbourhood } from './validation.js';
|
|
@@ -104,6 +104,7 @@ export function composeRuntime(options) {
|
|
|
104
104
|
const usableWhenHeld = options.usableWhenHeld ?? [];
|
|
105
105
|
const queuesWithoutReplica = new FencedMap(isFenced);
|
|
106
106
|
const pendings = new Map();
|
|
107
|
+
const landedAhead = new Map();
|
|
107
108
|
const seeds = new Map();
|
|
108
109
|
const seedWatchers = new Map();
|
|
109
110
|
const roomDrafts = new FencedMap(isFenced);
|
|
@@ -115,6 +116,7 @@ export function composeRuntime(options) {
|
|
|
115
116
|
const counted = new Map();
|
|
116
117
|
const unreadable = new Set();
|
|
117
118
|
const builtIn = builtInModel(roomTypes);
|
|
119
|
+
const typedPaths = options.adapter.typedPaths ?? [];
|
|
118
120
|
const models = new FencedMap(isFenced);
|
|
119
121
|
const liveModels = new FencedMap(isFenced);
|
|
120
122
|
const invalidModels = new Set();
|
|
@@ -141,7 +143,7 @@ export function composeRuntime(options) {
|
|
|
141
143
|
: modelOf(locator);
|
|
142
144
|
}
|
|
143
145
|
function composed(record) {
|
|
144
|
-
return compositionOf(record, builtIn);
|
|
146
|
+
return compositionOf(record, builtIn, typedPaths);
|
|
145
147
|
}
|
|
146
148
|
function compositionFor(record) {
|
|
147
149
|
return composed({ ...record, model: savedModel(record.locator) });
|
|
@@ -162,14 +164,14 @@ export function composeRuntime(options) {
|
|
|
162
164
|
const repairing = new Set();
|
|
163
165
|
const calling = new Set();
|
|
164
166
|
const repairable = new Map();
|
|
165
|
-
let outcomeRetention = null;
|
|
166
167
|
const answering = new Map();
|
|
167
168
|
let complete = false;
|
|
168
169
|
let recorded = false;
|
|
169
170
|
const stale = new Set();
|
|
170
171
|
let generation = 0;
|
|
171
|
-
let discovering = null;
|
|
172
172
|
const reloadsDue = new Set();
|
|
173
|
+
const highest = new Map();
|
|
174
|
+
const beyondHorizon = new Set();
|
|
173
175
|
const lanes = new Map();
|
|
174
176
|
const roomLanes = new Map();
|
|
175
177
|
let roomWork = 0;
|
|
@@ -189,6 +191,8 @@ export function composeRuntime(options) {
|
|
|
189
191
|
let withdrawConversations;
|
|
190
192
|
let currencyQueued = null;
|
|
191
193
|
let socketOpen = false;
|
|
194
|
+
let announced = false;
|
|
195
|
+
let missedChange = false;
|
|
192
196
|
const confirmed = new Set();
|
|
193
197
|
let lockGrace = null;
|
|
194
198
|
const lockListeners = new Set();
|
|
@@ -198,6 +202,8 @@ export function composeRuntime(options) {
|
|
|
198
202
|
const serviceModeListeners = new Set();
|
|
199
203
|
let member = null;
|
|
200
204
|
const memberListeners = new Set();
|
|
205
|
+
let authorityChanges = 0;
|
|
206
|
+
const authorityChangeListeners = new Set();
|
|
201
207
|
const unpreparedDemand = new Set();
|
|
202
208
|
const unpreparable = new Set();
|
|
203
209
|
let preparing = false;
|
|
@@ -247,6 +253,7 @@ export function composeRuntime(options) {
|
|
|
247
253
|
const installTurns = { busy: false, ahead: 0, finishing: [], starting: [] };
|
|
248
254
|
const conversations = createConversationStore({
|
|
249
255
|
call: (name, input) => invokeAction(name, input),
|
|
256
|
+
history: (request) => readConversationHistory(request),
|
|
250
257
|
durably,
|
|
251
258
|
stream: () => options.adapter.conversations,
|
|
252
259
|
publish: publishChangeSet,
|
|
@@ -254,7 +261,6 @@ export function composeRuntime(options) {
|
|
|
254
261
|
full: () => full,
|
|
255
262
|
closed: () => closed,
|
|
256
263
|
unanswered: resendLater,
|
|
257
|
-
expired: (operation) => pastHorizon(operation),
|
|
258
264
|
});
|
|
259
265
|
const journal = createLog();
|
|
260
266
|
const keptFiles = openedFiles(database, isFenced);
|
|
@@ -347,9 +353,9 @@ export function composeRuntime(options) {
|
|
|
347
353
|
return value === undefined ? null : structuredClone(value);
|
|
348
354
|
}
|
|
349
355
|
async function writtenRoomDraft(room, path, revision, input, next) {
|
|
350
|
-
const parts = readParts(input);
|
|
356
|
+
const parts = readParts(input, path);
|
|
351
357
|
if (path === '' || normalize(path) !== path)
|
|
352
|
-
throw runtimeError('room-draft-malformed');
|
|
358
|
+
throw runtimeError('room-draft-malformed', `room-draft-malformed at ${path}: a path inside the Room, such as notes/a.md`);
|
|
353
359
|
const local = await openLocal();
|
|
354
360
|
const current = roomDrafts.get(room)?.get(path);
|
|
355
361
|
if ((current?.revision ?? null) !== revision) {
|
|
@@ -649,11 +655,7 @@ export function composeRuntime(options) {
|
|
|
649
655
|
})();
|
|
650
656
|
}
|
|
651
657
|
function roomsOfReach(reach) {
|
|
652
|
-
return new Set([
|
|
653
|
-
...reach.rooms,
|
|
654
|
-
...reach.documents.map(roomOfKey),
|
|
655
|
-
...reach.areas.flatMap((area) => (area.types.length === 0 ? [] : [area.room])),
|
|
656
|
-
]);
|
|
658
|
+
return new Set([...reach.rooms, ...reach.documents.map(roomOfKey)]);
|
|
657
659
|
}
|
|
658
660
|
function reachedRooms(plan) {
|
|
659
661
|
const reached = plan.reach === null ? null : roomsOfReach(plan.reach);
|
|
@@ -792,7 +794,6 @@ export function composeRuntime(options) {
|
|
|
792
794
|
}
|
|
793
795
|
function readinessFor(port, reach) {
|
|
794
796
|
const required = requiredRooms(reach);
|
|
795
|
-
const search = port.search === 'required';
|
|
796
797
|
const coverage = required.rooms.map((room) => ({ room, state: roomState(room) }));
|
|
797
798
|
const preparing = coverage
|
|
798
799
|
.filter((room) => room.state === 'preparing' && !unpreparable.has(room.room))
|
|
@@ -805,12 +806,11 @@ export function composeRuntime(options) {
|
|
|
805
806
|
listed: required.listed,
|
|
806
807
|
rooms: coverage,
|
|
807
808
|
conversations: conversationCoverage(reach),
|
|
808
|
-
search: search ? 'required' : 'unused',
|
|
809
809
|
},
|
|
810
810
|
persistence,
|
|
811
811
|
connection: connectionOf(),
|
|
812
812
|
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
813
|
-
space: full ? 'full' :
|
|
813
|
+
space: full ? 'full' : null,
|
|
814
814
|
installation: installationOf(coverage),
|
|
815
815
|
lost: storageLoss !== null,
|
|
816
816
|
established: targets[port.target]?.established === true ||
|
|
@@ -899,8 +899,12 @@ export function composeRuntime(options) {
|
|
|
899
899
|
unpreparable.clear();
|
|
900
900
|
journal.record('space freed');
|
|
901
901
|
queueReadiness();
|
|
902
|
-
if (
|
|
903
|
-
|
|
902
|
+
if (closed || authorization === undefined)
|
|
903
|
+
return;
|
|
904
|
+
if (options.adapter.roomStream === undefined)
|
|
905
|
+
void sync('unstreamed', null);
|
|
906
|
+
else
|
|
907
|
+
void resynchronize();
|
|
904
908
|
}
|
|
905
909
|
function seedRoomHeld(room, version) {
|
|
906
910
|
return indexed(room) && reachedBy(room, version);
|
|
@@ -1137,7 +1141,7 @@ export function composeRuntime(options) {
|
|
|
1137
1141
|
refusal: scopedRefusals.get(room.locator) ?? null,
|
|
1138
1142
|
});
|
|
1139
1143
|
}
|
|
1140
|
-
async function applyDiscovery(found, started,
|
|
1144
|
+
async function applyDiscovery(found, started, left) {
|
|
1141
1145
|
const lockedBefore = lockedRooms();
|
|
1142
1146
|
for (const room of found) {
|
|
1143
1147
|
const fence = fences.get(room.locator);
|
|
@@ -1151,18 +1155,11 @@ export function composeRuntime(options) {
|
|
|
1151
1155
|
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
1152
1156
|
for (const locator of listedLocators)
|
|
1153
1157
|
scopedRefusals.delete(locator);
|
|
1154
|
-
if (
|
|
1158
|
+
if (left === undefined)
|
|
1155
1159
|
discovered.clear();
|
|
1156
|
-
else
|
|
1157
|
-
for (const locator of covered)
|
|
1158
|
-
discovered.delete(locator);
|
|
1159
1160
|
for (const room of listed)
|
|
1160
1161
|
discovered.set(room.locator, room);
|
|
1161
|
-
|
|
1162
|
-
if (options.adapter.exhaustiveDiscovery === true)
|
|
1163
|
-
await loseRooms(unlisted);
|
|
1164
|
-
else
|
|
1165
|
-
unlistedKept(unlisted);
|
|
1162
|
+
await loseRooms(left ?? unlistedHeld(new Set(found.map((room) => room.locator))));
|
|
1166
1163
|
for (const room of listed) {
|
|
1167
1164
|
const held = replicas.get(room.locator)?.record;
|
|
1168
1165
|
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
@@ -1193,15 +1190,6 @@ export function composeRuntime(options) {
|
|
|
1193
1190
|
]),
|
|
1194
1191
|
].filter((locator) => !listed.has(locator));
|
|
1195
1192
|
}
|
|
1196
|
-
function unlistedKept(unlisted) {
|
|
1197
|
-
for (const locator of unlisted) {
|
|
1198
|
-
const held = replicas.get(locator)?.record;
|
|
1199
|
-
if (held !== undefined)
|
|
1200
|
-
list(held, false);
|
|
1201
|
-
else
|
|
1202
|
-
rooms.delete(locator);
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
1193
|
function fence(locators) {
|
|
1206
1194
|
const fresh = [...new Set(locators)].filter((locator) => !fences.has(locator));
|
|
1207
1195
|
const removed = fresh.map((locator) => ({
|
|
@@ -1212,9 +1200,13 @@ export function composeRuntime(options) {
|
|
|
1212
1200
|
fences.set(locator, { at: discoveries, erased: false });
|
|
1213
1201
|
lastFenced.set(locator, ++fencings);
|
|
1214
1202
|
confirmed.delete(locator);
|
|
1215
|
-
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
1203
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []) {
|
|
1216
1204
|
pendings.delete(pendingKey(locator, batch.id));
|
|
1205
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1206
|
+
}
|
|
1217
1207
|
replicas.delete(locator);
|
|
1208
|
+
highest.delete(locator);
|
|
1209
|
+
beyondHorizon.delete(locator);
|
|
1218
1210
|
queuesWithoutReplica.delete(locator);
|
|
1219
1211
|
rooms.delete(locator);
|
|
1220
1212
|
roomDrafts.delete(locator);
|
|
@@ -1281,8 +1273,7 @@ export function composeRuntime(options) {
|
|
|
1281
1273
|
function retyped(persisted) {
|
|
1282
1274
|
if (persisted.status === 'unavailable')
|
|
1283
1275
|
return persisted;
|
|
1284
|
-
|
|
1285
|
-
return state.status === 'unavailable' ? state : { ...persisted, types: state.types };
|
|
1276
|
+
return { ...persisted, types: schemaTypes(persisted.snapshot.schema, roomTypes) };
|
|
1286
1277
|
}
|
|
1287
1278
|
function restored(key, held) {
|
|
1288
1279
|
const persisted = retyped(held);
|
|
@@ -1328,8 +1319,9 @@ export function composeRuntime(options) {
|
|
|
1328
1319
|
presentations.clear();
|
|
1329
1320
|
for (const record of held.presentation)
|
|
1330
1321
|
presentations.set(record.app, record.value);
|
|
1331
|
-
conversations.hydrate(heldConversations, chatDrafts, held.conversationList);
|
|
1322
|
+
conversations.hydrate(heldConversations, chatDrafts, held.conversationList, held.conversationCursor);
|
|
1332
1323
|
pendings.clear();
|
|
1324
|
+
landedAhead.clear();
|
|
1333
1325
|
for (const queue of queues) {
|
|
1334
1326
|
for (const { batch, state } of queue.pending) {
|
|
1335
1327
|
pendings.set(pendingKey(queue.locator, batch.id), state);
|
|
@@ -1478,14 +1470,12 @@ export function composeRuntime(options) {
|
|
|
1478
1470
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
1479
1471
|
};
|
|
1480
1472
|
}
|
|
1481
|
-
const REWRITE_LINKS = ['field', 'body'];
|
|
1482
|
-
const CHECKED_LINKS = ['field', 'section'];
|
|
1483
1473
|
async function sliceAt(record, view, queue, extra = [], moved = []) {
|
|
1484
1474
|
const local = await openLocal();
|
|
1485
1475
|
const needed = new Set([...touchedBy(queue), ...extra]);
|
|
1486
1476
|
const movedPaths = [...new Set([...movedBy(queue, record.locator), ...moved])];
|
|
1487
1477
|
if (movedPaths.length > 0) {
|
|
1488
|
-
for (const { path } of await local.referrers(record.key, view, movedPaths
|
|
1478
|
+
for (const { path } of await local.referrers(record.key, view, movedPaths)) {
|
|
1489
1479
|
needed.add(path);
|
|
1490
1480
|
}
|
|
1491
1481
|
}
|
|
@@ -1622,10 +1612,16 @@ export function composeRuntime(options) {
|
|
|
1622
1612
|
? read
|
|
1623
1613
|
: await stepped(slicingWith(read, overrides.set, overrides.removed));
|
|
1624
1614
|
const composition = composed(record);
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1615
|
+
const shown = queue.filter((batch) => {
|
|
1616
|
+
const held = landedAhead.get(pendingKey(locator, batch.id));
|
|
1617
|
+
return (held !== undefined &&
|
|
1618
|
+
Object.entries(held).some(([path, revision]) => confirmedLayer.revisions[path] !== revision));
|
|
1619
|
+
});
|
|
1620
|
+
const unshown = queue.filter((batch) => !shown.includes(batch));
|
|
1621
|
+
const { withdrawn, kept } = await stepped(standing(confirmedLayer, unshown, withdrawable(locator), localSaves, locator, composition));
|
|
1622
|
+
const founded = kept.filter((batch) => !unshown.includes(batch));
|
|
1627
1623
|
const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
|
|
1628
|
-
const unqueued =
|
|
1624
|
+
const unqueued = shown.map((batch) => ({ batch, landed: true }));
|
|
1629
1625
|
for (const batch of withdrawn) {
|
|
1630
1626
|
unqueued.push({ batch, landed: await landedWhole(locator, batch, confirmedLayer) });
|
|
1631
1627
|
}
|
|
@@ -1690,8 +1686,10 @@ export function composeRuntime(options) {
|
|
|
1690
1686
|
commit,
|
|
1691
1687
|
apply: (answer) => {
|
|
1692
1688
|
for (const batch of previousQueue) {
|
|
1693
|
-
if (
|
|
1694
|
-
|
|
1689
|
+
if (staying.has(batch.id))
|
|
1690
|
+
continue;
|
|
1691
|
+
pendings.delete(pendingKey(locator, batch.id));
|
|
1692
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1695
1693
|
}
|
|
1696
1694
|
for (const draft of written.values())
|
|
1697
1695
|
rememberRoomDraft(draft);
|
|
@@ -1740,11 +1738,7 @@ export function composeRuntime(options) {
|
|
|
1740
1738
|
const answer = answers?.collection;
|
|
1741
1739
|
if (answer === undefined)
|
|
1742
1740
|
return held;
|
|
1743
|
-
const
|
|
1744
|
-
if (refused !== null) {
|
|
1745
|
-
const where = refused.location === undefined ? '' : ` ${refused.location}`;
|
|
1746
|
-
journal.record(`model invalid ${locator} collection ${refused.identity} ${refused.rule}${where}`);
|
|
1747
|
-
}
|
|
1741
|
+
const state = attachedModel(answer, roomTypes);
|
|
1748
1742
|
models.set(locator, state);
|
|
1749
1743
|
return { collection: state };
|
|
1750
1744
|
}
|
|
@@ -1797,6 +1791,8 @@ export function composeRuntime(options) {
|
|
|
1797
1791
|
return true;
|
|
1798
1792
|
}
|
|
1799
1793
|
if (landing.kind === 'bootstrapped') {
|
|
1794
|
+
highest.delete(room.locator);
|
|
1795
|
+
beyondHorizon.delete(room.locator);
|
|
1800
1796
|
untransferred(room.locator);
|
|
1801
1797
|
startedOver.delete(room.locator);
|
|
1802
1798
|
catching.delete(room.locator);
|
|
@@ -1824,7 +1820,7 @@ export function composeRuntime(options) {
|
|
|
1824
1820
|
const replica = replicas.get(locator);
|
|
1825
1821
|
const room = rooms.get(locator);
|
|
1826
1822
|
if (replica === undefined || room === undefined) {
|
|
1827
|
-
throw runtimeError('room
|
|
1823
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
1828
1824
|
}
|
|
1829
1825
|
return { local, replica, room };
|
|
1830
1826
|
}
|
|
@@ -1885,7 +1881,7 @@ export function composeRuntime(options) {
|
|
|
1885
1881
|
const removed = changes.flatMap((change) => (change.kind === 'remove' ? [change.path] : []));
|
|
1886
1882
|
const referring = removed.length === 0
|
|
1887
1883
|
? []
|
|
1888
|
-
: (await local.referrers(record.key, 'confirmed', removed
|
|
1884
|
+
: (await local.referrers(record.key, 'confirmed', removed)).map(({ path }) => path);
|
|
1889
1885
|
const confirmedLayer = await sliceAt(record, 'confirmed', replica.queue, [...paths, ...paths.flatMap(ancestors), ...referring], moved);
|
|
1890
1886
|
const { visible } = await replayed(record, confirmedLayer, replica.queue);
|
|
1891
1887
|
const composition = composed(record);
|
|
@@ -1978,7 +1974,7 @@ export function composeRuntime(options) {
|
|
|
1978
1974
|
}
|
|
1979
1975
|
function referrersOf(local, key, view) {
|
|
1980
1976
|
return async (targets) => [
|
|
1981
|
-
...new Set((await local.referrers(key, view, targets
|
|
1977
|
+
...new Set((await local.referrers(key, view, targets)).map(({ path }) => path)),
|
|
1982
1978
|
];
|
|
1983
1979
|
}
|
|
1984
1980
|
async function sideOf(locator, replica, room, saving) {
|
|
@@ -2050,7 +2046,7 @@ export function composeRuntime(options) {
|
|
|
2050
2046
|
const target = replicas.get(destination);
|
|
2051
2047
|
const listedRoom = rooms.get(destination);
|
|
2052
2048
|
if (target === undefined || listedRoom === undefined) {
|
|
2053
|
-
throw runtimeError('room
|
|
2049
|
+
throw runtimeError('unknown-room', `Unknown Room ${destination}`);
|
|
2054
2050
|
}
|
|
2055
2051
|
const arriving = await savingIn(target, [], moves.map(({ to }) => to));
|
|
2056
2052
|
const built = await buildMove({
|
|
@@ -2113,10 +2109,10 @@ export function composeRuntime(options) {
|
|
|
2113
2109
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2114
2110
|
const { answer, touched } = committedBoth;
|
|
2115
2111
|
if (answer.fenced !== null) {
|
|
2116
|
-
throw runtimeError('room
|
|
2112
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2117
2113
|
}
|
|
2118
2114
|
if (fences.has(locator) || fences.has(destination)) {
|
|
2119
|
-
throw runtimeError('room
|
|
2115
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2120
2116
|
}
|
|
2121
2117
|
pendings.set(pendingKey(locator, id), {
|
|
2122
2118
|
drafts: saved,
|
|
@@ -2287,10 +2283,10 @@ export function composeRuntime(options) {
|
|
|
2287
2283
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2288
2284
|
const { answer, touched } = done;
|
|
2289
2285
|
if (answer.fenced !== null) {
|
|
2290
|
-
throw runtimeError('room
|
|
2286
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2291
2287
|
}
|
|
2292
2288
|
if (fences.has(locator))
|
|
2293
|
-
throw runtimeError('room
|
|
2289
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2294
2290
|
for (const origin of withdrawn.keys())
|
|
2295
2291
|
pendings.delete(pendingKey(locator, origin));
|
|
2296
2292
|
for (const [id, drafts] of drafted) {
|
|
@@ -2339,7 +2335,7 @@ export function composeRuntime(options) {
|
|
|
2339
2335
|
throw new Error('connection refused');
|
|
2340
2336
|
await local.keepUpload(locator, identifier, upload);
|
|
2341
2337
|
if (fences.has(locator))
|
|
2342
|
-
throw runtimeError('room
|
|
2338
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2343
2339
|
const key = pendingKey(locator, identifier);
|
|
2344
2340
|
pendings.set(key, { ...pendings.get(key), upload });
|
|
2345
2341
|
if (!continuing())
|
|
@@ -2664,6 +2660,24 @@ export function composeRuntime(options) {
|
|
|
2664
2660
|
return UNCERTAIN;
|
|
2665
2661
|
}
|
|
2666
2662
|
}
|
|
2663
|
+
async function readConversationHistory(request) {
|
|
2664
|
+
await ready;
|
|
2665
|
+
const stream = options.adapter.conversations;
|
|
2666
|
+
if (closed || refusal !== null || stream?.history === undefined)
|
|
2667
|
+
return UNCERTAIN;
|
|
2668
|
+
journal.record('transfer conversation history');
|
|
2669
|
+
try {
|
|
2670
|
+
const result = await stream.history(request);
|
|
2671
|
+
if (result.kind === 'revoked-authorization')
|
|
2672
|
+
await refuse('revoked-authorization');
|
|
2673
|
+
journal.record(`outcome conversation history ${result.kind}`);
|
|
2674
|
+
return calledResult(result);
|
|
2675
|
+
}
|
|
2676
|
+
catch {
|
|
2677
|
+
journal.record('outcome conversation history transport-uncertainty');
|
|
2678
|
+
return UNCERTAIN;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2667
2681
|
function unsettled(result) {
|
|
2668
2682
|
return result.kind !== 'action' && result.retryable;
|
|
2669
2683
|
}
|
|
@@ -2702,10 +2716,6 @@ export function composeRuntime(options) {
|
|
|
2702
2716
|
continue;
|
|
2703
2717
|
if (closed || signal.aborted)
|
|
2704
2718
|
return 'done';
|
|
2705
|
-
if (pastHorizon(held.operation)) {
|
|
2706
|
-
await local.dropCall(held.operation);
|
|
2707
|
-
continue;
|
|
2708
|
-
}
|
|
2709
2719
|
calling.add(held.operation);
|
|
2710
2720
|
try {
|
|
2711
2721
|
if (unsettled(await invokeAction(held.name, held.input)))
|
|
@@ -2725,6 +2735,33 @@ export function composeRuntime(options) {
|
|
|
2725
2735
|
const held = replicas.get(locator);
|
|
2726
2736
|
return (held !== undefined && options.adapter.reached(locator, held.record.roomVersion, roomVersion));
|
|
2727
2737
|
}
|
|
2738
|
+
function shownAt(locator) {
|
|
2739
|
+
const held = replicas.get(locator)?.record.roomVersion;
|
|
2740
|
+
const raised = highest.get(locator);
|
|
2741
|
+
if (held === undefined || raised === undefined)
|
|
2742
|
+
return held ?? raised;
|
|
2743
|
+
return options.adapter.reached(locator, held, raised) ? held : raised;
|
|
2744
|
+
}
|
|
2745
|
+
function showing(locator, roomVersion) {
|
|
2746
|
+
const at = shownAt(locator);
|
|
2747
|
+
if (at === undefined || !options.adapter.reached(locator, at, roomVersion)) {
|
|
2748
|
+
highest.set(locator, roomVersion);
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
function older(locator, roomVersion) {
|
|
2752
|
+
if (beyondHorizon.has(locator))
|
|
2753
|
+
return false;
|
|
2754
|
+
const at = shownAt(locator);
|
|
2755
|
+
return (at !== undefined &&
|
|
2756
|
+
options.adapter.reached(locator, at, roomVersion) &&
|
|
2757
|
+
!options.adapter.reached(locator, roomVersion, at));
|
|
2758
|
+
}
|
|
2759
|
+
function replaced(locators) {
|
|
2760
|
+
for (const locator of locators) {
|
|
2761
|
+
highest.delete(locator);
|
|
2762
|
+
beyondHorizon.add(locator);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2728
2765
|
async function writtenAfter(locator, roomVersion, path) {
|
|
2729
2766
|
const held = replicas.get(locator);
|
|
2730
2767
|
if (held === undefined)
|
|
@@ -2735,14 +2772,25 @@ export function composeRuntime(options) {
|
|
|
2735
2772
|
options.adapter.reached(locator, written, roomVersion) &&
|
|
2736
2773
|
!options.adapter.reached(locator, roomVersion, written));
|
|
2737
2774
|
}
|
|
2738
|
-
function
|
|
2739
|
-
|
|
2775
|
+
async function writtenLater(local, locator, key, paths, roomVersion) {
|
|
2776
|
+
if (paths.length === 0 || !older(locator, roomVersion))
|
|
2777
|
+
return new Set();
|
|
2778
|
+
const entries = await local.entries(key, 'confirmed', paths);
|
|
2779
|
+
return new Set(entries.flatMap(({ path, written }) => written !== null &&
|
|
2780
|
+
options.adapter.reached(locator, written, roomVersion) &&
|
|
2781
|
+
!options.adapter.reached(locator, roomVersion, written)
|
|
2782
|
+
? [path]
|
|
2783
|
+
: []));
|
|
2784
|
+
}
|
|
2785
|
+
function refreshingOf(local, locator, key, composition, liveness, replaying = false) {
|
|
2786
|
+
const write = replaying ? finishingWrite : exclusive;
|
|
2740
2787
|
return {
|
|
2741
2788
|
turn() {
|
|
2742
|
-
if (replaying
|
|
2743
|
-
installing(
|
|
2789
|
+
if (replaying)
|
|
2790
|
+
installing(locator);
|
|
2744
2791
|
},
|
|
2745
2792
|
prior: async (paths) => new Map((await local.entries(key, 'refreshed', paths)).map((entry) => [entry.path, entry])),
|
|
2793
|
+
later: (paths, roomVersion) => writtenLater(local, locator, key, paths, roomVersion),
|
|
2746
2794
|
async stage(page) {
|
|
2747
2795
|
for (let written = 0;;) {
|
|
2748
2796
|
const end = batchEnd(page.entries, written);
|
|
@@ -2932,7 +2980,7 @@ export function composeRuntime(options) {
|
|
|
2932
2980
|
finally {
|
|
2933
2981
|
hold.take();
|
|
2934
2982
|
}
|
|
2935
|
-
}, authorized, record, (models, kept) => (liveness() ? remodel(record.locator, models, kept) : kept), liveness, refreshingOf(local, record.key, composed(record), liveness));
|
|
2983
|
+
}, authorized, record, (models, kept) => (liveness() ? remodel(record.locator, models, kept) : kept), liveness, refreshingOf(local, record.locator, record.key, composed(record), liveness));
|
|
2936
2984
|
if (result.kind !== 'advanced') {
|
|
2937
2985
|
journal.record(`transfer refresh ${record.locator} ${result.kind}`);
|
|
2938
2986
|
return result;
|
|
@@ -3059,7 +3107,7 @@ export function composeRuntime(options) {
|
|
|
3059
3107
|
derive: derived,
|
|
3060
3108
|
async refreshing(locator, key, composition) {
|
|
3061
3109
|
await exclusive(() => local.unrefresh(key));
|
|
3062
|
-
return refreshingOf(local, key, composition, liveness,
|
|
3110
|
+
return refreshingOf(local, locator, key, composition, liveness, true);
|
|
3063
3111
|
},
|
|
3064
3112
|
abandon: (locator) => exclusive(async () => {
|
|
3065
3113
|
if (liveness())
|
|
@@ -3093,9 +3141,16 @@ export function composeRuntime(options) {
|
|
|
3093
3141
|
liveQueries.recover();
|
|
3094
3142
|
}
|
|
3095
3143
|
try {
|
|
3096
|
-
const result = await bootstrapRoom(reads, authorized, room, { collection: modelOf(room.locator) }, builtIn, (models, held) => (liveness() ? remodel(room.locator, models, held) : held), liveness, staging(local, liveness), reusable(room, local));
|
|
3144
|
+
const result = await bootstrapRoom(reads, authorized, room, { collection: modelOf(room.locator) }, builtIn, typedPaths, (models, held) => (liveness() ? remodel(room.locator, models, held) : held), liveness, staging(local, liveness), reusable(room, local));
|
|
3097
3145
|
const detail = result.kind === 'staged' ? result.record.roomVersion : result.kind;
|
|
3098
3146
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
3147
|
+
if (result.kind === 'staged' && older(room.locator, result.record.roomVersion)) {
|
|
3148
|
+
caughtUpTo(room.locator);
|
|
3149
|
+
installed(room.locator);
|
|
3150
|
+
await abandoned(local, room.locator);
|
|
3151
|
+
journal.record(`transfer bootstrap ${room.locator} older than shown`);
|
|
3152
|
+
return { kind: 'transport-uncertainty' };
|
|
3153
|
+
}
|
|
3099
3154
|
if (result.kind !== 'staged') {
|
|
3100
3155
|
caughtUpTo(room.locator);
|
|
3101
3156
|
installed(room.locator);
|
|
@@ -3146,6 +3201,7 @@ export function composeRuntime(options) {
|
|
|
3146
3201
|
return refreshed;
|
|
3147
3202
|
if (reloadsDue.has(room.locator))
|
|
3148
3203
|
return unconfirmed;
|
|
3204
|
+
replaced([room.locator]);
|
|
3149
3205
|
}
|
|
3150
3206
|
const from = standingOf(room.locator);
|
|
3151
3207
|
const bootstrapped = await pullBootstrap(authorized, room, local, live);
|
|
@@ -3190,7 +3246,7 @@ export function composeRuntime(options) {
|
|
|
3190
3246
|
return refreshed.kind === 'beyond-horizon' ? 'beyond-horizon' : refreshed;
|
|
3191
3247
|
}
|
|
3192
3248
|
async function withhold(withheld, room, local, progress) {
|
|
3193
|
-
if (withheld.kind === 'unconfirmed')
|
|
3249
|
+
if (withheld.kind === 'unconfirmed' || isFenced(room.locator))
|
|
3194
3250
|
return;
|
|
3195
3251
|
stale.add(room.locator);
|
|
3196
3252
|
if (withheld.kind === 'transport-uncertainty' || withheld.kind === 'retryable-refusal') {
|
|
@@ -3219,8 +3275,6 @@ export function composeRuntime(options) {
|
|
|
3219
3275
|
}
|
|
3220
3276
|
}
|
|
3221
3277
|
function leaving(batch, result) {
|
|
3222
|
-
if (result.kind === 'expired')
|
|
3223
|
-
return { batch, returned: returnedBy('expired') };
|
|
3224
3278
|
return {
|
|
3225
3279
|
batch,
|
|
3226
3280
|
returned: {
|
|
@@ -3253,10 +3307,11 @@ export function composeRuntime(options) {
|
|
|
3253
3307
|
logAccepted(room.locator, submitted, accepted);
|
|
3254
3308
|
let record = current.record;
|
|
3255
3309
|
let changes;
|
|
3256
|
-
if (!options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
3310
|
+
if (!options.adapter.reached(room.locator, held, accepted.roomVersion) &&
|
|
3311
|
+
!older(room.locator, accepted.roomVersion)) {
|
|
3257
3312
|
const paths = [...returnedPaths(submitted, accepted)];
|
|
3258
3313
|
const confirmedLayer = await stepped(slicing(await local.entries(record.key, 'confirmed', paths), paths));
|
|
3259
|
-
const back = await stepped(returning(confirmedLayer, submitted, accepted, accepted.roomVersion));
|
|
3314
|
+
const back = await stepped(returning(confirmedLayer, submitted, accepted, accepted.roomVersion, composed(record)));
|
|
3260
3315
|
record = {
|
|
3261
3316
|
...record,
|
|
3262
3317
|
roomVersion: options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
@@ -3270,6 +3325,7 @@ export function composeRuntime(options) {
|
|
|
3270
3325
|
...settled,
|
|
3271
3326
|
apply(answer) {
|
|
3272
3327
|
const set = settled.apply(answer);
|
|
3328
|
+
showing(room.locator, accepted.roomVersion);
|
|
3273
3329
|
pendings.delete(pendingKey(room.locator, submitted.id));
|
|
3274
3330
|
unreadable.delete(room.locator);
|
|
3275
3331
|
list(room, true);
|
|
@@ -3419,15 +3475,10 @@ export function composeRuntime(options) {
|
|
|
3419
3475
|
return;
|
|
3420
3476
|
moveTo({ ...status, ...attention() });
|
|
3421
3477
|
}
|
|
3422
|
-
async function discardHeld(head, room, local, signal) {
|
|
3423
|
-
const partner = partnerOf(head, room.locator);
|
|
3424
|
-
const other = partner === null ? undefined : discovered.get(partner);
|
|
3425
|
-
await discardPending(other === undefined ? [room] : [room, other], local, signal);
|
|
3426
|
-
}
|
|
3427
3478
|
async function submitRoom(room, local, authorized, progress, signal) {
|
|
3428
3479
|
while (continuing() && authorization !== undefined && !signal.aborted) {
|
|
3429
3480
|
const replica = replicas.get(room.locator);
|
|
3430
|
-
const head = replica?.queue
|
|
3481
|
+
const head = replica?.queue.find((batch) => !landedAhead.has(pendingKey(room.locator, batch.id)));
|
|
3431
3482
|
if (replica === undefined || head === undefined)
|
|
3432
3483
|
return 'done';
|
|
3433
3484
|
const state = pendings.get(pendingKey(room.locator, head.id));
|
|
@@ -3454,10 +3505,6 @@ export function composeRuntime(options) {
|
|
|
3454
3505
|
return 'done';
|
|
3455
3506
|
}
|
|
3456
3507
|
const lost = earlier || submission.state === 'submitted';
|
|
3457
|
-
if (lost && pastHorizon(head.id)) {
|
|
3458
|
-
await discardHeld(head, room, local, signal);
|
|
3459
|
-
return 'done';
|
|
3460
|
-
}
|
|
3461
3508
|
let result;
|
|
3462
3509
|
journal.record(`submission ${room.locator} ${head.id}`);
|
|
3463
3510
|
await exclusive(() => mark(local, room.locator, head, { state: 'submitted' }, lost));
|
|
@@ -3535,32 +3582,12 @@ export function composeRuntime(options) {
|
|
|
3535
3582
|
const other = partner === null ? undefined : discovered.get(partner);
|
|
3536
3583
|
if (result.kind === 'conflicted' ||
|
|
3537
3584
|
result.kind === 'rejected' ||
|
|
3538
|
-
result.kind === 'expired' ||
|
|
3539
3585
|
result.kind === 'retryable-refusal') {
|
|
3540
|
-
if (result.kind === 'expired' && lost && result.restored !== true) {
|
|
3541
|
-
journal.record(`outcome ${room.locator} ${head.id} expired`);
|
|
3542
|
-
await discardHeld(head, room, local, signal);
|
|
3543
|
-
return 'done';
|
|
3544
|
-
}
|
|
3545
|
-
if (result.kind === 'expired') {
|
|
3546
|
-
await exclusive(() => mark(local, room.locator, head, { state: 'waiting' }, partner !== null && lost));
|
|
3547
|
-
const there = other === undefined ? null : await advanced(other, local, authorized);
|
|
3548
|
-
if (other !== undefined && there !== null) {
|
|
3549
|
-
await withhold(there, other, local, progress);
|
|
3550
|
-
return 'done';
|
|
3551
|
-
}
|
|
3552
|
-
const withheld = await advanced(room, local, authorized);
|
|
3553
|
-
if (withheld !== null) {
|
|
3554
|
-
await withhold(withheld, room, local, progress);
|
|
3555
|
-
return 'done';
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
3586
|
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` +
|
|
3559
3587
|
(result.kind === 'conflicted' && result.roomVersion !== undefined
|
|
3560
3588
|
? ` ${result.roomVersion}`
|
|
3561
3589
|
: ''));
|
|
3562
|
-
|
|
3563
|
-
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
3590
|
+
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
3564
3591
|
}
|
|
3565
3592
|
if (result.kind === 'retryable-refusal') {
|
|
3566
3593
|
const reset = result.reset;
|
|
@@ -3568,7 +3595,7 @@ export function composeRuntime(options) {
|
|
|
3568
3595
|
await exclusive(() => mark(local, room.locator, head, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
3569
3596
|
return reset === undefined ? 'again' : { at: Date.parse(reset) };
|
|
3570
3597
|
}
|
|
3571
|
-
if (result.kind === 'conflicted' || result.kind === 'rejected'
|
|
3598
|
+
if (result.kind === 'conflicted' || result.kind === 'rejected') {
|
|
3572
3599
|
const left = leaving(head, result);
|
|
3573
3600
|
const refreshed = async () => ({
|
|
3574
3601
|
there: other === undefined ? null : await advanced(other, local, authorized),
|
|
@@ -3592,16 +3619,19 @@ export function composeRuntime(options) {
|
|
|
3592
3619
|
return;
|
|
3593
3620
|
}
|
|
3594
3621
|
const confirmedLayer = await sliceAt(current.record, 'confirmed', [head]);
|
|
3595
|
-
const held =
|
|
3596
|
-
(await landedWhole(room.locator, head, confirmedLayer))
|
|
3597
|
-
|
|
3598
|
-
|
|
3622
|
+
const held = result.kind === 'conflicted' &&
|
|
3623
|
+
(await landedWhole(room.locator, head, confirmedLayer, result.affected));
|
|
3624
|
+
if (held &&
|
|
3625
|
+
result.kind === 'conflicted' &&
|
|
3626
|
+
partner === null &&
|
|
3627
|
+
!landed(head, confirmedLayer)) {
|
|
3628
|
+
landedAhead.set(pendingKey(room.locator, head.id), Object.fromEntries([...touchedBy([head])].map((path) => [path, confirmedLayer.revisions[path]])));
|
|
3629
|
+
return;
|
|
3630
|
+
}
|
|
3599
3631
|
publishChangeSet(await settle(local, room.locator, current, current.record, current.queue.filter((batch) => batch.id !== head.id), { leaving: [held ? { ...left, returned: null } : left] }));
|
|
3600
3632
|
await unpair(local);
|
|
3601
3633
|
moveTo({ ...status, ...attention() });
|
|
3602
3634
|
});
|
|
3603
|
-
if (result.kind === 'expired')
|
|
3604
|
-
continue;
|
|
3605
3635
|
const { there, here } = judged ?? (await refreshed());
|
|
3606
3636
|
if (other !== undefined && there !== null)
|
|
3607
3637
|
await withhold(there, other, local, progress);
|
|
@@ -3621,67 +3651,6 @@ export function composeRuntime(options) {
|
|
|
3621
3651
|
}
|
|
3622
3652
|
return 'done';
|
|
3623
3653
|
}
|
|
3624
|
-
function pastHorizon(identity) {
|
|
3625
|
-
return (outcomeRetention !== null && identityTime(identity) + outcomeRetention * 1000 <= Date.now());
|
|
3626
|
-
}
|
|
3627
|
-
async function discardPending(rooms, local, signal) {
|
|
3628
|
-
const discarded = await exclusive(async () => {
|
|
3629
|
-
const held = rooms
|
|
3630
|
-
.filter(({ locator }) => !fences.has(locator))
|
|
3631
|
-
.map((room) => ({
|
|
3632
|
-
room,
|
|
3633
|
-
before: replicas.get(room.locator),
|
|
3634
|
-
queue: queueIn(room.locator),
|
|
3635
|
-
}));
|
|
3636
|
-
if (held.length === 0)
|
|
3637
|
-
return [];
|
|
3638
|
-
const answer = await local.commit({
|
|
3639
|
-
rooms: held.flatMap(({ room, before }) => before === undefined
|
|
3640
|
-
? []
|
|
3641
|
-
: [
|
|
3642
|
-
{
|
|
3643
|
-
locator: room.locator,
|
|
3644
|
-
key: before.record.key,
|
|
3645
|
-
roots: before.record.typedSelection?.roots ?? [],
|
|
3646
|
-
composed: composedOf(composed(before.record)),
|
|
3647
|
-
overlay: { set: [], cleared: [...before.overlaid] },
|
|
3648
|
-
},
|
|
3649
|
-
]),
|
|
3650
|
-
discards: held.map(({ room }) => room.locator),
|
|
3651
|
-
});
|
|
3652
|
-
for (const { room, before, queue } of held) {
|
|
3653
|
-
const { locator } = room;
|
|
3654
|
-
lastFenced.set(locator, ++fencings);
|
|
3655
|
-
for (const batch of queue)
|
|
3656
|
-
pendings.delete(pendingKey(locator, batch.id));
|
|
3657
|
-
queuesWithoutReplica.delete(locator);
|
|
3658
|
-
if (before !== undefined) {
|
|
3659
|
-
replicas.set(locator, {
|
|
3660
|
-
...before,
|
|
3661
|
-
queue: [],
|
|
3662
|
-
overlaid: new Set(),
|
|
3663
|
-
count: answer.counts[locator] ?? before.count,
|
|
3664
|
-
});
|
|
3665
|
-
}
|
|
3666
|
-
journal.record(`room discarded ${locator}`);
|
|
3667
|
-
}
|
|
3668
|
-
publishChangeSet(mergeChangeSets(held.map(({ room }) => changeSet(room.locator, {
|
|
3669
|
-
documents: answer.changed[room.locator] ?? [],
|
|
3670
|
-
queue: true,
|
|
3671
|
-
}))));
|
|
3672
|
-
moveTo({ ...status, ...attention() });
|
|
3673
|
-
return held.map(({ room }) => room);
|
|
3674
|
-
});
|
|
3675
|
-
for (const room of discarded) {
|
|
3676
|
-
if (signal.aborted)
|
|
3677
|
-
return;
|
|
3678
|
-
const progress = { complete: true, reachable: true };
|
|
3679
|
-
await roomTask(room.locator, (live) => reload(room, local, progress, live));
|
|
3680
|
-
if (signal.aborted)
|
|
3681
|
-
return;
|
|
3682
|
-
await rest(progress);
|
|
3683
|
-
}
|
|
3684
|
-
}
|
|
3685
3654
|
function awaitingAnswer(locator) {
|
|
3686
3655
|
answering.get(locator)?.release();
|
|
3687
3656
|
let release = () => { };
|
|
@@ -3703,6 +3672,30 @@ export function composeRuntime(options) {
|
|
|
3703
3672
|
JSON.stringify(held.replica) === JSON.stringify(room.replica) &&
|
|
3704
3673
|
JSON.stringify(held.typedSelection) === JSON.stringify(room.typedSelection ?? null)));
|
|
3705
3674
|
}
|
|
3675
|
+
async function keepNaming(local, room) {
|
|
3676
|
+
const held = replicas.get(room.locator)?.record;
|
|
3677
|
+
if (held === undefined || (held.name === room.name && held.handle === room.handle))
|
|
3678
|
+
return;
|
|
3679
|
+
await exclusive(async () => {
|
|
3680
|
+
if (!continuing())
|
|
3681
|
+
return;
|
|
3682
|
+
const replica = replicas.get(room.locator);
|
|
3683
|
+
if (replica === undefined)
|
|
3684
|
+
return;
|
|
3685
|
+
const record = { ...replica.record, name: room.name, handle: room.handle };
|
|
3686
|
+
await local.commit({
|
|
3687
|
+
rooms: [
|
|
3688
|
+
{
|
|
3689
|
+
locator: room.locator,
|
|
3690
|
+
key: record.key,
|
|
3691
|
+
record: storedRecord(record),
|
|
3692
|
+
composed: composedOf(composed(record)),
|
|
3693
|
+
},
|
|
3694
|
+
],
|
|
3695
|
+
});
|
|
3696
|
+
replicas.set(room.locator, { ...replica, record });
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3706
3699
|
async function retainModel(local, room, next) {
|
|
3707
3700
|
const key = room.locator;
|
|
3708
3701
|
const previous = modelOf(room.locator);
|
|
@@ -3720,6 +3713,8 @@ export function composeRuntime(options) {
|
|
|
3720
3713
|
if (held !== undefined) {
|
|
3721
3714
|
const record = {
|
|
3722
3715
|
...held.record,
|
|
3716
|
+
name: room.name,
|
|
3717
|
+
handle: room.handle,
|
|
3723
3718
|
operations: room.operations,
|
|
3724
3719
|
replica: room.replica,
|
|
3725
3720
|
typedSelection: room.typedSelection ?? null,
|
|
@@ -3822,7 +3817,6 @@ export function composeRuntime(options) {
|
|
|
3822
3817
|
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
3823
3818
|
handed = true;
|
|
3824
3819
|
}
|
|
3825
|
-
discovering?.release();
|
|
3826
3820
|
for (const room of ready) {
|
|
3827
3821
|
if (attempt !== 'unstreamed' && replicas.has(room.locator)) {
|
|
3828
3822
|
if (attempt === 'authorized')
|
|
@@ -3850,6 +3844,8 @@ export function composeRuntime(options) {
|
|
|
3850
3844
|
}
|
|
3851
3845
|
if (prior !== undefined)
|
|
3852
3846
|
await retainModel(local, room, modelOf(room.locator));
|
|
3847
|
+
if (prior !== undefined)
|
|
3848
|
+
await keepNaming(local, room);
|
|
3853
3849
|
if (discoveryPaused || authorization === undefined)
|
|
3854
3850
|
return false;
|
|
3855
3851
|
return room.replica.admission === 'admitted';
|
|
@@ -3903,7 +3899,39 @@ export function composeRuntime(options) {
|
|
|
3903
3899
|
caughtUpTo(room.locator);
|
|
3904
3900
|
}
|
|
3905
3901
|
}
|
|
3906
|
-
async function
|
|
3902
|
+
async function asked(authorized) {
|
|
3903
|
+
try {
|
|
3904
|
+
return (await options.adapter.discover?.(authorized)) ?? { kind: 'transport-uncertainty' };
|
|
3905
|
+
}
|
|
3906
|
+
catch {
|
|
3907
|
+
return { kind: 'transport-uncertainty' };
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
function malformed(listed) {
|
|
3911
|
+
for (const room of listed) {
|
|
3912
|
+
if (room.operations === undefined ||
|
|
3913
|
+
!['write', 'delete'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
3914
|
+
room.operations[operation].every((scope) => typeof scope === 'string')) ||
|
|
3915
|
+
room.replica === undefined ||
|
|
3916
|
+
typeof room.replica.selection !== 'string' ||
|
|
3917
|
+
room.replica.selection.length === 0 ||
|
|
3918
|
+
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
3919
|
+
!['locked', 'open'].includes(room.replica.offline)) {
|
|
3920
|
+
return `invalid model or replica declaration ${room.locator}`;
|
|
3921
|
+
}
|
|
3922
|
+
if (!declaredSelection(room))
|
|
3923
|
+
return `invalid typed selection declaration ${room.locator}`;
|
|
3924
|
+
}
|
|
3925
|
+
return null;
|
|
3926
|
+
}
|
|
3927
|
+
async function signIn(local, signedIn) {
|
|
3928
|
+
if (JSON.stringify(signedIn) === JSON.stringify(member))
|
|
3929
|
+
return;
|
|
3930
|
+
await local.fact('member', signedIn);
|
|
3931
|
+
member = signedIn;
|
|
3932
|
+
emitMember();
|
|
3933
|
+
}
|
|
3934
|
+
async function discoverRooms(local, progress, attempt, live, found) {
|
|
3907
3935
|
if (closed || authorization === undefined) {
|
|
3908
3936
|
notifyReady();
|
|
3909
3937
|
return;
|
|
@@ -3911,13 +3939,7 @@ export function composeRuntime(options) {
|
|
|
3911
3939
|
moveTo(transferring(status));
|
|
3912
3940
|
discoveries += 1;
|
|
3913
3941
|
const started = discoveries;
|
|
3914
|
-
|
|
3915
|
-
try {
|
|
3916
|
-
result = await options.adapter.discover(authorization, scope);
|
|
3917
|
-
}
|
|
3918
|
-
catch {
|
|
3919
|
-
result = { kind: 'transport-uncertainty' };
|
|
3920
|
-
}
|
|
3942
|
+
const result = found ?? (await asked(authorization));
|
|
3921
3943
|
if (result.kind === 'discovery')
|
|
3922
3944
|
roomsNamed = true;
|
|
3923
3945
|
if (!live()) {
|
|
@@ -3927,7 +3949,7 @@ export function composeRuntime(options) {
|
|
|
3927
3949
|
}
|
|
3928
3950
|
journal.record(`connection discover ${result.kind}`);
|
|
3929
3951
|
if (result.kind === 'transport-uncertainty') {
|
|
3930
|
-
await
|
|
3952
|
+
await cachedModels(local);
|
|
3931
3953
|
progress.reachable = false;
|
|
3932
3954
|
progress.complete = false;
|
|
3933
3955
|
notifyReady();
|
|
@@ -3948,26 +3970,12 @@ export function composeRuntime(options) {
|
|
|
3948
3970
|
notifyReady();
|
|
3949
3971
|
return;
|
|
3950
3972
|
}
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
room.replica.selection.length === 0 ||
|
|
3958
|
-
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
3959
|
-
!['locked', 'open'].includes(room.replica.offline)) {
|
|
3960
|
-
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
3961
|
-
await refuse('missing-required-guarantee');
|
|
3962
|
-
notifyReady();
|
|
3963
|
-
return;
|
|
3964
|
-
}
|
|
3965
|
-
if (!declaredSelection(room)) {
|
|
3966
|
-
journal.record(`invalid typed selection declaration ${room.locator}`);
|
|
3967
|
-
await refuse('missing-required-guarantee');
|
|
3968
|
-
notifyReady();
|
|
3969
|
-
return;
|
|
3970
|
-
}
|
|
3973
|
+
const invalid = malformed(result.rooms);
|
|
3974
|
+
if (invalid !== null) {
|
|
3975
|
+
journal.record(invalid);
|
|
3976
|
+
await refuse('missing-required-guarantee');
|
|
3977
|
+
notifyReady();
|
|
3978
|
+
return;
|
|
3971
3979
|
}
|
|
3972
3980
|
if (refusal !== null) {
|
|
3973
3981
|
refusal = null;
|
|
@@ -3975,7 +3983,6 @@ export function composeRuntime(options) {
|
|
|
3975
3983
|
}
|
|
3976
3984
|
const previousCapabilities = capabilities;
|
|
3977
3985
|
capabilities = result.capabilities;
|
|
3978
|
-
outcomeRetention = result.retention.outcome;
|
|
3979
3986
|
notifyReady();
|
|
3980
3987
|
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
3981
3988
|
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)) {
|
|
@@ -3983,13 +3990,8 @@ export function composeRuntime(options) {
|
|
|
3983
3990
|
await local.fact('providerLimits', limits);
|
|
3984
3991
|
await local.fact('capabilities', capabilities);
|
|
3985
3992
|
}
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
await local.fact('member', signedIn);
|
|
3989
|
-
member = signedIn;
|
|
3990
|
-
emitMember();
|
|
3991
|
-
}
|
|
3992
|
-
await synchronize(await applyDiscovery(result.rooms, started, result.covered), local, progress, attempt, live);
|
|
3993
|
+
await signIn(local, result.member ?? null);
|
|
3994
|
+
await synchronize(await applyDiscovery(result.rooms, started), local, progress, attempt, live);
|
|
3993
3995
|
}
|
|
3994
3996
|
async function ensureAuthorization() {
|
|
3995
3997
|
if (authorization !== undefined)
|
|
@@ -4058,14 +4060,13 @@ export function composeRuntime(options) {
|
|
|
4058
4060
|
void run.finally(() => working.delete(run));
|
|
4059
4061
|
return run;
|
|
4060
4062
|
}
|
|
4061
|
-
function sync(attempt) {
|
|
4063
|
+
function sync(attempt, found) {
|
|
4062
4064
|
return task(OPENING_LANE, async (live) => {
|
|
4063
4065
|
if (!live()) {
|
|
4064
4066
|
readyFromStore();
|
|
4065
4067
|
return;
|
|
4066
4068
|
}
|
|
4067
4069
|
const progress = { complete: true, reachable: true };
|
|
4068
|
-
const gate = discover();
|
|
4069
4070
|
underway = attempt === 'opened' ? progress : null;
|
|
4070
4071
|
if (attempt === 'opened')
|
|
4071
4072
|
streamed = progress;
|
|
@@ -4079,7 +4080,7 @@ export function composeRuntime(options) {
|
|
|
4079
4080
|
await ensureAuthorization();
|
|
4080
4081
|
if (!live())
|
|
4081
4082
|
return;
|
|
4082
|
-
await discoverRooms(local, progress, attempt, live);
|
|
4083
|
+
await discoverRooms(local, progress, attempt, live, found);
|
|
4083
4084
|
}
|
|
4084
4085
|
catch {
|
|
4085
4086
|
progress.complete = false;
|
|
@@ -4087,7 +4088,6 @@ export function composeRuntime(options) {
|
|
|
4087
4088
|
progress.reachable = false;
|
|
4088
4089
|
}
|
|
4089
4090
|
finally {
|
|
4090
|
-
gate.release();
|
|
4091
4091
|
notifyReady();
|
|
4092
4092
|
}
|
|
4093
4093
|
if (!live())
|
|
@@ -4099,34 +4099,59 @@ export function composeRuntime(options) {
|
|
|
4099
4099
|
if (handed || closed || progress.reachable)
|
|
4100
4100
|
return;
|
|
4101
4101
|
underway = null;
|
|
4102
|
-
journal.record('connection reopen');
|
|
4103
4102
|
await rest(progress, live);
|
|
4104
|
-
options.adapter.roomStream?.reopen();
|
|
4105
4103
|
});
|
|
4106
4104
|
}
|
|
4107
|
-
function
|
|
4105
|
+
function resynchronize() {
|
|
4108
4106
|
return task(OPENING_LANE, async (live) => {
|
|
4109
|
-
if (!live() || !
|
|
4107
|
+
if (!live() || !announced)
|
|
4110
4108
|
return;
|
|
4111
4109
|
const progress = { complete: true, reachable: true };
|
|
4112
|
-
const gate = discover();
|
|
4113
4110
|
moveTo(transferring(status));
|
|
4114
4111
|
try {
|
|
4115
4112
|
const local = await openLocal();
|
|
4116
4113
|
if (!live())
|
|
4117
4114
|
return;
|
|
4118
|
-
await
|
|
4115
|
+
await synchronize([...discovered.values()], local, progress, 'unstreamed', live);
|
|
4116
|
+
}
|
|
4117
|
+
catch {
|
|
4118
|
+
progress.complete = false;
|
|
4119
|
+
}
|
|
4120
|
+
if (live())
|
|
4121
|
+
await finishOpening(progress, live);
|
|
4122
|
+
});
|
|
4123
|
+
}
|
|
4124
|
+
function countAuthorityChange() {
|
|
4125
|
+
authorityChanges += 1;
|
|
4126
|
+
for (const listener of authorityChangeListeners)
|
|
4127
|
+
listener(authorityChanges);
|
|
4128
|
+
}
|
|
4129
|
+
function changeAuthority(change) {
|
|
4130
|
+
return task(OPENING_LANE, async (live) => {
|
|
4131
|
+
if (!live() || !announced)
|
|
4132
|
+
return;
|
|
4133
|
+
countAuthorityChange();
|
|
4134
|
+
const progress = { complete: true, reachable: true };
|
|
4135
|
+
moveTo(transferring(status));
|
|
4136
|
+
try {
|
|
4137
|
+
const local = await openLocal();
|
|
4119
4138
|
if (!live())
|
|
4120
4139
|
return;
|
|
4121
|
-
|
|
4140
|
+
const invalid = malformed(change.rooms);
|
|
4141
|
+
if (invalid !== null) {
|
|
4142
|
+
journal.record(invalid);
|
|
4143
|
+
await refuse('missing-required-guarantee');
|
|
4144
|
+
return;
|
|
4145
|
+
}
|
|
4146
|
+
if (change.member !== undefined)
|
|
4147
|
+
await signIn(local, change.member);
|
|
4148
|
+
discoveries += 1;
|
|
4149
|
+
const listed = await applyDiscovery(change.rooms, discoveries, change.left);
|
|
4150
|
+
await synchronize(listed, local, progress, 'authorized', live);
|
|
4122
4151
|
}
|
|
4123
4152
|
catch {
|
|
4124
4153
|
progress.complete = false;
|
|
4125
4154
|
}
|
|
4126
|
-
finally {
|
|
4127
|
-
gate.release();
|
|
4128
|
-
notifyReady();
|
|
4129
|
-
}
|
|
4130
4155
|
if (!live())
|
|
4131
4156
|
return;
|
|
4132
4157
|
if (!progress.complete)
|
|
@@ -4134,10 +4159,12 @@ export function composeRuntime(options) {
|
|
|
4134
4159
|
await rest(progress, live);
|
|
4135
4160
|
});
|
|
4136
4161
|
}
|
|
4137
|
-
function closedSocket(revoked, lost) {
|
|
4138
|
-
|
|
4139
|
-
const opened =
|
|
4162
|
+
function closedSocket(revoked, lost, authorityChanged) {
|
|
4163
|
+
notifyReady();
|
|
4164
|
+
const opened = announced;
|
|
4140
4165
|
socketOpen = false;
|
|
4166
|
+
announced = false;
|
|
4167
|
+
missedChange ||= authorityChanged;
|
|
4141
4168
|
handed = false;
|
|
4142
4169
|
naming = 0;
|
|
4143
4170
|
connectionLost();
|
|
@@ -4153,14 +4180,29 @@ export function composeRuntime(options) {
|
|
|
4153
4180
|
moveTo({
|
|
4154
4181
|
...status,
|
|
4155
4182
|
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
4156
|
-
providerUnreachable: status.providerUnreachable || !opened,
|
|
4183
|
+
providerUnreachable: status.providerUnreachable || (!opened && lost),
|
|
4157
4184
|
});
|
|
4185
|
+
void closing(opened);
|
|
4158
4186
|
}
|
|
4159
|
-
function
|
|
4187
|
+
function closing(opened) {
|
|
4188
|
+
return task(OPENING_LANE, async (live) => {
|
|
4189
|
+
if (!live())
|
|
4190
|
+
return;
|
|
4191
|
+
const local = await openLocal();
|
|
4192
|
+
if (!opened && !socketOpen)
|
|
4193
|
+
await cachedModels(local);
|
|
4194
|
+
await rest(null, live);
|
|
4195
|
+
});
|
|
4196
|
+
}
|
|
4197
|
+
function cachedModels(local) {
|
|
4198
|
+
return settleModels([...rooms.values()].map((room) => discovered.get(room.locator) ?? room), 'cached', local);
|
|
4199
|
+
}
|
|
4200
|
+
function caughtUpRooms(expired) {
|
|
4160
4201
|
const progress = underway;
|
|
4161
4202
|
underway = null;
|
|
4162
4203
|
naming = Math.max(0, naming - 1);
|
|
4163
4204
|
const cycle = progress ?? { complete: true, reachable: true };
|
|
4205
|
+
replaced(expired);
|
|
4164
4206
|
const reloading = progress === null ? [] : owedReloads(expired);
|
|
4165
4207
|
const replacing = reloadNow((room) => expired.includes(room.locator) || reloading.includes(room.locator), cycle);
|
|
4166
4208
|
return task(OPENING_LANE, async (live) => {
|
|
@@ -4394,21 +4436,11 @@ export function composeRuntime(options) {
|
|
|
4394
4436
|
...attention(),
|
|
4395
4437
|
});
|
|
4396
4438
|
}
|
|
4397
|
-
function discover() {
|
|
4398
|
-
discovering?.release();
|
|
4399
|
-
let release = () => { };
|
|
4400
|
-
const done = new Promise((resolve) => {
|
|
4401
|
-
release = resolve;
|
|
4402
|
-
});
|
|
4403
|
-
discovering = { done, release };
|
|
4404
|
-
return discovering;
|
|
4405
|
-
}
|
|
4406
4439
|
function applyRoom(change) {
|
|
4407
4440
|
const { locator } = change;
|
|
4408
4441
|
if (stopping)
|
|
4409
4442
|
return Promise.resolve();
|
|
4410
4443
|
return inRoom(locator, async (live) => {
|
|
4411
|
-
await discovering?.done;
|
|
4412
4444
|
await answering.get(locator)?.answered;
|
|
4413
4445
|
const progress = underway ?? { complete: true, reachable: true };
|
|
4414
4446
|
try {
|
|
@@ -4466,20 +4498,30 @@ export function composeRuntime(options) {
|
|
|
4466
4498
|
return;
|
|
4467
4499
|
if (event.kind === 'opened') {
|
|
4468
4500
|
socketOpen = true;
|
|
4501
|
+
announced = false;
|
|
4469
4502
|
handed = false;
|
|
4470
4503
|
naming = 0;
|
|
4471
4504
|
connectionLive();
|
|
4472
4505
|
generation += 1;
|
|
4473
4506
|
conversations.reopened();
|
|
4474
4507
|
recovery.resume();
|
|
4475
|
-
|
|
4508
|
+
}
|
|
4509
|
+
else if (event.kind === 'discovered') {
|
|
4510
|
+
if (!socketOpen)
|
|
4511
|
+
return;
|
|
4512
|
+
announced = true;
|
|
4513
|
+
if (missedChange) {
|
|
4514
|
+
missedChange = false;
|
|
4515
|
+
countAuthorityChange();
|
|
4516
|
+
}
|
|
4517
|
+
void sync('opened', event.discovery);
|
|
4476
4518
|
}
|
|
4477
4519
|
else if (event.kind === 'closed')
|
|
4478
|
-
closedSocket(event.revoked, event.lost);
|
|
4479
|
-
else if (event.kind === '
|
|
4480
|
-
void
|
|
4520
|
+
closedSocket(event.revoked, event.lost, event.authorityChanged);
|
|
4521
|
+
else if (event.kind === 'caught-up')
|
|
4522
|
+
void caughtUpRooms(event.expired);
|
|
4481
4523
|
else if (event.kind === 'authority-changed')
|
|
4482
|
-
void
|
|
4524
|
+
void changeAuthority(event);
|
|
4483
4525
|
else
|
|
4484
4526
|
void applyRoom(event);
|
|
4485
4527
|
});
|
|
@@ -4572,6 +4614,7 @@ export function composeRuntime(options) {
|
|
|
4572
4614
|
statusListeners.clear();
|
|
4573
4615
|
serviceModeListeners.clear();
|
|
4574
4616
|
memberListeners.clear();
|
|
4617
|
+
authorityChangeListeners.clear();
|
|
4575
4618
|
liveQueries.close();
|
|
4576
4619
|
queueSubscriptions.clear();
|
|
4577
4620
|
roomDraftListeners.clear();
|
|
@@ -4598,15 +4641,15 @@ export function composeRuntime(options) {
|
|
|
4598
4641
|
readyFromStore();
|
|
4599
4642
|
});
|
|
4600
4643
|
if (options.adapter.roomStream === undefined)
|
|
4601
|
-
void sync('unstreamed');
|
|
4644
|
+
void sync('unstreamed', null);
|
|
4602
4645
|
function planned(input, reach) {
|
|
4603
|
-
const plan =
|
|
4646
|
+
const plan = planWithin(input, roomFacts(reach), planReach(reach));
|
|
4604
4647
|
const named = plan.paths !== null &&
|
|
4605
4648
|
plan.rooms !== null &&
|
|
4606
4649
|
plan.paths.length * plan.rooms.length <= MAXIMUM_WINDOW;
|
|
4607
4650
|
return plan.limit === null && named ? plan : { ...plan, limit: capped(plan.limit) };
|
|
4608
4651
|
}
|
|
4609
|
-
function watchPlan(plan, deliver) {
|
|
4652
|
+
function watchPlan(plan, deliver, fail) {
|
|
4610
4653
|
let seeded = false;
|
|
4611
4654
|
let pending = true;
|
|
4612
4655
|
const shown = () => {
|
|
@@ -4632,7 +4675,7 @@ export function composeRuntime(options) {
|
|
|
4632
4675
|
}
|
|
4633
4676
|
seeded = false;
|
|
4634
4677
|
deliver(message);
|
|
4635
|
-
});
|
|
4678
|
+
}, fail);
|
|
4636
4679
|
return () => {
|
|
4637
4680
|
watchers.delete(shown);
|
|
4638
4681
|
if (watchers.size === 0)
|
|
@@ -4640,61 +4683,59 @@ export function composeRuntime(options) {
|
|
|
4640
4683
|
stop();
|
|
4641
4684
|
};
|
|
4642
4685
|
}
|
|
4643
|
-
async function searched(input, reach
|
|
4686
|
+
async function searched(input, reach) {
|
|
4644
4687
|
const asked = searchPlanOf(input);
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
const cover = covered(filter);
|
|
4654
|
-
awaitPreparation(cover);
|
|
4655
|
-
const pending = [...unlocking, ...('pending' in cover ? cover.pending : [])];
|
|
4656
|
-
const complete = pending.length === 0 && !('pending' in cover);
|
|
4657
|
-
const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
|
|
4658
|
-
const answering = complete
|
|
4659
|
-
? filter
|
|
4660
|
-
: { ...filter, rooms: reachedRooms(filter).filter((room) => !awaited.has(room)) };
|
|
4661
|
-
const answered = covered(answering);
|
|
4662
|
-
if ('pending' in answered)
|
|
4663
|
-
continue;
|
|
4664
|
-
const listed = conversations.searchable();
|
|
4665
|
-
const named = reach !== null && !reach.conversations
|
|
4666
|
-
? []
|
|
4667
|
-
: (asked.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
|
|
4668
|
-
const hits = await (await openLocal()).search({
|
|
4669
|
-
text: asked.text,
|
|
4670
|
-
limit: capped(asked.limit),
|
|
4671
|
-
after: asked.after,
|
|
4672
|
-
total: asked.total,
|
|
4673
|
-
documents: {
|
|
4674
|
-
plan: answering,
|
|
4675
|
-
rooms: answered.rooms,
|
|
4676
|
-
beneath: asked.collection
|
|
4677
|
-
? answered.rooms.map(({ locator, key }) => ({
|
|
4678
|
-
key,
|
|
4679
|
-
roots: replicas.get(locator)?.record.typedSelection?.roots ?? [],
|
|
4680
|
-
}))
|
|
4681
|
-
: [],
|
|
4682
|
-
},
|
|
4683
|
-
conversations: named,
|
|
4684
|
-
});
|
|
4685
|
-
if (JSON.stringify(covered(answering)) !== JSON.stringify(answered))
|
|
4686
|
-
continue;
|
|
4687
|
-
const coverage = {
|
|
4688
|
-
rooms: answered.coverage.rooms,
|
|
4689
|
-
conversations: named,
|
|
4690
|
-
overlay: answered.coverage.overlay,
|
|
4688
|
+
await ready;
|
|
4689
|
+
const search = options.adapter.search;
|
|
4690
|
+
if (search === undefined) {
|
|
4691
|
+
return {
|
|
4692
|
+
kind: 'undeclared',
|
|
4693
|
+
code: 'action-undeclared',
|
|
4694
|
+
fields: { name: 'search' },
|
|
4695
|
+
retryable: false,
|
|
4691
4696
|
};
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4697
|
+
}
|
|
4698
|
+
if (closed || refusal !== null || connectionOf() !== 'connected')
|
|
4699
|
+
return OFFLINE;
|
|
4700
|
+
const reached = reach === null ? null : new Set(reach.rooms);
|
|
4701
|
+
const named = asked.rooms === null
|
|
4702
|
+
? reached === null
|
|
4703
|
+
? null
|
|
4704
|
+
: [...reached]
|
|
4705
|
+
: asked.rooms.filter((room) => reached === null || reached.has(room));
|
|
4706
|
+
const rooms = named === null ? null : named.filter((room) => replicas.has(room)).sort();
|
|
4707
|
+
if (rooms !== null && rooms.length === 0)
|
|
4708
|
+
return { kind: 'found', documents: [] };
|
|
4709
|
+
try {
|
|
4710
|
+
await ensureAuthorization();
|
|
4711
|
+
}
|
|
4712
|
+
catch {
|
|
4713
|
+
return UNCERTAIN;
|
|
4714
|
+
}
|
|
4715
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
4716
|
+
return UNCERTAIN;
|
|
4717
|
+
journal.record('transfer search');
|
|
4718
|
+
try {
|
|
4719
|
+
const result = await search(authorization, { text: asked.text, rooms });
|
|
4720
|
+
if (result.kind === 'revoked-authorization')
|
|
4721
|
+
await refuse('revoked-authorization');
|
|
4722
|
+
journal.record(`outcome search ${result.kind}`);
|
|
4723
|
+
if (result.kind === 'rejected')
|
|
4724
|
+
return result;
|
|
4725
|
+
if (result.kind !== 'found')
|
|
4726
|
+
return failure(result);
|
|
4727
|
+
const kept = rooms === null ? null : new Set(rooms);
|
|
4728
|
+
return {
|
|
4729
|
+
kind: 'found',
|
|
4730
|
+
documents: result.documents.filter((hit) => kept === null || kept.has(hit.room)),
|
|
4731
|
+
};
|
|
4732
|
+
}
|
|
4733
|
+
catch {
|
|
4734
|
+
journal.record('outcome search transport-uncertainty');
|
|
4735
|
+
return UNCERTAIN;
|
|
4695
4736
|
}
|
|
4696
4737
|
}
|
|
4697
|
-
const watch = (topic, input, deliver) => {
|
|
4738
|
+
const watch = (topic, input, deliver, fail) => {
|
|
4698
4739
|
if (topic === 'room-drafts') {
|
|
4699
4740
|
const listener = { notify: deliver, ready: false };
|
|
4700
4741
|
roomDraftListeners.add(listener);
|
|
@@ -4743,7 +4784,7 @@ export function composeRuntime(options) {
|
|
|
4743
4784
|
};
|
|
4744
4785
|
}
|
|
4745
4786
|
if (topic === 'readiness') {
|
|
4746
|
-
return watchReadiness({ target: '', code: 'stored'
|
|
4787
|
+
return watchReadiness({ target: '', code: 'stored' }, null, deliver);
|
|
4747
4788
|
}
|
|
4748
4789
|
if (topic === 'rooms') {
|
|
4749
4790
|
unlistedRooms.add(deliver);
|
|
@@ -4788,6 +4829,13 @@ export function composeRuntime(options) {
|
|
|
4788
4829
|
memberListeners.delete(deliver);
|
|
4789
4830
|
};
|
|
4790
4831
|
}
|
|
4832
|
+
if (topic === 'authority-changes') {
|
|
4833
|
+
authorityChangeListeners.add(deliver);
|
|
4834
|
+
deliver(authorityChanges);
|
|
4835
|
+
return () => {
|
|
4836
|
+
authorityChangeListeners.delete(deliver);
|
|
4837
|
+
};
|
|
4838
|
+
}
|
|
4791
4839
|
if (topic === 'queue') {
|
|
4792
4840
|
const queue = { locator: input[0], notify: deliver };
|
|
4793
4841
|
queueSubscriptions.add(queue);
|
|
@@ -4796,7 +4844,7 @@ export function composeRuntime(options) {
|
|
|
4796
4844
|
queueSubscriptions.delete(queue);
|
|
4797
4845
|
};
|
|
4798
4846
|
}
|
|
4799
|
-
return watchPlan(planned(input[0], null), deliver);
|
|
4847
|
+
return watchPlan(planned(input[0], null), deliver, fail);
|
|
4800
4848
|
};
|
|
4801
4849
|
const md = {
|
|
4802
4850
|
loaded() {
|
|
@@ -4816,7 +4864,7 @@ export function composeRuntime(options) {
|
|
|
4816
4864
|
});
|
|
4817
4865
|
},
|
|
4818
4866
|
query: ((input) => rowsQuery(planned(input, null))),
|
|
4819
|
-
search: (input) => searched(input, null
|
|
4867
|
+
search: (input) => searched(input, null),
|
|
4820
4868
|
types(locator) {
|
|
4821
4869
|
return {
|
|
4822
4870
|
collection: modelTypes(modelOf(locator)),
|
|
@@ -4919,6 +4967,8 @@ export function composeRuntime(options) {
|
|
|
4919
4967
|
return journal.export();
|
|
4920
4968
|
},
|
|
4921
4969
|
cleanUp,
|
|
4970
|
+
unsent: () => database.ask({ kind: 'authored.unsent' }),
|
|
4971
|
+
reset: () => options.shell.reset(),
|
|
4922
4972
|
persistence() {
|
|
4923
4973
|
return persistence === 'granted';
|
|
4924
4974
|
},
|
|
@@ -4941,7 +4991,7 @@ export function composeRuntime(options) {
|
|
|
4941
4991
|
authorization = undefined;
|
|
4942
4992
|
discoveryPaused = false;
|
|
4943
4993
|
if (options.adapter.roomStream === undefined)
|
|
4944
|
-
await sync('unstreamed');
|
|
4994
|
+
await sync('unstreamed', null);
|
|
4945
4995
|
else
|
|
4946
4996
|
await task(OPENING_LANE, ensureAuthorization);
|
|
4947
4997
|
},
|
|
@@ -4964,7 +5014,6 @@ export function composeRuntime(options) {
|
|
|
4964
5014
|
md,
|
|
4965
5015
|
seed,
|
|
4966
5016
|
stop,
|
|
4967
|
-
lose: loseRooms,
|
|
4968
5017
|
watch,
|
|
4969
5018
|
persisted(answer) {
|
|
4970
5019
|
if (answer === persistence)
|