@agentshouse/mdruntime 0.1.0-cb122f2ba154d → 0.1.0-cb1f56e16291b
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 -48
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +4 -7
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +19 -14
- package/dist/client.d.ts +4 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +0 -1
- package/dist/collection.d.ts +0 -3
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +1 -17
- package/dist/compile.d.ts +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +61 -43
- package/dist/connection.d.ts +3 -5
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +900 -861
- 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 -13
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +136 -144
- package/dist/durability.d.ts +0 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +1 -0
- package/dist/host.d.ts +3 -6
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +17 -19
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -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/media.d.ts +1 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +19 -9
- package/dist/memory.d.ts +8 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +131 -81
- 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 +0 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -9
- package/dist/observation.d.ts +2 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +7 -2
- package/dist/outcome.d.ts +0 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/phrase.d.ts +3 -0
- package/dist/phrase.d.ts.map +1 -0
- package/dist/phrase.js +8 -0
- package/dist/query.d.ts +5 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +20 -1
- package/dist/reach.d.ts +6 -5
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +30 -37
- package/dist/react.d.ts +1 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/readiness.d.ts +2 -15
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +4 -10
- package/dist/recovery.d.ts +1 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +26 -0
- package/dist/refresh.d.ts +7 -5
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +46 -6
- package/dist/replica.d.ts +0 -1
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +3 -4
- package/dist/room-draft.d.ts +2 -1
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +65 -52
- package/dist/rows.d.ts +3 -2
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +34 -29
- package/dist/search.d.ts +12 -63
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +9 -52
- package/dist/shared.d.ts +0 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +0 -10
- package/dist/status.d.ts +0 -4
- package/dist/status.d.ts.map +1 -1
- package/dist/store.d.ts +15 -19
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +40 -39
- package/dist/stored.d.ts +33 -6
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +92 -111
- package/dist/tables.d.ts +34 -33
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +18 -54
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/validation.d.ts +3 -4
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +35 -96
- package/dist/wire.d.ts +1 -4
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +1 -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 {
|
|
1
|
+
import { parseFrontmatter, roomTarget, target } from '@agentshouse/mdmodel';
|
|
2
2
|
import { attachedModel, builtInModel, roomTypesOf } 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 { bodied, identityOf, planOf, plain, queryKey, } 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';
|
|
@@ -12,7 +12,7 @@ import { bootstrapRoom } from './bootstrap.js';
|
|
|
12
12
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
13
13
|
import { createConversationStore, } from './conversation.js';
|
|
14
14
|
import { leaving as leavingIn, partnerOf, queuedFile, } from './change.js';
|
|
15
|
-
import { available, collectionOf, composedOf,
|
|
15
|
+
import { available, collectionOf, composedOf, validRoot, } from './collection.js';
|
|
16
16
|
import { runtimeError } from './failure.js';
|
|
17
17
|
import { createLog } from './log.js';
|
|
18
18
|
import { binaryRecord } from './binary.js';
|
|
@@ -20,10 +20,9 @@ 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
|
-
import { compositionOf, entryAt, landed, movedBy, overlayOf, replaying, returning, returnedPaths,
|
|
25
|
+
import { compositionOf, entryAt, landed, movedBy, overlayOf, replaying, returning, returnedPaths, slicing, slicingWith, standing, storedRecord, touchedBy, } from './replica.js';
|
|
27
26
|
import { binaryBase, changedParts, changesOf, gatheredParts, held, heldFile, joinedDraft, moveTarget, readParts, readSavedParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
|
|
28
27
|
import { same, saving, STARTING, transferring, } from './status.js';
|
|
29
28
|
import { detectStorageLoss, nextGeneration, refusedForSpace, } from './durability.js';
|
|
@@ -34,7 +33,7 @@ import { paced, ROW_WEIGHT, stepped, yielded } from './turn.js';
|
|
|
34
33
|
import { identityTime, roomDraftRevision } from './identity.js';
|
|
35
34
|
import { ancestors, beneath, normalize } from './paths.js';
|
|
36
35
|
import { openRuntimeStore, } from './store.js';
|
|
37
|
-
import {
|
|
36
|
+
import { validatedNeighbourhood } from './validation.js';
|
|
38
37
|
import { unconfirmed } from './transfer.js';
|
|
39
38
|
import { governs, holds, narrowed, planReach, } from './reach.js';
|
|
40
39
|
function within(root, path) {
|
|
@@ -59,37 +58,34 @@ const DERIVE_BYTES = 1 << 23;
|
|
|
59
58
|
const DERIVE_FLOOR = 1 << 18;
|
|
60
59
|
// An index build aims at this much database work, so a Save, a query or the next part's progress waits behind at most one short build.
|
|
61
60
|
const DERIVE_AIM_MS = 250;
|
|
62
|
-
// A build past its floor of text stops after a second and a half of work, so a Room whose text costs ten times the usual
|
|
61
|
+
// A build past its floor of text stops after a second and a half of work, its full-text writes included, so a Room whose text costs ten times the usual holds a Save, a query or the next part's progress behind no longer build.
|
|
63
62
|
const DERIVE_LONGEST_MS = 1_500;
|
|
64
|
-
const WALK_BYTES = 1 << 20;
|
|
65
63
|
const QUERY_BYTES = 1 << 20;
|
|
66
64
|
const STAGE_BYTES = 1 << 20;
|
|
67
65
|
const STAGE_FLOOR = 1 << 16;
|
|
68
66
|
// A batch aims at this much database work, so a Save or a query waits behind at most one short write.
|
|
69
67
|
const STAGE_AIM_MS = 50;
|
|
70
68
|
const STREAMS_HELD = 4;
|
|
71
|
-
const NOTHING_HELD = {
|
|
72
|
-
const
|
|
73
|
-
// Bytes still arriving are reported at most this often,
|
|
69
|
+
const NOTHING_HELD = { bytes: 0 };
|
|
70
|
+
const QUEUED_TEXT = 1 << 23;
|
|
71
|
+
// Bytes still arriving are reported at most this often, so readiness is not rebuilt for every chunk the network hands over while a slow link still moves the figure.
|
|
74
72
|
const ARRIVAL_REPORT_MS = 250;
|
|
75
|
-
const CLEAN_CHANGES = 2_000;
|
|
76
73
|
// A Room that locks offline stays readable this long after the connection drops, so a laptop waking or changing network keeps working while a disconnected device locks soon after.
|
|
77
74
|
const LOCK_GRACE_MS = 120_000;
|
|
78
75
|
function operationOf(input) {
|
|
79
76
|
const operation = input?.operation_id;
|
|
80
77
|
return typeof operation === 'string' ? operation : null;
|
|
81
78
|
}
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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));
|
|
88
86
|
});
|
|
89
87
|
}
|
|
90
|
-
|
|
91
|
-
return `room ${locator}`;
|
|
92
|
-
}
|
|
88
|
+
const TEXT_READS = 4;
|
|
93
89
|
function pendingKey(locator, id) {
|
|
94
90
|
return `${locator}\u0000${id}`;
|
|
95
91
|
}
|
|
@@ -109,6 +105,7 @@ export function composeRuntime(options) {
|
|
|
109
105
|
const usableWhenHeld = options.usableWhenHeld ?? [];
|
|
110
106
|
const queuesWithoutReplica = new FencedMap(isFenced);
|
|
111
107
|
const pendings = new Map();
|
|
108
|
+
const landedAhead = new Map();
|
|
112
109
|
const seeds = new Map();
|
|
113
110
|
const seedWatchers = new Map();
|
|
114
111
|
const roomDrafts = new FencedMap(isFenced);
|
|
@@ -124,11 +121,19 @@ export function composeRuntime(options) {
|
|
|
124
121
|
const liveModels = new FencedMap(isFenced);
|
|
125
122
|
const invalidModels = new Set();
|
|
126
123
|
const discovered = new Map();
|
|
127
|
-
|
|
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
|
+
};
|
|
128
135
|
let roomsListed = false;
|
|
129
136
|
const scopedRefusals = new Map();
|
|
130
|
-
const sourceRefusals = new Map();
|
|
131
|
-
const declarationRefusals = new Set();
|
|
132
137
|
function modelOf(locator) {
|
|
133
138
|
return models.get(locator) ?? NO_MODEL;
|
|
134
139
|
}
|
|
@@ -151,10 +156,10 @@ export function composeRuntime(options) {
|
|
|
151
156
|
let closed = false;
|
|
152
157
|
let stopping = false;
|
|
153
158
|
let withdrawStream;
|
|
154
|
-
const announced = new Map();
|
|
155
159
|
let underway = null;
|
|
156
160
|
let streamed = null;
|
|
157
161
|
let handed = false;
|
|
162
|
+
let naming = 0;
|
|
158
163
|
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
159
164
|
const repairing = new Set();
|
|
160
165
|
const calling = new Set();
|
|
@@ -165,12 +170,13 @@ export function composeRuntime(options) {
|
|
|
165
170
|
let recorded = false;
|
|
166
171
|
const stale = new Set();
|
|
167
172
|
let generation = 0;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
const reloadsDue = new Map();
|
|
173
|
+
const reloadsDue = new Set();
|
|
174
|
+
const highest = new Map();
|
|
175
|
+
const beyondHorizon = new Set();
|
|
172
176
|
const lanes = new Map();
|
|
173
|
-
|
|
177
|
+
const roomLanes = new Map();
|
|
178
|
+
let roomWork = 0;
|
|
179
|
+
const roomReads = { free: ROOM_READS, waiting: [] };
|
|
174
180
|
const working = new Set();
|
|
175
181
|
let mutations = Promise.resolve();
|
|
176
182
|
let gathering = null;
|
|
@@ -186,6 +192,8 @@ export function composeRuntime(options) {
|
|
|
186
192
|
let withdrawConversations;
|
|
187
193
|
let currencyQueued = null;
|
|
188
194
|
let socketOpen = false;
|
|
195
|
+
let announced = false;
|
|
196
|
+
let missedChange = false;
|
|
189
197
|
const confirmed = new Set();
|
|
190
198
|
let lockGrace = null;
|
|
191
199
|
const lockListeners = new Set();
|
|
@@ -195,6 +203,8 @@ export function composeRuntime(options) {
|
|
|
195
203
|
const serviceModeListeners = new Set();
|
|
196
204
|
let member = null;
|
|
197
205
|
const memberListeners = new Set();
|
|
206
|
+
let authorityChanges = 0;
|
|
207
|
+
const authorityChangeListeners = new Set();
|
|
198
208
|
const unpreparedDemand = new Set();
|
|
199
209
|
const unpreparable = new Set();
|
|
200
210
|
let preparing = false;
|
|
@@ -224,12 +234,10 @@ export function composeRuntime(options) {
|
|
|
224
234
|
const streaming = new Set();
|
|
225
235
|
const underived = new Map();
|
|
226
236
|
const unindexed = new Map();
|
|
227
|
-
const
|
|
237
|
+
const startedOver = new Map();
|
|
228
238
|
let arrivalReported = 0;
|
|
229
|
-
let
|
|
230
|
-
|
|
231
|
-
let streams = { generation, free: STREAMS_HELD };
|
|
232
|
-
let queuedBytes = 0;
|
|
239
|
+
let streamsFree = STREAMS_HELD;
|
|
240
|
+
let queuedText = 0;
|
|
233
241
|
const installWaiters = [];
|
|
234
242
|
const stageBudget = {
|
|
235
243
|
bytes: STAGE_FLOOR * 4,
|
|
@@ -243,28 +251,14 @@ export function composeRuntime(options) {
|
|
|
243
251
|
ceiling: DERIVE_BYTES,
|
|
244
252
|
aim: DERIVE_AIM_MS,
|
|
245
253
|
};
|
|
246
|
-
|
|
254
|
+
const installTurns = { busy: false, ahead: 0, finishing: [], starting: [] };
|
|
247
255
|
const conversations = createConversationStore({
|
|
248
|
-
agents: declaredAgents,
|
|
249
|
-
documents: (declaration, after) => rowsQuery(planOf({
|
|
250
|
-
rooms: [declaration.locator],
|
|
251
|
-
types: [declaration.type],
|
|
252
|
-
select: {
|
|
253
|
-
fields: [
|
|
254
|
-
declaration.identity,
|
|
255
|
-
declaration.name,
|
|
256
|
-
...(declaration.retired === undefined ? [] : [declaration.retired]),
|
|
257
|
-
].map((field) => `/${escapePointer(field)}`),
|
|
258
|
-
},
|
|
259
|
-
limit: MAXIMUM_WINDOW,
|
|
260
|
-
...(after === undefined ? {} : { after }),
|
|
261
|
-
}, roomFacts())),
|
|
262
256
|
call: (name, input) => invokeAction(name, input),
|
|
257
|
+
history: (request) => readConversationHistory(request),
|
|
263
258
|
durably,
|
|
264
259
|
stream: () => options.adapter.conversations,
|
|
265
260
|
publish: publishChangeSet,
|
|
266
261
|
online: () => connectionOf() !== 'unreachable' && refusal === null,
|
|
267
|
-
listed: () => roomsListed,
|
|
268
262
|
full: () => full,
|
|
269
263
|
closed: () => closed,
|
|
270
264
|
unanswered: resendLater,
|
|
@@ -272,6 +266,8 @@ export function composeRuntime(options) {
|
|
|
272
266
|
});
|
|
273
267
|
const journal = createLog();
|
|
274
268
|
const keptFiles = openedFiles(database, isFenced);
|
|
269
|
+
const holding = new Map();
|
|
270
|
+
const unholdable = new Map();
|
|
275
271
|
let readyFromStore = () => { };
|
|
276
272
|
const ready = new Promise((resolve) => {
|
|
277
273
|
readyFromStore = resolve;
|
|
@@ -361,9 +357,9 @@ export function composeRuntime(options) {
|
|
|
361
357
|
return value === undefined ? null : structuredClone(value);
|
|
362
358
|
}
|
|
363
359
|
async function writtenRoomDraft(room, path, revision, input, next) {
|
|
364
|
-
const parts = readParts(input);
|
|
360
|
+
const parts = readParts(input, path);
|
|
365
361
|
if (path === '' || normalize(path) !== path)
|
|
366
|
-
throw runtimeError('room-draft-malformed');
|
|
362
|
+
throw runtimeError('room-draft-malformed', `room-draft-malformed at ${path}: a path inside the Room, such as notes/a.md`);
|
|
367
363
|
const local = await openLocal();
|
|
368
364
|
const current = roomDrafts.get(room)?.get(path);
|
|
369
365
|
if ((current?.revision ?? null) !== revision) {
|
|
@@ -412,7 +408,7 @@ export function composeRuntime(options) {
|
|
|
412
408
|
const changed = new Map();
|
|
413
409
|
for (const room of [...replicas.keys(), ...queuesWithoutReplica.keys()]) {
|
|
414
410
|
for (const { batch, state } of entriesOf(room)) {
|
|
415
|
-
if (state === '
|
|
411
|
+
if (state === 'local')
|
|
416
412
|
continue;
|
|
417
413
|
for (const { path } of batch.changes)
|
|
418
414
|
changed.set(`${room}\u0000${path}`, { room, path });
|
|
@@ -433,16 +429,9 @@ export function composeRuntime(options) {
|
|
|
433
429
|
returned: structuredClone(record.returned),
|
|
434
430
|
});
|
|
435
431
|
}
|
|
436
|
-
for (const [room, refusal] of sourceRefusals)
|
|
437
|
-
attentionRequired.push({ room, refusal: structuredClone(refusal) });
|
|
438
432
|
attentionRequired.sort((left, right) => {
|
|
439
433
|
if (left.room !== right.room)
|
|
440
434
|
return left.room < right.room ? -1 : 1;
|
|
441
|
-
if ('refusal' in left || 'refusal' in right) {
|
|
442
|
-
if ('refusal' in left && 'refusal' in right)
|
|
443
|
-
return 0;
|
|
444
|
-
return 'refusal' in left ? -1 : 1;
|
|
445
|
-
}
|
|
446
435
|
if (left.path !== right.path)
|
|
447
436
|
return left.path < right.path ? -1 : 1;
|
|
448
437
|
return 0;
|
|
@@ -597,9 +586,6 @@ export function composeRuntime(options) {
|
|
|
597
586
|
if (localSaves)
|
|
598
587
|
return Object.freeze({ batch, state: 'local' });
|
|
599
588
|
const state = pendings.get(pendingKey(locator, batch.id));
|
|
600
|
-
if (state?.accepted !== undefined) {
|
|
601
|
-
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
602
|
-
}
|
|
603
589
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
604
590
|
}
|
|
605
591
|
function entriesOf(locator) {
|
|
@@ -673,11 +659,7 @@ export function composeRuntime(options) {
|
|
|
673
659
|
})();
|
|
674
660
|
}
|
|
675
661
|
function roomsOfReach(reach) {
|
|
676
|
-
return new Set([
|
|
677
|
-
...reach.rooms,
|
|
678
|
-
...reach.documents.map(roomOfKey),
|
|
679
|
-
...reach.areas.flatMap((area) => (area.types.length === 0 ? [] : [area.room])),
|
|
680
|
-
]);
|
|
662
|
+
return new Set([...reach.rooms, ...reach.documents.map(roomOfKey)]);
|
|
681
663
|
}
|
|
682
664
|
function reachedRooms(plan) {
|
|
683
665
|
const reached = plan.reach === null ? null : roomsOfReach(plan.reach);
|
|
@@ -716,7 +698,7 @@ export function composeRuntime(options) {
|
|
|
716
698
|
coverage.push({
|
|
717
699
|
room: locator,
|
|
718
700
|
generation: replica.record.transfer,
|
|
719
|
-
current: !(catching.has(locator) ||
|
|
701
|
+
current: !(catching.has(locator) || transfers.has(locator)),
|
|
720
702
|
});
|
|
721
703
|
read.push({ locator, key: replica.record.key });
|
|
722
704
|
for (const batch of replica.queue)
|
|
@@ -743,8 +725,6 @@ export function composeRuntime(options) {
|
|
|
743
725
|
return { listed: named.every((room) => replicas.has(room)), rooms: named };
|
|
744
726
|
}
|
|
745
727
|
function roomState(locator) {
|
|
746
|
-
if (sourceRefusals.has(locator))
|
|
747
|
-
return 'refused';
|
|
748
728
|
const listedRoom = discovered.get(locator);
|
|
749
729
|
if (roomsListed &&
|
|
750
730
|
(listedRoom === undefined ||
|
|
@@ -759,7 +739,7 @@ export function composeRuntime(options) {
|
|
|
759
739
|
}
|
|
760
740
|
function conversationCoverage(reach) {
|
|
761
741
|
const declared = reach === null || reach.conversations;
|
|
762
|
-
if (!declared || options.adapter.conversations === undefined
|
|
742
|
+
if (!declared || options.adapter.conversations === undefined)
|
|
763
743
|
return 'undeclared';
|
|
764
744
|
if (!conversations.known() || !conversations.current())
|
|
765
745
|
return 'acquiring';
|
|
@@ -774,79 +754,50 @@ export function composeRuntime(options) {
|
|
|
774
754
|
? 'connected'
|
|
775
755
|
: 'connecting';
|
|
776
756
|
}
|
|
777
|
-
function
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
rule: held.code,
|
|
789
|
-
location: typeof location === 'string' ? location : null,
|
|
790
|
-
},
|
|
791
|
-
];
|
|
792
|
-
});
|
|
757
|
+
function bytesOf(room, underway) {
|
|
758
|
+
const transfer = transfers.get(room);
|
|
759
|
+
if (transfer !== undefined) {
|
|
760
|
+
const owed = (unindexed.get(room) ?? []).reduce((sum, part) => sum + part.bytes, 0);
|
|
761
|
+
return {
|
|
762
|
+
done: transfer.done.bytes - Math.ceil(owed),
|
|
763
|
+
total: Math.max(arrivals.get(room) ?? 0, transfer.done.bytes),
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
const held = underway ? NOTHING_HELD : (replicas.get(room)?.record.inventory ?? NOTHING_HELD);
|
|
767
|
+
return { done: held.bytes, total: arrivals.get(room) ?? held.bytes };
|
|
793
768
|
}
|
|
794
769
|
function installationOf(coverage) {
|
|
795
770
|
const rooms = { done: 0, total: 0 };
|
|
796
|
-
const parts = { done: 0, total: 0 };
|
|
797
771
|
const bytes = { done: 0, total: 0 };
|
|
798
772
|
let installing = 0;
|
|
799
773
|
let downloading = false;
|
|
800
|
-
let checking = 0;
|
|
801
774
|
for (const { room, state } of coverage) {
|
|
802
775
|
if (state === 'unadmitted')
|
|
803
776
|
continue;
|
|
804
777
|
rooms.total += 1;
|
|
805
778
|
if (state === 'complete')
|
|
806
779
|
rooms.done += 1;
|
|
807
|
-
const transfer = transfers.get(room);
|
|
808
780
|
const underway = state === 'acquiring' || (installs.has(room) && state !== 'complete');
|
|
809
781
|
if (underway)
|
|
810
782
|
installing += 1;
|
|
811
783
|
if (underway && (streaming.has(room) || !arrivals.has(room)))
|
|
812
784
|
downloading = true;
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const arrived = arrivals.get(room);
|
|
818
|
-
parts.done += transfer.done.parts;
|
|
819
|
-
parts.total += Math.max(arrived?.parts ?? 0, transfer.done.parts);
|
|
820
|
-
bytes.done += transfer.done.bytes - Math.ceil(owed + unchecking);
|
|
821
|
-
checking += Math.ceil(owed + unchecking) - Math.ceil(owed);
|
|
822
|
-
bytes.total += Math.max(arrived?.bytes ?? 0, transfer.done.bytes);
|
|
823
|
-
continue;
|
|
824
|
-
}
|
|
825
|
-
const held = underway ? NOTHING_HELD : (replicas.get(room)?.record.inventory ?? NOTHING_HELD);
|
|
826
|
-
const arrived = arrivals.get(room) ?? held;
|
|
827
|
-
parts.done += held.parts;
|
|
828
|
-
parts.total += arrived.parts;
|
|
829
|
-
bytes.done += held.bytes;
|
|
830
|
-
bytes.total += arrived.bytes;
|
|
785
|
+
const now = bytesOf(room, underway);
|
|
786
|
+
const earlier = underway ? startedOver.get(room) : undefined;
|
|
787
|
+
bytes.done += Math.max(now.done, earlier?.done ?? 0);
|
|
788
|
+
bytes.total += Math.max(now.total, earlier?.total ?? 0);
|
|
831
789
|
}
|
|
832
790
|
const step = installing === 0
|
|
833
791
|
? null
|
|
834
792
|
: downloading
|
|
835
793
|
? 'downloading'
|
|
836
|
-
: bytes.done
|
|
794
|
+
: bytes.done < bytes.total
|
|
837
795
|
? 'writing'
|
|
838
796
|
: 'replaying';
|
|
839
|
-
return {
|
|
840
|
-
step,
|
|
841
|
-
paused: installing > 0 && !onScreen ? 'hidden' : null,
|
|
842
|
-
rooms,
|
|
843
|
-
parts,
|
|
844
|
-
bytes,
|
|
845
|
-
};
|
|
797
|
+
return { step, rooms, bytes };
|
|
846
798
|
}
|
|
847
799
|
function readinessFor(port, reach) {
|
|
848
800
|
const required = requiredRooms(reach);
|
|
849
|
-
const search = port.search === 'required';
|
|
850
801
|
const coverage = required.rooms.map((room) => ({ room, state: roomState(room) }));
|
|
851
802
|
const preparing = coverage
|
|
852
803
|
.filter((room) => room.state === 'preparing' && !unpreparable.has(room.room))
|
|
@@ -859,14 +810,12 @@ export function composeRuntime(options) {
|
|
|
859
810
|
listed: required.listed,
|
|
860
811
|
rooms: coverage,
|
|
861
812
|
conversations: conversationCoverage(reach),
|
|
862
|
-
search: search ? 'required' : 'unused',
|
|
863
813
|
},
|
|
864
814
|
persistence,
|
|
865
815
|
connection: connectionOf(),
|
|
866
816
|
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
867
817
|
space: full ? 'full' : port.space,
|
|
868
818
|
installation: installationOf(coverage),
|
|
869
|
-
refused: refusedIn(required.rooms),
|
|
870
819
|
lost: storageLoss !== null,
|
|
871
820
|
established: targets[port.target]?.established === true ||
|
|
872
821
|
(usableWhenHeld.includes(port.target) && required.rooms.some((room) => reopened.has(room))),
|
|
@@ -954,8 +903,12 @@ export function composeRuntime(options) {
|
|
|
954
903
|
unpreparable.clear();
|
|
955
904
|
journal.record('space freed');
|
|
956
905
|
queueReadiness();
|
|
957
|
-
if (
|
|
958
|
-
|
|
906
|
+
if (closed || authorization === undefined)
|
|
907
|
+
return;
|
|
908
|
+
if (options.adapter.roomStream === undefined)
|
|
909
|
+
void sync('unstreamed', null);
|
|
910
|
+
else
|
|
911
|
+
void resynchronize();
|
|
959
912
|
}
|
|
960
913
|
function seedRoomHeld(room, version) {
|
|
961
914
|
return indexed(room) && reachedBy(room, version);
|
|
@@ -995,7 +948,24 @@ export function composeRuntime(options) {
|
|
|
995
948
|
function sameCover(left, right) {
|
|
996
949
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
997
950
|
}
|
|
951
|
+
async function unheldRows(local, plan, rows, rooms) {
|
|
952
|
+
if (plan.shape.kind !== 'documents' || !bodied(plan.shape))
|
|
953
|
+
return [];
|
|
954
|
+
if (!('documents' in rows) || !textOnline())
|
|
955
|
+
return [];
|
|
956
|
+
const named = new Map();
|
|
957
|
+
for (const { room, path, textHeld } of rows.documents) {
|
|
958
|
+
if (textHeld === false)
|
|
959
|
+
named.set(room, [...(named.get(room) ?? []), path]);
|
|
960
|
+
}
|
|
961
|
+
const unheld = [];
|
|
962
|
+
for (const { locator, key } of rooms) {
|
|
963
|
+
unheld.push(...(await unheldIn(local, locator, key, named.get(locator) ?? [])));
|
|
964
|
+
}
|
|
965
|
+
return unheld.filter((one) => unholdable.get(textKey(one)) !== one.revision);
|
|
966
|
+
}
|
|
998
967
|
async function coveredRows(plan, cover) {
|
|
968
|
+
const asked = new Set();
|
|
999
969
|
for (;;) {
|
|
1000
970
|
const before = cover();
|
|
1001
971
|
if ('pending' in before)
|
|
@@ -1006,8 +976,14 @@ export function composeRuntime(options) {
|
|
|
1006
976
|
const current = () => local.changes(keys) === changes;
|
|
1007
977
|
const rows = await windowRows(local, plan, before.rooms, current);
|
|
1008
978
|
const after = cover();
|
|
1009
|
-
if (rows
|
|
979
|
+
if (rows === null || !sameCover(before, after))
|
|
980
|
+
continue;
|
|
981
|
+
const unheld = (await unheldRows(local, plan, rows, before.rooms)).filter((one) => !asked.has(textKey(one)));
|
|
982
|
+
if (unheld.length === 0)
|
|
1010
983
|
return { rows, cover: before, current };
|
|
984
|
+
for (const one of unheld)
|
|
985
|
+
asked.add(textKey(one));
|
|
986
|
+
await holdTexts(unheld);
|
|
1011
987
|
}
|
|
1012
988
|
}
|
|
1013
989
|
async function windowRows(local, plan, rooms, unchanged) {
|
|
@@ -1103,6 +1079,8 @@ export function composeRuntime(options) {
|
|
|
1103
1079
|
}
|
|
1104
1080
|
async function refuse(reason, provider = null) {
|
|
1105
1081
|
discoveryPaused = true;
|
|
1082
|
+
inFlight.abort();
|
|
1083
|
+
inFlight = new AbortController();
|
|
1106
1084
|
try {
|
|
1107
1085
|
if (reason === 'revoked-authorization') {
|
|
1108
1086
|
await exclusive(async () => {
|
|
@@ -1187,10 +1165,10 @@ export function composeRuntime(options) {
|
|
|
1187
1165
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
1188
1166
|
roomVersion: replicas.get(room.locator)?.record.roomVersion ?? null,
|
|
1189
1167
|
synchronizationAvailable,
|
|
1190
|
-
refusal: scopedRefusals.get(room.locator) ??
|
|
1168
|
+
refusal: scopedRefusals.get(room.locator) ?? null,
|
|
1191
1169
|
});
|
|
1192
1170
|
}
|
|
1193
|
-
async function applyDiscovery(found, started,
|
|
1171
|
+
async function applyDiscovery(found, started, left) {
|
|
1194
1172
|
const lockedBefore = lockedRooms();
|
|
1195
1173
|
for (const room of found) {
|
|
1196
1174
|
const fence = fences.get(room.locator);
|
|
@@ -1204,18 +1182,11 @@ export function composeRuntime(options) {
|
|
|
1204
1182
|
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
1205
1183
|
for (const locator of listedLocators)
|
|
1206
1184
|
scopedRefusals.delete(locator);
|
|
1207
|
-
if (
|
|
1185
|
+
if (left === undefined)
|
|
1208
1186
|
discovered.clear();
|
|
1209
|
-
else
|
|
1210
|
-
for (const locator of covered)
|
|
1211
|
-
discovered.delete(locator);
|
|
1212
1187
|
for (const room of listed)
|
|
1213
1188
|
discovered.set(room.locator, room);
|
|
1214
|
-
|
|
1215
|
-
if (options.adapter.exhaustiveDiscovery === true)
|
|
1216
|
-
await loseRooms(unlisted);
|
|
1217
|
-
else
|
|
1218
|
-
unlistedKept(unlisted);
|
|
1189
|
+
await loseRooms(left ?? unlistedHeld(new Set(found.map((room) => room.locator))));
|
|
1219
1190
|
for (const room of listed) {
|
|
1220
1191
|
const held = replicas.get(room.locator)?.record;
|
|
1221
1192
|
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
@@ -1246,15 +1217,6 @@ export function composeRuntime(options) {
|
|
|
1246
1217
|
]),
|
|
1247
1218
|
].filter((locator) => !listed.has(locator));
|
|
1248
1219
|
}
|
|
1249
|
-
function unlistedKept(unlisted) {
|
|
1250
|
-
for (const locator of unlisted) {
|
|
1251
|
-
const held = replicas.get(locator)?.record;
|
|
1252
|
-
if (held !== undefined)
|
|
1253
|
-
list(held, false);
|
|
1254
|
-
else
|
|
1255
|
-
rooms.delete(locator);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
1220
|
function fence(locators) {
|
|
1259
1221
|
const fresh = [...new Set(locators)].filter((locator) => !fences.has(locator));
|
|
1260
1222
|
const removed = fresh.map((locator) => ({
|
|
@@ -1265,24 +1227,26 @@ export function composeRuntime(options) {
|
|
|
1265
1227
|
fences.set(locator, { at: discoveries, erased: false });
|
|
1266
1228
|
lastFenced.set(locator, ++fencings);
|
|
1267
1229
|
confirmed.delete(locator);
|
|
1268
|
-
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
1230
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []) {
|
|
1269
1231
|
pendings.delete(pendingKey(locator, batch.id));
|
|
1232
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1233
|
+
}
|
|
1270
1234
|
replicas.delete(locator);
|
|
1235
|
+
highest.delete(locator);
|
|
1236
|
+
beyondHorizon.delete(locator);
|
|
1271
1237
|
queuesWithoutReplica.delete(locator);
|
|
1272
1238
|
rooms.delete(locator);
|
|
1273
1239
|
roomDrafts.delete(locator);
|
|
1274
1240
|
attachedFiles.delete(locator);
|
|
1275
1241
|
untransferred(locator);
|
|
1242
|
+
startedOver.delete(locator);
|
|
1276
1243
|
catching.delete(locator);
|
|
1277
1244
|
discovered.delete(locator);
|
|
1278
|
-
announced.delete(locator);
|
|
1279
1245
|
unreadable.delete(locator);
|
|
1280
1246
|
stale.delete(locator);
|
|
1281
1247
|
repairable.delete(locator);
|
|
1282
1248
|
unpreparable.delete(locator);
|
|
1283
1249
|
scopedRefusals.delete(locator);
|
|
1284
|
-
sourceRefusals.delete(locator);
|
|
1285
|
-
declarationRefusals.delete(locator);
|
|
1286
1250
|
models.delete(locator);
|
|
1287
1251
|
liveModels.delete(locator);
|
|
1288
1252
|
invalidModels.delete(locator);
|
|
@@ -1373,6 +1337,7 @@ export function composeRuntime(options) {
|
|
|
1373
1337
|
transfers.clear();
|
|
1374
1338
|
arrivals.clear();
|
|
1375
1339
|
streaming.clear();
|
|
1340
|
+
startedOver.clear();
|
|
1376
1341
|
limits = held.providerLimits;
|
|
1377
1342
|
capabilities = held.capabilities;
|
|
1378
1343
|
member = held.member;
|
|
@@ -1382,8 +1347,9 @@ export function composeRuntime(options) {
|
|
|
1382
1347
|
presentations.clear();
|
|
1383
1348
|
for (const record of held.presentation)
|
|
1384
1349
|
presentations.set(record.app, record.value);
|
|
1385
|
-
conversations.hydrate(heldConversations, chatDrafts, held.conversationList);
|
|
1350
|
+
conversations.hydrate(heldConversations, chatDrafts, held.conversationList, held.conversationCursor);
|
|
1386
1351
|
pendings.clear();
|
|
1352
|
+
landedAhead.clear();
|
|
1387
1353
|
for (const queue of queues) {
|
|
1388
1354
|
for (const { batch, state } of queue.pending) {
|
|
1389
1355
|
pendings.set(pendingKey(queue.locator, batch.id), state);
|
|
@@ -1534,6 +1500,16 @@ export function composeRuntime(options) {
|
|
|
1534
1500
|
}
|
|
1535
1501
|
const REWRITE_LINKS = ['field', 'body'];
|
|
1536
1502
|
const CHECKED_LINKS = ['field', 'section'];
|
|
1503
|
+
const BODY_LINKS = ['body'];
|
|
1504
|
+
async function textsNeeded(local, record, named) {
|
|
1505
|
+
const paths = [...new Set(named.map((one) => (typeof one === 'string' ? one : one.path)))];
|
|
1506
|
+
const unheld = await unheldIn(local, record.locator, record.key, paths);
|
|
1507
|
+
if (unheld.length === 0)
|
|
1508
|
+
return null;
|
|
1509
|
+
await holdTexts(unheld);
|
|
1510
|
+
const [first] = await unheldIn(local, record.locator, record.key, unheld.map(({ path }) => path));
|
|
1511
|
+
return first?.path ?? null;
|
|
1512
|
+
}
|
|
1537
1513
|
async function sliceAt(record, view, queue, extra = [], moved = []) {
|
|
1538
1514
|
const local = await openLocal();
|
|
1539
1515
|
const needed = new Set([...touchedBy(queue), ...extra]);
|
|
@@ -1634,8 +1610,6 @@ export function composeRuntime(options) {
|
|
|
1634
1610
|
function unpairedIn(locator, batch) {
|
|
1635
1611
|
if (!queueIn(locator).some((held) => held.id === batch.id))
|
|
1636
1612
|
return false;
|
|
1637
|
-
if (pendings.get(pendingKey(locator, batch.id))?.accepted !== undefined)
|
|
1638
|
-
return false;
|
|
1639
1613
|
return !leavingIn(batch, locator) || withdrawable(locator)(batch);
|
|
1640
1614
|
}
|
|
1641
1615
|
async function together(local, settlements) {
|
|
@@ -1678,10 +1652,16 @@ export function composeRuntime(options) {
|
|
|
1678
1652
|
? read
|
|
1679
1653
|
: await stepped(slicingWith(read, overrides.set, overrides.removed));
|
|
1680
1654
|
const composition = composed(record);
|
|
1681
|
-
const
|
|
1682
|
-
|
|
1655
|
+
const shown = queue.filter((batch) => {
|
|
1656
|
+
const held = landedAhead.get(pendingKey(locator, batch.id));
|
|
1657
|
+
return (held !== undefined &&
|
|
1658
|
+
Object.entries(held).some(([path, revision]) => confirmedLayer.revisions[path] !== revision));
|
|
1659
|
+
});
|
|
1660
|
+
const unshown = queue.filter((batch) => !shown.includes(batch));
|
|
1661
|
+
const { withdrawn, kept } = await stepped(standing(confirmedLayer, unshown, withdrawable(locator), localSaves, locator, composition));
|
|
1662
|
+
const founded = kept.filter((batch) => !unshown.includes(batch));
|
|
1683
1663
|
const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
|
|
1684
|
-
const unqueued =
|
|
1664
|
+
const unqueued = shown.map((batch) => ({ batch, landed: true }));
|
|
1685
1665
|
for (const batch of withdrawn) {
|
|
1686
1666
|
unqueued.push({ batch, landed: await landedWhole(locator, batch, confirmedLayer) });
|
|
1687
1667
|
}
|
|
@@ -1746,8 +1726,10 @@ export function composeRuntime(options) {
|
|
|
1746
1726
|
commit,
|
|
1747
1727
|
apply: (answer) => {
|
|
1748
1728
|
for (const batch of previousQueue) {
|
|
1749
|
-
if (
|
|
1750
|
-
|
|
1729
|
+
if (staying.has(batch.id))
|
|
1730
|
+
continue;
|
|
1731
|
+
pendings.delete(pendingKey(locator, batch.id));
|
|
1732
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1751
1733
|
}
|
|
1752
1734
|
for (const draft of written.values())
|
|
1753
1735
|
rememberRoomDraft(draft);
|
|
@@ -1804,28 +1786,34 @@ export function composeRuntime(options) {
|
|
|
1804
1786
|
models.set(locator, state);
|
|
1805
1787
|
return { collection: state };
|
|
1806
1788
|
}
|
|
1807
|
-
function publish(local, room,
|
|
1789
|
+
function publish(local, room, reached, landing, live) {
|
|
1808
1790
|
const landed = exclusive(async () => {
|
|
1809
1791
|
if (!live()) {
|
|
1810
1792
|
caughtUpTo(room.locator);
|
|
1811
1793
|
return true;
|
|
1812
1794
|
}
|
|
1813
1795
|
const prior = replicas.get(room.locator);
|
|
1814
|
-
if (landing.
|
|
1815
|
-
(
|
|
1816
|
-
|
|
1796
|
+
if (!stands(room.locator, landing.from)) {
|
|
1797
|
+
if (landing.kind === 'refreshed') {
|
|
1798
|
+
await local.unrefresh(reached.key);
|
|
1799
|
+
return false;
|
|
1800
|
+
}
|
|
1801
|
+
caughtUpTo(room.locator);
|
|
1802
|
+
await abandoned(local, room.locator);
|
|
1817
1803
|
return false;
|
|
1818
1804
|
}
|
|
1805
|
+
const record = landing.kind === 'refreshed' && prior !== undefined
|
|
1806
|
+
? {
|
|
1807
|
+
...prior.record,
|
|
1808
|
+
roomVersion: reached.roomVersion,
|
|
1809
|
+
...(landing.remodelled ? { model: reached.model } : {}),
|
|
1810
|
+
}
|
|
1811
|
+
: reached;
|
|
1819
1812
|
adoptModels(room.locator, record);
|
|
1820
1813
|
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
1821
|
-
const roomVersion = record.roomVersion;
|
|
1822
|
-
const accepted = queue.filter((batch) => {
|
|
1823
|
-
const at = pendings.get(pendingKey(room.locator, batch.id))?.accepted;
|
|
1824
|
-
return at !== undefined && options.adapter.reached(room.locator, roomVersion, at);
|
|
1825
|
-
});
|
|
1826
1814
|
let set;
|
|
1827
1815
|
try {
|
|
1828
|
-
set = await settle(local, room.locator, prior, record, queue
|
|
1816
|
+
set = await settle(local, room.locator, prior, record, queue, landing.kind === 'refreshed'
|
|
1829
1817
|
? {
|
|
1830
1818
|
room: true,
|
|
1831
1819
|
view: 'refreshed',
|
|
@@ -1847,24 +1835,26 @@ export function composeRuntime(options) {
|
|
|
1847
1835
|
return true;
|
|
1848
1836
|
}
|
|
1849
1837
|
if (landing.kind === 'bootstrapped') {
|
|
1838
|
+
highest.delete(room.locator);
|
|
1839
|
+
beyondHorizon.delete(room.locator);
|
|
1850
1840
|
untransferred(room.locator);
|
|
1841
|
+
startedOver.delete(room.locator);
|
|
1851
1842
|
catching.delete(room.locator);
|
|
1852
|
-
|
|
1843
|
+
if (prior !== undefined && prior.record.key !== record.key)
|
|
1844
|
+
sweep();
|
|
1845
|
+
name(room.locator);
|
|
1853
1846
|
}
|
|
1854
|
-
for (const batch of accepted)
|
|
1855
|
-
pendings.delete(pendingKey(room.locator, batch.id));
|
|
1856
1847
|
stale.delete(room.locator);
|
|
1857
1848
|
unpreparable.delete(room.locator);
|
|
1858
1849
|
if (unreadable.delete(room.locator))
|
|
1859
1850
|
scopedRefusals.delete(room.locator);
|
|
1860
|
-
sourceRefusals.delete(room.locator);
|
|
1861
|
-
declarationRefusals.delete(room.locator);
|
|
1862
1851
|
list(room, true);
|
|
1863
1852
|
publishChangeSet(set);
|
|
1853
|
+
if (landing.kind === 'bootstrapped')
|
|
1854
|
+
void heldAgain(room.locator);
|
|
1864
1855
|
await unpair(local);
|
|
1865
1856
|
moveTo({ ...status, ...attention() });
|
|
1866
|
-
|
|
1867
|
-
if (accepted.length > 0 && (replicas.get(room.locator)?.queue.length ?? 0) > 0) {
|
|
1857
|
+
if (landing.kind === 'bootstrapped' && (replicas.get(room.locator)?.queue.length ?? 0) > 0) {
|
|
1868
1858
|
submitLater(room.locator);
|
|
1869
1859
|
}
|
|
1870
1860
|
return true;
|
|
@@ -1876,7 +1866,7 @@ export function composeRuntime(options) {
|
|
|
1876
1866
|
const replica = replicas.get(locator);
|
|
1877
1867
|
const room = rooms.get(locator);
|
|
1878
1868
|
if (replica === undefined || room === undefined) {
|
|
1879
|
-
throw runtimeError('room
|
|
1869
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
1880
1870
|
}
|
|
1881
1871
|
return { local, replica, room };
|
|
1882
1872
|
}
|
|
@@ -1938,6 +1928,19 @@ export function composeRuntime(options) {
|
|
|
1938
1928
|
const referring = removed.length === 0
|
|
1939
1929
|
? []
|
|
1940
1930
|
: (await local.referrers(record.key, 'confirmed', removed, CHECKED_LINKS)).map(({ path }) => path);
|
|
1931
|
+
const unheld = await textsNeeded(local, record, [
|
|
1932
|
+
...paths,
|
|
1933
|
+
...referring,
|
|
1934
|
+
...(moved.length === 0
|
|
1935
|
+
? []
|
|
1936
|
+
: await local.referrers(record.key, 'confirmed', moved, REWRITE_LINKS)),
|
|
1937
|
+
...(removed.length === 0
|
|
1938
|
+
? []
|
|
1939
|
+
: await local.referrers(record.key, 'confirmed', removed, BODY_LINKS)),
|
|
1940
|
+
]);
|
|
1941
|
+
if (unheld !== null) {
|
|
1942
|
+
return { refusal: { code: 'transport-uncertainty', retryable: true, file: unheld } };
|
|
1943
|
+
}
|
|
1941
1944
|
const confirmedLayer = await sliceAt(record, 'confirmed', replica.queue, [...paths, ...paths.flatMap(ancestors), ...referring], moved);
|
|
1942
1945
|
const { visible } = await replayed(record, confirmedLayer, replica.queue);
|
|
1943
1946
|
const composition = composed(record);
|
|
@@ -1968,18 +1971,6 @@ export function composeRuntime(options) {
|
|
|
1968
1971
|
return undefined;
|
|
1969
1972
|
return entry.binary !== null ? null : entry.content;
|
|
1970
1973
|
}
|
|
1971
|
-
function walkOf(local, key, view) {
|
|
1972
|
-
return async (after) => {
|
|
1973
|
-
const page = await local.walk(key, view, after, WALK_BYTES);
|
|
1974
|
-
return {
|
|
1975
|
-
entries: page.entries.map(({ path, content, binary }) => ({
|
|
1976
|
-
path,
|
|
1977
|
-
content: binary !== null ? null : content,
|
|
1978
|
-
})),
|
|
1979
|
-
next: page.next,
|
|
1980
|
-
};
|
|
1981
|
-
};
|
|
1982
|
-
}
|
|
1983
1974
|
function lookupOf(local, key, view) {
|
|
1984
1975
|
return async (paths) => {
|
|
1985
1976
|
if (paths.length === 0)
|
|
@@ -1996,37 +1987,6 @@ export function composeRuntime(options) {
|
|
|
1996
1987
|
const read = parseFrontmatter(entry.content ?? '');
|
|
1997
1988
|
return { type: read.ok ? (read.frontmatter.type ?? 'any') : undefined, binary: false };
|
|
1998
1989
|
}
|
|
1999
|
-
function overriddenWalk(walk, overrides) {
|
|
2000
|
-
const sorted = [...overrides.keys()].sort();
|
|
2001
|
-
const beyond = (path) => {
|
|
2002
|
-
if (path === null)
|
|
2003
|
-
return 0;
|
|
2004
|
-
let low = 0;
|
|
2005
|
-
let high = sorted.length;
|
|
2006
|
-
while (low < high) {
|
|
2007
|
-
const middle = (low + high) >>> 1;
|
|
2008
|
-
if (sorted[middle] <= path)
|
|
2009
|
-
low = middle + 1;
|
|
2010
|
-
else
|
|
2011
|
-
high = middle;
|
|
2012
|
-
}
|
|
2013
|
-
return low;
|
|
2014
|
-
};
|
|
2015
|
-
return async (after) => {
|
|
2016
|
-
const page = await walk(after);
|
|
2017
|
-
const end = page.next;
|
|
2018
|
-
const added = sorted
|
|
2019
|
-
.slice(beyond(after), end === null ? sorted.length : beyond(end))
|
|
2020
|
-
.flatMap((path) => {
|
|
2021
|
-
const entry = overrides.get(path);
|
|
2022
|
-
return entry === null || entry === undefined
|
|
2023
|
-
? []
|
|
2024
|
-
: [{ path, content: entry.binary !== null ? null : entry.content }];
|
|
2025
|
-
});
|
|
2026
|
-
const entries = [...page.entries.filter(({ path }) => !overrides.has(path)), ...added].sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
2027
|
-
return { entries, next: end };
|
|
2028
|
-
};
|
|
2029
|
-
}
|
|
2030
1990
|
function overriddenLookup(lookup, overrides) {
|
|
2031
1991
|
return async (paths) => {
|
|
2032
1992
|
const known = new Map(await lookup(paths.filter((path) => !overrides.has(path))));
|
|
@@ -2044,9 +2004,8 @@ export function composeRuntime(options) {
|
|
|
2044
2004
|
}
|
|
2045
2005
|
function validatorOf(record, confirmedLayer) {
|
|
2046
2006
|
return async (resulting, touched) => {
|
|
2047
|
-
const composition = compositionFor(record);
|
|
2048
2007
|
const local = await openLocal();
|
|
2049
|
-
const
|
|
2008
|
+
const changes = [];
|
|
2050
2009
|
const overrides = new Map();
|
|
2051
2010
|
const pace = paced();
|
|
2052
2011
|
for (const path of new Set([...confirmedLayer.held, ...touched])) {
|
|
@@ -2057,19 +2016,13 @@ export function composeRuntime(options) {
|
|
|
2057
2016
|
continue;
|
|
2058
2017
|
overrides.set(path, now);
|
|
2059
2018
|
if (textOf(was) !== textOf(now))
|
|
2060
|
-
|
|
2019
|
+
changes.push({ path, was: textOf(was), now: textOf(now) });
|
|
2061
2020
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
texts: textsOf(local, record.key, 'confirmed'),
|
|
2068
|
-
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
2069
|
-
}));
|
|
2070
|
-
if (clean)
|
|
2071
|
-
return { verdict: 'accept' };
|
|
2072
|
-
return validatedCollection(overriddenWalk(walkOf(local, record.key, 'confirmed'), overrides), lookup, composition);
|
|
2021
|
+
return validatedNeighbourhood(changes, compositionFor(record), {
|
|
2022
|
+
lookup: overriddenLookup(lookupOf(local, record.key, 'confirmed'), overrides),
|
|
2023
|
+
texts: textsOf(local, record.key, 'confirmed'),
|
|
2024
|
+
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
2025
|
+
});
|
|
2073
2026
|
};
|
|
2074
2027
|
}
|
|
2075
2028
|
function textsOf(local, key, view) {
|
|
@@ -2146,15 +2099,19 @@ export function composeRuntime(options) {
|
|
|
2146
2099
|
}
|
|
2147
2100
|
const moves = saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
|
|
2148
2101
|
const sourceSaving = await savingIn(replica, moves.map(({ path }) => ({ kind: 'remove', path })), saved.map(({ path }) => path), moves.map(({ path }) => path));
|
|
2102
|
+
if ('refusal' in sourceSaving)
|
|
2103
|
+
return { outcome: 'refused', ...sourceSaving.refusal };
|
|
2149
2104
|
const changed = saved.flatMap(({ path, parts }) => changedParts(path, parts, sourceSaving.visible));
|
|
2150
2105
|
if (changed.length > 0)
|
|
2151
2106
|
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
2152
2107
|
const target = replicas.get(destination);
|
|
2153
2108
|
const listedRoom = rooms.get(destination);
|
|
2154
2109
|
if (target === undefined || listedRoom === undefined) {
|
|
2155
|
-
throw runtimeError('room
|
|
2110
|
+
throw runtimeError('unknown-room', `Unknown Room ${destination}`);
|
|
2156
2111
|
}
|
|
2157
2112
|
const arriving = await savingIn(target, [], moves.map(({ to }) => to));
|
|
2113
|
+
if ('refusal' in arriving)
|
|
2114
|
+
return { outcome: 'refused', ...arriving.refusal };
|
|
2158
2115
|
const built = await buildMove({
|
|
2159
2116
|
source: await sideOf(locator, replica, room, sourceSaving),
|
|
2160
2117
|
destination: await sideOf(destination, target, listedRoom, arriving),
|
|
@@ -2215,10 +2172,10 @@ export function composeRuntime(options) {
|
|
|
2215
2172
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2216
2173
|
const { answer, touched } = committedBoth;
|
|
2217
2174
|
if (answer.fenced !== null) {
|
|
2218
|
-
throw runtimeError('room
|
|
2175
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2219
2176
|
}
|
|
2220
2177
|
if (fences.has(locator) || fences.has(destination)) {
|
|
2221
|
-
throw runtimeError('room
|
|
2178
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2222
2179
|
}
|
|
2223
2180
|
pendings.set(pendingKey(locator, id), {
|
|
2224
2181
|
drafts: saved,
|
|
@@ -2271,6 +2228,8 @@ export function composeRuntime(options) {
|
|
|
2271
2228
|
...saved.map(({ path }) => path),
|
|
2272
2229
|
...parts.flatMap(({ bases }) => bases.map(({ path }) => path)),
|
|
2273
2230
|
]);
|
|
2231
|
+
if ('refusal' in saving)
|
|
2232
|
+
return { outcome: 'refused', ...saving.refusal };
|
|
2274
2233
|
const changed = saved.flatMap(({ path, parts: held }) => changedParts(path, held, saving.visible));
|
|
2275
2234
|
if (changed.length > 0)
|
|
2276
2235
|
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
@@ -2389,10 +2348,10 @@ export function composeRuntime(options) {
|
|
|
2389
2348
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2390
2349
|
const { answer, touched } = done;
|
|
2391
2350
|
if (answer.fenced !== null) {
|
|
2392
|
-
throw runtimeError('room
|
|
2351
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2393
2352
|
}
|
|
2394
2353
|
if (fences.has(locator))
|
|
2395
|
-
throw runtimeError('room
|
|
2354
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2396
2355
|
for (const origin of withdrawn.keys())
|
|
2397
2356
|
pendings.delete(pendingKey(locator, origin));
|
|
2398
2357
|
for (const [id, drafts] of drafted) {
|
|
@@ -2441,7 +2400,7 @@ export function composeRuntime(options) {
|
|
|
2441
2400
|
throw new Error('connection refused');
|
|
2442
2401
|
await local.keepUpload(locator, identifier, upload);
|
|
2443
2402
|
if (fences.has(locator))
|
|
2444
|
-
throw runtimeError('room
|
|
2403
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2445
2404
|
const key = pendingKey(locator, identifier);
|
|
2446
2405
|
pendings.set(key, { ...pendings.get(key), upload });
|
|
2447
2406
|
if (!continuing())
|
|
@@ -2578,6 +2537,83 @@ export function composeRuntime(options) {
|
|
|
2578
2537
|
return UNCERTAIN;
|
|
2579
2538
|
}
|
|
2580
2539
|
}
|
|
2540
|
+
async function heldAgain(locator) {
|
|
2541
|
+
const replica = replicas.get(locator);
|
|
2542
|
+
if (replica === undefined || !textOnline())
|
|
2543
|
+
return;
|
|
2544
|
+
try {
|
|
2545
|
+
const wanted = await (await openLocal()).wanted(locator, replica.record.key);
|
|
2546
|
+
await holdTexts(wanted.map(({ path, revision }) => ({ locator, path, revision })));
|
|
2547
|
+
}
|
|
2548
|
+
catch {
|
|
2549
|
+
journal.record(`hold text ${locator} unwritten`);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
function textOnline() {
|
|
2553
|
+
return !closed && refusal === null && connectionOf() === 'connected';
|
|
2554
|
+
}
|
|
2555
|
+
function textKey({ locator, path }) {
|
|
2556
|
+
return documentKey(locator, path);
|
|
2557
|
+
}
|
|
2558
|
+
async function readText({ locator, path, revision }) {
|
|
2559
|
+
const replica = replicas.get(locator);
|
|
2560
|
+
if (replica === undefined || !textOnline())
|
|
2561
|
+
return;
|
|
2562
|
+
try {
|
|
2563
|
+
await ensureAuthorization();
|
|
2564
|
+
}
|
|
2565
|
+
catch {
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
2569
|
+
return;
|
|
2570
|
+
const since = fencings;
|
|
2571
|
+
journal.record(`hold text ${locator} ${path}`);
|
|
2572
|
+
const read = await reads
|
|
2573
|
+
.bootstrap(authorization, { locator, selection: replica.record.replica.selection, path })
|
|
2574
|
+
.catch(() => null);
|
|
2575
|
+
journal.record(`outcome text ${read?.kind ?? 'transport-uncertainty'}`);
|
|
2576
|
+
if (read === null || read.kind !== 'bootstrap' || fencedSince(locator, since))
|
|
2577
|
+
return;
|
|
2578
|
+
const content = read.documents[path];
|
|
2579
|
+
const now = replicas.get(locator);
|
|
2580
|
+
if (content === undefined || read.revisions[path] !== revision || now === undefined) {
|
|
2581
|
+
unholdable.set(textKey({ locator, path, revision }), revision);
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
const held = await (await openLocal()).hold(locator, now.record.key, compositionFor(now.record), [{ path, revision, content }]);
|
|
2585
|
+
if (held.length === 0)
|
|
2586
|
+
unholdable.set(textKey({ locator, path, revision }), revision);
|
|
2587
|
+
else
|
|
2588
|
+
publishChangeSet(changeSet(locator, { documents: held }));
|
|
2589
|
+
}
|
|
2590
|
+
function holdText(unheld) {
|
|
2591
|
+
const key = textKey(unheld);
|
|
2592
|
+
const running = holding.get(key);
|
|
2593
|
+
if (running !== undefined)
|
|
2594
|
+
return running;
|
|
2595
|
+
const started = readText(unheld).finally(() => holding.delete(key));
|
|
2596
|
+
holding.set(key, started);
|
|
2597
|
+
return started;
|
|
2598
|
+
}
|
|
2599
|
+
async function holdTexts(wanted) {
|
|
2600
|
+
const queue = wanted.filter((one) => unholdable.get(textKey(one)) !== one.revision);
|
|
2601
|
+
await Promise.all(Array.from({ length: Math.min(TEXT_READS, queue.length) }, async () => {
|
|
2602
|
+
for (let next = queue.shift(); next !== undefined; next = queue.shift()) {
|
|
2603
|
+
await holdText(next);
|
|
2604
|
+
}
|
|
2605
|
+
}));
|
|
2606
|
+
}
|
|
2607
|
+
async function unheldIn(local, locator, key, paths) {
|
|
2608
|
+
if (paths.length === 0)
|
|
2609
|
+
return [];
|
|
2610
|
+
return (await local.entries(key, 'confirmed', paths)).flatMap((entry) => entry.content === null &&
|
|
2611
|
+
entry.binary === null &&
|
|
2612
|
+
entry.outline !== null &&
|
|
2613
|
+
entry.revision !== null
|
|
2614
|
+
? [{ locator, path: entry.path, revision: entry.revision }]
|
|
2615
|
+
: []);
|
|
2616
|
+
}
|
|
2581
2617
|
function fetchBinary(request, name) {
|
|
2582
2618
|
return transferBinary(request, (authorization) => keptFiles.fetch(name, () => options.adapter.binary(authorization, request)), binaryAnswer);
|
|
2583
2619
|
}
|
|
@@ -2713,14 +2749,16 @@ export function composeRuntime(options) {
|
|
|
2713
2749
|
}
|
|
2714
2750
|
async function cleanUp() {
|
|
2715
2751
|
const freed = await database.ask({ kind: 'opened.clean' });
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2752
|
+
const local = await openLocal();
|
|
2753
|
+
let texts = 0;
|
|
2754
|
+
for (const [locator, replica] of replicas) {
|
|
2755
|
+
const released = await local.unhold(locator, replica.record.key, compositionFor(replica.record));
|
|
2756
|
+
texts += released.bytes;
|
|
2757
|
+
if (released.paths.length > 0) {
|
|
2758
|
+
publishChangeSet(changeSet(locator, { documents: released.paths }));
|
|
2759
|
+
}
|
|
2722
2760
|
}
|
|
2723
|
-
return
|
|
2761
|
+
return freed + texts + ((await options.shell.cleanUp?.()) ?? 0);
|
|
2724
2762
|
}
|
|
2725
2763
|
function declaredAction(name) {
|
|
2726
2764
|
for (const capability of capabilities) {
|
|
@@ -2773,6 +2811,24 @@ export function composeRuntime(options) {
|
|
|
2773
2811
|
return UNCERTAIN;
|
|
2774
2812
|
}
|
|
2775
2813
|
}
|
|
2814
|
+
async function readConversationHistory(request) {
|
|
2815
|
+
await ready;
|
|
2816
|
+
const stream = options.adapter.conversations;
|
|
2817
|
+
if (closed || refusal !== null || stream?.history === undefined)
|
|
2818
|
+
return UNCERTAIN;
|
|
2819
|
+
journal.record('transfer conversation history');
|
|
2820
|
+
try {
|
|
2821
|
+
const result = await stream.history(request);
|
|
2822
|
+
if (result.kind === 'revoked-authorization')
|
|
2823
|
+
await refuse('revoked-authorization');
|
|
2824
|
+
journal.record(`outcome conversation history ${result.kind}`);
|
|
2825
|
+
return calledResult(result);
|
|
2826
|
+
}
|
|
2827
|
+
catch {
|
|
2828
|
+
journal.record('outcome conversation history transport-uncertainty');
|
|
2829
|
+
return UNCERTAIN;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2776
2832
|
function unsettled(result) {
|
|
2777
2833
|
return result.kind !== 'action' && result.retryable;
|
|
2778
2834
|
}
|
|
@@ -2834,6 +2890,33 @@ export function composeRuntime(options) {
|
|
|
2834
2890
|
const held = replicas.get(locator);
|
|
2835
2891
|
return (held !== undefined && options.adapter.reached(locator, held.record.roomVersion, roomVersion));
|
|
2836
2892
|
}
|
|
2893
|
+
function shownAt(locator) {
|
|
2894
|
+
const held = replicas.get(locator)?.record.roomVersion;
|
|
2895
|
+
const raised = highest.get(locator);
|
|
2896
|
+
if (held === undefined || raised === undefined)
|
|
2897
|
+
return held ?? raised;
|
|
2898
|
+
return options.adapter.reached(locator, held, raised) ? held : raised;
|
|
2899
|
+
}
|
|
2900
|
+
function showing(locator, roomVersion) {
|
|
2901
|
+
const at = shownAt(locator);
|
|
2902
|
+
if (at === undefined || !options.adapter.reached(locator, at, roomVersion)) {
|
|
2903
|
+
highest.set(locator, roomVersion);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
function older(locator, roomVersion) {
|
|
2907
|
+
if (beyondHorizon.has(locator))
|
|
2908
|
+
return false;
|
|
2909
|
+
const at = shownAt(locator);
|
|
2910
|
+
return (at !== undefined &&
|
|
2911
|
+
options.adapter.reached(locator, at, roomVersion) &&
|
|
2912
|
+
!options.adapter.reached(locator, roomVersion, at));
|
|
2913
|
+
}
|
|
2914
|
+
function replaced(locators) {
|
|
2915
|
+
for (const locator of locators) {
|
|
2916
|
+
highest.delete(locator);
|
|
2917
|
+
beyondHorizon.add(locator);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2837
2920
|
async function writtenAfter(locator, roomVersion, path) {
|
|
2838
2921
|
const held = replicas.get(locator);
|
|
2839
2922
|
if (held === undefined)
|
|
@@ -2844,122 +2927,27 @@ export function composeRuntime(options) {
|
|
|
2844
2927
|
options.adapter.reached(locator, written, roomVersion) &&
|
|
2845
2928
|
!options.adapter.reached(locator, roomVersion, written));
|
|
2846
2929
|
}
|
|
2847
|
-
function
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
else
|
|
2857
|
-
known.set(path, entry.binary !== null ? null : entry.content);
|
|
2858
|
-
}
|
|
2859
|
-
return known;
|
|
2860
|
-
};
|
|
2861
|
-
}
|
|
2862
|
-
async function unreadableRoom(texts, walk, paths) {
|
|
2863
|
-
if (paths !== null) {
|
|
2864
|
-
const text = {};
|
|
2865
|
-
for (const [path, content] of await texts(paths))
|
|
2866
|
-
if (content !== null)
|
|
2867
|
-
text[path] = content;
|
|
2868
|
-
return stepped(unreadableIn(text, Object.keys(text)));
|
|
2869
|
-
}
|
|
2870
|
-
for (let after = null;;) {
|
|
2871
|
-
const page = await walk(after);
|
|
2872
|
-
const text = {};
|
|
2873
|
-
for (const { path, content } of page.entries)
|
|
2874
|
-
if (content !== null)
|
|
2875
|
-
text[path] = content;
|
|
2876
|
-
const refused = await stepped(unreadableIn(text, Object.keys(text)));
|
|
2877
|
-
if (refused !== null)
|
|
2878
|
-
return refused;
|
|
2879
|
-
if (page.next === null)
|
|
2880
|
-
return null;
|
|
2881
|
-
after = page.next;
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
async function validatedRoom(local, record, view, previous, overrides = null, checked = null) {
|
|
2885
|
-
const composition = composed(record);
|
|
2886
|
-
const modeled = record.model.status !== 'unavailable' || record.typedSelection !== null;
|
|
2887
|
-
const lookup = overrides === null
|
|
2888
|
-
? lookupOf(local, record.key, view)
|
|
2889
|
-
: overriddenLookup(lookupOf(local, record.key, view), overrides);
|
|
2890
|
-
const texts = overrides === null
|
|
2891
|
-
? textsOf(local, record.key, view)
|
|
2892
|
-
: overriddenTexts(textsOf(local, record.key, view), overrides);
|
|
2893
|
-
const walking = overrides === null
|
|
2894
|
-
? walkOf(local, record.key, view)
|
|
2895
|
-
: overriddenWalk(walkOf(local, record.key, view), overrides);
|
|
2896
|
-
const walk = checked === null
|
|
2897
|
-
? walking
|
|
2898
|
-
: async (after) => {
|
|
2899
|
-
const page = await walking(after);
|
|
2900
|
-
checked(page.entries.reduce((sum, entry) => sum + (entry.content?.length ?? 0), 0));
|
|
2901
|
-
return page;
|
|
2902
|
-
};
|
|
2903
|
-
let changes = null;
|
|
2904
|
-
let reshaped = false;
|
|
2905
|
-
if (previous !== null && previous.changed.length <= CLEAN_CHANGES) {
|
|
2906
|
-
const [was, now] = await Promise.all([
|
|
2907
|
-
local.entries(record.key, 'confirmed', previous.changed),
|
|
2908
|
-
overrides === null
|
|
2909
|
-
? local.entries(record.key, view, previous.changed)
|
|
2910
|
-
: Promise.resolve([]),
|
|
2911
|
-
]);
|
|
2912
|
-
const before = new Map(was.map((entry) => [entry.path, entry]));
|
|
2913
|
-
const after = overrides === null
|
|
2914
|
-
? new Map(now.map((entry) => [entry.path, entry]))
|
|
2915
|
-
: overrides;
|
|
2916
|
-
changes = previous.changed.flatMap((path) => {
|
|
2917
|
-
const one = before.get(path) ?? null;
|
|
2918
|
-
const other = after.get(path) ?? null;
|
|
2919
|
-
reshaped ||= textDiffers(one, other);
|
|
2920
|
-
const held = textOf(one);
|
|
2921
|
-
const next = textOf(other);
|
|
2922
|
-
return held === next ? [] : [{ path, was: held, now: next }];
|
|
2923
|
-
});
|
|
2924
|
-
}
|
|
2925
|
-
const named = changes === null ? null : changes.map(({ path }) => path);
|
|
2926
|
-
const textChanged = changes === null || changes.length > 0 || reshaped;
|
|
2927
|
-
const modelsChanged = previous === null || !sameComposition(composed(previous.record), composition);
|
|
2928
|
-
if (modeled && (textChanged || modelsChanged)) {
|
|
2929
|
-
const clean = previous !== null &&
|
|
2930
|
-
changes !== null &&
|
|
2931
|
-
(await changedCleanly(changes, composed(previous.record), composition, {
|
|
2932
|
-
lookup,
|
|
2933
|
-
texts,
|
|
2934
|
-
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
2935
|
-
}));
|
|
2936
|
-
if (clean)
|
|
2937
|
-
return null;
|
|
2938
|
-
const verdict = await validatedCollection(walk, lookup, composition);
|
|
2939
|
-
if (verdict.verdict === 'accept')
|
|
2940
|
-
return null;
|
|
2941
|
-
if (verdict.rule !== 'model-unavailable')
|
|
2942
|
-
return { kind: 'refused', refusal: verdict };
|
|
2943
|
-
return unreadableRoom(texts, walk, named);
|
|
2944
|
-
}
|
|
2945
|
-
if (textChanged)
|
|
2946
|
-
return unreadableRoom(texts, walk, named);
|
|
2947
|
-
return null;
|
|
2930
|
+
async function writtenLater(local, locator, key, paths, roomVersion) {
|
|
2931
|
+
if (paths.length === 0 || !older(locator, roomVersion))
|
|
2932
|
+
return new Set();
|
|
2933
|
+
const entries = await local.entries(key, 'confirmed', paths);
|
|
2934
|
+
return new Set(entries.flatMap(({ path, written }) => written !== null &&
|
|
2935
|
+
options.adapter.reached(locator, written, roomVersion) &&
|
|
2936
|
+
!options.adapter.reached(locator, roomVersion, written)
|
|
2937
|
+
? [path]
|
|
2938
|
+
: []));
|
|
2948
2939
|
}
|
|
2949
|
-
function refreshingOf(local, key, composition, liveness, replaying =
|
|
2950
|
-
const write = replaying
|
|
2940
|
+
function refreshingOf(local, locator, key, composition, liveness, replaying = false) {
|
|
2941
|
+
const write = replaying ? finishingWrite : exclusive;
|
|
2951
2942
|
return {
|
|
2952
|
-
|
|
2953
|
-
if (replaying
|
|
2954
|
-
|
|
2955
|
-
installing(replaying);
|
|
2956
|
-
await screened();
|
|
2943
|
+
turn() {
|
|
2944
|
+
if (replaying)
|
|
2945
|
+
installing(locator);
|
|
2957
2946
|
},
|
|
2958
2947
|
prior: async (paths) => new Map((await local.entries(key, 'refreshed', paths)).map((entry) => [entry.path, entry])),
|
|
2948
|
+
later: (paths, roomVersion) => writtenLater(local, locator, key, paths, roomVersion),
|
|
2959
2949
|
async stage(page) {
|
|
2960
2950
|
for (let written = 0;;) {
|
|
2961
|
-
if (replaying !== null)
|
|
2962
|
-
await screened();
|
|
2963
2951
|
const end = batchEnd(page.entries, written);
|
|
2964
2952
|
const last = end === page.entries.length;
|
|
2965
2953
|
const entries = page.entries.slice(written, end);
|
|
@@ -3004,32 +2992,71 @@ export function composeRuntime(options) {
|
|
|
3004
2992
|
return done;
|
|
3005
2993
|
}
|
|
3006
2994
|
function installWrite(work) {
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
2995
|
+
return installTurn(installTurns.starting, work);
|
|
2996
|
+
}
|
|
2997
|
+
function finishingWrite(work) {
|
|
2998
|
+
return installTurn(installTurns.finishing, work);
|
|
2999
|
+
}
|
|
3000
|
+
function installTurn(queue, work) {
|
|
3001
|
+
return new Promise((resolve, reject) => {
|
|
3002
|
+
queue.push(() => void exclusive(work)
|
|
3003
|
+
.then(resolve, reject)
|
|
3004
|
+
.finally(() => {
|
|
3005
|
+
installTurns.busy = false;
|
|
3006
|
+
nextInstallTurn();
|
|
3007
|
+
}));
|
|
3008
|
+
nextInstallTurn();
|
|
3009
|
+
});
|
|
3010
3010
|
}
|
|
3011
|
-
function
|
|
3012
|
-
if (
|
|
3013
|
-
return
|
|
3014
|
-
|
|
3015
|
-
|
|
3011
|
+
function nextInstallTurn() {
|
|
3012
|
+
if (installTurns.busy || installTurns.ahead > 0)
|
|
3013
|
+
return;
|
|
3014
|
+
const turn = installTurns.finishing.shift() ?? installTurns.starting.shift();
|
|
3015
|
+
if (turn === undefined)
|
|
3016
|
+
return;
|
|
3017
|
+
installTurns.busy = true;
|
|
3018
|
+
turn();
|
|
3019
|
+
}
|
|
3020
|
+
async function ahead(work) {
|
|
3021
|
+
let taken = false;
|
|
3022
|
+
const hold = {
|
|
3023
|
+
take() {
|
|
3024
|
+
if (taken)
|
|
3025
|
+
return;
|
|
3026
|
+
taken = true;
|
|
3027
|
+
installTurns.ahead += 1;
|
|
3028
|
+
},
|
|
3029
|
+
release() {
|
|
3030
|
+
if (!taken)
|
|
3031
|
+
return;
|
|
3032
|
+
taken = false;
|
|
3033
|
+
installTurns.ahead -= 1;
|
|
3034
|
+
resumeInstall();
|
|
3035
|
+
},
|
|
3036
|
+
};
|
|
3037
|
+
hold.take();
|
|
3038
|
+
try {
|
|
3039
|
+
return await work(hold);
|
|
3040
|
+
}
|
|
3041
|
+
finally {
|
|
3042
|
+
hold.release();
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
function resumeInstall() {
|
|
3046
|
+
if (installTurns.ahead > 0)
|
|
3047
|
+
return;
|
|
3048
|
+
void yielded().then(nextInstallTurn);
|
|
3016
3049
|
}
|
|
3017
3050
|
async function heldStream() {
|
|
3018
|
-
|
|
3019
|
-
if (streams.generation !== generation)
|
|
3020
|
-
streams = { generation, free: STREAMS_HELD };
|
|
3021
|
-
const held = streams;
|
|
3022
|
-
if (held.free > 0) {
|
|
3023
|
-
held.free -= 1;
|
|
3024
|
-
return once(() => {
|
|
3025
|
-
held.free += 1;
|
|
3026
|
-
});
|
|
3027
|
-
}
|
|
3051
|
+
while (streamsFree === 0)
|
|
3028
3052
|
await new Promise((admit) => installWaiters.push(admit));
|
|
3029
|
-
|
|
3053
|
+
streamsFree -= 1;
|
|
3054
|
+
return once(() => {
|
|
3055
|
+
streamsFree += 1;
|
|
3056
|
+
});
|
|
3030
3057
|
}
|
|
3031
3058
|
async function unqueued() {
|
|
3032
|
-
while (
|
|
3059
|
+
while (queuedText >= QUEUED_TEXT) {
|
|
3033
3060
|
await new Promise((admit) => installWaiters.push(admit));
|
|
3034
3061
|
}
|
|
3035
3062
|
}
|
|
@@ -3054,14 +3081,19 @@ export function composeRuntime(options) {
|
|
|
3054
3081
|
streaming.delete(locator);
|
|
3055
3082
|
underived.delete(locator);
|
|
3056
3083
|
unindexed.delete(locator);
|
|
3057
|
-
|
|
3084
|
+
}
|
|
3085
|
+
function setBack(locator) {
|
|
3086
|
+
const now = bytesOf(locator, true);
|
|
3087
|
+
const earlier = startedOver.get(locator);
|
|
3088
|
+
startedOver.set(locator, {
|
|
3089
|
+
done: Math.max(now.done, earlier?.done ?? 0),
|
|
3090
|
+
total: Math.max(now.total, earlier?.total ?? 0),
|
|
3091
|
+
});
|
|
3058
3092
|
}
|
|
3059
3093
|
function owedIndexes(locator, bytes, text) {
|
|
3060
3094
|
if (bytes <= 0 || text <= 0)
|
|
3061
3095
|
return;
|
|
3062
|
-
unindexed.set(locator, [...(unindexed.get(locator) ?? []), { bytes
|
|
3063
|
-
const check = unchecked.get(locator) ?? { bytes: 0, text: 0, done: 0 };
|
|
3064
|
-
unchecked.set(locator, { ...check, bytes: check.bytes + bytes / 2, text: check.text + text });
|
|
3096
|
+
unindexed.set(locator, [...(unindexed.get(locator) ?? []), { bytes, text }]);
|
|
3065
3097
|
}
|
|
3066
3098
|
function built(locator, { done, text }) {
|
|
3067
3099
|
const owed = unindexed.get(locator) ?? [];
|
|
@@ -3087,41 +3119,34 @@ export function composeRuntime(options) {
|
|
|
3087
3119
|
if (installs.delete(locator))
|
|
3088
3120
|
queueReadiness();
|
|
3089
3121
|
}
|
|
3090
|
-
function
|
|
3091
|
-
if (value === onScreen)
|
|
3092
|
-
return;
|
|
3093
|
-
onScreen = value;
|
|
3094
|
-
journal.record(`installation ${value ? 'shown' : 'hidden'}`);
|
|
3095
|
-
queueReadiness();
|
|
3096
|
-
if (!value)
|
|
3097
|
-
return;
|
|
3098
|
-
for (const resolve of shownWaiters.splice(0))
|
|
3099
|
-
resolve();
|
|
3100
|
-
}
|
|
3101
|
-
async function pullRefresh(authorized, held, liveness) {
|
|
3122
|
+
async function pullRefresh(authorized, held, liveness, hold, refresh) {
|
|
3102
3123
|
if (!liveness())
|
|
3103
3124
|
return unconfirmed;
|
|
3104
|
-
const turn = generation;
|
|
3105
3125
|
const { record } = held;
|
|
3106
|
-
const
|
|
3126
|
+
const from = standingOf(record.locator);
|
|
3107
3127
|
const local = await openLocal();
|
|
3108
3128
|
try {
|
|
3109
3129
|
await exclusive(() => local.unrefresh(record.key));
|
|
3110
|
-
const result = await advancement(
|
|
3130
|
+
const result = await advancement(async (authorization, request) => {
|
|
3131
|
+
hold.release();
|
|
3132
|
+
try {
|
|
3133
|
+
return await refresh(authorization, request);
|
|
3134
|
+
}
|
|
3135
|
+
finally {
|
|
3136
|
+
hold.take();
|
|
3137
|
+
}
|
|
3138
|
+
}, authorized, record, (models, kept) => (liveness() ? remodel(record.locator, models, kept) : kept), liveness, refreshingOf(local, record.locator, record.key, composed(record), liveness));
|
|
3111
3139
|
if (result.kind !== 'advanced') {
|
|
3112
3140
|
journal.record(`transfer refresh ${record.locator} ${result.kind}`);
|
|
3113
3141
|
return result;
|
|
3114
3142
|
}
|
|
3115
|
-
const refused = await validatedRoom(local, result.record, 'refreshed', {
|
|
3116
|
-
record,
|
|
3117
|
-
changed: [...result.changed],
|
|
3118
|
-
});
|
|
3119
|
-
if (refused !== null) {
|
|
3120
|
-
journal.record(`transfer refresh ${record.locator} refused`);
|
|
3121
|
-
return refused;
|
|
3122
|
-
}
|
|
3123
3143
|
journal.record(`transfer refresh ${record.locator} ${result.record.roomVersion}`);
|
|
3124
|
-
return {
|
|
3144
|
+
return {
|
|
3145
|
+
kind: 'advanced',
|
|
3146
|
+
record: result.record,
|
|
3147
|
+
from,
|
|
3148
|
+
remodelled: result.record.model !== record.model,
|
|
3149
|
+
};
|
|
3125
3150
|
}
|
|
3126
3151
|
catch (error) {
|
|
3127
3152
|
if (refusedForSpace(error))
|
|
@@ -3129,9 +3154,6 @@ export function composeRuntime(options) {
|
|
|
3129
3154
|
journal.record(`transfer refresh ${record.locator} transport-uncertainty`);
|
|
3130
3155
|
throw error;
|
|
3131
3156
|
}
|
|
3132
|
-
finally {
|
|
3133
|
-
reads.finished(record.locator, turn);
|
|
3134
|
-
}
|
|
3135
3157
|
}
|
|
3136
3158
|
const heldContents = new Map();
|
|
3137
3159
|
function heldContentOf(local, key) {
|
|
@@ -3149,14 +3171,20 @@ export function composeRuntime(options) {
|
|
|
3149
3171
|
heldContents.set(key, made);
|
|
3150
3172
|
return made;
|
|
3151
3173
|
}
|
|
3174
|
+
async function abandoned(local, locator) {
|
|
3175
|
+
await local.abandon(locator);
|
|
3176
|
+
setBack(locator);
|
|
3177
|
+
untransferred(locator);
|
|
3178
|
+
liveQueries.recover();
|
|
3179
|
+
sweep();
|
|
3180
|
+
}
|
|
3152
3181
|
function staging(local, liveness) {
|
|
3153
3182
|
const derived = async (locator, key, composition) => {
|
|
3154
3183
|
underived.delete(locator);
|
|
3155
3184
|
for (;;) {
|
|
3156
|
-
await screened();
|
|
3157
3185
|
if (!liveness())
|
|
3158
3186
|
return false;
|
|
3159
|
-
const done = await
|
|
3187
|
+
const done = await finishingWrite(async () => liveness()
|
|
3160
3188
|
? timed(deriveBudget, () => local.rederive(key, composition, deriveBudget.bytes, DERIVE_LONGEST_MS, DERIVE_FLOOR))
|
|
3161
3189
|
: null);
|
|
3162
3190
|
if (done === null)
|
|
@@ -3170,19 +3198,13 @@ export function composeRuntime(options) {
|
|
|
3170
3198
|
held: (locator) => transfers.get(locator) ?? null,
|
|
3171
3199
|
async turn(locator, step) {
|
|
3172
3200
|
installing(locator);
|
|
3173
|
-
|
|
3174
|
-
await screened();
|
|
3175
|
-
const release = step === 'downloading' ? await heldStream() : () => { };
|
|
3176
|
-
if (onScreen || closed)
|
|
3177
|
-
return release;
|
|
3178
|
-
release();
|
|
3179
|
-
}
|
|
3201
|
+
return step === 'downloading' ? heldStream() : () => { };
|
|
3180
3202
|
},
|
|
3181
3203
|
unqueued,
|
|
3182
|
-
queued(
|
|
3183
|
-
|
|
3204
|
+
queued(text) {
|
|
3205
|
+
queuedText += text;
|
|
3184
3206
|
return once(() => {
|
|
3185
|
-
|
|
3207
|
+
queuedText -= text;
|
|
3186
3208
|
});
|
|
3187
3209
|
},
|
|
3188
3210
|
arriving(locator) {
|
|
@@ -3191,7 +3213,7 @@ export function composeRuntime(options) {
|
|
|
3191
3213
|
at(bytes) {
|
|
3192
3214
|
if (ended)
|
|
3193
3215
|
return;
|
|
3194
|
-
arrivals.set(locator,
|
|
3216
|
+
arrivals.set(locator, bytes);
|
|
3195
3217
|
streaming.add(locator);
|
|
3196
3218
|
const now = performance.now();
|
|
3197
3219
|
if (now - arrivalReported < ARRIVAL_REPORT_MS)
|
|
@@ -3199,14 +3221,6 @@ export function composeRuntime(options) {
|
|
|
3199
3221
|
arrivalReported = now;
|
|
3200
3222
|
queueReadiness();
|
|
3201
3223
|
},
|
|
3202
|
-
part() {
|
|
3203
|
-
const arrived = arrivals.get(locator);
|
|
3204
|
-
arrivals.set(locator, {
|
|
3205
|
-
parts: (arrived?.parts ?? 0) + 1,
|
|
3206
|
-
bytes: arrived?.bytes ?? 0,
|
|
3207
|
-
});
|
|
3208
|
-
queueReadiness();
|
|
3209
|
-
},
|
|
3210
3224
|
end() {
|
|
3211
3225
|
if (ended)
|
|
3212
3226
|
return;
|
|
@@ -3216,14 +3230,10 @@ export function composeRuntime(options) {
|
|
|
3216
3230
|
},
|
|
3217
3231
|
};
|
|
3218
3232
|
},
|
|
3219
|
-
|
|
3220
|
-
const later = announced.get(locator);
|
|
3221
|
-
return later === undefined ? [] : [later];
|
|
3222
|
-
},
|
|
3233
|
+
streamed: options.adapter.roomStream !== undefined,
|
|
3223
3234
|
async stage(record, page, composition) {
|
|
3224
3235
|
const { locator } = record;
|
|
3225
3236
|
installing(locator);
|
|
3226
|
-
await screened();
|
|
3227
3237
|
if (!liveness())
|
|
3228
3238
|
return null;
|
|
3229
3239
|
const key = await installWrite(async () => {
|
|
@@ -3252,19 +3262,16 @@ export function composeRuntime(options) {
|
|
|
3252
3262
|
derive: derived,
|
|
3253
3263
|
async refreshing(locator, key, composition) {
|
|
3254
3264
|
await exclusive(() => local.unrefresh(key));
|
|
3255
|
-
return refreshingOf(local, key, composition, liveness,
|
|
3265
|
+
return refreshingOf(local, locator, key, composition, liveness, true);
|
|
3256
3266
|
},
|
|
3257
3267
|
abandon: (locator) => exclusive(async () => {
|
|
3258
|
-
if (
|
|
3259
|
-
|
|
3260
|
-
await local.abandon(locator);
|
|
3261
|
-
untransferred(locator);
|
|
3262
|
-
liveQueries.recover();
|
|
3263
|
-
sweep();
|
|
3268
|
+
if (liveness())
|
|
3269
|
+
await abandoned(local, locator);
|
|
3264
3270
|
}),
|
|
3265
3271
|
setAside: async (locator) => {
|
|
3266
3272
|
if (!liveness())
|
|
3267
3273
|
return;
|
|
3274
|
+
setBack(locator);
|
|
3268
3275
|
untransferred(locator);
|
|
3269
3276
|
liveQueries.recover();
|
|
3270
3277
|
},
|
|
@@ -3273,19 +3280,6 @@ export function composeRuntime(options) {
|
|
|
3273
3280
|
sweep();
|
|
3274
3281
|
}),
|
|
3275
3282
|
content: (key) => heldContentOf(local, key),
|
|
3276
|
-
async validate(record, view) {
|
|
3277
|
-
const { locator } = record;
|
|
3278
|
-
const refused = await validatedRoom(local, record, view, null, null, (text) => {
|
|
3279
|
-
const check = unchecked.get(locator);
|
|
3280
|
-
if (check === undefined)
|
|
3281
|
-
return;
|
|
3282
|
-
unchecked.set(locator, { ...check, done: check.done + text });
|
|
3283
|
-
queueReadiness();
|
|
3284
|
-
});
|
|
3285
|
-
unchecked.delete(locator);
|
|
3286
|
-
queueReadiness();
|
|
3287
|
-
return refused;
|
|
3288
|
-
},
|
|
3289
3283
|
};
|
|
3290
3284
|
}
|
|
3291
3285
|
function reusable(room, local) {
|
|
@@ -3297,7 +3291,6 @@ export function composeRuntime(options) {
|
|
|
3297
3291
|
async function pullBootstrap(authorized, room, local, liveness) {
|
|
3298
3292
|
if (!liveness())
|
|
3299
3293
|
return unconfirmed;
|
|
3300
|
-
const turn = generation;
|
|
3301
3294
|
if (replicas.has(room.locator)) {
|
|
3302
3295
|
catching.add(room.locator);
|
|
3303
3296
|
liveQueries.recover();
|
|
@@ -3306,6 +3299,13 @@ export function composeRuntime(options) {
|
|
|
3306
3299
|
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));
|
|
3307
3300
|
const detail = result.kind === 'staged' ? result.record.roomVersion : result.kind;
|
|
3308
3301
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
3302
|
+
if (result.kind === 'staged' && older(room.locator, result.record.roomVersion)) {
|
|
3303
|
+
caughtUpTo(room.locator);
|
|
3304
|
+
installed(room.locator);
|
|
3305
|
+
await abandoned(local, room.locator);
|
|
3306
|
+
journal.record(`transfer bootstrap ${room.locator} older than shown`);
|
|
3307
|
+
return { kind: 'transport-uncertainty' };
|
|
3308
|
+
}
|
|
3309
3309
|
if (result.kind !== 'staged') {
|
|
3310
3310
|
caughtUpTo(room.locator);
|
|
3311
3311
|
installed(room.locator);
|
|
@@ -3320,9 +3320,6 @@ export function composeRuntime(options) {
|
|
|
3320
3320
|
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
3321
3321
|
throw error;
|
|
3322
3322
|
}
|
|
3323
|
-
finally {
|
|
3324
|
-
reads.finished(room.locator, turn);
|
|
3325
|
-
}
|
|
3326
3323
|
}
|
|
3327
3324
|
function caughtUpTo(locator) {
|
|
3328
3325
|
if (catching.delete(locator))
|
|
@@ -3330,72 +3327,83 @@ export function composeRuntime(options) {
|
|
|
3330
3327
|
}
|
|
3331
3328
|
function behind(room) {
|
|
3332
3329
|
const held = replicas.get(room.locator)?.record.roomVersion;
|
|
3333
|
-
return
|
|
3334
|
-
[announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later)));
|
|
3330
|
+
return held !== undefined && !options.adapter.reached(room.locator, held, room.roomVersion);
|
|
3335
3331
|
}
|
|
3336
|
-
|
|
3337
|
-
const
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
return
|
|
3332
|
+
function standingOf(locator) {
|
|
3333
|
+
const held = replicas.get(locator);
|
|
3334
|
+
return { key: held?.record.key ?? null, confirmed: held?.confirmed ?? 0 };
|
|
3335
|
+
}
|
|
3336
|
+
function stands(locator, from) {
|
|
3337
|
+
const now = standingOf(locator);
|
|
3338
|
+
return now.key === from.key && now.confirmed === from.confirmed;
|
|
3343
3339
|
}
|
|
3344
|
-
async function
|
|
3340
|
+
async function advance(room, local, authorized, running) {
|
|
3345
3341
|
const live = () => running() && !full;
|
|
3346
3342
|
if (!live())
|
|
3347
3343
|
return unconfirmed;
|
|
3348
3344
|
const held = replicas.get(room.locator);
|
|
3349
|
-
if (held !== undefined) {
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
emitRooms();
|
|
3361
|
-
}
|
|
3362
|
-
return null;
|
|
3363
|
-
}
|
|
3364
|
-
if (refreshed.kind === 'advanced') {
|
|
3365
|
-
const landing = { kind: 'refreshed', base: refreshed.base };
|
|
3366
|
-
if (!(await publish(local, room, refreshed.record, landing, live))) {
|
|
3367
|
-
const now = replicas.get(room.locator)?.record.roomVersion;
|
|
3368
|
-
const reached = now !== undefined &&
|
|
3369
|
-
options.adapter.reached(room.locator, now, refreshed.record.roomVersion);
|
|
3370
|
-
return reached ? null : advance(room, local, authorized, live);
|
|
3371
|
-
}
|
|
3372
|
-
await retainModel(local, room, modelOf(room.locator));
|
|
3373
|
-
return null;
|
|
3374
|
-
}
|
|
3375
|
-
if (refreshed.kind !== 'beyond-horizon')
|
|
3345
|
+
if (held !== undefined && options.adapter.roomStream !== undefined) {
|
|
3346
|
+
stale.delete(room.locator);
|
|
3347
|
+
name(room.locator);
|
|
3348
|
+
return null;
|
|
3349
|
+
}
|
|
3350
|
+
const refresh = reads.refresh;
|
|
3351
|
+
if (held !== undefined && refresh !== undefined) {
|
|
3352
|
+
const refreshed = await ahead((hold) => refreshedRoom(room, local, authorized, held, live, hold, refresh));
|
|
3353
|
+
if (refreshed === 'behind')
|
|
3354
|
+
return advance(room, local, authorized, live);
|
|
3355
|
+
if (refreshed !== 'beyond-horizon')
|
|
3376
3356
|
return refreshed;
|
|
3377
|
-
if (
|
|
3378
|
-
owed.add(room.locator);
|
|
3379
|
-
liveQueries.recover();
|
|
3380
|
-
return unconfirmed;
|
|
3381
|
-
}
|
|
3382
|
-
if (reloadsDue.get(room.locator) === generation)
|
|
3357
|
+
if (reloadsDue.has(room.locator))
|
|
3383
3358
|
return unconfirmed;
|
|
3359
|
+
replaced([room.locator]);
|
|
3384
3360
|
}
|
|
3361
|
+
const from = standingOf(room.locator);
|
|
3385
3362
|
const bootstrapped = await pullBootstrap(authorized, room, local, live);
|
|
3386
|
-
if (bootstrapped.kind
|
|
3387
|
-
|
|
3363
|
+
if (bootstrapped.kind !== 'staged')
|
|
3364
|
+
return live() ? bootstrapped : unconfirmed;
|
|
3365
|
+
const landing = { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from };
|
|
3366
|
+
if (await publish(local, room, bootstrapped.record, landing, live)) {
|
|
3388
3367
|
return live() ? null : unconfirmed;
|
|
3389
3368
|
}
|
|
3390
|
-
return
|
|
3369
|
+
return reachedBy(room.locator, bootstrapped.record.roomVersion)
|
|
3370
|
+
? null
|
|
3371
|
+
: advance(room, local, authorized, live);
|
|
3372
|
+
}
|
|
3373
|
+
async function refreshedRoom(room, local, authorized, held, live, hold, refresh) {
|
|
3374
|
+
const refreshed = await pullRefresh(authorized, held, live, hold, refresh);
|
|
3375
|
+
if (!live())
|
|
3376
|
+
return unconfirmed;
|
|
3377
|
+
if (refreshed.kind === 'still-current') {
|
|
3378
|
+
stale.delete(room.locator);
|
|
3379
|
+
if (unreadable.delete(room.locator)) {
|
|
3380
|
+
scopedRefusals.delete(room.locator);
|
|
3381
|
+
list(room, true);
|
|
3382
|
+
emitRooms();
|
|
3383
|
+
}
|
|
3384
|
+
return null;
|
|
3385
|
+
}
|
|
3386
|
+
if (refreshed.kind === 'advanced') {
|
|
3387
|
+
const landing = {
|
|
3388
|
+
kind: 'refreshed',
|
|
3389
|
+
from: refreshed.from,
|
|
3390
|
+
remodelled: refreshed.remodelled,
|
|
3391
|
+
};
|
|
3392
|
+
if (!(await publish(local, room, refreshed.record, landing, live))) {
|
|
3393
|
+
const now = replicas.get(room.locator)?.record.roomVersion;
|
|
3394
|
+
const reached = now !== undefined &&
|
|
3395
|
+
options.adapter.reached(room.locator, now, refreshed.record.roomVersion);
|
|
3396
|
+
return reached ? null : 'behind';
|
|
3397
|
+
}
|
|
3398
|
+
await retainModel(local, room, modelOf(room.locator));
|
|
3399
|
+
return null;
|
|
3400
|
+
}
|
|
3401
|
+
return refreshed.kind === 'beyond-horizon' ? 'beyond-horizon' : refreshed;
|
|
3391
3402
|
}
|
|
3392
3403
|
async function withhold(withheld, room, local, progress) {
|
|
3393
|
-
if (withheld.kind === 'unconfirmed')
|
|
3404
|
+
if (withheld.kind === 'unconfirmed' || isFenced(room.locator))
|
|
3394
3405
|
return;
|
|
3395
|
-
|
|
3396
|
-
stale.delete(room.locator);
|
|
3397
|
-
else
|
|
3398
|
-
stale.add(room.locator);
|
|
3406
|
+
stale.add(room.locator);
|
|
3399
3407
|
if (withheld.kind === 'transport-uncertainty' || withheld.kind === 'retryable-refusal') {
|
|
3400
3408
|
const reset = withheld.kind === 'retryable-refusal' ? withheld.reset : undefined;
|
|
3401
3409
|
repairable.set(room.locator, reset);
|
|
@@ -3417,30 +3425,10 @@ export function composeRuntime(options) {
|
|
|
3417
3425
|
list(room, false);
|
|
3418
3426
|
emitRooms();
|
|
3419
3427
|
}
|
|
3420
|
-
else if (withheld.kind === 'refused') {
|
|
3421
|
-
refusedSource(room, withheld);
|
|
3422
|
-
}
|
|
3423
3428
|
else if (withheld.kind === 'revoked-authorization') {
|
|
3424
3429
|
await refuse('revoked-authorization');
|
|
3425
3430
|
}
|
|
3426
3431
|
}
|
|
3427
|
-
function refusedSource(room, { refusal }) {
|
|
3428
|
-
sourceRefusals.set(room.locator, {
|
|
3429
|
-
code: refusal.rule,
|
|
3430
|
-
retryable: false,
|
|
3431
|
-
fields: {
|
|
3432
|
-
file: refusal.file,
|
|
3433
|
-
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
3434
|
-
...(refusal.referrers === undefined ? {} : { referrers: refusal.referrers }),
|
|
3435
|
-
},
|
|
3436
|
-
});
|
|
3437
|
-
declarationRefusals.delete(room.locator);
|
|
3438
|
-
unreadable.add(room.locator);
|
|
3439
|
-
list(room, false);
|
|
3440
|
-
emitRooms();
|
|
3441
|
-
moveTo({ ...status, ...attention() });
|
|
3442
|
-
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
3443
|
-
}
|
|
3444
3432
|
function leaving(batch, result) {
|
|
3445
3433
|
if (result.kind === 'expired')
|
|
3446
3434
|
return { batch, returned: returnedBy('expired') };
|
|
@@ -3462,8 +3450,8 @@ export function composeRuntime(options) {
|
|
|
3462
3450
|
function settling(acceptance) {
|
|
3463
3451
|
return acceptance !== null && 'apply' in acceptance;
|
|
3464
3452
|
}
|
|
3465
|
-
function
|
|
3466
|
-
return settling(acceptance) ?
|
|
3453
|
+
function failureOf(acceptance) {
|
|
3454
|
+
return settling(acceptance) ? null : acceptance;
|
|
3467
3455
|
}
|
|
3468
3456
|
async function acceptance(room, local, submitted, accepted) {
|
|
3469
3457
|
if (!continuing())
|
|
@@ -3476,7 +3464,8 @@ export function composeRuntime(options) {
|
|
|
3476
3464
|
logAccepted(room.locator, submitted, accepted);
|
|
3477
3465
|
let record = current.record;
|
|
3478
3466
|
let changes;
|
|
3479
|
-
if (!options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
3467
|
+
if (!options.adapter.reached(room.locator, held, accepted.roomVersion) &&
|
|
3468
|
+
!older(room.locator, accepted.roomVersion)) {
|
|
3480
3469
|
const paths = [...returnedPaths(submitted, accepted)];
|
|
3481
3470
|
const confirmedLayer = await stepped(slicing(await local.entries(record.key, 'confirmed', paths), paths));
|
|
3482
3471
|
const back = await stepped(returning(confirmedLayer, submitted, accepted, accepted.roomVersion));
|
|
@@ -3486,34 +3475,6 @@ export function composeRuntime(options) {
|
|
|
3486
3475
|
? accepted.roomVersion
|
|
3487
3476
|
: held,
|
|
3488
3477
|
};
|
|
3489
|
-
const overrides = new Map([
|
|
3490
|
-
...back.set.map((entry) => [entry.path, entry]),
|
|
3491
|
-
...back.removed.map((path) => [path, null]),
|
|
3492
|
-
]);
|
|
3493
|
-
const refused = await validatedRoom(local, record, 'confirmed', { record: current.record, changed: [...overrides.keys()] }, overrides);
|
|
3494
|
-
if (refused !== null) {
|
|
3495
|
-
const key = pendingKey(room.locator, submitted.id);
|
|
3496
|
-
return {
|
|
3497
|
-
locator: room.locator,
|
|
3498
|
-
left: [],
|
|
3499
|
-
commit: {
|
|
3500
|
-
rooms: [],
|
|
3501
|
-
writes: [
|
|
3502
|
-
{
|
|
3503
|
-
locator: room.locator,
|
|
3504
|
-
accepted: [{ id: submitted.id, roomVersion: accepted.roomVersion }],
|
|
3505
|
-
},
|
|
3506
|
-
],
|
|
3507
|
-
},
|
|
3508
|
-
refused,
|
|
3509
|
-
apply() {
|
|
3510
|
-
const state = pendings.get(key);
|
|
3511
|
-
if (state !== undefined && !fences.has(room.locator))
|
|
3512
|
-
pendings.set(key, { ...state, accepted: accepted.roomVersion });
|
|
3513
|
-
return changeSet(room.locator, { queue: true });
|
|
3514
|
-
},
|
|
3515
|
-
};
|
|
3516
|
-
}
|
|
3517
3478
|
changes = back;
|
|
3518
3479
|
}
|
|
3519
3480
|
const settled = await settlement(local, room.locator, current, record, current.queue.filter((batch) => batch.id !== submitted.id), { room: true, ...(changes === undefined ? {} : { changes }) });
|
|
@@ -3521,10 +3482,9 @@ export function composeRuntime(options) {
|
|
|
3521
3482
|
...settled,
|
|
3522
3483
|
apply(answer) {
|
|
3523
3484
|
const set = settled.apply(answer);
|
|
3485
|
+
showing(room.locator, accepted.roomVersion);
|
|
3524
3486
|
pendings.delete(pendingKey(room.locator, submitted.id));
|
|
3525
3487
|
unreadable.delete(room.locator);
|
|
3526
|
-
sourceRefusals.delete(room.locator);
|
|
3527
|
-
declarationRefusals.delete(room.locator);
|
|
3528
3488
|
list(room, true);
|
|
3529
3489
|
return set;
|
|
3530
3490
|
},
|
|
@@ -3542,13 +3502,9 @@ export function composeRuntime(options) {
|
|
|
3542
3502
|
const failed = await exclusive(async () => {
|
|
3543
3503
|
const adopting = await acceptance(room, local, submitted, accepted);
|
|
3544
3504
|
await adoptedTogether(local, [adopting]);
|
|
3545
|
-
return
|
|
3505
|
+
return failureOf(adopting);
|
|
3546
3506
|
});
|
|
3547
|
-
|
|
3548
|
-
return true;
|
|
3549
|
-
if (failed.kind === 'refused' && continuing())
|
|
3550
|
-
refusedSource(room, failed);
|
|
3551
|
-
return false;
|
|
3507
|
+
return failed === null;
|
|
3552
3508
|
}
|
|
3553
3509
|
async function adoptedMove(room, partner, local, submitted, accepted) {
|
|
3554
3510
|
const other = discovered.get(partner);
|
|
@@ -3562,18 +3518,10 @@ export function composeRuntime(options) {
|
|
|
3562
3518
|
...accepted.destination,
|
|
3563
3519
|
});
|
|
3564
3520
|
await adoptedTogether(local, [left, arrived]);
|
|
3565
|
-
|
|
3566
|
-
if (other !== undefined && refusedArrival?.kind === 'refused' && continuing()) {
|
|
3567
|
-
refusedSource(other, refusedArrival);
|
|
3568
|
-
}
|
|
3569
|
-
return refusalOf(left);
|
|
3521
|
+
return failureOf(left);
|
|
3570
3522
|
});
|
|
3571
3523
|
submitLater(partner);
|
|
3572
|
-
|
|
3573
|
-
return true;
|
|
3574
|
-
if (failed.kind === 'refused' && continuing())
|
|
3575
|
-
refusedSource(room, failed);
|
|
3576
|
-
return false;
|
|
3524
|
+
return failed === null;
|
|
3577
3525
|
}
|
|
3578
3526
|
function queueIn(locator) {
|
|
3579
3527
|
return replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
@@ -3692,12 +3640,10 @@ export function composeRuntime(options) {
|
|
|
3692
3640
|
async function submitRoom(room, local, authorized, progress, signal) {
|
|
3693
3641
|
while (continuing() && authorization !== undefined && !signal.aborted) {
|
|
3694
3642
|
const replica = replicas.get(room.locator);
|
|
3695
|
-
const head = replica?.queue
|
|
3643
|
+
const head = replica?.queue.find((batch) => !landedAhead.has(pendingKey(room.locator, batch.id)));
|
|
3696
3644
|
if (replica === undefined || head === undefined)
|
|
3697
3645
|
return 'done';
|
|
3698
3646
|
const state = pendings.get(pendingKey(room.locator, head.id));
|
|
3699
|
-
if (state?.accepted !== undefined)
|
|
3700
|
-
return 'done';
|
|
3701
3647
|
const partner = partnerOf(head, room.locator);
|
|
3702
3648
|
if (partner !== null && !leavingIn(head, room.locator)) {
|
|
3703
3649
|
submitLater(partner);
|
|
@@ -3811,12 +3757,12 @@ export function composeRuntime(options) {
|
|
|
3811
3757
|
}
|
|
3812
3758
|
if (result.kind === 'expired') {
|
|
3813
3759
|
await exclusive(() => mark(local, room.locator, head, { state: 'waiting' }, partner !== null && lost));
|
|
3814
|
-
const there = other === undefined ? null : await
|
|
3760
|
+
const there = other === undefined ? null : await advanced(other, local, authorized);
|
|
3815
3761
|
if (other !== undefined && there !== null) {
|
|
3816
3762
|
await withhold(there, other, local, progress);
|
|
3817
3763
|
return 'done';
|
|
3818
3764
|
}
|
|
3819
|
-
const withheld = await
|
|
3765
|
+
const withheld = await advanced(room, local, authorized);
|
|
3820
3766
|
if (withheld !== null) {
|
|
3821
3767
|
await withhold(withheld, room, local, progress);
|
|
3822
3768
|
return 'done';
|
|
@@ -3838,8 +3784,8 @@ export function composeRuntime(options) {
|
|
|
3838
3784
|
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
3839
3785
|
const left = leaving(head, result);
|
|
3840
3786
|
const refreshed = async () => ({
|
|
3841
|
-
there: other === undefined ? null : await
|
|
3842
|
-
here: await
|
|
3787
|
+
there: other === undefined ? null : await advanced(other, local, authorized),
|
|
3788
|
+
here: await advanced(room, local, authorized),
|
|
3843
3789
|
});
|
|
3844
3790
|
const judged = result.kind === 'conflicted' && other !== undefined ? await refreshed() : null;
|
|
3845
3791
|
if (judged !== null && (judged.there !== null || judged.here !== null)) {
|
|
@@ -3863,6 +3809,13 @@ export function composeRuntime(options) {
|
|
|
3863
3809
|
(await landedWhole(room.locator, head, confirmedLayer))) ||
|
|
3864
3810
|
(result.kind === 'conflicted' &&
|
|
3865
3811
|
(await landedWhole(room.locator, head, confirmedLayer, result.affected)));
|
|
3812
|
+
if (held &&
|
|
3813
|
+
result.kind === 'conflicted' &&
|
|
3814
|
+
partner === null &&
|
|
3815
|
+
!landed(head, confirmedLayer)) {
|
|
3816
|
+
landedAhead.set(pendingKey(room.locator, head.id), Object.fromEntries([...touchedBy([head])].map((path) => [path, confirmedLayer.revisions[path]])));
|
|
3817
|
+
return;
|
|
3818
|
+
}
|
|
3866
3819
|
publishChangeSet(await settle(local, room.locator, current, current.record, current.queue.filter((batch) => batch.id !== head.id), { leaving: [held ? { ...left, returned: null } : left] }));
|
|
3867
3820
|
await unpair(local);
|
|
3868
3821
|
moveTo({ ...status, ...attention() });
|
|
@@ -3919,8 +3872,10 @@ export function composeRuntime(options) {
|
|
|
3919
3872
|
for (const { room, before, queue } of held) {
|
|
3920
3873
|
const { locator } = room;
|
|
3921
3874
|
lastFenced.set(locator, ++fencings);
|
|
3922
|
-
for (const batch of queue)
|
|
3875
|
+
for (const batch of queue) {
|
|
3923
3876
|
pendings.delete(pendingKey(locator, batch.id));
|
|
3877
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
3878
|
+
}
|
|
3924
3879
|
queuesWithoutReplica.delete(locator);
|
|
3925
3880
|
if (before !== undefined) {
|
|
3926
3881
|
replicas.set(locator, {
|
|
@@ -3943,25 +3898,22 @@ export function composeRuntime(options) {
|
|
|
3943
3898
|
if (signal.aborted)
|
|
3944
3899
|
return;
|
|
3945
3900
|
const progress = { complete: true, reachable: true };
|
|
3946
|
-
await roomTask(room.locator,
|
|
3947
|
-
await Promise.race([
|
|
3948
|
-
reload(room, local, progress, () => live() && !signal.aborted),
|
|
3949
|
-
abandonedOn(signal),
|
|
3950
|
-
]);
|
|
3951
|
-
});
|
|
3901
|
+
await roomTask(room.locator, (live) => reload(room, local, progress, live));
|
|
3952
3902
|
if (signal.aborted)
|
|
3953
3903
|
return;
|
|
3954
3904
|
await rest(progress);
|
|
3955
3905
|
}
|
|
3956
3906
|
}
|
|
3957
3907
|
function awaitingAnswer(locator) {
|
|
3908
|
+
answering.get(locator)?.release();
|
|
3958
3909
|
let release = () => { };
|
|
3959
|
-
const
|
|
3910
|
+
const answered = new Promise((resolve) => {
|
|
3960
3911
|
release = resolve;
|
|
3961
3912
|
});
|
|
3962
|
-
|
|
3913
|
+
const awaited = { answered, release };
|
|
3914
|
+
answering.set(locator, awaited);
|
|
3963
3915
|
return () => {
|
|
3964
|
-
if (answering.get(locator)
|
|
3916
|
+
if (answering.get(locator) === awaited)
|
|
3965
3917
|
answering.delete(locator);
|
|
3966
3918
|
release();
|
|
3967
3919
|
};
|
|
@@ -3973,6 +3925,30 @@ export function composeRuntime(options) {
|
|
|
3973
3925
|
JSON.stringify(held.replica) === JSON.stringify(room.replica) &&
|
|
3974
3926
|
JSON.stringify(held.typedSelection) === JSON.stringify(room.typedSelection ?? null)));
|
|
3975
3927
|
}
|
|
3928
|
+
async function keepNaming(local, room) {
|
|
3929
|
+
const held = replicas.get(room.locator)?.record;
|
|
3930
|
+
if (held === undefined || (held.name === room.name && held.handle === room.handle))
|
|
3931
|
+
return;
|
|
3932
|
+
await exclusive(async () => {
|
|
3933
|
+
if (!continuing())
|
|
3934
|
+
return;
|
|
3935
|
+
const replica = replicas.get(room.locator);
|
|
3936
|
+
if (replica === undefined)
|
|
3937
|
+
return;
|
|
3938
|
+
const record = { ...replica.record, name: room.name, handle: room.handle };
|
|
3939
|
+
await local.commit({
|
|
3940
|
+
rooms: [
|
|
3941
|
+
{
|
|
3942
|
+
locator: room.locator,
|
|
3943
|
+
key: record.key,
|
|
3944
|
+
record: storedRecord(record),
|
|
3945
|
+
composed: composedOf(composed(record)),
|
|
3946
|
+
},
|
|
3947
|
+
],
|
|
3948
|
+
});
|
|
3949
|
+
replicas.set(room.locator, { ...replica, record });
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3976
3952
|
async function retainModel(local, room, next) {
|
|
3977
3953
|
const key = room.locator;
|
|
3978
3954
|
const previous = modelOf(room.locator);
|
|
@@ -3988,26 +3964,15 @@ export function composeRuntime(options) {
|
|
|
3988
3964
|
const held = replicas.get(room.locator);
|
|
3989
3965
|
let changed = null;
|
|
3990
3966
|
if (held !== undefined) {
|
|
3991
|
-
const
|
|
3967
|
+
const record = {
|
|
3992
3968
|
...held.record,
|
|
3969
|
+
name: room.name,
|
|
3970
|
+
handle: room.handle,
|
|
3993
3971
|
operations: room.operations,
|
|
3994
3972
|
replica: room.replica,
|
|
3995
3973
|
typedSelection: room.typedSelection ?? null,
|
|
3996
3974
|
model: next,
|
|
3997
3975
|
};
|
|
3998
|
-
let read = 'staged';
|
|
3999
|
-
if (!sameMembership(declared, held.record)) {
|
|
4000
|
-
const verdict = await validatedCollection(walkOf(local, held.record.key, 'confirmed'), lookupOf(local, held.record.key, 'confirmed'), composed(declared));
|
|
4001
|
-
read =
|
|
4002
|
-
verdict.verdict !== 'refuse'
|
|
4003
|
-
? 'staged'
|
|
4004
|
-
: verdict.rule === 'model-unavailable'
|
|
4005
|
-
? 'deferred'
|
|
4006
|
-
: { kind: 'refused', refusal: verdict };
|
|
4007
|
-
}
|
|
4008
|
-
const record = read === 'staged'
|
|
4009
|
-
? declared
|
|
4010
|
-
: { ...declared, typedSelection: held.record.typedSelection };
|
|
4011
3976
|
const confirmedLayer = await sliceAt(record, 'confirmed', held.queue, held.overlaid);
|
|
4012
3977
|
const replayedLayer = await replayed(record, confirmedLayer, held.queue);
|
|
4013
3978
|
const diff = await overlayDiff(record, held, replayedLayer.visible, replayedLayer.touched);
|
|
@@ -4029,16 +3994,6 @@ export function composeRuntime(options) {
|
|
|
4029
3994
|
documents: answer.changed[room.locator] ?? [],
|
|
4030
3995
|
room: true,
|
|
4031
3996
|
});
|
|
4032
|
-
if (read === 'staged' || read === 'deferred') {
|
|
4033
|
-
if (declarationRefusals.delete(room.locator)) {
|
|
4034
|
-
sourceRefusals.delete(room.locator);
|
|
4035
|
-
unreadable.delete(room.locator);
|
|
4036
|
-
}
|
|
4037
|
-
}
|
|
4038
|
-
else {
|
|
4039
|
-
refusedSource(room, read);
|
|
4040
|
-
declarationRefusals.add(room.locator);
|
|
4041
|
-
}
|
|
4042
3997
|
}
|
|
4043
3998
|
heldModel(room, next);
|
|
4044
3999
|
if (changed === null)
|
|
@@ -4083,6 +4038,17 @@ export function composeRuntime(options) {
|
|
|
4083
4038
|
emitRooms();
|
|
4084
4039
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
4085
4040
|
}
|
|
4041
|
+
function name(locator) {
|
|
4042
|
+
const stream = options.adapter.roomStream;
|
|
4043
|
+
const record = replicas.get(locator)?.record;
|
|
4044
|
+
const room = discovered.get(locator);
|
|
4045
|
+
if (stream === undefined || record === undefined || room === undefined)
|
|
4046
|
+
return;
|
|
4047
|
+
if (!socketOpen || !handed || record.replica.selection !== room.replica.selection)
|
|
4048
|
+
return;
|
|
4049
|
+
naming += 1;
|
|
4050
|
+
stream.hold([{ locator, roomVersion: record.roomVersion }]);
|
|
4051
|
+
}
|
|
4086
4052
|
function heldRooms(listed) {
|
|
4087
4053
|
return listed.flatMap((room) => {
|
|
4088
4054
|
const record = replicas.get(room.locator)?.record;
|
|
@@ -4100,14 +4066,19 @@ export function composeRuntime(options) {
|
|
|
4100
4066
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
4101
4067
|
});
|
|
4102
4068
|
if (attempt === 'opened') {
|
|
4069
|
+
naming += 1;
|
|
4103
4070
|
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
4104
4071
|
handed = true;
|
|
4105
4072
|
}
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4073
|
+
for (const room of ready) {
|
|
4074
|
+
if (attempt !== 'unstreamed' && replicas.has(room.locator)) {
|
|
4075
|
+
if (attempt === 'authorized')
|
|
4076
|
+
name(room.locator);
|
|
4077
|
+
continue;
|
|
4078
|
+
}
|
|
4079
|
+
void roomTask(room.locator, (live) => loadRoom(room, local, progress, live)).then(() => rest(progress));
|
|
4080
|
+
}
|
|
4081
|
+
const failed = prepared.find((room) => room.status === 'rejected');
|
|
4111
4082
|
if (failed !== undefined)
|
|
4112
4083
|
throw failed.reason;
|
|
4113
4084
|
}
|
|
@@ -4126,12 +4097,16 @@ export function composeRuntime(options) {
|
|
|
4126
4097
|
}
|
|
4127
4098
|
if (prior !== undefined)
|
|
4128
4099
|
await retainModel(local, room, modelOf(room.locator));
|
|
4100
|
+
if (prior !== undefined)
|
|
4101
|
+
await keepNaming(local, room);
|
|
4129
4102
|
if (discoveryPaused || authorization === undefined)
|
|
4130
4103
|
return false;
|
|
4131
4104
|
return room.replica.admission === 'admitted';
|
|
4132
4105
|
}
|
|
4133
4106
|
async function caughtUp(room, local, progress, live) {
|
|
4134
|
-
if (
|
|
4107
|
+
if (options.adapter.roomStream !== undefined || authorization === undefined)
|
|
4108
|
+
return;
|
|
4109
|
+
if (!live() || !behind(room))
|
|
4135
4110
|
return;
|
|
4136
4111
|
const withheld = await advance(room, local, authorization, live);
|
|
4137
4112
|
if (withheld !== null)
|
|
@@ -4144,6 +4119,8 @@ export function composeRuntime(options) {
|
|
|
4144
4119
|
async function loadRoom(room, local, progress, live) {
|
|
4145
4120
|
if (!live() || authorization === undefined)
|
|
4146
4121
|
return;
|
|
4122
|
+
if (replicas.has(room.locator) && !(await prepareRoom(room, local, progress)))
|
|
4123
|
+
return;
|
|
4147
4124
|
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
4148
4125
|
await reload(room, local, progress, live);
|
|
4149
4126
|
return;
|
|
@@ -4161,20 +4138,53 @@ export function composeRuntime(options) {
|
|
|
4161
4138
|
reloadsDue.delete(room.locator);
|
|
4162
4139
|
if (!live() || authorization === undefined)
|
|
4163
4140
|
return;
|
|
4141
|
+
const from = standingOf(room.locator);
|
|
4164
4142
|
const bootstrapped = await pullBootstrap(authorization, room, local, live);
|
|
4165
4143
|
if (bootstrapped.kind !== 'staged') {
|
|
4166
4144
|
if (live())
|
|
4167
4145
|
await withhold(bootstrapped, room, local, progress);
|
|
4168
4146
|
return;
|
|
4169
4147
|
}
|
|
4170
|
-
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed }, live);
|
|
4148
|
+
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from }, live);
|
|
4171
4149
|
await caughtUp(room, local, progress, live);
|
|
4172
4150
|
}
|
|
4173
4151
|
finally {
|
|
4174
4152
|
caughtUpTo(room.locator);
|
|
4175
4153
|
}
|
|
4176
4154
|
}
|
|
4177
|
-
async function
|
|
4155
|
+
async function asked(authorized) {
|
|
4156
|
+
try {
|
|
4157
|
+
return (await options.adapter.discover?.(authorized)) ?? { kind: 'transport-uncertainty' };
|
|
4158
|
+
}
|
|
4159
|
+
catch {
|
|
4160
|
+
return { kind: 'transport-uncertainty' };
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
function malformed(listed) {
|
|
4164
|
+
for (const room of listed) {
|
|
4165
|
+
if (room.operations === undefined ||
|
|
4166
|
+
!['write', 'delete'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
4167
|
+
room.operations[operation].every((scope) => typeof scope === 'string')) ||
|
|
4168
|
+
room.replica === undefined ||
|
|
4169
|
+
typeof room.replica.selection !== 'string' ||
|
|
4170
|
+
room.replica.selection.length === 0 ||
|
|
4171
|
+
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
4172
|
+
!['locked', 'open'].includes(room.replica.offline)) {
|
|
4173
|
+
return `invalid model or replica declaration ${room.locator}`;
|
|
4174
|
+
}
|
|
4175
|
+
if (!declaredSelection(room))
|
|
4176
|
+
return `invalid typed selection declaration ${room.locator}`;
|
|
4177
|
+
}
|
|
4178
|
+
return null;
|
|
4179
|
+
}
|
|
4180
|
+
async function signIn(local, signedIn) {
|
|
4181
|
+
if (JSON.stringify(signedIn) === JSON.stringify(member))
|
|
4182
|
+
return;
|
|
4183
|
+
await local.fact('member', signedIn);
|
|
4184
|
+
member = signedIn;
|
|
4185
|
+
emitMember();
|
|
4186
|
+
}
|
|
4187
|
+
async function discoverRooms(local, progress, attempt, live, found) {
|
|
4178
4188
|
if (closed || authorization === undefined) {
|
|
4179
4189
|
notifyReady();
|
|
4180
4190
|
return;
|
|
@@ -4182,13 +4192,7 @@ export function composeRuntime(options) {
|
|
|
4182
4192
|
moveTo(transferring(status));
|
|
4183
4193
|
discoveries += 1;
|
|
4184
4194
|
const started = discoveries;
|
|
4185
|
-
|
|
4186
|
-
try {
|
|
4187
|
-
result = await options.adapter.discover(authorization, scope);
|
|
4188
|
-
}
|
|
4189
|
-
catch {
|
|
4190
|
-
result = { kind: 'transport-uncertainty' };
|
|
4191
|
-
}
|
|
4195
|
+
const result = found ?? (await asked(authorization));
|
|
4192
4196
|
if (result.kind === 'discovery')
|
|
4193
4197
|
roomsNamed = true;
|
|
4194
4198
|
if (!live()) {
|
|
@@ -4198,7 +4202,7 @@ export function composeRuntime(options) {
|
|
|
4198
4202
|
}
|
|
4199
4203
|
journal.record(`connection discover ${result.kind}`);
|
|
4200
4204
|
if (result.kind === 'transport-uncertainty') {
|
|
4201
|
-
await
|
|
4205
|
+
await cachedModels(local);
|
|
4202
4206
|
progress.reachable = false;
|
|
4203
4207
|
progress.complete = false;
|
|
4204
4208
|
notifyReady();
|
|
@@ -4219,26 +4223,12 @@ export function composeRuntime(options) {
|
|
|
4219
4223
|
notifyReady();
|
|
4220
4224
|
return;
|
|
4221
4225
|
}
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
room.replica.selection.length === 0 ||
|
|
4229
|
-
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
4230
|
-
!['locked', 'open'].includes(room.replica.offline)) {
|
|
4231
|
-
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
4232
|
-
await refuse('missing-required-guarantee');
|
|
4233
|
-
notifyReady();
|
|
4234
|
-
return;
|
|
4235
|
-
}
|
|
4236
|
-
if (!declaredSelection(room)) {
|
|
4237
|
-
journal.record(`invalid typed selection declaration ${room.locator}`);
|
|
4238
|
-
await refuse('missing-required-guarantee');
|
|
4239
|
-
notifyReady();
|
|
4240
|
-
return;
|
|
4241
|
-
}
|
|
4226
|
+
const invalid = malformed(result.rooms);
|
|
4227
|
+
if (invalid !== null) {
|
|
4228
|
+
journal.record(invalid);
|
|
4229
|
+
await refuse('missing-required-guarantee');
|
|
4230
|
+
notifyReady();
|
|
4231
|
+
return;
|
|
4242
4232
|
}
|
|
4243
4233
|
if (refusal !== null) {
|
|
4244
4234
|
refusal = null;
|
|
@@ -4253,15 +4243,9 @@ export function composeRuntime(options) {
|
|
|
4253
4243
|
limits = result.providerLimits;
|
|
4254
4244
|
await local.fact('providerLimits', limits);
|
|
4255
4245
|
await local.fact('capabilities', capabilities);
|
|
4256
|
-
conversations.declared();
|
|
4257
|
-
}
|
|
4258
|
-
const signedIn = result.member ?? null;
|
|
4259
|
-
if (JSON.stringify(signedIn) !== JSON.stringify(member)) {
|
|
4260
|
-
await local.fact('member', signedIn);
|
|
4261
|
-
member = signedIn;
|
|
4262
|
-
emitMember();
|
|
4263
4246
|
}
|
|
4264
|
-
await
|
|
4247
|
+
await signIn(local, result.member ?? null);
|
|
4248
|
+
await synchronize(await applyDiscovery(result.rooms, started), local, progress, attempt, live);
|
|
4265
4249
|
}
|
|
4266
4250
|
async function ensureAuthorization() {
|
|
4267
4251
|
if (authorization !== undefined)
|
|
@@ -4276,23 +4260,45 @@ export function composeRuntime(options) {
|
|
|
4276
4260
|
}
|
|
4277
4261
|
}
|
|
4278
4262
|
async function admittedRead(work) {
|
|
4279
|
-
|
|
4280
|
-
|
|
4263
|
+
while (roomReads.free === 0) {
|
|
4264
|
+
await new Promise((admit) => roomReads.waiting.push(admit));
|
|
4281
4265
|
}
|
|
4282
|
-
|
|
4283
|
-
while (reads.free === 0)
|
|
4284
|
-
await new Promise((admit) => reads.waiting.push(admit));
|
|
4285
|
-
reads.free -= 1;
|
|
4266
|
+
roomReads.free -= 1;
|
|
4286
4267
|
try {
|
|
4287
4268
|
await work();
|
|
4288
4269
|
}
|
|
4289
4270
|
finally {
|
|
4290
|
-
|
|
4291
|
-
|
|
4271
|
+
roomReads.free += 1;
|
|
4272
|
+
roomReads.waiting.shift()?.();
|
|
4292
4273
|
}
|
|
4293
4274
|
}
|
|
4275
|
+
function inRoom(locator, work) {
|
|
4276
|
+
roomWork += 1;
|
|
4277
|
+
const ran = (roomLanes.get(locator) ?? Promise.resolve()).then(async () => {
|
|
4278
|
+
try {
|
|
4279
|
+
return await work(continuing);
|
|
4280
|
+
}
|
|
4281
|
+
finally {
|
|
4282
|
+
roomWork -= 1;
|
|
4283
|
+
}
|
|
4284
|
+
});
|
|
4285
|
+
const chain = ran.then(() => undefined, () => undefined);
|
|
4286
|
+
roomLanes.set(locator, chain);
|
|
4287
|
+
working.add(chain);
|
|
4288
|
+
void chain.finally(() => {
|
|
4289
|
+
working.delete(chain);
|
|
4290
|
+
if (roomLanes.get(locator) === chain)
|
|
4291
|
+
roomLanes.delete(locator);
|
|
4292
|
+
});
|
|
4293
|
+
return ran;
|
|
4294
|
+
}
|
|
4294
4295
|
function roomTask(locator, work) {
|
|
4295
|
-
|
|
4296
|
+
if (stopping)
|
|
4297
|
+
return Promise.resolve();
|
|
4298
|
+
return inRoom(locator, (live) => admittedRead(() => work(live))).catch(() => undefined);
|
|
4299
|
+
}
|
|
4300
|
+
function advanced(room, local, authorized) {
|
|
4301
|
+
return inRoom(room.locator, (live) => advance(room, local, authorized, live));
|
|
4296
4302
|
}
|
|
4297
4303
|
function task(lane, work) {
|
|
4298
4304
|
if (stopping)
|
|
@@ -4308,23 +4314,19 @@ export function composeRuntime(options) {
|
|
|
4308
4314
|
void run.finally(() => working.delete(run));
|
|
4309
4315
|
return run;
|
|
4310
4316
|
}
|
|
4311
|
-
function sync(attempt) {
|
|
4317
|
+
function sync(attempt, found) {
|
|
4312
4318
|
return task(OPENING_LANE, async (live) => {
|
|
4313
4319
|
if (!live()) {
|
|
4314
4320
|
readyFromStore();
|
|
4315
4321
|
return;
|
|
4316
4322
|
}
|
|
4317
4323
|
const progress = { complete: true, reachable: true };
|
|
4318
|
-
|
|
4319
|
-
if (attempt === 'opened')
|
|
4320
|
-
opened = generation;
|
|
4321
|
-
underway = attempt === 'opened' ? { generation, progress } : null;
|
|
4324
|
+
underway = attempt === 'opened' ? progress : null;
|
|
4322
4325
|
if (attempt === 'opened')
|
|
4323
4326
|
streamed = progress;
|
|
4324
4327
|
complete = false;
|
|
4325
4328
|
recorded = false;
|
|
4326
4329
|
moveTo(transferring(status));
|
|
4327
|
-
handed = false;
|
|
4328
4330
|
try {
|
|
4329
4331
|
const local = await openLocal();
|
|
4330
4332
|
if (!live())
|
|
@@ -4332,7 +4334,7 @@ export function composeRuntime(options) {
|
|
|
4332
4334
|
await ensureAuthorization();
|
|
4333
4335
|
if (!live())
|
|
4334
4336
|
return;
|
|
4335
|
-
await discoverRooms(local, progress, attempt, live);
|
|
4337
|
+
await discoverRooms(local, progress, attempt, live, found);
|
|
4336
4338
|
}
|
|
4337
4339
|
catch {
|
|
4338
4340
|
progress.complete = false;
|
|
@@ -4340,7 +4342,6 @@ export function composeRuntime(options) {
|
|
|
4340
4342
|
progress.reachable = false;
|
|
4341
4343
|
}
|
|
4342
4344
|
finally {
|
|
4343
|
-
gate.release();
|
|
4344
4345
|
notifyReady();
|
|
4345
4346
|
}
|
|
4346
4347
|
if (!live())
|
|
@@ -4352,35 +4353,59 @@ export function composeRuntime(options) {
|
|
|
4352
4353
|
if (handed || closed || progress.reachable)
|
|
4353
4354
|
return;
|
|
4354
4355
|
underway = null;
|
|
4355
|
-
opened = null;
|
|
4356
|
-
journal.record('connection reopen');
|
|
4357
4356
|
await rest(progress, live);
|
|
4358
|
-
options.adapter.roomStream?.reopen();
|
|
4359
4357
|
});
|
|
4360
4358
|
}
|
|
4361
|
-
function
|
|
4359
|
+
function resynchronize() {
|
|
4362
4360
|
return task(OPENING_LANE, async (live) => {
|
|
4363
|
-
if (!live() || !
|
|
4361
|
+
if (!live() || !announced)
|
|
4364
4362
|
return;
|
|
4365
4363
|
const progress = { complete: true, reachable: true };
|
|
4366
|
-
const gate = discover();
|
|
4367
4364
|
moveTo(transferring(status));
|
|
4368
4365
|
try {
|
|
4369
4366
|
const local = await openLocal();
|
|
4370
4367
|
if (!live())
|
|
4371
4368
|
return;
|
|
4372
|
-
await
|
|
4369
|
+
await synchronize([...discovered.values()], local, progress, 'unstreamed', live);
|
|
4370
|
+
}
|
|
4371
|
+
catch {
|
|
4372
|
+
progress.complete = false;
|
|
4373
|
+
}
|
|
4374
|
+
if (live())
|
|
4375
|
+
await finishOpening(progress, live);
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
function countAuthorityChange() {
|
|
4379
|
+
authorityChanges += 1;
|
|
4380
|
+
for (const listener of authorityChangeListeners)
|
|
4381
|
+
listener(authorityChanges);
|
|
4382
|
+
}
|
|
4383
|
+
function changeAuthority(change) {
|
|
4384
|
+
return task(OPENING_LANE, async (live) => {
|
|
4385
|
+
if (!live() || !announced)
|
|
4386
|
+
return;
|
|
4387
|
+
countAuthorityChange();
|
|
4388
|
+
const progress = { complete: true, reachable: true };
|
|
4389
|
+
moveTo(transferring(status));
|
|
4390
|
+
try {
|
|
4391
|
+
const local = await openLocal();
|
|
4373
4392
|
if (!live())
|
|
4374
4393
|
return;
|
|
4375
|
-
|
|
4394
|
+
const invalid = malformed(change.rooms);
|
|
4395
|
+
if (invalid !== null) {
|
|
4396
|
+
journal.record(invalid);
|
|
4397
|
+
await refuse('missing-required-guarantee');
|
|
4398
|
+
return;
|
|
4399
|
+
}
|
|
4400
|
+
if (change.member !== undefined)
|
|
4401
|
+
await signIn(local, change.member);
|
|
4402
|
+
discoveries += 1;
|
|
4403
|
+
const listed = await applyDiscovery(change.rooms, discoveries, change.left);
|
|
4404
|
+
await synchronize(listed, local, progress, 'authorized', live);
|
|
4376
4405
|
}
|
|
4377
4406
|
catch {
|
|
4378
4407
|
progress.complete = false;
|
|
4379
4408
|
}
|
|
4380
|
-
finally {
|
|
4381
|
-
gate.release();
|
|
4382
|
-
notifyReady();
|
|
4383
|
-
}
|
|
4384
4409
|
if (!live())
|
|
4385
4410
|
return;
|
|
4386
4411
|
if (!progress.complete)
|
|
@@ -4388,15 +4413,17 @@ export function composeRuntime(options) {
|
|
|
4388
4413
|
await rest(progress, live);
|
|
4389
4414
|
});
|
|
4390
4415
|
}
|
|
4391
|
-
function closedSocket(revoked, lost) {
|
|
4392
|
-
|
|
4393
|
-
const opened =
|
|
4416
|
+
function closedSocket(revoked, lost, authorityChanged) {
|
|
4417
|
+
notifyReady();
|
|
4418
|
+
const opened = announced;
|
|
4394
4419
|
socketOpen = false;
|
|
4420
|
+
announced = false;
|
|
4421
|
+
missedChange ||= authorityChanged;
|
|
4422
|
+
handed = false;
|
|
4423
|
+
naming = 0;
|
|
4395
4424
|
connectionLost();
|
|
4396
|
-
if (lost)
|
|
4397
|
-
reads.lost();
|
|
4425
|
+
if (lost)
|
|
4398
4426
|
conversations.lost();
|
|
4399
|
-
}
|
|
4400
4427
|
if (revoked) {
|
|
4401
4428
|
void refuse('revoked-authorization');
|
|
4402
4429
|
return;
|
|
@@ -4407,18 +4434,32 @@ export function composeRuntime(options) {
|
|
|
4407
4434
|
moveTo({
|
|
4408
4435
|
...status,
|
|
4409
4436
|
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
4410
|
-
providerUnreachable: status.providerUnreachable || !opened,
|
|
4437
|
+
providerUnreachable: status.providerUnreachable || (!opened && lost),
|
|
4438
|
+
});
|
|
4439
|
+
void closing(opened);
|
|
4440
|
+
}
|
|
4441
|
+
function closing(opened) {
|
|
4442
|
+
return task(OPENING_LANE, async (live) => {
|
|
4443
|
+
if (!live())
|
|
4444
|
+
return;
|
|
4445
|
+
const local = await openLocal();
|
|
4446
|
+
if (!opened && !socketOpen)
|
|
4447
|
+
await cachedModels(local);
|
|
4448
|
+
await rest(null, live);
|
|
4411
4449
|
});
|
|
4412
4450
|
}
|
|
4413
|
-
function
|
|
4414
|
-
|
|
4451
|
+
function cachedModels(local) {
|
|
4452
|
+
return settleModels([...rooms.values()].map((room) => discovered.get(room.locator) ?? room), 'cached', local);
|
|
4453
|
+
}
|
|
4454
|
+
function caughtUpRooms(expired) {
|
|
4455
|
+
const progress = underway;
|
|
4456
|
+
underway = null;
|
|
4457
|
+
naming = Math.max(0, naming - 1);
|
|
4415
4458
|
const cycle = progress ?? { complete: true, reachable: true };
|
|
4416
|
-
|
|
4417
|
-
opened = null;
|
|
4459
|
+
replaced(expired);
|
|
4418
4460
|
const reloading = progress === null ? [] : owedReloads(expired);
|
|
4419
4461
|
const replacing = reloadNow((room) => expired.includes(room.locator) || reloading.includes(room.locator), cycle);
|
|
4420
4462
|
return task(OPENING_LANE, async (live) => {
|
|
4421
|
-
underway = null;
|
|
4422
4463
|
if (!live())
|
|
4423
4464
|
return;
|
|
4424
4465
|
if (progress === null) {
|
|
@@ -4426,20 +4467,14 @@ export function composeRuntime(options) {
|
|
|
4426
4467
|
await rest(cycle, live);
|
|
4427
4468
|
return;
|
|
4428
4469
|
}
|
|
4429
|
-
await roomsCarried([...expired, ...reloading]);
|
|
4430
|
-
if (!live())
|
|
4431
|
-
return;
|
|
4432
4470
|
try {
|
|
4433
4471
|
const local = await openLocal();
|
|
4434
|
-
|
|
4472
|
+
await replacing;
|
|
4435
4473
|
if (progress.reachable) {
|
|
4436
4474
|
await settleModels([...discovered.values()].filter((room) => replicas.has(room.locator) &&
|
|
4437
4475
|
!expired.includes(room.locator) &&
|
|
4438
4476
|
!reloading.includes(room.locator)), 'current', local);
|
|
4439
4477
|
}
|
|
4440
|
-
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
4441
|
-
if (failed !== undefined)
|
|
4442
|
-
throw failed.reason;
|
|
4443
4478
|
}
|
|
4444
4479
|
catch {
|
|
4445
4480
|
progress.complete = false;
|
|
@@ -4451,11 +4486,8 @@ export function composeRuntime(options) {
|
|
|
4451
4486
|
}
|
|
4452
4487
|
function owedReloads(expired) {
|
|
4453
4488
|
const reloading = [...discovered.values()]
|
|
4454
|
-
.filter((room) => replicas.has(room.locator) &&
|
|
4455
|
-
!expired.includes(room.locator) &&
|
|
4456
|
-
(owed.has(room.locator) || revoked(room.locator)))
|
|
4489
|
+
.filter((room) => replicas.has(room.locator) && !expired.includes(room.locator) && revoked(room.locator))
|
|
4457
4490
|
.map((room) => room.locator);
|
|
4458
|
-
owed.clear();
|
|
4459
4491
|
for (const locator of reloading)
|
|
4460
4492
|
catching.add(locator);
|
|
4461
4493
|
liveQueries.recover();
|
|
@@ -4464,16 +4496,17 @@ export function composeRuntime(options) {
|
|
|
4464
4496
|
async function reloadNow(replaced, progress) {
|
|
4465
4497
|
const reloaded = [...discovered.values()].filter((room) => replicas.has(room.locator) && replaced(room));
|
|
4466
4498
|
for (const room of reloaded)
|
|
4467
|
-
reloadsDue.
|
|
4499
|
+
reloadsDue.add(room.locator);
|
|
4468
4500
|
try {
|
|
4469
4501
|
const local = await openLocal();
|
|
4470
|
-
|
|
4502
|
+
for (const room of reloaded) {
|
|
4503
|
+
void roomTask(room.locator, (live) => reload(room, local, progress, live)).then(() => rest(progress));
|
|
4504
|
+
}
|
|
4471
4505
|
}
|
|
4472
4506
|
catch {
|
|
4473
4507
|
for (const room of reloaded)
|
|
4474
4508
|
reloadsDue.delete(room.locator);
|
|
4475
4509
|
progress.reachable = false;
|
|
4476
|
-
return [];
|
|
4477
4510
|
}
|
|
4478
4511
|
}
|
|
4479
4512
|
async function settleModels(held, status, local) {
|
|
@@ -4484,7 +4517,7 @@ export function composeRuntime(options) {
|
|
|
4484
4517
|
const settled = await exclusive(async () => {
|
|
4485
4518
|
if (!continuing())
|
|
4486
4519
|
return [];
|
|
4487
|
-
const unchanged = held.filter((room) => owed(room) &&
|
|
4520
|
+
const unchanged = held.filter((room) => owed(room) && declaredAsHeld(room));
|
|
4488
4521
|
for (const room of unchanged) {
|
|
4489
4522
|
const next = { ...modelOf(room.locator), status };
|
|
4490
4523
|
const replica = replicas.get(room.locator);
|
|
@@ -4544,14 +4577,10 @@ export function composeRuntime(options) {
|
|
|
4544
4577
|
try {
|
|
4545
4578
|
const local = await openLocal();
|
|
4546
4579
|
await roomTask(locator, async (live) => {
|
|
4547
|
-
|
|
4548
|
-
if (!current() || authorization === undefined)
|
|
4580
|
+
if (!live() || authorization === undefined)
|
|
4549
4581
|
return;
|
|
4550
|
-
const withheld = await
|
|
4551
|
-
|
|
4552
|
-
abandonedOn(signal),
|
|
4553
|
-
]);
|
|
4554
|
-
if (withheld !== null && current())
|
|
4582
|
+
const withheld = await advance(room, local, authorization, live);
|
|
4583
|
+
if (withheld !== null && live())
|
|
4555
4584
|
await withhold(withheld, room, local, progress);
|
|
4556
4585
|
});
|
|
4557
4586
|
}
|
|
@@ -4606,7 +4635,6 @@ export function composeRuntime(options) {
|
|
|
4606
4635
|
progress.complete = false;
|
|
4607
4636
|
return;
|
|
4608
4637
|
}
|
|
4609
|
-
reads.applied();
|
|
4610
4638
|
submitAll();
|
|
4611
4639
|
for (const locator of stale)
|
|
4612
4640
|
if (repairable.has(locator))
|
|
@@ -4637,7 +4665,7 @@ export function composeRuntime(options) {
|
|
|
4637
4665
|
if (!live())
|
|
4638
4666
|
return;
|
|
4639
4667
|
let lastCompleted = status.lastCompleted;
|
|
4640
|
-
let synced = complete && stale.size === 0 && !pending();
|
|
4668
|
+
let synced = complete && roomWork === 0 && naming === 0 && stale.size === 0 && !pending();
|
|
4641
4669
|
if (synced && !recorded) {
|
|
4642
4670
|
const completedAt = new Date().toISOString();
|
|
4643
4671
|
try {
|
|
@@ -4652,74 +4680,78 @@ export function composeRuntime(options) {
|
|
|
4652
4680
|
}
|
|
4653
4681
|
}
|
|
4654
4682
|
moveTo({
|
|
4655
|
-
state: pending()
|
|
4683
|
+
state: pending()
|
|
4684
|
+
? 'pending'
|
|
4685
|
+
: synced && complete && roomWork === 0 && naming === 0
|
|
4686
|
+
? 'synced'
|
|
4687
|
+
: 'syncing',
|
|
4656
4688
|
lastCompleted,
|
|
4657
|
-
providerUnreachable: !progress.reachable,
|
|
4689
|
+
providerUnreachable: progress === null ? status.providerUnreachable : !progress.reachable,
|
|
4658
4690
|
...attention(),
|
|
4659
4691
|
});
|
|
4660
4692
|
}
|
|
4661
|
-
function
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
}
|
|
4669
|
-
function applyRoom(locator, roomVersion) {
|
|
4670
|
-
announced.set(locator, roomVersion);
|
|
4671
|
-
return task(roomLane(locator), async (live) => {
|
|
4672
|
-
if (discovering?.generation === generation)
|
|
4673
|
-
await discovering.done;
|
|
4674
|
-
const saving = answering.get(locator);
|
|
4675
|
-
if (saving?.generation === generation)
|
|
4676
|
-
await saving.answered;
|
|
4677
|
-
if (!live() || authorization === undefined)
|
|
4678
|
-
return;
|
|
4679
|
-
const room = discovered.get(locator);
|
|
4680
|
-
const held = replicas.get(locator);
|
|
4681
|
-
if (room === undefined || room.replica.admission !== 'admitted')
|
|
4682
|
-
return;
|
|
4683
|
-
if (held !== undefined &&
|
|
4684
|
-
(held.record.replica.selection !== room.replica.selection ||
|
|
4685
|
-
options.adapter.reached(locator, held.record.roomVersion, roomVersion))) {
|
|
4686
|
-
return;
|
|
4687
|
-
}
|
|
4688
|
-
const during = underway?.generation === generation ? underway.progress : null;
|
|
4689
|
-
const progress = during ?? { complete: true, reachable: true };
|
|
4690
|
-
if (during === null)
|
|
4691
|
-
moveTo(transferring(status));
|
|
4693
|
+
function applyRoom(change) {
|
|
4694
|
+
const { locator } = change;
|
|
4695
|
+
if (stopping)
|
|
4696
|
+
return Promise.resolve();
|
|
4697
|
+
return inRoom(locator, async (live) => {
|
|
4698
|
+
await answering.get(locator)?.answered;
|
|
4699
|
+
const progress = underway ?? { complete: true, reachable: true };
|
|
4692
4700
|
try {
|
|
4693
4701
|
const local = await openLocal();
|
|
4694
|
-
|
|
4695
|
-
if (!live()
|
|
4696
|
-
return;
|
|
4697
|
-
const
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4702
|
+
for (;;) {
|
|
4703
|
+
if (!live())
|
|
4704
|
+
return null;
|
|
4705
|
+
const room = discovered.get(locator);
|
|
4706
|
+
const held = replicas.get(locator);
|
|
4707
|
+
if (room === undefined ||
|
|
4708
|
+
held === undefined ||
|
|
4709
|
+
room.replica.admission !== 'admitted' ||
|
|
4710
|
+
held.record.replica.selection !== room.replica.selection) {
|
|
4711
|
+
return null;
|
|
4712
|
+
}
|
|
4713
|
+
const at = held.record.roomVersion;
|
|
4714
|
+
if (options.adapter.reached(locator, at, change.roomVersion))
|
|
4715
|
+
return progress;
|
|
4716
|
+
if (!options.adapter.reached(locator, at, change.from)) {
|
|
4717
|
+
name(locator);
|
|
4718
|
+
return progress;
|
|
4719
|
+
}
|
|
4720
|
+
if (underway === null)
|
|
4721
|
+
moveTo(transferring(status));
|
|
4722
|
+
const writing = () => live() && !full;
|
|
4723
|
+
let outlined = false;
|
|
4724
|
+
const refreshed = await ahead((hold) => pullRefresh(authorization ?? {}, held, writing, hold, async () => {
|
|
4725
|
+
const page = await change.read();
|
|
4726
|
+
const paths = Object.keys(page.outlines ?? {});
|
|
4727
|
+
outlined = paths.length > 0;
|
|
4728
|
+
await local.remember(locator, held.record.key, paths);
|
|
4729
|
+
return page;
|
|
4730
|
+
}));
|
|
4731
|
+
if (refreshed.kind !== 'advanced')
|
|
4732
|
+
return progress;
|
|
4733
|
+
const landing = {
|
|
4734
|
+
kind: 'refreshed',
|
|
4735
|
+
from: refreshed.from,
|
|
4736
|
+
remodelled: refreshed.remodelled,
|
|
4737
|
+
};
|
|
4738
|
+
if (await publish(local, room, refreshed.record, landing, writing)) {
|
|
4739
|
+
stale.delete(locator);
|
|
4740
|
+
await retainModel(local, room, modelOf(locator));
|
|
4741
|
+
if (outlined)
|
|
4742
|
+
void heldAgain(locator);
|
|
4743
|
+
return progress;
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4701
4746
|
}
|
|
4702
4747
|
catch {
|
|
4748
|
+
if (!stale.has(locator))
|
|
4749
|
+
name(locator);
|
|
4703
4750
|
stale.add(locator);
|
|
4751
|
+
return progress;
|
|
4704
4752
|
}
|
|
4705
|
-
|
|
4706
|
-
if (during === null)
|
|
4707
|
-
await rest(progress, live);
|
|
4708
|
-
}
|
|
4709
|
-
});
|
|
4753
|
+
}).then((progress) => rest(progress), () => undefined);
|
|
4710
4754
|
}
|
|
4711
|
-
function roomsCarried(replacing) {
|
|
4712
|
-
const replaced = new Set(replacing.map(roomLane));
|
|
4713
|
-
return Promise.all([...lanes]
|
|
4714
|
-
.filter(([lane, held]) => lane !== OPENING_LANE && !replaced.has(lane) && held.generation === generation)
|
|
4715
|
-
.map(([, held]) => held.chain));
|
|
4716
|
-
}
|
|
4717
|
-
const receipts = {
|
|
4718
|
-
heads: async () => (await openLocal()).receivedHeads(),
|
|
4719
|
-
parts: async (key, count) => (await openLocal()).receivedParts(key, count),
|
|
4720
|
-
keep: async (key, head, part) => (await openLocal()).keepReceived(key, head, part),
|
|
4721
|
-
drop: async (keys) => (await openLocal()).dropReceived(keys),
|
|
4722
|
-
};
|
|
4723
4755
|
function observeStream() {
|
|
4724
4756
|
const stream = options.adapter.roomStream;
|
|
4725
4757
|
if (stream === undefined || withdrawStream !== undefined)
|
|
@@ -4729,31 +4761,35 @@ export function composeRuntime(options) {
|
|
|
4729
4761
|
return;
|
|
4730
4762
|
if (event.kind === 'opened') {
|
|
4731
4763
|
socketOpen = true;
|
|
4764
|
+
announced = false;
|
|
4765
|
+
handed = false;
|
|
4766
|
+
naming = 0;
|
|
4732
4767
|
connectionLive();
|
|
4733
4768
|
generation += 1;
|
|
4734
|
-
for (const admit of installWaiters.splice(0))
|
|
4735
|
-
admit();
|
|
4736
|
-
reads.opened();
|
|
4737
4769
|
conversations.reopened();
|
|
4738
4770
|
recovery.resume();
|
|
4739
|
-
|
|
4771
|
+
}
|
|
4772
|
+
else if (event.kind === 'discovered') {
|
|
4773
|
+
if (!socketOpen)
|
|
4774
|
+
return;
|
|
4775
|
+
announced = true;
|
|
4776
|
+
if (missedChange) {
|
|
4777
|
+
missedChange = false;
|
|
4778
|
+
countAuthorityChange();
|
|
4779
|
+
}
|
|
4780
|
+
void sync('opened', event.discovery);
|
|
4781
|
+
for (const locator of replicas.keys())
|
|
4782
|
+
void heldAgain(locator);
|
|
4740
4783
|
}
|
|
4741
4784
|
else if (event.kind === 'closed')
|
|
4742
|
-
closedSocket(event.revoked, event.lost);
|
|
4743
|
-
else if (event.kind === '
|
|
4744
|
-
void
|
|
4785
|
+
closedSocket(event.revoked, event.lost, event.authorityChanged);
|
|
4786
|
+
else if (event.kind === 'caught-up')
|
|
4787
|
+
void caughtUpRooms(event.expired);
|
|
4745
4788
|
else if (event.kind === 'authority-changed')
|
|
4746
|
-
void
|
|
4789
|
+
void changeAuthority(event);
|
|
4747
4790
|
else
|
|
4748
|
-
void applyRoom(event
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
async function heldRevisions(locator, paths) {
|
|
4752
|
-
const held = replicas.get(locator);
|
|
4753
|
-
if (held === undefined || paths.length === 0)
|
|
4754
|
-
return new Map();
|
|
4755
|
-
const entries = await (await openLocal()).entries(held.record.key, 'confirmed', paths);
|
|
4756
|
-
return new Map(entries.flatMap(({ path, revision }) => revision === null ? [] : [[path, revision]]));
|
|
4791
|
+
void applyRoom(event);
|
|
4792
|
+
});
|
|
4757
4793
|
}
|
|
4758
4794
|
function writesHeld() {
|
|
4759
4795
|
return serviceMode === 'write-halted' || haltedRefusal;
|
|
@@ -4826,8 +4862,6 @@ export function composeRuntime(options) {
|
|
|
4826
4862
|
notifyDiscovered();
|
|
4827
4863
|
if (probing !== null)
|
|
4828
4864
|
clearInterval(probing);
|
|
4829
|
-
for (const resolve of shownWaiters.splice(0))
|
|
4830
|
-
resolve();
|
|
4831
4865
|
connectionLive();
|
|
4832
4866
|
lockListeners.clear();
|
|
4833
4867
|
readinessWatches.clear();
|
|
@@ -4845,6 +4879,7 @@ export function composeRuntime(options) {
|
|
|
4845
4879
|
statusListeners.clear();
|
|
4846
4880
|
serviceModeListeners.clear();
|
|
4847
4881
|
memberListeners.clear();
|
|
4882
|
+
authorityChangeListeners.clear();
|
|
4848
4883
|
liveQueries.close();
|
|
4849
4884
|
queueSubscriptions.clear();
|
|
4850
4885
|
roomDraftListeners.clear();
|
|
@@ -4852,6 +4887,7 @@ export function composeRuntime(options) {
|
|
|
4852
4887
|
}
|
|
4853
4888
|
async function stop() {
|
|
4854
4889
|
stopping = true;
|
|
4890
|
+
inFlight.abort();
|
|
4855
4891
|
await recovery.retire();
|
|
4856
4892
|
await Promise.all(working);
|
|
4857
4893
|
await mutations;
|
|
@@ -4870,7 +4906,7 @@ export function composeRuntime(options) {
|
|
|
4870
4906
|
readyFromStore();
|
|
4871
4907
|
});
|
|
4872
4908
|
if (options.adapter.roomStream === undefined)
|
|
4873
|
-
void sync('unstreamed');
|
|
4909
|
+
void sync('unstreamed', null);
|
|
4874
4910
|
function planned(input, reach) {
|
|
4875
4911
|
const plan = planOf(input, roomFacts(reach), planReach(reach));
|
|
4876
4912
|
const named = plan.paths !== null &&
|
|
@@ -4878,7 +4914,7 @@ export function composeRuntime(options) {
|
|
|
4878
4914
|
plan.paths.length * plan.rooms.length <= MAXIMUM_WINDOW;
|
|
4879
4915
|
return plan.limit === null && named ? plan : { ...plan, limit: capped(plan.limit) };
|
|
4880
4916
|
}
|
|
4881
|
-
function watchPlan(plan, deliver) {
|
|
4917
|
+
function watchPlan(plan, deliver, fail) {
|
|
4882
4918
|
let seeded = false;
|
|
4883
4919
|
let pending = true;
|
|
4884
4920
|
const shown = () => {
|
|
@@ -4904,7 +4940,7 @@ export function composeRuntime(options) {
|
|
|
4904
4940
|
}
|
|
4905
4941
|
seeded = false;
|
|
4906
4942
|
deliver(message);
|
|
4907
|
-
});
|
|
4943
|
+
}, fail);
|
|
4908
4944
|
return () => {
|
|
4909
4945
|
watchers.delete(shown);
|
|
4910
4946
|
if (watchers.size === 0)
|
|
@@ -4912,61 +4948,59 @@ export function composeRuntime(options) {
|
|
|
4912
4948
|
stop();
|
|
4913
4949
|
};
|
|
4914
4950
|
}
|
|
4915
|
-
async function searched(input, reach
|
|
4951
|
+
async function searched(input, reach) {
|
|
4916
4952
|
const asked = searchPlanOf(input);
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
rooms: answered.coverage.rooms,
|
|
4961
|
-
conversations: named,
|
|
4962
|
-
overlay: answered.coverage.overlay,
|
|
4953
|
+
await ready;
|
|
4954
|
+
const search = options.adapter.search;
|
|
4955
|
+
if (search === undefined) {
|
|
4956
|
+
return {
|
|
4957
|
+
kind: 'undeclared',
|
|
4958
|
+
code: 'action-undeclared',
|
|
4959
|
+
fields: { name: 'search' },
|
|
4960
|
+
retryable: false,
|
|
4961
|
+
};
|
|
4962
|
+
}
|
|
4963
|
+
if (closed || refusal !== null || connectionOf() !== 'connected')
|
|
4964
|
+
return OFFLINE;
|
|
4965
|
+
const reached = reach === null ? null : new Set(reach.rooms);
|
|
4966
|
+
const named = asked.rooms === null
|
|
4967
|
+
? reached === null
|
|
4968
|
+
? null
|
|
4969
|
+
: [...reached]
|
|
4970
|
+
: asked.rooms.filter((room) => reached === null || reached.has(room));
|
|
4971
|
+
const rooms = named === null ? null : named.filter((room) => replicas.has(room)).sort();
|
|
4972
|
+
if (rooms !== null && rooms.length === 0)
|
|
4973
|
+
return { kind: 'found', documents: [] };
|
|
4974
|
+
try {
|
|
4975
|
+
await ensureAuthorization();
|
|
4976
|
+
}
|
|
4977
|
+
catch {
|
|
4978
|
+
return UNCERTAIN;
|
|
4979
|
+
}
|
|
4980
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
4981
|
+
return UNCERTAIN;
|
|
4982
|
+
journal.record('transfer search');
|
|
4983
|
+
try {
|
|
4984
|
+
const result = await search(authorization, { text: asked.text, rooms });
|
|
4985
|
+
if (result.kind === 'revoked-authorization')
|
|
4986
|
+
await refuse('revoked-authorization');
|
|
4987
|
+
journal.record(`outcome search ${result.kind}`);
|
|
4988
|
+
if (result.kind === 'rejected')
|
|
4989
|
+
return result;
|
|
4990
|
+
if (result.kind !== 'found')
|
|
4991
|
+
return failure(result);
|
|
4992
|
+
const kept = rooms === null ? null : new Set(rooms);
|
|
4993
|
+
return {
|
|
4994
|
+
kind: 'found',
|
|
4995
|
+
documents: result.documents.filter((hit) => kept === null || kept.has(hit.room)),
|
|
4963
4996
|
};
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4997
|
+
}
|
|
4998
|
+
catch {
|
|
4999
|
+
journal.record('outcome search transport-uncertainty');
|
|
5000
|
+
return UNCERTAIN;
|
|
4967
5001
|
}
|
|
4968
5002
|
}
|
|
4969
|
-
const watch = (topic, input, deliver) => {
|
|
5003
|
+
const watch = (topic, input, deliver, fail) => {
|
|
4970
5004
|
if (topic === 'room-drafts') {
|
|
4971
5005
|
const listener = { notify: deliver, ready: false };
|
|
4972
5006
|
roomDraftListeners.add(listener);
|
|
@@ -5015,7 +5049,7 @@ export function composeRuntime(options) {
|
|
|
5015
5049
|
};
|
|
5016
5050
|
}
|
|
5017
5051
|
if (topic === 'readiness') {
|
|
5018
|
-
return watchReadiness({ target: '', code: 'stored', space: null
|
|
5052
|
+
return watchReadiness({ target: '', code: 'stored', space: null }, null, deliver);
|
|
5019
5053
|
}
|
|
5020
5054
|
if (topic === 'rooms') {
|
|
5021
5055
|
unlistedRooms.add(deliver);
|
|
@@ -5060,6 +5094,13 @@ export function composeRuntime(options) {
|
|
|
5060
5094
|
memberListeners.delete(deliver);
|
|
5061
5095
|
};
|
|
5062
5096
|
}
|
|
5097
|
+
if (topic === 'authority-changes') {
|
|
5098
|
+
authorityChangeListeners.add(deliver);
|
|
5099
|
+
deliver(authorityChanges);
|
|
5100
|
+
return () => {
|
|
5101
|
+
authorityChangeListeners.delete(deliver);
|
|
5102
|
+
};
|
|
5103
|
+
}
|
|
5063
5104
|
if (topic === 'queue') {
|
|
5064
5105
|
const queue = { locator: input[0], notify: deliver };
|
|
5065
5106
|
queueSubscriptions.add(queue);
|
|
@@ -5068,7 +5109,7 @@ export function composeRuntime(options) {
|
|
|
5068
5109
|
queueSubscriptions.delete(queue);
|
|
5069
5110
|
};
|
|
5070
5111
|
}
|
|
5071
|
-
return watchPlan(planned(input[0], null), deliver);
|
|
5112
|
+
return watchPlan(planned(input[0], null), deliver, fail);
|
|
5072
5113
|
};
|
|
5073
5114
|
const md = {
|
|
5074
5115
|
loaded() {
|
|
@@ -5088,7 +5129,7 @@ export function composeRuntime(options) {
|
|
|
5088
5129
|
});
|
|
5089
5130
|
},
|
|
5090
5131
|
query: ((input) => rowsQuery(planned(input, null))),
|
|
5091
|
-
search: (input) => searched(input, null
|
|
5132
|
+
search: (input) => searched(input, null),
|
|
5092
5133
|
types(locator) {
|
|
5093
5134
|
return {
|
|
5094
5135
|
collection: modelTypes(modelOf(locator)),
|
|
@@ -5213,7 +5254,7 @@ export function composeRuntime(options) {
|
|
|
5213
5254
|
authorization = undefined;
|
|
5214
5255
|
discoveryPaused = false;
|
|
5215
5256
|
if (options.adapter.roomStream === undefined)
|
|
5216
|
-
await sync('unstreamed');
|
|
5257
|
+
await sync('unstreamed', null);
|
|
5217
5258
|
else
|
|
5218
5259
|
await task(OPENING_LANE, ensureAuthorization);
|
|
5219
5260
|
},
|
|
@@ -5236,7 +5277,6 @@ export function composeRuntime(options) {
|
|
|
5236
5277
|
md,
|
|
5237
5278
|
seed,
|
|
5238
5279
|
stop,
|
|
5239
|
-
lose: loseRooms,
|
|
5240
5280
|
watch,
|
|
5241
5281
|
persisted(answer) {
|
|
5242
5282
|
if (answer === persistence)
|
|
@@ -5245,7 +5285,6 @@ export function composeRuntime(options) {
|
|
|
5245
5285
|
journal.record(`persistence ${answer}`);
|
|
5246
5286
|
queueReadiness();
|
|
5247
5287
|
},
|
|
5248
|
-
shown: screen,
|
|
5249
5288
|
within: (reach, port) => {
|
|
5250
5289
|
if (full)
|
|
5251
5290
|
void probed();
|