@agentshouse/mdruntime 0.1.0-cab6762661a85 → 0.1.0-cac707d698ca2
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 +68 -52
- 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 +3 -3
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +15 -11
- 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 +613 -549
- 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 +15 -14
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +136 -164
- 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 +10 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +163 -82
- 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 +6 -4
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +28 -4
- 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/search.d.ts +12 -63
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +9 -52
- package/dist/store.d.ts +6 -15
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +11 -34
- package/dist/stored.d.ts +3 -4
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +66 -122
- package/dist/tables.d.ts +8 -23
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +22 -50
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +4 -1
- 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/reads.d.ts +0 -11
- package/dist/reads.d.ts.map +0 -1
- package/dist/reads.js +0 -91
- 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 {
|
|
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';
|
|
@@ -20,7 +20,6 @@ import { attachedFile } from './file.js';
|
|
|
20
20
|
import { openedFiles } from './opened.js';
|
|
21
21
|
import { ABSENT_TARGET, absentBinary, attachAnswer, binaryAnswer, binaryLinkAnswer, calledResult, failure, historyAnswer, LOCKED_UNREADABLE, lockedRoom, OFFLINE, undeclared, UNCERTAIN, UNREADABLE, } from './outcome.js';
|
|
22
22
|
import { createRecovery } from './recovery.js';
|
|
23
|
-
import { doorReads } from './reads.js';
|
|
24
23
|
import { advancement } from './refresh.js';
|
|
25
24
|
import { buildBatch, buildMove, joined, limiting, queuedDocuments, requeuing, } from './save.js';
|
|
26
25
|
import { compositionOf, entryAt, landed, movedBy, overlayOf, replaying, returning, returnedPaths, slicing, slicingWith, standing, storedRecord, touchedBy, } from './replica.js';
|
|
@@ -31,7 +30,7 @@ import { readinessOf, } from './readiness.js';
|
|
|
31
30
|
import { FencedMap } from './fence.js';
|
|
32
31
|
import { sameBinary } from './overlay.js';
|
|
33
32
|
import { paced, ROW_WEIGHT, stepped, yielded } from './turn.js';
|
|
34
|
-
import {
|
|
33
|
+
import { roomDraftRevision } from './identity.js';
|
|
35
34
|
import { ancestors, beneath, normalize } from './paths.js';
|
|
36
35
|
import { openRuntimeStore, } from './store.js';
|
|
37
36
|
import { validatedNeighbourhood } from './validation.js';
|
|
@@ -77,17 +76,15 @@ function operationOf(input) {
|
|
|
77
76
|
const operation = input?.operation_id;
|
|
78
77
|
return typeof operation === 'string' ? operation : null;
|
|
79
78
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
function readUntil(read, signal) {
|
|
80
|
+
if (signal.aborted)
|
|
81
|
+
return Promise.resolve({ kind: 'transport-uncertainty' });
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const stop = () => resolve({ kind: 'transport-uncertainty' });
|
|
84
|
+
signal.addEventListener('abort', stop, { once: true });
|
|
85
|
+
read.then(resolve, reject).finally(() => signal.removeEventListener('abort', stop));
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
|
-
function roomLane(locator) {
|
|
89
|
-
return `room ${locator}`;
|
|
90
|
-
}
|
|
91
88
|
function pendingKey(locator, id) {
|
|
92
89
|
return `${locator}\u0000${id}`;
|
|
93
90
|
}
|
|
@@ -107,6 +104,7 @@ export function composeRuntime(options) {
|
|
|
107
104
|
const usableWhenHeld = options.usableWhenHeld ?? [];
|
|
108
105
|
const queuesWithoutReplica = new FencedMap(isFenced);
|
|
109
106
|
const pendings = new Map();
|
|
107
|
+
const landedAhead = new Map();
|
|
110
108
|
const seeds = new Map();
|
|
111
109
|
const seedWatchers = new Map();
|
|
112
110
|
const roomDrafts = new FencedMap(isFenced);
|
|
@@ -118,11 +116,22 @@ export function composeRuntime(options) {
|
|
|
118
116
|
const counted = new Map();
|
|
119
117
|
const unreadable = new Set();
|
|
120
118
|
const builtIn = builtInModel(roomTypes);
|
|
119
|
+
const typedPaths = options.adapter.typedPaths ?? [];
|
|
121
120
|
const models = new FencedMap(isFenced);
|
|
122
121
|
const liveModels = new FencedMap(isFenced);
|
|
123
122
|
const invalidModels = new Set();
|
|
124
123
|
const discovered = new Map();
|
|
125
|
-
|
|
124
|
+
let inFlight = new AbortController();
|
|
125
|
+
const refreshed = options.adapter.refresh?.bind(options.adapter);
|
|
126
|
+
const reads = {
|
|
127
|
+
bootstrap: (authorized, request) => readUntil(options.adapter.bootstrap(authorized, request), inFlight.signal),
|
|
128
|
+
...(refreshed === undefined
|
|
129
|
+
? {}
|
|
130
|
+
: {
|
|
131
|
+
refresh: (authorized, request) => readUntil(refreshed(authorized, request), inFlight.signal),
|
|
132
|
+
}),
|
|
133
|
+
reached: (locator, roomVersion, target) => options.adapter.reached(locator, roomVersion, target),
|
|
134
|
+
};
|
|
126
135
|
let roomsListed = false;
|
|
127
136
|
const scopedRefusals = new Map();
|
|
128
137
|
function modelOf(locator) {
|
|
@@ -134,7 +143,7 @@ export function composeRuntime(options) {
|
|
|
134
143
|
: modelOf(locator);
|
|
135
144
|
}
|
|
136
145
|
function composed(record) {
|
|
137
|
-
return compositionOf(record, builtIn);
|
|
146
|
+
return compositionOf(record, builtIn, typedPaths);
|
|
138
147
|
}
|
|
139
148
|
function compositionFor(record) {
|
|
140
149
|
return composed({ ...record, model: savedModel(record.locator) });
|
|
@@ -147,26 +156,26 @@ export function composeRuntime(options) {
|
|
|
147
156
|
let closed = false;
|
|
148
157
|
let stopping = false;
|
|
149
158
|
let withdrawStream;
|
|
150
|
-
const announced = new Map();
|
|
151
159
|
let underway = null;
|
|
152
160
|
let streamed = null;
|
|
153
161
|
let handed = false;
|
|
162
|
+
let naming = 0;
|
|
154
163
|
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
155
164
|
const repairing = new Set();
|
|
156
165
|
const calling = new Set();
|
|
157
166
|
const repairable = new Map();
|
|
158
|
-
let outcomeRetention = null;
|
|
159
167
|
const answering = new Map();
|
|
160
168
|
let complete = false;
|
|
161
169
|
let recorded = false;
|
|
162
170
|
const stale = new Set();
|
|
163
171
|
let generation = 0;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
const reloadsDue = new Map();
|
|
172
|
+
const reloadsDue = new Set();
|
|
173
|
+
const highest = new Map();
|
|
174
|
+
const beyondHorizon = new Set();
|
|
168
175
|
const lanes = new Map();
|
|
169
|
-
|
|
176
|
+
const roomLanes = new Map();
|
|
177
|
+
let roomWork = 0;
|
|
178
|
+
const roomReads = { free: ROOM_READS, waiting: [] };
|
|
170
179
|
const working = new Set();
|
|
171
180
|
let mutations = Promise.resolve();
|
|
172
181
|
let gathering = null;
|
|
@@ -182,6 +191,8 @@ export function composeRuntime(options) {
|
|
|
182
191
|
let withdrawConversations;
|
|
183
192
|
let currencyQueued = null;
|
|
184
193
|
let socketOpen = false;
|
|
194
|
+
let announced = false;
|
|
195
|
+
let missedChange = false;
|
|
185
196
|
const confirmed = new Set();
|
|
186
197
|
let lockGrace = null;
|
|
187
198
|
const lockListeners = new Set();
|
|
@@ -191,6 +202,8 @@ export function composeRuntime(options) {
|
|
|
191
202
|
const serviceModeListeners = new Set();
|
|
192
203
|
let member = null;
|
|
193
204
|
const memberListeners = new Set();
|
|
205
|
+
let authorityChanges = 0;
|
|
206
|
+
const authorityChangeListeners = new Set();
|
|
194
207
|
const unpreparedDemand = new Set();
|
|
195
208
|
const unpreparable = new Set();
|
|
196
209
|
let preparing = false;
|
|
@@ -222,7 +235,7 @@ export function composeRuntime(options) {
|
|
|
222
235
|
const unindexed = new Map();
|
|
223
236
|
const startedOver = new Map();
|
|
224
237
|
let arrivalReported = 0;
|
|
225
|
-
let
|
|
238
|
+
let streamsFree = STREAMS_HELD;
|
|
226
239
|
let queuedText = 0;
|
|
227
240
|
const installWaiters = [];
|
|
228
241
|
const stageBudget = {
|
|
@@ -239,30 +252,15 @@ export function composeRuntime(options) {
|
|
|
239
252
|
};
|
|
240
253
|
const installTurns = { busy: false, ahead: 0, finishing: [], starting: [] };
|
|
241
254
|
const conversations = createConversationStore({
|
|
242
|
-
agents: declaredAgents,
|
|
243
|
-
documents: (declaration, after) => rowsQuery(planOf({
|
|
244
|
-
rooms: [declaration.locator],
|
|
245
|
-
types: [declaration.type],
|
|
246
|
-
select: {
|
|
247
|
-
fields: [
|
|
248
|
-
declaration.identity,
|
|
249
|
-
declaration.name,
|
|
250
|
-
...(declaration.retired === undefined ? [] : [declaration.retired]),
|
|
251
|
-
].map((field) => `/${escapePointer(field)}`),
|
|
252
|
-
},
|
|
253
|
-
limit: MAXIMUM_WINDOW,
|
|
254
|
-
...(after === undefined ? {} : { after }),
|
|
255
|
-
}, roomFacts())),
|
|
256
255
|
call: (name, input) => invokeAction(name, input),
|
|
256
|
+
history: (request) => readConversationHistory(request),
|
|
257
257
|
durably,
|
|
258
258
|
stream: () => options.adapter.conversations,
|
|
259
259
|
publish: publishChangeSet,
|
|
260
260
|
online: () => connectionOf() !== 'unreachable' && refusal === null,
|
|
261
|
-
listed: () => roomsListed,
|
|
262
261
|
full: () => full,
|
|
263
262
|
closed: () => closed,
|
|
264
263
|
unanswered: resendLater,
|
|
265
|
-
expired: (operation) => pastHorizon(operation),
|
|
266
264
|
});
|
|
267
265
|
const journal = createLog();
|
|
268
266
|
const keptFiles = openedFiles(database, isFenced);
|
|
@@ -355,9 +353,9 @@ export function composeRuntime(options) {
|
|
|
355
353
|
return value === undefined ? null : structuredClone(value);
|
|
356
354
|
}
|
|
357
355
|
async function writtenRoomDraft(room, path, revision, input, next) {
|
|
358
|
-
const parts = readParts(input);
|
|
356
|
+
const parts = readParts(input, path);
|
|
359
357
|
if (path === '' || normalize(path) !== path)
|
|
360
|
-
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`);
|
|
361
359
|
const local = await openLocal();
|
|
362
360
|
const current = roomDrafts.get(room)?.get(path);
|
|
363
361
|
if ((current?.revision ?? null) !== revision) {
|
|
@@ -657,11 +655,7 @@ export function composeRuntime(options) {
|
|
|
657
655
|
})();
|
|
658
656
|
}
|
|
659
657
|
function roomsOfReach(reach) {
|
|
660
|
-
return new Set([
|
|
661
|
-
...reach.rooms,
|
|
662
|
-
...reach.documents.map(roomOfKey),
|
|
663
|
-
...reach.areas.flatMap((area) => (area.types.length === 0 ? [] : [area.room])),
|
|
664
|
-
]);
|
|
658
|
+
return new Set([...reach.rooms, ...reach.documents.map(roomOfKey)]);
|
|
665
659
|
}
|
|
666
660
|
function reachedRooms(plan) {
|
|
667
661
|
const reached = plan.reach === null ? null : roomsOfReach(plan.reach);
|
|
@@ -700,7 +694,7 @@ export function composeRuntime(options) {
|
|
|
700
694
|
coverage.push({
|
|
701
695
|
room: locator,
|
|
702
696
|
generation: replica.record.transfer,
|
|
703
|
-
current: !(catching.has(locator) ||
|
|
697
|
+
current: !(catching.has(locator) || transfers.has(locator)),
|
|
704
698
|
});
|
|
705
699
|
read.push({ locator, key: replica.record.key });
|
|
706
700
|
for (const batch of replica.queue)
|
|
@@ -741,7 +735,7 @@ export function composeRuntime(options) {
|
|
|
741
735
|
}
|
|
742
736
|
function conversationCoverage(reach) {
|
|
743
737
|
const declared = reach === null || reach.conversations;
|
|
744
|
-
if (!declared || options.adapter.conversations === undefined
|
|
738
|
+
if (!declared || options.adapter.conversations === undefined)
|
|
745
739
|
return 'undeclared';
|
|
746
740
|
if (!conversations.known() || !conversations.current())
|
|
747
741
|
return 'acquiring';
|
|
@@ -800,7 +794,6 @@ export function composeRuntime(options) {
|
|
|
800
794
|
}
|
|
801
795
|
function readinessFor(port, reach) {
|
|
802
796
|
const required = requiredRooms(reach);
|
|
803
|
-
const search = port.search === 'required';
|
|
804
797
|
const coverage = required.rooms.map((room) => ({ room, state: roomState(room) }));
|
|
805
798
|
const preparing = coverage
|
|
806
799
|
.filter((room) => room.state === 'preparing' && !unpreparable.has(room.room))
|
|
@@ -813,12 +806,11 @@ export function composeRuntime(options) {
|
|
|
813
806
|
listed: required.listed,
|
|
814
807
|
rooms: coverage,
|
|
815
808
|
conversations: conversationCoverage(reach),
|
|
816
|
-
search: search ? 'required' : 'unused',
|
|
817
809
|
},
|
|
818
810
|
persistence,
|
|
819
811
|
connection: connectionOf(),
|
|
820
812
|
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
821
|
-
space: full ? 'full' :
|
|
813
|
+
space: full ? 'full' : null,
|
|
822
814
|
installation: installationOf(coverage),
|
|
823
815
|
lost: storageLoss !== null,
|
|
824
816
|
established: targets[port.target]?.established === true ||
|
|
@@ -907,8 +899,12 @@ export function composeRuntime(options) {
|
|
|
907
899
|
unpreparable.clear();
|
|
908
900
|
journal.record('space freed');
|
|
909
901
|
queueReadiness();
|
|
910
|
-
if (
|
|
911
|
-
|
|
902
|
+
if (closed || authorization === undefined)
|
|
903
|
+
return;
|
|
904
|
+
if (options.adapter.roomStream === undefined)
|
|
905
|
+
void sync('unstreamed', null);
|
|
906
|
+
else
|
|
907
|
+
void resynchronize();
|
|
912
908
|
}
|
|
913
909
|
function seedRoomHeld(room, version) {
|
|
914
910
|
return indexed(room) && reachedBy(room, version);
|
|
@@ -1056,6 +1052,8 @@ export function composeRuntime(options) {
|
|
|
1056
1052
|
}
|
|
1057
1053
|
async function refuse(reason, provider = null) {
|
|
1058
1054
|
discoveryPaused = true;
|
|
1055
|
+
inFlight.abort();
|
|
1056
|
+
inFlight = new AbortController();
|
|
1059
1057
|
try {
|
|
1060
1058
|
if (reason === 'revoked-authorization') {
|
|
1061
1059
|
await exclusive(async () => {
|
|
@@ -1143,7 +1141,7 @@ export function composeRuntime(options) {
|
|
|
1143
1141
|
refusal: scopedRefusals.get(room.locator) ?? null,
|
|
1144
1142
|
});
|
|
1145
1143
|
}
|
|
1146
|
-
async function applyDiscovery(found, started,
|
|
1144
|
+
async function applyDiscovery(found, started, left) {
|
|
1147
1145
|
const lockedBefore = lockedRooms();
|
|
1148
1146
|
for (const room of found) {
|
|
1149
1147
|
const fence = fences.get(room.locator);
|
|
@@ -1157,18 +1155,11 @@ export function composeRuntime(options) {
|
|
|
1157
1155
|
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
1158
1156
|
for (const locator of listedLocators)
|
|
1159
1157
|
scopedRefusals.delete(locator);
|
|
1160
|
-
if (
|
|
1158
|
+
if (left === undefined)
|
|
1161
1159
|
discovered.clear();
|
|
1162
|
-
else
|
|
1163
|
-
for (const locator of covered)
|
|
1164
|
-
discovered.delete(locator);
|
|
1165
1160
|
for (const room of listed)
|
|
1166
1161
|
discovered.set(room.locator, room);
|
|
1167
|
-
|
|
1168
|
-
if (options.adapter.exhaustiveDiscovery === true)
|
|
1169
|
-
await loseRooms(unlisted);
|
|
1170
|
-
else
|
|
1171
|
-
unlistedKept(unlisted);
|
|
1162
|
+
await loseRooms(left ?? unlistedHeld(new Set(found.map((room) => room.locator))));
|
|
1172
1163
|
for (const room of listed) {
|
|
1173
1164
|
const held = replicas.get(room.locator)?.record;
|
|
1174
1165
|
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
@@ -1199,15 +1190,6 @@ export function composeRuntime(options) {
|
|
|
1199
1190
|
]),
|
|
1200
1191
|
].filter((locator) => !listed.has(locator));
|
|
1201
1192
|
}
|
|
1202
|
-
function unlistedKept(unlisted) {
|
|
1203
|
-
for (const locator of unlisted) {
|
|
1204
|
-
const held = replicas.get(locator)?.record;
|
|
1205
|
-
if (held !== undefined)
|
|
1206
|
-
list(held, false);
|
|
1207
|
-
else
|
|
1208
|
-
rooms.delete(locator);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
1193
|
function fence(locators) {
|
|
1212
1194
|
const fresh = [...new Set(locators)].filter((locator) => !fences.has(locator));
|
|
1213
1195
|
const removed = fresh.map((locator) => ({
|
|
@@ -1218,9 +1200,13 @@ export function composeRuntime(options) {
|
|
|
1218
1200
|
fences.set(locator, { at: discoveries, erased: false });
|
|
1219
1201
|
lastFenced.set(locator, ++fencings);
|
|
1220
1202
|
confirmed.delete(locator);
|
|
1221
|
-
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
1203
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []) {
|
|
1222
1204
|
pendings.delete(pendingKey(locator, batch.id));
|
|
1205
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1206
|
+
}
|
|
1223
1207
|
replicas.delete(locator);
|
|
1208
|
+
highest.delete(locator);
|
|
1209
|
+
beyondHorizon.delete(locator);
|
|
1224
1210
|
queuesWithoutReplica.delete(locator);
|
|
1225
1211
|
rooms.delete(locator);
|
|
1226
1212
|
roomDrafts.delete(locator);
|
|
@@ -1229,7 +1215,6 @@ export function composeRuntime(options) {
|
|
|
1229
1215
|
startedOver.delete(locator);
|
|
1230
1216
|
catching.delete(locator);
|
|
1231
1217
|
discovered.delete(locator);
|
|
1232
|
-
announced.delete(locator);
|
|
1233
1218
|
unreadable.delete(locator);
|
|
1234
1219
|
stale.delete(locator);
|
|
1235
1220
|
repairable.delete(locator);
|
|
@@ -1288,8 +1273,7 @@ export function composeRuntime(options) {
|
|
|
1288
1273
|
function retyped(persisted) {
|
|
1289
1274
|
if (persisted.status === 'unavailable')
|
|
1290
1275
|
return persisted;
|
|
1291
|
-
|
|
1292
|
-
return state.status === 'unavailable' ? state : { ...persisted, types: state.types };
|
|
1276
|
+
return { ...persisted, types: schemaTypes(persisted.snapshot.schema, roomTypes) };
|
|
1293
1277
|
}
|
|
1294
1278
|
function restored(key, held) {
|
|
1295
1279
|
const persisted = retyped(held);
|
|
@@ -1335,8 +1319,9 @@ export function composeRuntime(options) {
|
|
|
1335
1319
|
presentations.clear();
|
|
1336
1320
|
for (const record of held.presentation)
|
|
1337
1321
|
presentations.set(record.app, record.value);
|
|
1338
|
-
conversations.hydrate(heldConversations, chatDrafts, held.conversationList);
|
|
1322
|
+
conversations.hydrate(heldConversations, chatDrafts, held.conversationList, held.conversationCursor);
|
|
1339
1323
|
pendings.clear();
|
|
1324
|
+
landedAhead.clear();
|
|
1340
1325
|
for (const queue of queues) {
|
|
1341
1326
|
for (const { batch, state } of queue.pending) {
|
|
1342
1327
|
pendings.set(pendingKey(queue.locator, batch.id), state);
|
|
@@ -1485,14 +1470,12 @@ export function composeRuntime(options) {
|
|
|
1485
1470
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
1486
1471
|
};
|
|
1487
1472
|
}
|
|
1488
|
-
const REWRITE_LINKS = ['field', 'body'];
|
|
1489
|
-
const CHECKED_LINKS = ['field', 'section'];
|
|
1490
1473
|
async function sliceAt(record, view, queue, extra = [], moved = []) {
|
|
1491
1474
|
const local = await openLocal();
|
|
1492
1475
|
const needed = new Set([...touchedBy(queue), ...extra]);
|
|
1493
1476
|
const movedPaths = [...new Set([...movedBy(queue, record.locator), ...moved])];
|
|
1494
1477
|
if (movedPaths.length > 0) {
|
|
1495
|
-
for (const { path } of await local.referrers(record.key, view, movedPaths
|
|
1478
|
+
for (const { path } of await local.referrers(record.key, view, movedPaths)) {
|
|
1496
1479
|
needed.add(path);
|
|
1497
1480
|
}
|
|
1498
1481
|
}
|
|
@@ -1629,10 +1612,16 @@ export function composeRuntime(options) {
|
|
|
1629
1612
|
? read
|
|
1630
1613
|
: await stepped(slicingWith(read, overrides.set, overrides.removed));
|
|
1631
1614
|
const composition = composed(record);
|
|
1632
|
-
const
|
|
1633
|
-
|
|
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));
|
|
1634
1623
|
const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
|
|
1635
|
-
const unqueued =
|
|
1624
|
+
const unqueued = shown.map((batch) => ({ batch, landed: true }));
|
|
1636
1625
|
for (const batch of withdrawn) {
|
|
1637
1626
|
unqueued.push({ batch, landed: await landedWhole(locator, batch, confirmedLayer) });
|
|
1638
1627
|
}
|
|
@@ -1697,8 +1686,10 @@ export function composeRuntime(options) {
|
|
|
1697
1686
|
commit,
|
|
1698
1687
|
apply: (answer) => {
|
|
1699
1688
|
for (const batch of previousQueue) {
|
|
1700
|
-
if (
|
|
1701
|
-
|
|
1689
|
+
if (staying.has(batch.id))
|
|
1690
|
+
continue;
|
|
1691
|
+
pendings.delete(pendingKey(locator, batch.id));
|
|
1692
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1702
1693
|
}
|
|
1703
1694
|
for (const draft of written.values())
|
|
1704
1695
|
rememberRoomDraft(draft);
|
|
@@ -1747,26 +1738,33 @@ export function composeRuntime(options) {
|
|
|
1747
1738
|
const answer = answers?.collection;
|
|
1748
1739
|
if (answer === undefined)
|
|
1749
1740
|
return held;
|
|
1750
|
-
const
|
|
1751
|
-
if (refused !== null) {
|
|
1752
|
-
const where = refused.location === undefined ? '' : ` ${refused.location}`;
|
|
1753
|
-
journal.record(`model invalid ${locator} collection ${refused.identity} ${refused.rule}${where}`);
|
|
1754
|
-
}
|
|
1741
|
+
const state = attachedModel(answer, roomTypes);
|
|
1755
1742
|
models.set(locator, state);
|
|
1756
1743
|
return { collection: state };
|
|
1757
1744
|
}
|
|
1758
|
-
function publish(local, room,
|
|
1745
|
+
function publish(local, room, reached, landing, live) {
|
|
1759
1746
|
const landed = exclusive(async () => {
|
|
1760
1747
|
if (!live()) {
|
|
1761
1748
|
caughtUpTo(room.locator);
|
|
1762
1749
|
return true;
|
|
1763
1750
|
}
|
|
1764
1751
|
const prior = replicas.get(room.locator);
|
|
1765
|
-
if (landing.
|
|
1766
|
-
(
|
|
1767
|
-
|
|
1752
|
+
if (!stands(room.locator, landing.from)) {
|
|
1753
|
+
if (landing.kind === 'refreshed') {
|
|
1754
|
+
await local.unrefresh(reached.key);
|
|
1755
|
+
return false;
|
|
1756
|
+
}
|
|
1757
|
+
caughtUpTo(room.locator);
|
|
1758
|
+
await abandoned(local, room.locator);
|
|
1768
1759
|
return false;
|
|
1769
1760
|
}
|
|
1761
|
+
const record = landing.kind === 'refreshed' && prior !== undefined
|
|
1762
|
+
? {
|
|
1763
|
+
...prior.record,
|
|
1764
|
+
roomVersion: reached.roomVersion,
|
|
1765
|
+
...(landing.remodelled ? { model: reached.model } : {}),
|
|
1766
|
+
}
|
|
1767
|
+
: reached;
|
|
1770
1768
|
adoptModels(room.locator, record);
|
|
1771
1769
|
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
1772
1770
|
let set;
|
|
@@ -1793,11 +1791,14 @@ export function composeRuntime(options) {
|
|
|
1793
1791
|
return true;
|
|
1794
1792
|
}
|
|
1795
1793
|
if (landing.kind === 'bootstrapped') {
|
|
1794
|
+
highest.delete(room.locator);
|
|
1795
|
+
beyondHorizon.delete(room.locator);
|
|
1796
1796
|
untransferred(room.locator);
|
|
1797
1797
|
startedOver.delete(room.locator);
|
|
1798
1798
|
catching.delete(room.locator);
|
|
1799
1799
|
if (prior !== undefined && prior.record.key !== record.key)
|
|
1800
1800
|
sweep();
|
|
1801
|
+
name(room.locator);
|
|
1801
1802
|
}
|
|
1802
1803
|
stale.delete(room.locator);
|
|
1803
1804
|
unpreparable.delete(room.locator);
|
|
@@ -1807,7 +1808,9 @@ export function composeRuntime(options) {
|
|
|
1807
1808
|
publishChangeSet(set);
|
|
1808
1809
|
await unpair(local);
|
|
1809
1810
|
moveTo({ ...status, ...attention() });
|
|
1810
|
-
|
|
1811
|
+
if (landing.kind === 'bootstrapped' && (replicas.get(room.locator)?.queue.length ?? 0) > 0) {
|
|
1812
|
+
submitLater(room.locator);
|
|
1813
|
+
}
|
|
1811
1814
|
return true;
|
|
1812
1815
|
});
|
|
1813
1816
|
return landing.kind === 'refreshed' ? landed : landed.finally(() => installed(room.locator));
|
|
@@ -1817,7 +1820,7 @@ export function composeRuntime(options) {
|
|
|
1817
1820
|
const replica = replicas.get(locator);
|
|
1818
1821
|
const room = rooms.get(locator);
|
|
1819
1822
|
if (replica === undefined || room === undefined) {
|
|
1820
|
-
throw runtimeError('room
|
|
1823
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
1821
1824
|
}
|
|
1822
1825
|
return { local, replica, room };
|
|
1823
1826
|
}
|
|
@@ -1878,7 +1881,7 @@ export function composeRuntime(options) {
|
|
|
1878
1881
|
const removed = changes.flatMap((change) => (change.kind === 'remove' ? [change.path] : []));
|
|
1879
1882
|
const referring = removed.length === 0
|
|
1880
1883
|
? []
|
|
1881
|
-
: (await local.referrers(record.key, 'confirmed', removed
|
|
1884
|
+
: (await local.referrers(record.key, 'confirmed', removed)).map(({ path }) => path);
|
|
1882
1885
|
const confirmedLayer = await sliceAt(record, 'confirmed', replica.queue, [...paths, ...paths.flatMap(ancestors), ...referring], moved);
|
|
1883
1886
|
const { visible } = await replayed(record, confirmedLayer, replica.queue);
|
|
1884
1887
|
const composition = composed(record);
|
|
@@ -1971,7 +1974,7 @@ export function composeRuntime(options) {
|
|
|
1971
1974
|
}
|
|
1972
1975
|
function referrersOf(local, key, view) {
|
|
1973
1976
|
return async (targets) => [
|
|
1974
|
-
...new Set((await local.referrers(key, view, targets
|
|
1977
|
+
...new Set((await local.referrers(key, view, targets)).map(({ path }) => path)),
|
|
1975
1978
|
];
|
|
1976
1979
|
}
|
|
1977
1980
|
async function sideOf(locator, replica, room, saving) {
|
|
@@ -2043,7 +2046,7 @@ export function composeRuntime(options) {
|
|
|
2043
2046
|
const target = replicas.get(destination);
|
|
2044
2047
|
const listedRoom = rooms.get(destination);
|
|
2045
2048
|
if (target === undefined || listedRoom === undefined) {
|
|
2046
|
-
throw runtimeError('room
|
|
2049
|
+
throw runtimeError('unknown-room', `Unknown Room ${destination}`);
|
|
2047
2050
|
}
|
|
2048
2051
|
const arriving = await savingIn(target, [], moves.map(({ to }) => to));
|
|
2049
2052
|
const built = await buildMove({
|
|
@@ -2106,10 +2109,10 @@ export function composeRuntime(options) {
|
|
|
2106
2109
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2107
2110
|
const { answer, touched } = committedBoth;
|
|
2108
2111
|
if (answer.fenced !== null) {
|
|
2109
|
-
throw runtimeError('room
|
|
2112
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2110
2113
|
}
|
|
2111
2114
|
if (fences.has(locator) || fences.has(destination)) {
|
|
2112
|
-
throw runtimeError('room
|
|
2115
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2113
2116
|
}
|
|
2114
2117
|
pendings.set(pendingKey(locator, id), {
|
|
2115
2118
|
drafts: saved,
|
|
@@ -2280,10 +2283,10 @@ export function composeRuntime(options) {
|
|
|
2280
2283
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2281
2284
|
const { answer, touched } = done;
|
|
2282
2285
|
if (answer.fenced !== null) {
|
|
2283
|
-
throw runtimeError('room
|
|
2286
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2284
2287
|
}
|
|
2285
2288
|
if (fences.has(locator))
|
|
2286
|
-
throw runtimeError('room
|
|
2289
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2287
2290
|
for (const origin of withdrawn.keys())
|
|
2288
2291
|
pendings.delete(pendingKey(locator, origin));
|
|
2289
2292
|
for (const [id, drafts] of drafted) {
|
|
@@ -2332,7 +2335,7 @@ export function composeRuntime(options) {
|
|
|
2332
2335
|
throw new Error('connection refused');
|
|
2333
2336
|
await local.keepUpload(locator, identifier, upload);
|
|
2334
2337
|
if (fences.has(locator))
|
|
2335
|
-
throw runtimeError('room
|
|
2338
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2336
2339
|
const key = pendingKey(locator, identifier);
|
|
2337
2340
|
pendings.set(key, { ...pendings.get(key), upload });
|
|
2338
2341
|
if (!continuing())
|
|
@@ -2606,13 +2609,6 @@ export function composeRuntime(options) {
|
|
|
2606
2609
|
const freed = await database.ask({ kind: 'opened.clean' });
|
|
2607
2610
|
return freed + ((await options.shell.cleanUp?.()) ?? 0);
|
|
2608
2611
|
}
|
|
2609
|
-
function declaredAgents() {
|
|
2610
|
-
for (const capability of capabilities) {
|
|
2611
|
-
if (capability.conversationAgents !== undefined)
|
|
2612
|
-
return capability.conversationAgents;
|
|
2613
|
-
}
|
|
2614
|
-
return undefined;
|
|
2615
|
-
}
|
|
2616
2612
|
function declaredAction(name) {
|
|
2617
2613
|
for (const capability of capabilities) {
|
|
2618
2614
|
for (const action of capability.actions ?? []) {
|
|
@@ -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);
|
|
@@ -2845,18 +2893,12 @@ export function composeRuntime(options) {
|
|
|
2845
2893
|
void yielded().then(nextInstallTurn);
|
|
2846
2894
|
}
|
|
2847
2895
|
async function heldStream() {
|
|
2848
|
-
|
|
2849
|
-
if (streams.generation !== generation)
|
|
2850
|
-
streams = { generation, free: STREAMS_HELD };
|
|
2851
|
-
const held = streams;
|
|
2852
|
-
if (held.free > 0) {
|
|
2853
|
-
held.free -= 1;
|
|
2854
|
-
return once(() => {
|
|
2855
|
-
held.free += 1;
|
|
2856
|
-
});
|
|
2857
|
-
}
|
|
2896
|
+
while (streamsFree === 0)
|
|
2858
2897
|
await new Promise((admit) => installWaiters.push(admit));
|
|
2859
|
-
|
|
2898
|
+
streamsFree -= 1;
|
|
2899
|
+
return once(() => {
|
|
2900
|
+
streamsFree += 1;
|
|
2901
|
+
});
|
|
2860
2902
|
}
|
|
2861
2903
|
async function unqueued() {
|
|
2862
2904
|
while (queuedText >= QUEUED_TEXT) {
|
|
@@ -2922,33 +2964,34 @@ export function composeRuntime(options) {
|
|
|
2922
2964
|
if (installs.delete(locator))
|
|
2923
2965
|
queueReadiness();
|
|
2924
2966
|
}
|
|
2925
|
-
async function pullRefresh(authorized, held, liveness, hold) {
|
|
2967
|
+
async function pullRefresh(authorized, held, liveness, hold, refresh) {
|
|
2926
2968
|
if (!liveness())
|
|
2927
2969
|
return unconfirmed;
|
|
2928
|
-
const turn = generation;
|
|
2929
2970
|
const { record } = held;
|
|
2930
|
-
const
|
|
2971
|
+
const from = standingOf(record.locator);
|
|
2931
2972
|
const local = await openLocal();
|
|
2932
2973
|
try {
|
|
2933
2974
|
await exclusive(() => local.unrefresh(record.key));
|
|
2934
|
-
const result = await advancement({
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
}
|
|
2944
|
-
},
|
|
2945
|
-
}, authorized, record, (models, kept) => (liveness() ? remodel(record.locator, models, kept) : kept), liveness, refreshingOf(local, record.key, composed(record), liveness));
|
|
2975
|
+
const result = await advancement(async (authorization, request) => {
|
|
2976
|
+
hold.release();
|
|
2977
|
+
try {
|
|
2978
|
+
return await refresh(authorization, request);
|
|
2979
|
+
}
|
|
2980
|
+
finally {
|
|
2981
|
+
hold.take();
|
|
2982
|
+
}
|
|
2983
|
+
}, authorized, record, (models, kept) => (liveness() ? remodel(record.locator, models, kept) : kept), liveness, refreshingOf(local, record.locator, record.key, composed(record), liveness));
|
|
2946
2984
|
if (result.kind !== 'advanced') {
|
|
2947
2985
|
journal.record(`transfer refresh ${record.locator} ${result.kind}`);
|
|
2948
2986
|
return result;
|
|
2949
2987
|
}
|
|
2950
2988
|
journal.record(`transfer refresh ${record.locator} ${result.record.roomVersion}`);
|
|
2951
|
-
return {
|
|
2989
|
+
return {
|
|
2990
|
+
kind: 'advanced',
|
|
2991
|
+
record: result.record,
|
|
2992
|
+
from,
|
|
2993
|
+
remodelled: result.record.model !== record.model,
|
|
2994
|
+
};
|
|
2952
2995
|
}
|
|
2953
2996
|
catch (error) {
|
|
2954
2997
|
if (refusedForSpace(error))
|
|
@@ -2956,9 +2999,6 @@ export function composeRuntime(options) {
|
|
|
2956
2999
|
journal.record(`transfer refresh ${record.locator} transport-uncertainty`);
|
|
2957
3000
|
throw error;
|
|
2958
3001
|
}
|
|
2959
|
-
finally {
|
|
2960
|
-
reads.finished(record.locator, turn);
|
|
2961
|
-
}
|
|
2962
3002
|
}
|
|
2963
3003
|
const heldContents = new Map();
|
|
2964
3004
|
function heldContentOf(local, key) {
|
|
@@ -2976,6 +3016,13 @@ export function composeRuntime(options) {
|
|
|
2976
3016
|
heldContents.set(key, made);
|
|
2977
3017
|
return made;
|
|
2978
3018
|
}
|
|
3019
|
+
async function abandoned(local, locator) {
|
|
3020
|
+
await local.abandon(locator);
|
|
3021
|
+
setBack(locator);
|
|
3022
|
+
untransferred(locator);
|
|
3023
|
+
liveQueries.recover();
|
|
3024
|
+
sweep();
|
|
3025
|
+
}
|
|
2979
3026
|
function staging(local, liveness) {
|
|
2980
3027
|
const derived = async (locator, key, composition) => {
|
|
2981
3028
|
underived.delete(locator);
|
|
@@ -3028,10 +3075,7 @@ export function composeRuntime(options) {
|
|
|
3028
3075
|
},
|
|
3029
3076
|
};
|
|
3030
3077
|
},
|
|
3031
|
-
|
|
3032
|
-
const later = announced.get(locator);
|
|
3033
|
-
return later === undefined ? [] : [later];
|
|
3034
|
-
},
|
|
3078
|
+
streamed: options.adapter.roomStream !== undefined,
|
|
3035
3079
|
async stage(record, page, composition) {
|
|
3036
3080
|
const { locator } = record;
|
|
3037
3081
|
installing(locator);
|
|
@@ -3063,16 +3107,11 @@ export function composeRuntime(options) {
|
|
|
3063
3107
|
derive: derived,
|
|
3064
3108
|
async refreshing(locator, key, composition) {
|
|
3065
3109
|
await exclusive(() => local.unrefresh(key));
|
|
3066
|
-
return refreshingOf(local, key, composition, liveness,
|
|
3110
|
+
return refreshingOf(local, locator, key, composition, liveness, true);
|
|
3067
3111
|
},
|
|
3068
3112
|
abandon: (locator) => exclusive(async () => {
|
|
3069
|
-
if (
|
|
3070
|
-
|
|
3071
|
-
await local.abandon(locator);
|
|
3072
|
-
setBack(locator);
|
|
3073
|
-
untransferred(locator);
|
|
3074
|
-
liveQueries.recover();
|
|
3075
|
-
sweep();
|
|
3113
|
+
if (liveness())
|
|
3114
|
+
await abandoned(local, locator);
|
|
3076
3115
|
}),
|
|
3077
3116
|
setAside: async (locator) => {
|
|
3078
3117
|
if (!liveness())
|
|
@@ -3097,15 +3136,21 @@ export function composeRuntime(options) {
|
|
|
3097
3136
|
async function pullBootstrap(authorized, room, local, liveness) {
|
|
3098
3137
|
if (!liveness())
|
|
3099
3138
|
return unconfirmed;
|
|
3100
|
-
const turn = generation;
|
|
3101
3139
|
if (replicas.has(room.locator)) {
|
|
3102
3140
|
catching.add(room.locator);
|
|
3103
3141
|
liveQueries.recover();
|
|
3104
3142
|
}
|
|
3105
3143
|
try {
|
|
3106
|
-
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));
|
|
3107
3145
|
const detail = result.kind === 'staged' ? result.record.roomVersion : result.kind;
|
|
3108
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
|
+
}
|
|
3109
3154
|
if (result.kind !== 'staged') {
|
|
3110
3155
|
caughtUpTo(room.locator);
|
|
3111
3156
|
installed(room.locator);
|
|
@@ -3120,9 +3165,6 @@ export function composeRuntime(options) {
|
|
|
3120
3165
|
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
3121
3166
|
throw error;
|
|
3122
3167
|
}
|
|
3123
|
-
finally {
|
|
3124
|
-
reads.finished(room.locator, turn);
|
|
3125
|
-
}
|
|
3126
3168
|
}
|
|
3127
3169
|
function caughtUpTo(locator) {
|
|
3128
3170
|
if (catching.delete(locator))
|
|
@@ -3130,45 +3172,51 @@ export function composeRuntime(options) {
|
|
|
3130
3172
|
}
|
|
3131
3173
|
function behind(room) {
|
|
3132
3174
|
const held = replicas.get(room.locator)?.record.roomVersion;
|
|
3133
|
-
return
|
|
3134
|
-
[announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later)));
|
|
3175
|
+
return held !== undefined && !options.adapter.reached(room.locator, held, room.roomVersion);
|
|
3135
3176
|
}
|
|
3136
|
-
|
|
3137
|
-
const
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
return
|
|
3177
|
+
function standingOf(locator) {
|
|
3178
|
+
const held = replicas.get(locator);
|
|
3179
|
+
return { key: held?.record.key ?? null, confirmed: held?.confirmed ?? 0 };
|
|
3180
|
+
}
|
|
3181
|
+
function stands(locator, from) {
|
|
3182
|
+
const now = standingOf(locator);
|
|
3183
|
+
return now.key === from.key && now.confirmed === from.confirmed;
|
|
3143
3184
|
}
|
|
3144
|
-
async function
|
|
3185
|
+
async function advance(room, local, authorized, running) {
|
|
3145
3186
|
const live = () => running() && !full;
|
|
3146
3187
|
if (!live())
|
|
3147
3188
|
return unconfirmed;
|
|
3148
3189
|
const held = replicas.get(room.locator);
|
|
3149
|
-
if (held !== undefined) {
|
|
3150
|
-
|
|
3190
|
+
if (held !== undefined && options.adapter.roomStream !== undefined) {
|
|
3191
|
+
stale.delete(room.locator);
|
|
3192
|
+
name(room.locator);
|
|
3193
|
+
return null;
|
|
3194
|
+
}
|
|
3195
|
+
const refresh = reads.refresh;
|
|
3196
|
+
if (held !== undefined && refresh !== undefined) {
|
|
3197
|
+
const refreshed = await ahead((hold) => refreshedRoom(room, local, authorized, held, live, hold, refresh));
|
|
3151
3198
|
if (refreshed === 'behind')
|
|
3152
3199
|
return advance(room, local, authorized, live);
|
|
3153
3200
|
if (refreshed !== 'beyond-horizon')
|
|
3154
3201
|
return refreshed;
|
|
3155
|
-
if (
|
|
3156
|
-
owed.add(room.locator);
|
|
3157
|
-
liveQueries.recover();
|
|
3158
|
-
return unconfirmed;
|
|
3159
|
-
}
|
|
3160
|
-
if (reloadsDue.get(room.locator) === generation)
|
|
3202
|
+
if (reloadsDue.has(room.locator))
|
|
3161
3203
|
return unconfirmed;
|
|
3204
|
+
replaced([room.locator]);
|
|
3162
3205
|
}
|
|
3206
|
+
const from = standingOf(room.locator);
|
|
3163
3207
|
const bootstrapped = await pullBootstrap(authorized, room, local, live);
|
|
3164
|
-
if (bootstrapped.kind
|
|
3165
|
-
|
|
3208
|
+
if (bootstrapped.kind !== 'staged')
|
|
3209
|
+
return live() ? bootstrapped : unconfirmed;
|
|
3210
|
+
const landing = { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from };
|
|
3211
|
+
if (await publish(local, room, bootstrapped.record, landing, live)) {
|
|
3166
3212
|
return live() ? null : unconfirmed;
|
|
3167
3213
|
}
|
|
3168
|
-
return
|
|
3214
|
+
return reachedBy(room.locator, bootstrapped.record.roomVersion)
|
|
3215
|
+
? null
|
|
3216
|
+
: advance(room, local, authorized, live);
|
|
3169
3217
|
}
|
|
3170
|
-
async function refreshedRoom(room, local, authorized, held, live, hold) {
|
|
3171
|
-
const refreshed = await pullRefresh(authorized, held, live, hold);
|
|
3218
|
+
async function refreshedRoom(room, local, authorized, held, live, hold, refresh) {
|
|
3219
|
+
const refreshed = await pullRefresh(authorized, held, live, hold, refresh);
|
|
3172
3220
|
if (!live())
|
|
3173
3221
|
return unconfirmed;
|
|
3174
3222
|
if (refreshed.kind === 'still-current') {
|
|
@@ -3181,7 +3229,11 @@ export function composeRuntime(options) {
|
|
|
3181
3229
|
return null;
|
|
3182
3230
|
}
|
|
3183
3231
|
if (refreshed.kind === 'advanced') {
|
|
3184
|
-
const landing = {
|
|
3232
|
+
const landing = {
|
|
3233
|
+
kind: 'refreshed',
|
|
3234
|
+
from: refreshed.from,
|
|
3235
|
+
remodelled: refreshed.remodelled,
|
|
3236
|
+
};
|
|
3185
3237
|
if (!(await publish(local, room, refreshed.record, landing, live))) {
|
|
3186
3238
|
const now = replicas.get(room.locator)?.record.roomVersion;
|
|
3187
3239
|
const reached = now !== undefined &&
|
|
@@ -3194,7 +3246,7 @@ export function composeRuntime(options) {
|
|
|
3194
3246
|
return refreshed.kind === 'beyond-horizon' ? 'beyond-horizon' : refreshed;
|
|
3195
3247
|
}
|
|
3196
3248
|
async function withhold(withheld, room, local, progress) {
|
|
3197
|
-
if (withheld.kind === 'unconfirmed')
|
|
3249
|
+
if (withheld.kind === 'unconfirmed' || isFenced(room.locator))
|
|
3198
3250
|
return;
|
|
3199
3251
|
stale.add(room.locator);
|
|
3200
3252
|
if (withheld.kind === 'transport-uncertainty' || withheld.kind === 'retryable-refusal') {
|
|
@@ -3223,8 +3275,6 @@ export function composeRuntime(options) {
|
|
|
3223
3275
|
}
|
|
3224
3276
|
}
|
|
3225
3277
|
function leaving(batch, result) {
|
|
3226
|
-
if (result.kind === 'expired')
|
|
3227
|
-
return { batch, returned: returnedBy('expired') };
|
|
3228
3278
|
return {
|
|
3229
3279
|
batch,
|
|
3230
3280
|
returned: {
|
|
@@ -3257,10 +3307,11 @@ export function composeRuntime(options) {
|
|
|
3257
3307
|
logAccepted(room.locator, submitted, accepted);
|
|
3258
3308
|
let record = current.record;
|
|
3259
3309
|
let changes;
|
|
3260
|
-
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)) {
|
|
3261
3312
|
const paths = [...returnedPaths(submitted, accepted)];
|
|
3262
3313
|
const confirmedLayer = await stepped(slicing(await local.entries(record.key, 'confirmed', paths), paths));
|
|
3263
|
-
const back = await stepped(returning(confirmedLayer, submitted, accepted, accepted.roomVersion));
|
|
3314
|
+
const back = await stepped(returning(confirmedLayer, submitted, accepted, accepted.roomVersion, composed(record)));
|
|
3264
3315
|
record = {
|
|
3265
3316
|
...record,
|
|
3266
3317
|
roomVersion: options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
@@ -3274,6 +3325,7 @@ export function composeRuntime(options) {
|
|
|
3274
3325
|
...settled,
|
|
3275
3326
|
apply(answer) {
|
|
3276
3327
|
const set = settled.apply(answer);
|
|
3328
|
+
showing(room.locator, accepted.roomVersion);
|
|
3277
3329
|
pendings.delete(pendingKey(room.locator, submitted.id));
|
|
3278
3330
|
unreadable.delete(room.locator);
|
|
3279
3331
|
list(room, true);
|
|
@@ -3423,15 +3475,10 @@ export function composeRuntime(options) {
|
|
|
3423
3475
|
return;
|
|
3424
3476
|
moveTo({ ...status, ...attention() });
|
|
3425
3477
|
}
|
|
3426
|
-
async function discardHeld(head, room, local, signal) {
|
|
3427
|
-
const partner = partnerOf(head, room.locator);
|
|
3428
|
-
const other = partner === null ? undefined : discovered.get(partner);
|
|
3429
|
-
await discardPending(other === undefined ? [room] : [room, other], local, signal);
|
|
3430
|
-
}
|
|
3431
3478
|
async function submitRoom(room, local, authorized, progress, signal) {
|
|
3432
3479
|
while (continuing() && authorization !== undefined && !signal.aborted) {
|
|
3433
3480
|
const replica = replicas.get(room.locator);
|
|
3434
|
-
const head = replica?.queue
|
|
3481
|
+
const head = replica?.queue.find((batch) => !landedAhead.has(pendingKey(room.locator, batch.id)));
|
|
3435
3482
|
if (replica === undefined || head === undefined)
|
|
3436
3483
|
return 'done';
|
|
3437
3484
|
const state = pendings.get(pendingKey(room.locator, head.id));
|
|
@@ -3458,10 +3505,6 @@ export function composeRuntime(options) {
|
|
|
3458
3505
|
return 'done';
|
|
3459
3506
|
}
|
|
3460
3507
|
const lost = earlier || submission.state === 'submitted';
|
|
3461
|
-
if (lost && pastHorizon(head.id)) {
|
|
3462
|
-
await discardHeld(head, room, local, signal);
|
|
3463
|
-
return 'done';
|
|
3464
|
-
}
|
|
3465
3508
|
let result;
|
|
3466
3509
|
journal.record(`submission ${room.locator} ${head.id}`);
|
|
3467
3510
|
await exclusive(() => mark(local, room.locator, head, { state: 'submitted' }, lost));
|
|
@@ -3539,32 +3582,12 @@ export function composeRuntime(options) {
|
|
|
3539
3582
|
const other = partner === null ? undefined : discovered.get(partner);
|
|
3540
3583
|
if (result.kind === 'conflicted' ||
|
|
3541
3584
|
result.kind === 'rejected' ||
|
|
3542
|
-
result.kind === 'expired' ||
|
|
3543
3585
|
result.kind === 'retryable-refusal') {
|
|
3544
|
-
if (result.kind === 'expired' && lost && result.restored !== true) {
|
|
3545
|
-
journal.record(`outcome ${room.locator} ${head.id} expired`);
|
|
3546
|
-
await discardHeld(head, room, local, signal);
|
|
3547
|
-
return 'done';
|
|
3548
|
-
}
|
|
3549
|
-
if (result.kind === 'expired') {
|
|
3550
|
-
await exclusive(() => mark(local, room.locator, head, { state: 'waiting' }, partner !== null && lost));
|
|
3551
|
-
const there = other === undefined ? null : await advance(other, local, authorized, continuing);
|
|
3552
|
-
if (other !== undefined && there !== null) {
|
|
3553
|
-
await withhold(there, other, local, progress);
|
|
3554
|
-
return 'done';
|
|
3555
|
-
}
|
|
3556
|
-
const withheld = await advance(room, local, authorized, continuing);
|
|
3557
|
-
if (withheld !== null) {
|
|
3558
|
-
await withhold(withheld, room, local, progress);
|
|
3559
|
-
return 'done';
|
|
3560
|
-
}
|
|
3561
|
-
}
|
|
3562
3586
|
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` +
|
|
3563
3587
|
(result.kind === 'conflicted' && result.roomVersion !== undefined
|
|
3564
3588
|
? ` ${result.roomVersion}`
|
|
3565
3589
|
: ''));
|
|
3566
|
-
|
|
3567
|
-
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
3590
|
+
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
3568
3591
|
}
|
|
3569
3592
|
if (result.kind === 'retryable-refusal') {
|
|
3570
3593
|
const reset = result.reset;
|
|
@@ -3572,11 +3595,11 @@ export function composeRuntime(options) {
|
|
|
3572
3595
|
await exclusive(() => mark(local, room.locator, head, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
3573
3596
|
return reset === undefined ? 'again' : { at: Date.parse(reset) };
|
|
3574
3597
|
}
|
|
3575
|
-
if (result.kind === 'conflicted' || result.kind === 'rejected'
|
|
3598
|
+
if (result.kind === 'conflicted' || result.kind === 'rejected') {
|
|
3576
3599
|
const left = leaving(head, result);
|
|
3577
3600
|
const refreshed = async () => ({
|
|
3578
|
-
there: other === undefined ? null : await
|
|
3579
|
-
here: await
|
|
3601
|
+
there: other === undefined ? null : await advanced(other, local, authorized),
|
|
3602
|
+
here: await advanced(room, local, authorized),
|
|
3580
3603
|
});
|
|
3581
3604
|
const judged = result.kind === 'conflicted' && other !== undefined ? await refreshed() : null;
|
|
3582
3605
|
if (judged !== null && (judged.there !== null || judged.here !== null)) {
|
|
@@ -3596,16 +3619,19 @@ export function composeRuntime(options) {
|
|
|
3596
3619
|
return;
|
|
3597
3620
|
}
|
|
3598
3621
|
const confirmedLayer = await sliceAt(current.record, 'confirmed', [head]);
|
|
3599
|
-
const held =
|
|
3600
|
-
(await landedWhole(room.locator, head, confirmedLayer))
|
|
3601
|
-
|
|
3602
|
-
|
|
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
|
+
}
|
|
3603
3631
|
publishChangeSet(await settle(local, room.locator, current, current.record, current.queue.filter((batch) => batch.id !== head.id), { leaving: [held ? { ...left, returned: null } : left] }));
|
|
3604
3632
|
await unpair(local);
|
|
3605
3633
|
moveTo({ ...status, ...attention() });
|
|
3606
3634
|
});
|
|
3607
|
-
if (result.kind === 'expired')
|
|
3608
|
-
continue;
|
|
3609
3635
|
const { there, here } = judged ?? (await refreshed());
|
|
3610
3636
|
if (other !== undefined && there !== null)
|
|
3611
3637
|
await withhold(there, other, local, progress);
|
|
@@ -3625,80 +3651,16 @@ export function composeRuntime(options) {
|
|
|
3625
3651
|
}
|
|
3626
3652
|
return 'done';
|
|
3627
3653
|
}
|
|
3628
|
-
function pastHorizon(identity) {
|
|
3629
|
-
return (outcomeRetention !== null && identityTime(identity) + outcomeRetention * 1000 <= Date.now());
|
|
3630
|
-
}
|
|
3631
|
-
async function discardPending(rooms, local, signal) {
|
|
3632
|
-
const discarded = await exclusive(async () => {
|
|
3633
|
-
const held = rooms
|
|
3634
|
-
.filter(({ locator }) => !fences.has(locator))
|
|
3635
|
-
.map((room) => ({
|
|
3636
|
-
room,
|
|
3637
|
-
before: replicas.get(room.locator),
|
|
3638
|
-
queue: queueIn(room.locator),
|
|
3639
|
-
}));
|
|
3640
|
-
if (held.length === 0)
|
|
3641
|
-
return [];
|
|
3642
|
-
const answer = await local.commit({
|
|
3643
|
-
rooms: held.flatMap(({ room, before }) => before === undefined
|
|
3644
|
-
? []
|
|
3645
|
-
: [
|
|
3646
|
-
{
|
|
3647
|
-
locator: room.locator,
|
|
3648
|
-
key: before.record.key,
|
|
3649
|
-
roots: before.record.typedSelection?.roots ?? [],
|
|
3650
|
-
composed: composedOf(composed(before.record)),
|
|
3651
|
-
overlay: { set: [], cleared: [...before.overlaid] },
|
|
3652
|
-
},
|
|
3653
|
-
]),
|
|
3654
|
-
discards: held.map(({ room }) => room.locator),
|
|
3655
|
-
});
|
|
3656
|
-
for (const { room, before, queue } of held) {
|
|
3657
|
-
const { locator } = room;
|
|
3658
|
-
lastFenced.set(locator, ++fencings);
|
|
3659
|
-
for (const batch of queue)
|
|
3660
|
-
pendings.delete(pendingKey(locator, batch.id));
|
|
3661
|
-
queuesWithoutReplica.delete(locator);
|
|
3662
|
-
if (before !== undefined) {
|
|
3663
|
-
replicas.set(locator, {
|
|
3664
|
-
...before,
|
|
3665
|
-
queue: [],
|
|
3666
|
-
overlaid: new Set(),
|
|
3667
|
-
count: answer.counts[locator] ?? before.count,
|
|
3668
|
-
});
|
|
3669
|
-
}
|
|
3670
|
-
journal.record(`room discarded ${locator}`);
|
|
3671
|
-
}
|
|
3672
|
-
publishChangeSet(mergeChangeSets(held.map(({ room }) => changeSet(room.locator, {
|
|
3673
|
-
documents: answer.changed[room.locator] ?? [],
|
|
3674
|
-
queue: true,
|
|
3675
|
-
}))));
|
|
3676
|
-
moveTo({ ...status, ...attention() });
|
|
3677
|
-
return held.map(({ room }) => room);
|
|
3678
|
-
});
|
|
3679
|
-
for (const room of discarded) {
|
|
3680
|
-
if (signal.aborted)
|
|
3681
|
-
return;
|
|
3682
|
-
const progress = { complete: true, reachable: true };
|
|
3683
|
-
await roomTask(room.locator, async (live) => {
|
|
3684
|
-
await Promise.race([
|
|
3685
|
-
reload(room, local, progress, () => live() && !signal.aborted),
|
|
3686
|
-
abandonedOn(signal),
|
|
3687
|
-
]);
|
|
3688
|
-
});
|
|
3689
|
-
if (signal.aborted)
|
|
3690
|
-
return;
|
|
3691
|
-
await rest(progress);
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
3654
|
function awaitingAnswer(locator) {
|
|
3655
|
+
answering.get(locator)?.release();
|
|
3695
3656
|
let release = () => { };
|
|
3696
|
-
const
|
|
3657
|
+
const answered = new Promise((resolve) => {
|
|
3697
3658
|
release = resolve;
|
|
3698
3659
|
});
|
|
3699
|
-
|
|
3660
|
+
const awaited = { answered, release };
|
|
3661
|
+
answering.set(locator, awaited);
|
|
3700
3662
|
return () => {
|
|
3701
|
-
if (answering.get(locator)
|
|
3663
|
+
if (answering.get(locator) === awaited)
|
|
3702
3664
|
answering.delete(locator);
|
|
3703
3665
|
release();
|
|
3704
3666
|
};
|
|
@@ -3710,6 +3672,30 @@ export function composeRuntime(options) {
|
|
|
3710
3672
|
JSON.stringify(held.replica) === JSON.stringify(room.replica) &&
|
|
3711
3673
|
JSON.stringify(held.typedSelection) === JSON.stringify(room.typedSelection ?? null)));
|
|
3712
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
|
+
}
|
|
3713
3699
|
async function retainModel(local, room, next) {
|
|
3714
3700
|
const key = room.locator;
|
|
3715
3701
|
const previous = modelOf(room.locator);
|
|
@@ -3727,6 +3713,8 @@ export function composeRuntime(options) {
|
|
|
3727
3713
|
if (held !== undefined) {
|
|
3728
3714
|
const record = {
|
|
3729
3715
|
...held.record,
|
|
3716
|
+
name: room.name,
|
|
3717
|
+
handle: room.handle,
|
|
3730
3718
|
operations: room.operations,
|
|
3731
3719
|
replica: room.replica,
|
|
3732
3720
|
typedSelection: room.typedSelection ?? null,
|
|
@@ -3797,6 +3785,17 @@ export function composeRuntime(options) {
|
|
|
3797
3785
|
emitRooms();
|
|
3798
3786
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
3799
3787
|
}
|
|
3788
|
+
function name(locator) {
|
|
3789
|
+
const stream = options.adapter.roomStream;
|
|
3790
|
+
const record = replicas.get(locator)?.record;
|
|
3791
|
+
const room = discovered.get(locator);
|
|
3792
|
+
if (stream === undefined || record === undefined || room === undefined)
|
|
3793
|
+
return;
|
|
3794
|
+
if (!socketOpen || !handed || record.replica.selection !== room.replica.selection)
|
|
3795
|
+
return;
|
|
3796
|
+
naming += 1;
|
|
3797
|
+
stream.hold([{ locator, roomVersion: record.roomVersion }]);
|
|
3798
|
+
}
|
|
3800
3799
|
function heldRooms(listed) {
|
|
3801
3800
|
return listed.flatMap((room) => {
|
|
3802
3801
|
const record = replicas.get(room.locator)?.record;
|
|
@@ -3814,14 +3813,19 @@ export function composeRuntime(options) {
|
|
|
3814
3813
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
3815
3814
|
});
|
|
3816
3815
|
if (attempt === 'opened') {
|
|
3816
|
+
naming += 1;
|
|
3817
3817
|
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
3818
3818
|
handed = true;
|
|
3819
3819
|
}
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3820
|
+
for (const room of ready) {
|
|
3821
|
+
if (attempt !== 'unstreamed' && replicas.has(room.locator)) {
|
|
3822
|
+
if (attempt === 'authorized')
|
|
3823
|
+
name(room.locator);
|
|
3824
|
+
continue;
|
|
3825
|
+
}
|
|
3826
|
+
void roomTask(room.locator, (live) => loadRoom(room, local, progress, live)).then(() => rest(progress));
|
|
3827
|
+
}
|
|
3828
|
+
const failed = prepared.find((room) => room.status === 'rejected');
|
|
3825
3829
|
if (failed !== undefined)
|
|
3826
3830
|
throw failed.reason;
|
|
3827
3831
|
}
|
|
@@ -3840,12 +3844,16 @@ export function composeRuntime(options) {
|
|
|
3840
3844
|
}
|
|
3841
3845
|
if (prior !== undefined)
|
|
3842
3846
|
await retainModel(local, room, modelOf(room.locator));
|
|
3847
|
+
if (prior !== undefined)
|
|
3848
|
+
await keepNaming(local, room);
|
|
3843
3849
|
if (discoveryPaused || authorization === undefined)
|
|
3844
3850
|
return false;
|
|
3845
3851
|
return room.replica.admission === 'admitted';
|
|
3846
3852
|
}
|
|
3847
3853
|
async function caughtUp(room, local, progress, live) {
|
|
3848
|
-
if (
|
|
3854
|
+
if (options.adapter.roomStream !== undefined || authorization === undefined)
|
|
3855
|
+
return;
|
|
3856
|
+
if (!live() || !behind(room))
|
|
3849
3857
|
return;
|
|
3850
3858
|
const withheld = await advance(room, local, authorization, live);
|
|
3851
3859
|
if (withheld !== null)
|
|
@@ -3858,6 +3866,8 @@ export function composeRuntime(options) {
|
|
|
3858
3866
|
async function loadRoom(room, local, progress, live) {
|
|
3859
3867
|
if (!live() || authorization === undefined)
|
|
3860
3868
|
return;
|
|
3869
|
+
if (replicas.has(room.locator) && !(await prepareRoom(room, local, progress)))
|
|
3870
|
+
return;
|
|
3861
3871
|
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
3862
3872
|
await reload(room, local, progress, live);
|
|
3863
3873
|
return;
|
|
@@ -3875,20 +3885,53 @@ export function composeRuntime(options) {
|
|
|
3875
3885
|
reloadsDue.delete(room.locator);
|
|
3876
3886
|
if (!live() || authorization === undefined)
|
|
3877
3887
|
return;
|
|
3888
|
+
const from = standingOf(room.locator);
|
|
3878
3889
|
const bootstrapped = await pullBootstrap(authorization, room, local, live);
|
|
3879
3890
|
if (bootstrapped.kind !== 'staged') {
|
|
3880
3891
|
if (live())
|
|
3881
3892
|
await withhold(bootstrapped, room, local, progress);
|
|
3882
3893
|
return;
|
|
3883
3894
|
}
|
|
3884
|
-
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed }, live);
|
|
3895
|
+
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from }, live);
|
|
3885
3896
|
await caughtUp(room, local, progress, live);
|
|
3886
3897
|
}
|
|
3887
3898
|
finally {
|
|
3888
3899
|
caughtUpTo(room.locator);
|
|
3889
3900
|
}
|
|
3890
3901
|
}
|
|
3891
|
-
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) {
|
|
3892
3935
|
if (closed || authorization === undefined) {
|
|
3893
3936
|
notifyReady();
|
|
3894
3937
|
return;
|
|
@@ -3896,13 +3939,7 @@ export function composeRuntime(options) {
|
|
|
3896
3939
|
moveTo(transferring(status));
|
|
3897
3940
|
discoveries += 1;
|
|
3898
3941
|
const started = discoveries;
|
|
3899
|
-
|
|
3900
|
-
try {
|
|
3901
|
-
result = await options.adapter.discover(authorization, scope);
|
|
3902
|
-
}
|
|
3903
|
-
catch {
|
|
3904
|
-
result = { kind: 'transport-uncertainty' };
|
|
3905
|
-
}
|
|
3942
|
+
const result = found ?? (await asked(authorization));
|
|
3906
3943
|
if (result.kind === 'discovery')
|
|
3907
3944
|
roomsNamed = true;
|
|
3908
3945
|
if (!live()) {
|
|
@@ -3912,7 +3949,7 @@ export function composeRuntime(options) {
|
|
|
3912
3949
|
}
|
|
3913
3950
|
journal.record(`connection discover ${result.kind}`);
|
|
3914
3951
|
if (result.kind === 'transport-uncertainty') {
|
|
3915
|
-
await
|
|
3952
|
+
await cachedModels(local);
|
|
3916
3953
|
progress.reachable = false;
|
|
3917
3954
|
progress.complete = false;
|
|
3918
3955
|
notifyReady();
|
|
@@ -3933,26 +3970,12 @@ export function composeRuntime(options) {
|
|
|
3933
3970
|
notifyReady();
|
|
3934
3971
|
return;
|
|
3935
3972
|
}
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
room.replica.selection.length === 0 ||
|
|
3943
|
-
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
3944
|
-
!['locked', 'open'].includes(room.replica.offline)) {
|
|
3945
|
-
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
3946
|
-
await refuse('missing-required-guarantee');
|
|
3947
|
-
notifyReady();
|
|
3948
|
-
return;
|
|
3949
|
-
}
|
|
3950
|
-
if (!declaredSelection(room)) {
|
|
3951
|
-
journal.record(`invalid typed selection declaration ${room.locator}`);
|
|
3952
|
-
await refuse('missing-required-guarantee');
|
|
3953
|
-
notifyReady();
|
|
3954
|
-
return;
|
|
3955
|
-
}
|
|
3973
|
+
const invalid = malformed(result.rooms);
|
|
3974
|
+
if (invalid !== null) {
|
|
3975
|
+
journal.record(invalid);
|
|
3976
|
+
await refuse('missing-required-guarantee');
|
|
3977
|
+
notifyReady();
|
|
3978
|
+
return;
|
|
3956
3979
|
}
|
|
3957
3980
|
if (refusal !== null) {
|
|
3958
3981
|
refusal = null;
|
|
@@ -3960,22 +3983,15 @@ export function composeRuntime(options) {
|
|
|
3960
3983
|
}
|
|
3961
3984
|
const previousCapabilities = capabilities;
|
|
3962
3985
|
capabilities = result.capabilities;
|
|
3963
|
-
outcomeRetention = result.retention.outcome;
|
|
3964
3986
|
notifyReady();
|
|
3965
3987
|
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
3966
3988
|
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)) {
|
|
3967
3989
|
limits = result.providerLimits;
|
|
3968
3990
|
await local.fact('providerLimits', limits);
|
|
3969
3991
|
await local.fact('capabilities', capabilities);
|
|
3970
|
-
conversations.declared();
|
|
3971
3992
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
await local.fact('member', signedIn);
|
|
3975
|
-
member = signedIn;
|
|
3976
|
-
emitMember();
|
|
3977
|
-
}
|
|
3978
|
-
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);
|
|
3979
3995
|
}
|
|
3980
3996
|
async function ensureAuthorization() {
|
|
3981
3997
|
if (authorization !== undefined)
|
|
@@ -3990,23 +4006,45 @@ export function composeRuntime(options) {
|
|
|
3990
4006
|
}
|
|
3991
4007
|
}
|
|
3992
4008
|
async function admittedRead(work) {
|
|
3993
|
-
|
|
3994
|
-
|
|
4009
|
+
while (roomReads.free === 0) {
|
|
4010
|
+
await new Promise((admit) => roomReads.waiting.push(admit));
|
|
3995
4011
|
}
|
|
3996
|
-
|
|
3997
|
-
while (reads.free === 0)
|
|
3998
|
-
await new Promise((admit) => reads.waiting.push(admit));
|
|
3999
|
-
reads.free -= 1;
|
|
4012
|
+
roomReads.free -= 1;
|
|
4000
4013
|
try {
|
|
4001
4014
|
await work();
|
|
4002
4015
|
}
|
|
4003
4016
|
finally {
|
|
4004
|
-
|
|
4005
|
-
|
|
4017
|
+
roomReads.free += 1;
|
|
4018
|
+
roomReads.waiting.shift()?.();
|
|
4006
4019
|
}
|
|
4007
4020
|
}
|
|
4021
|
+
function inRoom(locator, work) {
|
|
4022
|
+
roomWork += 1;
|
|
4023
|
+
const ran = (roomLanes.get(locator) ?? Promise.resolve()).then(async () => {
|
|
4024
|
+
try {
|
|
4025
|
+
return await work(continuing);
|
|
4026
|
+
}
|
|
4027
|
+
finally {
|
|
4028
|
+
roomWork -= 1;
|
|
4029
|
+
}
|
|
4030
|
+
});
|
|
4031
|
+
const chain = ran.then(() => undefined, () => undefined);
|
|
4032
|
+
roomLanes.set(locator, chain);
|
|
4033
|
+
working.add(chain);
|
|
4034
|
+
void chain.finally(() => {
|
|
4035
|
+
working.delete(chain);
|
|
4036
|
+
if (roomLanes.get(locator) === chain)
|
|
4037
|
+
roomLanes.delete(locator);
|
|
4038
|
+
});
|
|
4039
|
+
return ran;
|
|
4040
|
+
}
|
|
4008
4041
|
function roomTask(locator, work) {
|
|
4009
|
-
|
|
4042
|
+
if (stopping)
|
|
4043
|
+
return Promise.resolve();
|
|
4044
|
+
return inRoom(locator, (live) => admittedRead(() => work(live))).catch(() => undefined);
|
|
4045
|
+
}
|
|
4046
|
+
function advanced(room, local, authorized) {
|
|
4047
|
+
return inRoom(room.locator, (live) => advance(room, local, authorized, live));
|
|
4010
4048
|
}
|
|
4011
4049
|
function task(lane, work) {
|
|
4012
4050
|
if (stopping)
|
|
@@ -4022,23 +4060,19 @@ export function composeRuntime(options) {
|
|
|
4022
4060
|
void run.finally(() => working.delete(run));
|
|
4023
4061
|
return run;
|
|
4024
4062
|
}
|
|
4025
|
-
function sync(attempt) {
|
|
4063
|
+
function sync(attempt, found) {
|
|
4026
4064
|
return task(OPENING_LANE, async (live) => {
|
|
4027
4065
|
if (!live()) {
|
|
4028
4066
|
readyFromStore();
|
|
4029
4067
|
return;
|
|
4030
4068
|
}
|
|
4031
4069
|
const progress = { complete: true, reachable: true };
|
|
4032
|
-
|
|
4033
|
-
if (attempt === 'opened')
|
|
4034
|
-
opened = generation;
|
|
4035
|
-
underway = attempt === 'opened' ? { generation, progress } : null;
|
|
4070
|
+
underway = attempt === 'opened' ? progress : null;
|
|
4036
4071
|
if (attempt === 'opened')
|
|
4037
4072
|
streamed = progress;
|
|
4038
4073
|
complete = false;
|
|
4039
4074
|
recorded = false;
|
|
4040
4075
|
moveTo(transferring(status));
|
|
4041
|
-
handed = false;
|
|
4042
4076
|
try {
|
|
4043
4077
|
const local = await openLocal();
|
|
4044
4078
|
if (!live())
|
|
@@ -4046,7 +4080,7 @@ export function composeRuntime(options) {
|
|
|
4046
4080
|
await ensureAuthorization();
|
|
4047
4081
|
if (!live())
|
|
4048
4082
|
return;
|
|
4049
|
-
await discoverRooms(local, progress, attempt, live);
|
|
4083
|
+
await discoverRooms(local, progress, attempt, live, found);
|
|
4050
4084
|
}
|
|
4051
4085
|
catch {
|
|
4052
4086
|
progress.complete = false;
|
|
@@ -4054,7 +4088,6 @@ export function composeRuntime(options) {
|
|
|
4054
4088
|
progress.reachable = false;
|
|
4055
4089
|
}
|
|
4056
4090
|
finally {
|
|
4057
|
-
gate.release();
|
|
4058
4091
|
notifyReady();
|
|
4059
4092
|
}
|
|
4060
4093
|
if (!live())
|
|
@@ -4066,35 +4099,59 @@ export function composeRuntime(options) {
|
|
|
4066
4099
|
if (handed || closed || progress.reachable)
|
|
4067
4100
|
return;
|
|
4068
4101
|
underway = null;
|
|
4069
|
-
opened = null;
|
|
4070
|
-
journal.record('connection reopen');
|
|
4071
4102
|
await rest(progress, live);
|
|
4072
|
-
options.adapter.roomStream?.reopen();
|
|
4073
4103
|
});
|
|
4074
4104
|
}
|
|
4075
|
-
function
|
|
4105
|
+
function resynchronize() {
|
|
4076
4106
|
return task(OPENING_LANE, async (live) => {
|
|
4077
|
-
if (!live() || !
|
|
4107
|
+
if (!live() || !announced)
|
|
4078
4108
|
return;
|
|
4079
4109
|
const progress = { complete: true, reachable: true };
|
|
4080
|
-
const gate = discover();
|
|
4081
4110
|
moveTo(transferring(status));
|
|
4082
4111
|
try {
|
|
4083
4112
|
const local = await openLocal();
|
|
4084
4113
|
if (!live())
|
|
4085
4114
|
return;
|
|
4086
|
-
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();
|
|
4087
4138
|
if (!live())
|
|
4088
4139
|
return;
|
|
4089
|
-
|
|
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);
|
|
4090
4151
|
}
|
|
4091
4152
|
catch {
|
|
4092
4153
|
progress.complete = false;
|
|
4093
4154
|
}
|
|
4094
|
-
finally {
|
|
4095
|
-
gate.release();
|
|
4096
|
-
notifyReady();
|
|
4097
|
-
}
|
|
4098
4155
|
if (!live())
|
|
4099
4156
|
return;
|
|
4100
4157
|
if (!progress.complete)
|
|
@@ -4102,15 +4159,17 @@ export function composeRuntime(options) {
|
|
|
4102
4159
|
await rest(progress, live);
|
|
4103
4160
|
});
|
|
4104
4161
|
}
|
|
4105
|
-
function closedSocket(revoked, lost) {
|
|
4106
|
-
|
|
4107
|
-
const opened =
|
|
4162
|
+
function closedSocket(revoked, lost, authorityChanged) {
|
|
4163
|
+
notifyReady();
|
|
4164
|
+
const opened = announced;
|
|
4108
4165
|
socketOpen = false;
|
|
4166
|
+
announced = false;
|
|
4167
|
+
missedChange ||= authorityChanged;
|
|
4168
|
+
handed = false;
|
|
4169
|
+
naming = 0;
|
|
4109
4170
|
connectionLost();
|
|
4110
|
-
if (lost)
|
|
4111
|
-
reads.lost();
|
|
4171
|
+
if (lost)
|
|
4112
4172
|
conversations.lost();
|
|
4113
|
-
}
|
|
4114
4173
|
if (revoked) {
|
|
4115
4174
|
void refuse('revoked-authorization');
|
|
4116
4175
|
return;
|
|
@@ -4121,18 +4180,32 @@ export function composeRuntime(options) {
|
|
|
4121
4180
|
moveTo({
|
|
4122
4181
|
...status,
|
|
4123
4182
|
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
4124
|
-
providerUnreachable: status.providerUnreachable || !opened,
|
|
4183
|
+
providerUnreachable: status.providerUnreachable || (!opened && lost),
|
|
4125
4184
|
});
|
|
4185
|
+
void closing(opened);
|
|
4126
4186
|
}
|
|
4127
|
-
function
|
|
4128
|
-
|
|
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) {
|
|
4201
|
+
const progress = underway;
|
|
4202
|
+
underway = null;
|
|
4203
|
+
naming = Math.max(0, naming - 1);
|
|
4129
4204
|
const cycle = progress ?? { complete: true, reachable: true };
|
|
4130
|
-
|
|
4131
|
-
opened = null;
|
|
4205
|
+
replaced(expired);
|
|
4132
4206
|
const reloading = progress === null ? [] : owedReloads(expired);
|
|
4133
4207
|
const replacing = reloadNow((room) => expired.includes(room.locator) || reloading.includes(room.locator), cycle);
|
|
4134
4208
|
return task(OPENING_LANE, async (live) => {
|
|
4135
|
-
underway = null;
|
|
4136
4209
|
if (!live())
|
|
4137
4210
|
return;
|
|
4138
4211
|
if (progress === null) {
|
|
@@ -4140,20 +4213,14 @@ export function composeRuntime(options) {
|
|
|
4140
4213
|
await rest(cycle, live);
|
|
4141
4214
|
return;
|
|
4142
4215
|
}
|
|
4143
|
-
await roomsCarried([...expired, ...reloading]);
|
|
4144
|
-
if (!live())
|
|
4145
|
-
return;
|
|
4146
4216
|
try {
|
|
4147
4217
|
const local = await openLocal();
|
|
4148
|
-
|
|
4218
|
+
await replacing;
|
|
4149
4219
|
if (progress.reachable) {
|
|
4150
4220
|
await settleModels([...discovered.values()].filter((room) => replicas.has(room.locator) &&
|
|
4151
4221
|
!expired.includes(room.locator) &&
|
|
4152
4222
|
!reloading.includes(room.locator)), 'current', local);
|
|
4153
4223
|
}
|
|
4154
|
-
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
4155
|
-
if (failed !== undefined)
|
|
4156
|
-
throw failed.reason;
|
|
4157
4224
|
}
|
|
4158
4225
|
catch {
|
|
4159
4226
|
progress.complete = false;
|
|
@@ -4165,11 +4232,8 @@ export function composeRuntime(options) {
|
|
|
4165
4232
|
}
|
|
4166
4233
|
function owedReloads(expired) {
|
|
4167
4234
|
const reloading = [...discovered.values()]
|
|
4168
|
-
.filter((room) => replicas.has(room.locator) &&
|
|
4169
|
-
!expired.includes(room.locator) &&
|
|
4170
|
-
(owed.has(room.locator) || revoked(room.locator)))
|
|
4235
|
+
.filter((room) => replicas.has(room.locator) && !expired.includes(room.locator) && revoked(room.locator))
|
|
4171
4236
|
.map((room) => room.locator);
|
|
4172
|
-
owed.clear();
|
|
4173
4237
|
for (const locator of reloading)
|
|
4174
4238
|
catching.add(locator);
|
|
4175
4239
|
liveQueries.recover();
|
|
@@ -4178,16 +4242,17 @@ export function composeRuntime(options) {
|
|
|
4178
4242
|
async function reloadNow(replaced, progress) {
|
|
4179
4243
|
const reloaded = [...discovered.values()].filter((room) => replicas.has(room.locator) && replaced(room));
|
|
4180
4244
|
for (const room of reloaded)
|
|
4181
|
-
reloadsDue.
|
|
4245
|
+
reloadsDue.add(room.locator);
|
|
4182
4246
|
try {
|
|
4183
4247
|
const local = await openLocal();
|
|
4184
|
-
|
|
4248
|
+
for (const room of reloaded) {
|
|
4249
|
+
void roomTask(room.locator, (live) => reload(room, local, progress, live)).then(() => rest(progress));
|
|
4250
|
+
}
|
|
4185
4251
|
}
|
|
4186
4252
|
catch {
|
|
4187
4253
|
for (const room of reloaded)
|
|
4188
4254
|
reloadsDue.delete(room.locator);
|
|
4189
4255
|
progress.reachable = false;
|
|
4190
|
-
return [];
|
|
4191
4256
|
}
|
|
4192
4257
|
}
|
|
4193
4258
|
async function settleModels(held, status, local) {
|
|
@@ -4258,14 +4323,10 @@ export function composeRuntime(options) {
|
|
|
4258
4323
|
try {
|
|
4259
4324
|
const local = await openLocal();
|
|
4260
4325
|
await roomTask(locator, async (live) => {
|
|
4261
|
-
|
|
4262
|
-
if (!current() || authorization === undefined)
|
|
4326
|
+
if (!live() || authorization === undefined)
|
|
4263
4327
|
return;
|
|
4264
|
-
const withheld = await
|
|
4265
|
-
|
|
4266
|
-
abandonedOn(signal),
|
|
4267
|
-
]);
|
|
4268
|
-
if (withheld !== null && current())
|
|
4328
|
+
const withheld = await advance(room, local, authorization, live);
|
|
4329
|
+
if (withheld !== null && live())
|
|
4269
4330
|
await withhold(withheld, room, local, progress);
|
|
4270
4331
|
});
|
|
4271
4332
|
}
|
|
@@ -4320,7 +4381,6 @@ export function composeRuntime(options) {
|
|
|
4320
4381
|
progress.complete = false;
|
|
4321
4382
|
return;
|
|
4322
4383
|
}
|
|
4323
|
-
reads.applied();
|
|
4324
4384
|
submitAll();
|
|
4325
4385
|
for (const locator of stale)
|
|
4326
4386
|
if (repairable.has(locator))
|
|
@@ -4351,7 +4411,7 @@ export function composeRuntime(options) {
|
|
|
4351
4411
|
if (!live())
|
|
4352
4412
|
return;
|
|
4353
4413
|
let lastCompleted = status.lastCompleted;
|
|
4354
|
-
let synced = complete && stale.size === 0 && !pending();
|
|
4414
|
+
let synced = complete && roomWork === 0 && naming === 0 && stale.size === 0 && !pending();
|
|
4355
4415
|
if (synced && !recorded) {
|
|
4356
4416
|
const completedAt = new Date().toISOString();
|
|
4357
4417
|
try {
|
|
@@ -4366,75 +4426,69 @@ export function composeRuntime(options) {
|
|
|
4366
4426
|
}
|
|
4367
4427
|
}
|
|
4368
4428
|
moveTo({
|
|
4369
|
-
state: pending()
|
|
4429
|
+
state: pending()
|
|
4430
|
+
? 'pending'
|
|
4431
|
+
: synced && complete && roomWork === 0 && naming === 0
|
|
4432
|
+
? 'synced'
|
|
4433
|
+
: 'syncing',
|
|
4370
4434
|
lastCompleted,
|
|
4371
|
-
providerUnreachable: !progress.reachable,
|
|
4435
|
+
providerUnreachable: progress === null ? status.providerUnreachable : !progress.reachable,
|
|
4372
4436
|
...attention(),
|
|
4373
4437
|
});
|
|
4374
4438
|
}
|
|
4375
|
-
function
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
}
|
|
4383
|
-
function applyRoom(locator, roomVersion) {
|
|
4384
|
-
announced.set(locator, roomVersion);
|
|
4385
|
-
return task(roomLane(locator), async (live) => {
|
|
4386
|
-
if (discovering?.generation === generation)
|
|
4387
|
-
await discovering.done;
|
|
4388
|
-
const saving = answering.get(locator);
|
|
4389
|
-
if (saving?.generation === generation)
|
|
4390
|
-
await saving.answered;
|
|
4391
|
-
if (!live() || authorization === undefined)
|
|
4392
|
-
return;
|
|
4393
|
-
const room = discovered.get(locator);
|
|
4394
|
-
const held = replicas.get(locator);
|
|
4395
|
-
if (room === undefined || room.replica.admission !== 'admitted')
|
|
4396
|
-
return;
|
|
4397
|
-
if (held !== undefined &&
|
|
4398
|
-
(held.record.replica.selection !== room.replica.selection ||
|
|
4399
|
-
options.adapter.reached(locator, held.record.roomVersion, roomVersion))) {
|
|
4400
|
-
return;
|
|
4401
|
-
}
|
|
4402
|
-
const during = underway?.generation === generation ? underway.progress : null;
|
|
4403
|
-
const progress = during ?? { complete: true, reachable: true };
|
|
4404
|
-
if (during === null)
|
|
4405
|
-
moveTo(transferring(status));
|
|
4439
|
+
function applyRoom(change) {
|
|
4440
|
+
const { locator } = change;
|
|
4441
|
+
if (stopping)
|
|
4442
|
+
return Promise.resolve();
|
|
4443
|
+
return inRoom(locator, async (live) => {
|
|
4444
|
+
await answering.get(locator)?.answered;
|
|
4445
|
+
const progress = underway ?? { complete: true, reachable: true };
|
|
4406
4446
|
try {
|
|
4407
4447
|
const local = await openLocal();
|
|
4408
|
-
|
|
4409
|
-
if (!live()
|
|
4410
|
-
return;
|
|
4411
|
-
const
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4448
|
+
for (;;) {
|
|
4449
|
+
if (!live())
|
|
4450
|
+
return null;
|
|
4451
|
+
const room = discovered.get(locator);
|
|
4452
|
+
const held = replicas.get(locator);
|
|
4453
|
+
if (room === undefined ||
|
|
4454
|
+
held === undefined ||
|
|
4455
|
+
room.replica.admission !== 'admitted' ||
|
|
4456
|
+
held.record.replica.selection !== room.replica.selection) {
|
|
4457
|
+
return null;
|
|
4458
|
+
}
|
|
4459
|
+
const at = held.record.roomVersion;
|
|
4460
|
+
if (options.adapter.reached(locator, at, change.roomVersion))
|
|
4461
|
+
return progress;
|
|
4462
|
+
if (!options.adapter.reached(locator, at, change.from)) {
|
|
4463
|
+
name(locator);
|
|
4464
|
+
return progress;
|
|
4465
|
+
}
|
|
4466
|
+
if (underway === null)
|
|
4467
|
+
moveTo(transferring(status));
|
|
4468
|
+
const writing = () => live() && !full;
|
|
4469
|
+
const refreshed = await ahead((hold) => pullRefresh(authorization ?? {}, held, writing, hold, () => change.read()));
|
|
4470
|
+
if (refreshed.kind !== 'advanced')
|
|
4471
|
+
return progress;
|
|
4472
|
+
const landing = {
|
|
4473
|
+
kind: 'refreshed',
|
|
4474
|
+
from: refreshed.from,
|
|
4475
|
+
remodelled: refreshed.remodelled,
|
|
4476
|
+
};
|
|
4477
|
+
if (await publish(local, room, refreshed.record, landing, writing)) {
|
|
4478
|
+
stale.delete(locator);
|
|
4479
|
+
await retainModel(local, room, modelOf(locator));
|
|
4480
|
+
return progress;
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4415
4483
|
}
|
|
4416
4484
|
catch {
|
|
4485
|
+
if (!stale.has(locator))
|
|
4486
|
+
name(locator);
|
|
4417
4487
|
stale.add(locator);
|
|
4488
|
+
return progress;
|
|
4418
4489
|
}
|
|
4419
|
-
|
|
4420
|
-
if (during === null)
|
|
4421
|
-
await rest(progress, live);
|
|
4422
|
-
}
|
|
4423
|
-
});
|
|
4490
|
+
}).then((progress) => rest(progress), () => undefined);
|
|
4424
4491
|
}
|
|
4425
|
-
function roomsCarried(replacing) {
|
|
4426
|
-
const replaced = new Set(replacing.map(roomLane));
|
|
4427
|
-
return Promise.all([...lanes]
|
|
4428
|
-
.filter(([lane, held]) => lane !== OPENING_LANE && !replaced.has(lane) && held.generation === generation)
|
|
4429
|
-
.map(([, held]) => held.chain));
|
|
4430
|
-
}
|
|
4431
|
-
const receipts = {
|
|
4432
|
-
heads: async () => (await openLocal()).receivedHeads(),
|
|
4433
|
-
parts: async (key, count) => (await openLocal()).receivedParts(key, count),
|
|
4434
|
-
keep: async (key, head, part) => (await openLocal()).keepReceived(key, head, part),
|
|
4435
|
-
drop: async (keys) => (await openLocal()).dropReceived(keys),
|
|
4436
|
-
ahead,
|
|
4437
|
-
};
|
|
4438
4492
|
function observeStream() {
|
|
4439
4493
|
const stream = options.adapter.roomStream;
|
|
4440
4494
|
if (stream === undefined || withdrawStream !== undefined)
|
|
@@ -4444,31 +4498,33 @@ export function composeRuntime(options) {
|
|
|
4444
4498
|
return;
|
|
4445
4499
|
if (event.kind === 'opened') {
|
|
4446
4500
|
socketOpen = true;
|
|
4501
|
+
announced = false;
|
|
4502
|
+
handed = false;
|
|
4503
|
+
naming = 0;
|
|
4447
4504
|
connectionLive();
|
|
4448
4505
|
generation += 1;
|
|
4449
|
-
for (const admit of installWaiters.splice(0))
|
|
4450
|
-
admit();
|
|
4451
|
-
reads.opened();
|
|
4452
4506
|
conversations.reopened();
|
|
4453
4507
|
recovery.resume();
|
|
4454
|
-
|
|
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);
|
|
4455
4518
|
}
|
|
4456
4519
|
else if (event.kind === 'closed')
|
|
4457
|
-
closedSocket(event.revoked, event.lost);
|
|
4458
|
-
else if (event.kind === '
|
|
4459
|
-
void
|
|
4520
|
+
closedSocket(event.revoked, event.lost, event.authorityChanged);
|
|
4521
|
+
else if (event.kind === 'caught-up')
|
|
4522
|
+
void caughtUpRooms(event.expired);
|
|
4460
4523
|
else if (event.kind === 'authority-changed')
|
|
4461
|
-
void
|
|
4524
|
+
void changeAuthority(event);
|
|
4462
4525
|
else
|
|
4463
|
-
void applyRoom(event
|
|
4464
|
-
}
|
|
4465
|
-
}
|
|
4466
|
-
async function heldRevisions(locator, paths) {
|
|
4467
|
-
const held = replicas.get(locator);
|
|
4468
|
-
if (held === undefined || paths.length === 0)
|
|
4469
|
-
return new Map();
|
|
4470
|
-
const entries = await ahead(async () => (await openLocal()).entries(held.record.key, 'confirmed', paths));
|
|
4471
|
-
return new Map(entries.flatMap(({ path, revision }) => revision === null ? [] : [[path, revision]]));
|
|
4526
|
+
void applyRoom(event);
|
|
4527
|
+
});
|
|
4472
4528
|
}
|
|
4473
4529
|
function writesHeld() {
|
|
4474
4530
|
return serviceMode === 'write-halted' || haltedRefusal;
|
|
@@ -4558,6 +4614,7 @@ export function composeRuntime(options) {
|
|
|
4558
4614
|
statusListeners.clear();
|
|
4559
4615
|
serviceModeListeners.clear();
|
|
4560
4616
|
memberListeners.clear();
|
|
4617
|
+
authorityChangeListeners.clear();
|
|
4561
4618
|
liveQueries.close();
|
|
4562
4619
|
queueSubscriptions.clear();
|
|
4563
4620
|
roomDraftListeners.clear();
|
|
@@ -4565,6 +4622,7 @@ export function composeRuntime(options) {
|
|
|
4565
4622
|
}
|
|
4566
4623
|
async function stop() {
|
|
4567
4624
|
stopping = true;
|
|
4625
|
+
inFlight.abort();
|
|
4568
4626
|
await recovery.retire();
|
|
4569
4627
|
await Promise.all(working);
|
|
4570
4628
|
await mutations;
|
|
@@ -4583,15 +4641,15 @@ export function composeRuntime(options) {
|
|
|
4583
4641
|
readyFromStore();
|
|
4584
4642
|
});
|
|
4585
4643
|
if (options.adapter.roomStream === undefined)
|
|
4586
|
-
void sync('unstreamed');
|
|
4644
|
+
void sync('unstreamed', null);
|
|
4587
4645
|
function planned(input, reach) {
|
|
4588
|
-
const plan =
|
|
4646
|
+
const plan = planWithin(input, roomFacts(reach), planReach(reach));
|
|
4589
4647
|
const named = plan.paths !== null &&
|
|
4590
4648
|
plan.rooms !== null &&
|
|
4591
4649
|
plan.paths.length * plan.rooms.length <= MAXIMUM_WINDOW;
|
|
4592
4650
|
return plan.limit === null && named ? plan : { ...plan, limit: capped(plan.limit) };
|
|
4593
4651
|
}
|
|
4594
|
-
function watchPlan(plan, deliver) {
|
|
4652
|
+
function watchPlan(plan, deliver, fail) {
|
|
4595
4653
|
let seeded = false;
|
|
4596
4654
|
let pending = true;
|
|
4597
4655
|
const shown = () => {
|
|
@@ -4617,7 +4675,7 @@ export function composeRuntime(options) {
|
|
|
4617
4675
|
}
|
|
4618
4676
|
seeded = false;
|
|
4619
4677
|
deliver(message);
|
|
4620
|
-
});
|
|
4678
|
+
}, fail);
|
|
4621
4679
|
return () => {
|
|
4622
4680
|
watchers.delete(shown);
|
|
4623
4681
|
if (watchers.size === 0)
|
|
@@ -4625,61 +4683,59 @@ export function composeRuntime(options) {
|
|
|
4625
4683
|
stop();
|
|
4626
4684
|
};
|
|
4627
4685
|
}
|
|
4628
|
-
async function searched(input, reach
|
|
4686
|
+
async function searched(input, reach) {
|
|
4629
4687
|
const asked = searchPlanOf(input);
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
const cover = covered(filter);
|
|
4639
|
-
awaitPreparation(cover);
|
|
4640
|
-
const pending = [...unlocking, ...('pending' in cover ? cover.pending : [])];
|
|
4641
|
-
const complete = pending.length === 0 && !('pending' in cover);
|
|
4642
|
-
const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
|
|
4643
|
-
const answering = complete
|
|
4644
|
-
? filter
|
|
4645
|
-
: { ...filter, rooms: reachedRooms(filter).filter((room) => !awaited.has(room)) };
|
|
4646
|
-
const answered = covered(answering);
|
|
4647
|
-
if ('pending' in answered)
|
|
4648
|
-
continue;
|
|
4649
|
-
const listed = conversations.searchable();
|
|
4650
|
-
const named = reach !== null && !reach.conversations
|
|
4651
|
-
? []
|
|
4652
|
-
: (asked.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
|
|
4653
|
-
const hits = await (await openLocal()).search({
|
|
4654
|
-
text: asked.text,
|
|
4655
|
-
limit: capped(asked.limit),
|
|
4656
|
-
after: asked.after,
|
|
4657
|
-
total: asked.total,
|
|
4658
|
-
documents: {
|
|
4659
|
-
plan: answering,
|
|
4660
|
-
rooms: answered.rooms,
|
|
4661
|
-
beneath: asked.collection
|
|
4662
|
-
? answered.rooms.map(({ locator, key }) => ({
|
|
4663
|
-
key,
|
|
4664
|
-
roots: replicas.get(locator)?.record.typedSelection?.roots ?? [],
|
|
4665
|
-
}))
|
|
4666
|
-
: [],
|
|
4667
|
-
},
|
|
4668
|
-
conversations: named,
|
|
4669
|
-
});
|
|
4670
|
-
if (JSON.stringify(covered(answering)) !== JSON.stringify(answered))
|
|
4671
|
-
continue;
|
|
4672
|
-
const coverage = {
|
|
4673
|
-
rooms: answered.coverage.rooms,
|
|
4674
|
-
conversations: named,
|
|
4675
|
-
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,
|
|
4676
4696
|
};
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
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;
|
|
4680
4736
|
}
|
|
4681
4737
|
}
|
|
4682
|
-
const watch = (topic, input, deliver) => {
|
|
4738
|
+
const watch = (topic, input, deliver, fail) => {
|
|
4683
4739
|
if (topic === 'room-drafts') {
|
|
4684
4740
|
const listener = { notify: deliver, ready: false };
|
|
4685
4741
|
roomDraftListeners.add(listener);
|
|
@@ -4728,7 +4784,7 @@ export function composeRuntime(options) {
|
|
|
4728
4784
|
};
|
|
4729
4785
|
}
|
|
4730
4786
|
if (topic === 'readiness') {
|
|
4731
|
-
return watchReadiness({ target: '', code: 'stored'
|
|
4787
|
+
return watchReadiness({ target: '', code: 'stored' }, null, deliver);
|
|
4732
4788
|
}
|
|
4733
4789
|
if (topic === 'rooms') {
|
|
4734
4790
|
unlistedRooms.add(deliver);
|
|
@@ -4773,6 +4829,13 @@ export function composeRuntime(options) {
|
|
|
4773
4829
|
memberListeners.delete(deliver);
|
|
4774
4830
|
};
|
|
4775
4831
|
}
|
|
4832
|
+
if (topic === 'authority-changes') {
|
|
4833
|
+
authorityChangeListeners.add(deliver);
|
|
4834
|
+
deliver(authorityChanges);
|
|
4835
|
+
return () => {
|
|
4836
|
+
authorityChangeListeners.delete(deliver);
|
|
4837
|
+
};
|
|
4838
|
+
}
|
|
4776
4839
|
if (topic === 'queue') {
|
|
4777
4840
|
const queue = { locator: input[0], notify: deliver };
|
|
4778
4841
|
queueSubscriptions.add(queue);
|
|
@@ -4781,7 +4844,7 @@ export function composeRuntime(options) {
|
|
|
4781
4844
|
queueSubscriptions.delete(queue);
|
|
4782
4845
|
};
|
|
4783
4846
|
}
|
|
4784
|
-
return watchPlan(planned(input[0], null), deliver);
|
|
4847
|
+
return watchPlan(planned(input[0], null), deliver, fail);
|
|
4785
4848
|
};
|
|
4786
4849
|
const md = {
|
|
4787
4850
|
loaded() {
|
|
@@ -4801,7 +4864,7 @@ export function composeRuntime(options) {
|
|
|
4801
4864
|
});
|
|
4802
4865
|
},
|
|
4803
4866
|
query: ((input) => rowsQuery(planned(input, null))),
|
|
4804
|
-
search: (input) => searched(input, null
|
|
4867
|
+
search: (input) => searched(input, null),
|
|
4805
4868
|
types(locator) {
|
|
4806
4869
|
return {
|
|
4807
4870
|
collection: modelTypes(modelOf(locator)),
|
|
@@ -4904,6 +4967,8 @@ export function composeRuntime(options) {
|
|
|
4904
4967
|
return journal.export();
|
|
4905
4968
|
},
|
|
4906
4969
|
cleanUp,
|
|
4970
|
+
unsent: () => database.ask({ kind: 'authored.unsent' }),
|
|
4971
|
+
reset: () => options.shell.reset(),
|
|
4907
4972
|
persistence() {
|
|
4908
4973
|
return persistence === 'granted';
|
|
4909
4974
|
},
|
|
@@ -4926,7 +4991,7 @@ export function composeRuntime(options) {
|
|
|
4926
4991
|
authorization = undefined;
|
|
4927
4992
|
discoveryPaused = false;
|
|
4928
4993
|
if (options.adapter.roomStream === undefined)
|
|
4929
|
-
await sync('unstreamed');
|
|
4994
|
+
await sync('unstreamed', null);
|
|
4930
4995
|
else
|
|
4931
4996
|
await task(OPENING_LANE, ensureAuthorization);
|
|
4932
4997
|
},
|
|
@@ -4949,7 +5014,6 @@ export function composeRuntime(options) {
|
|
|
4949
5014
|
md,
|
|
4950
5015
|
seed,
|
|
4951
5016
|
stop,
|
|
4952
|
-
lose: loseRooms,
|
|
4953
5017
|
watch,
|
|
4954
5018
|
persisted(answer) {
|
|
4955
5019
|
if (answer === persistence)
|