@agentshouse/mdruntime 0.1.0-cba6f2439bb25 → 0.1.0-cbe358f60d1d3
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 +66 -52
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +6 -9
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +28 -22
- package/dist/chain.d.ts +2 -1
- package/dist/chain.d.ts.map +1 -1
- package/dist/client.d.ts +9 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -1
- package/dist/collection.d.ts +2 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +1 -17
- package/dist/compile.d.ts +1 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +91 -276
- package/dist/connection.d.ts +8 -8
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +738 -968
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +7 -0
- package/dist/conversation.d.ts +15 -14
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +141 -167
- package/dist/durability.d.ts +6 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +2 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +13 -5
- package/dist/host.d.ts +3 -7
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +22 -27
- package/dist/identity.d.ts +0 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -3
- package/dist/index.d.ts +11 -7
- 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 +9 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +132 -82
- package/dist/messages.d.ts +2 -2
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +15 -16
- package/dist/model.d.ts +1 -3
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -12
- 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/query.d.ts +4 -209
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -505
- package/dist/reach.d.ts +7 -7
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +34 -45
- package/dist/react.d.ts +3 -1
- 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 +6 -4
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +26 -5
- package/dist/replica.d.ts +2 -2
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +2 -5
- package/dist/room-draft.d.ts +4 -3
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +65 -52
- package/dist/rows.d.ts +1 -17
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +4 -137
- 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 +5 -20
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +10 -44
- package/dist/stored.d.ts +0 -6
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +42 -115
- package/dist/tables.d.ts +10 -34
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +23 -57
- 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 +39 -97
- package/dist/windows.d.ts +1 -12
- package/dist/windows.d.ts.map +1 -1
- package/dist/windows.js +2 -75
- package/dist/wire.d.ts +9 -4
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +3 -1
- package/package.json +2 -2
- package/dist/fulltext.d.ts +0 -27
- package/dist/fulltext.d.ts.map +0 -1
- package/dist/fulltext.js +0 -370
- package/dist/reads.d.ts +0 -11
- package/dist/reads.d.ts.map +0 -1
- package/dist/reads.js +0 -91
- package/dist/words.d.ts +0 -19
- package/dist/words.d.ts.map +0 -1
- package/dist/words.js +0 -156
package/dist/connection.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { identityOf, parseFrontmatter, plain, queryKey, 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 { planWithin, } from './query.js';
|
|
6
6
|
import { createLiveQueries } from './live.js';
|
|
7
7
|
import { seededResult } from './seed.js';
|
|
8
8
|
import { documentKey, roomOfKey } from './rows.js';
|
|
@@ -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';
|
|
@@ -31,10 +30,10 @@ import { readinessOf, } from './readiness.js';
|
|
|
31
30
|
import { FencedMap } from './fence.js';
|
|
32
31
|
import { sameBinary } from './overlay.js';
|
|
33
32
|
import { paced, ROW_WEIGHT, stepped, yielded } from './turn.js';
|
|
34
|
-
import {
|
|
33
|
+
import { roomDraftRevision } from './identity.js';
|
|
35
34
|
import { ancestors, beneath, normalize } from './paths.js';
|
|
36
35
|
import { openRuntimeStore, } from './store.js';
|
|
37
|
-
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,33 @@ 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
|
-
function roomLane(locator) {
|
|
91
|
-
return `room ${locator}`;
|
|
92
|
-
}
|
|
93
88
|
function pendingKey(locator, id) {
|
|
94
89
|
return `${locator}\u0000${id}`;
|
|
95
90
|
}
|
|
@@ -109,6 +104,7 @@ export function composeRuntime(options) {
|
|
|
109
104
|
const usableWhenHeld = options.usableWhenHeld ?? [];
|
|
110
105
|
const queuesWithoutReplica = new FencedMap(isFenced);
|
|
111
106
|
const pendings = new Map();
|
|
107
|
+
const landedAhead = new Map();
|
|
112
108
|
const seeds = new Map();
|
|
113
109
|
const seedWatchers = new Map();
|
|
114
110
|
const roomDrafts = new FencedMap(isFenced);
|
|
@@ -120,15 +116,24 @@ export function composeRuntime(options) {
|
|
|
120
116
|
const counted = new Map();
|
|
121
117
|
const unreadable = new Set();
|
|
122
118
|
const builtIn = builtInModel(roomTypes);
|
|
119
|
+
const typedPaths = options.adapter.typedPaths ?? [];
|
|
123
120
|
const models = new FencedMap(isFenced);
|
|
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
|
}
|
|
@@ -138,7 +143,7 @@ export function composeRuntime(options) {
|
|
|
138
143
|
: modelOf(locator);
|
|
139
144
|
}
|
|
140
145
|
function composed(record) {
|
|
141
|
-
return compositionOf(record, builtIn);
|
|
146
|
+
return compositionOf(record, builtIn, typedPaths);
|
|
142
147
|
}
|
|
143
148
|
function compositionFor(record) {
|
|
144
149
|
return composed({ ...record, model: savedModel(record.locator) });
|
|
@@ -151,26 +156,26 @@ 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();
|
|
161
166
|
const repairable = new Map();
|
|
162
|
-
let outcomeRetention = null;
|
|
163
167
|
const answering = new Map();
|
|
164
168
|
let complete = false;
|
|
165
169
|
let recorded = false;
|
|
166
170
|
const stale = new Set();
|
|
167
171
|
let generation = 0;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
const reloadsDue = new Map();
|
|
172
|
+
const reloadsDue = new Set();
|
|
173
|
+
const highest = new Map();
|
|
174
|
+
const beyondHorizon = new Set();
|
|
172
175
|
const lanes = new Map();
|
|
173
|
-
|
|
176
|
+
const roomLanes = new Map();
|
|
177
|
+
let roomWork = 0;
|
|
178
|
+
const roomReads = { free: ROOM_READS, waiting: [] };
|
|
174
179
|
const working = new Set();
|
|
175
180
|
let mutations = Promise.resolve();
|
|
176
181
|
let gathering = null;
|
|
@@ -186,6 +191,8 @@ export function composeRuntime(options) {
|
|
|
186
191
|
let withdrawConversations;
|
|
187
192
|
let currencyQueued = null;
|
|
188
193
|
let socketOpen = false;
|
|
194
|
+
let announced = false;
|
|
195
|
+
let missedChange = false;
|
|
189
196
|
const confirmed = new Set();
|
|
190
197
|
let lockGrace = null;
|
|
191
198
|
const lockListeners = new Set();
|
|
@@ -195,6 +202,8 @@ export function composeRuntime(options) {
|
|
|
195
202
|
const serviceModeListeners = new Set();
|
|
196
203
|
let member = null;
|
|
197
204
|
const memberListeners = new Set();
|
|
205
|
+
let authorityChanges = 0;
|
|
206
|
+
const authorityChangeListeners = new Set();
|
|
198
207
|
const unpreparedDemand = new Set();
|
|
199
208
|
const unpreparable = new Set();
|
|
200
209
|
let preparing = false;
|
|
@@ -224,12 +233,10 @@ export function composeRuntime(options) {
|
|
|
224
233
|
const streaming = new Set();
|
|
225
234
|
const underived = new Map();
|
|
226
235
|
const unindexed = new Map();
|
|
227
|
-
const
|
|
236
|
+
const startedOver = new Map();
|
|
228
237
|
let arrivalReported = 0;
|
|
229
|
-
let
|
|
230
|
-
|
|
231
|
-
let streams = { generation, free: STREAMS_HELD };
|
|
232
|
-
let queuedBytes = 0;
|
|
238
|
+
let streamsFree = STREAMS_HELD;
|
|
239
|
+
let queuedText = 0;
|
|
233
240
|
const installWaiters = [];
|
|
234
241
|
const stageBudget = {
|
|
235
242
|
bytes: STAGE_FLOOR * 4,
|
|
@@ -243,32 +250,17 @@ export function composeRuntime(options) {
|
|
|
243
250
|
ceiling: DERIVE_BYTES,
|
|
244
251
|
aim: DERIVE_AIM_MS,
|
|
245
252
|
};
|
|
246
|
-
const installTurns = { busy: false, finishing: [], starting: [] };
|
|
253
|
+
const installTurns = { busy: false, ahead: 0, finishing: [], starting: [] };
|
|
247
254
|
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
255
|
call: (name, input) => invokeAction(name, input),
|
|
256
|
+
history: (request) => readConversationHistory(request),
|
|
263
257
|
durably,
|
|
264
258
|
stream: () => options.adapter.conversations,
|
|
265
259
|
publish: publishChangeSet,
|
|
266
260
|
online: () => connectionOf() !== 'unreachable' && refusal === null,
|
|
267
|
-
listed: () => roomsListed,
|
|
268
261
|
full: () => full,
|
|
269
262
|
closed: () => closed,
|
|
270
263
|
unanswered: resendLater,
|
|
271
|
-
expired: (operation) => pastHorizon(operation),
|
|
272
264
|
});
|
|
273
265
|
const journal = createLog();
|
|
274
266
|
const keptFiles = openedFiles(database, isFenced);
|
|
@@ -361,9 +353,9 @@ export function composeRuntime(options) {
|
|
|
361
353
|
return value === undefined ? null : structuredClone(value);
|
|
362
354
|
}
|
|
363
355
|
async function writtenRoomDraft(room, path, revision, input, next) {
|
|
364
|
-
const parts = readParts(input);
|
|
356
|
+
const parts = readParts(input, path);
|
|
365
357
|
if (path === '' || normalize(path) !== path)
|
|
366
|
-
throw runtimeError('room-draft-malformed');
|
|
358
|
+
throw runtimeError('room-draft-malformed', `room-draft-malformed at ${path}: a path inside the Room, such as notes/a.md`);
|
|
367
359
|
const local = await openLocal();
|
|
368
360
|
const current = roomDrafts.get(room)?.get(path);
|
|
369
361
|
if ((current?.revision ?? null) !== revision) {
|
|
@@ -412,7 +404,7 @@ export function composeRuntime(options) {
|
|
|
412
404
|
const changed = new Map();
|
|
413
405
|
for (const room of [...replicas.keys(), ...queuesWithoutReplica.keys()]) {
|
|
414
406
|
for (const { batch, state } of entriesOf(room)) {
|
|
415
|
-
if (state === '
|
|
407
|
+
if (state === 'local')
|
|
416
408
|
continue;
|
|
417
409
|
for (const { path } of batch.changes)
|
|
418
410
|
changed.set(`${room}\u0000${path}`, { room, path });
|
|
@@ -433,16 +425,9 @@ export function composeRuntime(options) {
|
|
|
433
425
|
returned: structuredClone(record.returned),
|
|
434
426
|
});
|
|
435
427
|
}
|
|
436
|
-
for (const [room, refusal] of sourceRefusals)
|
|
437
|
-
attentionRequired.push({ room, refusal: structuredClone(refusal) });
|
|
438
428
|
attentionRequired.sort((left, right) => {
|
|
439
429
|
if (left.room !== right.room)
|
|
440
430
|
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
431
|
if (left.path !== right.path)
|
|
447
432
|
return left.path < right.path ? -1 : 1;
|
|
448
433
|
return 0;
|
|
@@ -597,9 +582,6 @@ export function composeRuntime(options) {
|
|
|
597
582
|
if (localSaves)
|
|
598
583
|
return Object.freeze({ batch, state: 'local' });
|
|
599
584
|
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
585
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
604
586
|
}
|
|
605
587
|
function entriesOf(locator) {
|
|
@@ -673,11 +655,7 @@ export function composeRuntime(options) {
|
|
|
673
655
|
})();
|
|
674
656
|
}
|
|
675
657
|
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
|
-
]);
|
|
658
|
+
return new Set([...reach.rooms, ...reach.documents.map(roomOfKey)]);
|
|
681
659
|
}
|
|
682
660
|
function reachedRooms(plan) {
|
|
683
661
|
const reached = plan.reach === null ? null : roomsOfReach(plan.reach);
|
|
@@ -716,7 +694,7 @@ export function composeRuntime(options) {
|
|
|
716
694
|
coverage.push({
|
|
717
695
|
room: locator,
|
|
718
696
|
generation: replica.record.transfer,
|
|
719
|
-
current: !(catching.has(locator) ||
|
|
697
|
+
current: !(catching.has(locator) || transfers.has(locator)),
|
|
720
698
|
});
|
|
721
699
|
read.push({ locator, key: replica.record.key });
|
|
722
700
|
for (const batch of replica.queue)
|
|
@@ -743,8 +721,6 @@ export function composeRuntime(options) {
|
|
|
743
721
|
return { listed: named.every((room) => replicas.has(room)), rooms: named };
|
|
744
722
|
}
|
|
745
723
|
function roomState(locator) {
|
|
746
|
-
if (sourceRefusals.has(locator))
|
|
747
|
-
return 'refused';
|
|
748
724
|
const listedRoom = discovered.get(locator);
|
|
749
725
|
if (roomsListed &&
|
|
750
726
|
(listedRoom === undefined ||
|
|
@@ -759,7 +735,7 @@ export function composeRuntime(options) {
|
|
|
759
735
|
}
|
|
760
736
|
function conversationCoverage(reach) {
|
|
761
737
|
const declared = reach === null || reach.conversations;
|
|
762
|
-
if (!declared || options.adapter.conversations === undefined
|
|
738
|
+
if (!declared || options.adapter.conversations === undefined)
|
|
763
739
|
return 'undeclared';
|
|
764
740
|
if (!conversations.known() || !conversations.current())
|
|
765
741
|
return 'acquiring';
|
|
@@ -774,79 +750,50 @@ export function composeRuntime(options) {
|
|
|
774
750
|
? 'connected'
|
|
775
751
|
: 'connecting';
|
|
776
752
|
}
|
|
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
|
-
});
|
|
753
|
+
function bytesOf(room, underway) {
|
|
754
|
+
const transfer = transfers.get(room);
|
|
755
|
+
if (transfer !== undefined) {
|
|
756
|
+
const owed = (unindexed.get(room) ?? []).reduce((sum, part) => sum + part.bytes, 0);
|
|
757
|
+
return {
|
|
758
|
+
done: transfer.done.bytes - Math.ceil(owed),
|
|
759
|
+
total: Math.max(arrivals.get(room) ?? 0, transfer.done.bytes),
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
const held = underway ? NOTHING_HELD : (replicas.get(room)?.record.inventory ?? NOTHING_HELD);
|
|
763
|
+
return { done: held.bytes, total: arrivals.get(room) ?? held.bytes };
|
|
793
764
|
}
|
|
794
765
|
function installationOf(coverage) {
|
|
795
766
|
const rooms = { done: 0, total: 0 };
|
|
796
|
-
const parts = { done: 0, total: 0 };
|
|
797
767
|
const bytes = { done: 0, total: 0 };
|
|
798
768
|
let installing = 0;
|
|
799
769
|
let downloading = false;
|
|
800
|
-
let checking = 0;
|
|
801
770
|
for (const { room, state } of coverage) {
|
|
802
771
|
if (state === 'unadmitted')
|
|
803
772
|
continue;
|
|
804
773
|
rooms.total += 1;
|
|
805
774
|
if (state === 'complete')
|
|
806
775
|
rooms.done += 1;
|
|
807
|
-
const transfer = transfers.get(room);
|
|
808
776
|
const underway = state === 'acquiring' || (installs.has(room) && state !== 'complete');
|
|
809
777
|
if (underway)
|
|
810
778
|
installing += 1;
|
|
811
779
|
if (underway && (streaming.has(room) || !arrivals.has(room)))
|
|
812
780
|
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;
|
|
781
|
+
const now = bytesOf(room, underway);
|
|
782
|
+
const earlier = underway ? startedOver.get(room) : undefined;
|
|
783
|
+
bytes.done += Math.max(now.done, earlier?.done ?? 0);
|
|
784
|
+
bytes.total += Math.max(now.total, earlier?.total ?? 0);
|
|
831
785
|
}
|
|
832
786
|
const step = installing === 0
|
|
833
787
|
? null
|
|
834
788
|
: downloading
|
|
835
789
|
? 'downloading'
|
|
836
|
-
: bytes.done
|
|
790
|
+
: bytes.done < bytes.total
|
|
837
791
|
? 'writing'
|
|
838
792
|
: 'replaying';
|
|
839
|
-
return {
|
|
840
|
-
step,
|
|
841
|
-
paused: installing > 0 && !onScreen ? 'hidden' : null,
|
|
842
|
-
rooms,
|
|
843
|
-
parts,
|
|
844
|
-
bytes,
|
|
845
|
-
};
|
|
793
|
+
return { step, rooms, bytes };
|
|
846
794
|
}
|
|
847
795
|
function readinessFor(port, reach) {
|
|
848
796
|
const required = requiredRooms(reach);
|
|
849
|
-
const search = port.search === 'required';
|
|
850
797
|
const coverage = required.rooms.map((room) => ({ room, state: roomState(room) }));
|
|
851
798
|
const preparing = coverage
|
|
852
799
|
.filter((room) => room.state === 'preparing' && !unpreparable.has(room.room))
|
|
@@ -859,14 +806,12 @@ export function composeRuntime(options) {
|
|
|
859
806
|
listed: required.listed,
|
|
860
807
|
rooms: coverage,
|
|
861
808
|
conversations: conversationCoverage(reach),
|
|
862
|
-
search: search ? 'required' : 'unused',
|
|
863
809
|
},
|
|
864
810
|
persistence,
|
|
865
811
|
connection: connectionOf(),
|
|
866
812
|
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
867
|
-
space: full ? 'full' :
|
|
813
|
+
space: full ? 'full' : null,
|
|
868
814
|
installation: installationOf(coverage),
|
|
869
|
-
refused: refusedIn(required.rooms),
|
|
870
815
|
lost: storageLoss !== null,
|
|
871
816
|
established: targets[port.target]?.established === true ||
|
|
872
817
|
(usableWhenHeld.includes(port.target) && required.rooms.some((room) => reopened.has(room))),
|
|
@@ -954,8 +899,12 @@ export function composeRuntime(options) {
|
|
|
954
899
|
unpreparable.clear();
|
|
955
900
|
journal.record('space freed');
|
|
956
901
|
queueReadiness();
|
|
957
|
-
if (
|
|
958
|
-
|
|
902
|
+
if (closed || authorization === undefined)
|
|
903
|
+
return;
|
|
904
|
+
if (options.adapter.roomStream === undefined)
|
|
905
|
+
void sync('unstreamed', null);
|
|
906
|
+
else
|
|
907
|
+
void resynchronize();
|
|
959
908
|
}
|
|
960
909
|
function seedRoomHeld(room, version) {
|
|
961
910
|
return indexed(room) && reachedBy(room, version);
|
|
@@ -1103,6 +1052,8 @@ export function composeRuntime(options) {
|
|
|
1103
1052
|
}
|
|
1104
1053
|
async function refuse(reason, provider = null) {
|
|
1105
1054
|
discoveryPaused = true;
|
|
1055
|
+
inFlight.abort();
|
|
1056
|
+
inFlight = new AbortController();
|
|
1106
1057
|
try {
|
|
1107
1058
|
if (reason === 'revoked-authorization') {
|
|
1108
1059
|
await exclusive(async () => {
|
|
@@ -1187,10 +1138,10 @@ export function composeRuntime(options) {
|
|
|
1187
1138
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
1188
1139
|
roomVersion: replicas.get(room.locator)?.record.roomVersion ?? null,
|
|
1189
1140
|
synchronizationAvailable,
|
|
1190
|
-
refusal: scopedRefusals.get(room.locator) ??
|
|
1141
|
+
refusal: scopedRefusals.get(room.locator) ?? null,
|
|
1191
1142
|
});
|
|
1192
1143
|
}
|
|
1193
|
-
async function applyDiscovery(found, started,
|
|
1144
|
+
async function applyDiscovery(found, started, left) {
|
|
1194
1145
|
const lockedBefore = lockedRooms();
|
|
1195
1146
|
for (const room of found) {
|
|
1196
1147
|
const fence = fences.get(room.locator);
|
|
@@ -1204,18 +1155,11 @@ export function composeRuntime(options) {
|
|
|
1204
1155
|
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
1205
1156
|
for (const locator of listedLocators)
|
|
1206
1157
|
scopedRefusals.delete(locator);
|
|
1207
|
-
if (
|
|
1158
|
+
if (left === undefined)
|
|
1208
1159
|
discovered.clear();
|
|
1209
|
-
else
|
|
1210
|
-
for (const locator of covered)
|
|
1211
|
-
discovered.delete(locator);
|
|
1212
1160
|
for (const room of listed)
|
|
1213
1161
|
discovered.set(room.locator, room);
|
|
1214
|
-
|
|
1215
|
-
if (options.adapter.exhaustiveDiscovery === true)
|
|
1216
|
-
await loseRooms(unlisted);
|
|
1217
|
-
else
|
|
1218
|
-
unlistedKept(unlisted);
|
|
1162
|
+
await loseRooms(left ?? unlistedHeld(new Set(found.map((room) => room.locator))));
|
|
1219
1163
|
for (const room of listed) {
|
|
1220
1164
|
const held = replicas.get(room.locator)?.record;
|
|
1221
1165
|
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
@@ -1246,15 +1190,6 @@ export function composeRuntime(options) {
|
|
|
1246
1190
|
]),
|
|
1247
1191
|
].filter((locator) => !listed.has(locator));
|
|
1248
1192
|
}
|
|
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
1193
|
function fence(locators) {
|
|
1259
1194
|
const fresh = [...new Set(locators)].filter((locator) => !fences.has(locator));
|
|
1260
1195
|
const removed = fresh.map((locator) => ({
|
|
@@ -1265,24 +1200,26 @@ export function composeRuntime(options) {
|
|
|
1265
1200
|
fences.set(locator, { at: discoveries, erased: false });
|
|
1266
1201
|
lastFenced.set(locator, ++fencings);
|
|
1267
1202
|
confirmed.delete(locator);
|
|
1268
|
-
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
1203
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []) {
|
|
1269
1204
|
pendings.delete(pendingKey(locator, batch.id));
|
|
1205
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1206
|
+
}
|
|
1270
1207
|
replicas.delete(locator);
|
|
1208
|
+
highest.delete(locator);
|
|
1209
|
+
beyondHorizon.delete(locator);
|
|
1271
1210
|
queuesWithoutReplica.delete(locator);
|
|
1272
1211
|
rooms.delete(locator);
|
|
1273
1212
|
roomDrafts.delete(locator);
|
|
1274
1213
|
attachedFiles.delete(locator);
|
|
1275
1214
|
untransferred(locator);
|
|
1215
|
+
startedOver.delete(locator);
|
|
1276
1216
|
catching.delete(locator);
|
|
1277
1217
|
discovered.delete(locator);
|
|
1278
|
-
announced.delete(locator);
|
|
1279
1218
|
unreadable.delete(locator);
|
|
1280
1219
|
stale.delete(locator);
|
|
1281
1220
|
repairable.delete(locator);
|
|
1282
1221
|
unpreparable.delete(locator);
|
|
1283
1222
|
scopedRefusals.delete(locator);
|
|
1284
|
-
sourceRefusals.delete(locator);
|
|
1285
|
-
declarationRefusals.delete(locator);
|
|
1286
1223
|
models.delete(locator);
|
|
1287
1224
|
liveModels.delete(locator);
|
|
1288
1225
|
invalidModels.delete(locator);
|
|
@@ -1373,6 +1310,7 @@ export function composeRuntime(options) {
|
|
|
1373
1310
|
transfers.clear();
|
|
1374
1311
|
arrivals.clear();
|
|
1375
1312
|
streaming.clear();
|
|
1313
|
+
startedOver.clear();
|
|
1376
1314
|
limits = held.providerLimits;
|
|
1377
1315
|
capabilities = held.capabilities;
|
|
1378
1316
|
member = held.member;
|
|
@@ -1382,8 +1320,9 @@ export function composeRuntime(options) {
|
|
|
1382
1320
|
presentations.clear();
|
|
1383
1321
|
for (const record of held.presentation)
|
|
1384
1322
|
presentations.set(record.app, record.value);
|
|
1385
|
-
conversations.hydrate(heldConversations, chatDrafts, held.conversationList);
|
|
1323
|
+
conversations.hydrate(heldConversations, chatDrafts, held.conversationList, held.conversationCursor);
|
|
1386
1324
|
pendings.clear();
|
|
1325
|
+
landedAhead.clear();
|
|
1387
1326
|
for (const queue of queues) {
|
|
1388
1327
|
for (const { batch, state } of queue.pending) {
|
|
1389
1328
|
pendings.set(pendingKey(queue.locator, batch.id), state);
|
|
@@ -1634,8 +1573,6 @@ export function composeRuntime(options) {
|
|
|
1634
1573
|
function unpairedIn(locator, batch) {
|
|
1635
1574
|
if (!queueIn(locator).some((held) => held.id === batch.id))
|
|
1636
1575
|
return false;
|
|
1637
|
-
if (pendings.get(pendingKey(locator, batch.id))?.accepted !== undefined)
|
|
1638
|
-
return false;
|
|
1639
1576
|
return !leavingIn(batch, locator) || withdrawable(locator)(batch);
|
|
1640
1577
|
}
|
|
1641
1578
|
async function together(local, settlements) {
|
|
@@ -1678,10 +1615,16 @@ export function composeRuntime(options) {
|
|
|
1678
1615
|
? read
|
|
1679
1616
|
: await stepped(slicingWith(read, overrides.set, overrides.removed));
|
|
1680
1617
|
const composition = composed(record);
|
|
1681
|
-
const
|
|
1682
|
-
|
|
1618
|
+
const shown = queue.filter((batch) => {
|
|
1619
|
+
const held = landedAhead.get(pendingKey(locator, batch.id));
|
|
1620
|
+
return (held !== undefined &&
|
|
1621
|
+
Object.entries(held).some(([path, revision]) => confirmedLayer.revisions[path] !== revision));
|
|
1622
|
+
});
|
|
1623
|
+
const unshown = queue.filter((batch) => !shown.includes(batch));
|
|
1624
|
+
const { withdrawn, kept } = await stepped(standing(confirmedLayer, unshown, withdrawable(locator), localSaves, locator, composition));
|
|
1625
|
+
const founded = kept.filter((batch) => !unshown.includes(batch));
|
|
1683
1626
|
const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
|
|
1684
|
-
const unqueued =
|
|
1627
|
+
const unqueued = shown.map((batch) => ({ batch, landed: true }));
|
|
1685
1628
|
for (const batch of withdrawn) {
|
|
1686
1629
|
unqueued.push({ batch, landed: await landedWhole(locator, batch, confirmedLayer) });
|
|
1687
1630
|
}
|
|
@@ -1746,8 +1689,10 @@ export function composeRuntime(options) {
|
|
|
1746
1689
|
commit,
|
|
1747
1690
|
apply: (answer) => {
|
|
1748
1691
|
for (const batch of previousQueue) {
|
|
1749
|
-
if (
|
|
1750
|
-
|
|
1692
|
+
if (staying.has(batch.id))
|
|
1693
|
+
continue;
|
|
1694
|
+
pendings.delete(pendingKey(locator, batch.id));
|
|
1695
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1751
1696
|
}
|
|
1752
1697
|
for (const draft of written.values())
|
|
1753
1698
|
rememberRoomDraft(draft);
|
|
@@ -1804,28 +1749,34 @@ export function composeRuntime(options) {
|
|
|
1804
1749
|
models.set(locator, state);
|
|
1805
1750
|
return { collection: state };
|
|
1806
1751
|
}
|
|
1807
|
-
function publish(local, room,
|
|
1752
|
+
function publish(local, room, reached, landing, live) {
|
|
1808
1753
|
const landed = exclusive(async () => {
|
|
1809
1754
|
if (!live()) {
|
|
1810
1755
|
caughtUpTo(room.locator);
|
|
1811
1756
|
return true;
|
|
1812
1757
|
}
|
|
1813
1758
|
const prior = replicas.get(room.locator);
|
|
1814
|
-
if (landing.
|
|
1815
|
-
(
|
|
1816
|
-
|
|
1759
|
+
if (!stands(room.locator, landing.from)) {
|
|
1760
|
+
if (landing.kind === 'refreshed') {
|
|
1761
|
+
await local.unrefresh(reached.key);
|
|
1762
|
+
return false;
|
|
1763
|
+
}
|
|
1764
|
+
caughtUpTo(room.locator);
|
|
1765
|
+
await abandoned(local, room.locator);
|
|
1817
1766
|
return false;
|
|
1818
1767
|
}
|
|
1768
|
+
const record = landing.kind === 'refreshed' && prior !== undefined
|
|
1769
|
+
? {
|
|
1770
|
+
...prior.record,
|
|
1771
|
+
roomVersion: reached.roomVersion,
|
|
1772
|
+
...(landing.remodelled ? { model: reached.model } : {}),
|
|
1773
|
+
}
|
|
1774
|
+
: reached;
|
|
1819
1775
|
adoptModels(room.locator, record);
|
|
1820
1776
|
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
1777
|
let set;
|
|
1827
1778
|
try {
|
|
1828
|
-
set = await settle(local, room.locator, prior, record, queue
|
|
1779
|
+
set = await settle(local, room.locator, prior, record, queue, landing.kind === 'refreshed'
|
|
1829
1780
|
? {
|
|
1830
1781
|
room: true,
|
|
1831
1782
|
view: 'refreshed',
|
|
@@ -1847,24 +1798,24 @@ export function composeRuntime(options) {
|
|
|
1847
1798
|
return true;
|
|
1848
1799
|
}
|
|
1849
1800
|
if (landing.kind === 'bootstrapped') {
|
|
1801
|
+
highest.delete(room.locator);
|
|
1802
|
+
beyondHorizon.delete(room.locator);
|
|
1850
1803
|
untransferred(room.locator);
|
|
1804
|
+
startedOver.delete(room.locator);
|
|
1851
1805
|
catching.delete(room.locator);
|
|
1852
|
-
|
|
1806
|
+
if (prior !== undefined && prior.record.key !== record.key)
|
|
1807
|
+
sweep();
|
|
1808
|
+
name(room.locator);
|
|
1853
1809
|
}
|
|
1854
|
-
for (const batch of accepted)
|
|
1855
|
-
pendings.delete(pendingKey(room.locator, batch.id));
|
|
1856
1810
|
stale.delete(room.locator);
|
|
1857
1811
|
unpreparable.delete(room.locator);
|
|
1858
1812
|
if (unreadable.delete(room.locator))
|
|
1859
1813
|
scopedRefusals.delete(room.locator);
|
|
1860
|
-
sourceRefusals.delete(room.locator);
|
|
1861
|
-
declarationRefusals.delete(room.locator);
|
|
1862
1814
|
list(room, true);
|
|
1863
1815
|
publishChangeSet(set);
|
|
1864
1816
|
await unpair(local);
|
|
1865
1817
|
moveTo({ ...status, ...attention() });
|
|
1866
|
-
|
|
1867
|
-
if (accepted.length > 0 && (replicas.get(room.locator)?.queue.length ?? 0) > 0) {
|
|
1818
|
+
if (landing.kind === 'bootstrapped' && (replicas.get(room.locator)?.queue.length ?? 0) > 0) {
|
|
1868
1819
|
submitLater(room.locator);
|
|
1869
1820
|
}
|
|
1870
1821
|
return true;
|
|
@@ -1876,7 +1827,7 @@ export function composeRuntime(options) {
|
|
|
1876
1827
|
const replica = replicas.get(locator);
|
|
1877
1828
|
const room = rooms.get(locator);
|
|
1878
1829
|
if (replica === undefined || room === undefined) {
|
|
1879
|
-
throw runtimeError('room
|
|
1830
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
1880
1831
|
}
|
|
1881
1832
|
return { local, replica, room };
|
|
1882
1833
|
}
|
|
@@ -1968,18 +1919,6 @@ export function composeRuntime(options) {
|
|
|
1968
1919
|
return undefined;
|
|
1969
1920
|
return entry.binary !== null ? null : entry.content;
|
|
1970
1921
|
}
|
|
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
1922
|
function lookupOf(local, key, view) {
|
|
1984
1923
|
return async (paths) => {
|
|
1985
1924
|
if (paths.length === 0)
|
|
@@ -1996,37 +1935,6 @@ export function composeRuntime(options) {
|
|
|
1996
1935
|
const read = parseFrontmatter(entry.content ?? '');
|
|
1997
1936
|
return { type: read.ok ? (read.frontmatter.type ?? 'any') : undefined, binary: false };
|
|
1998
1937
|
}
|
|
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
1938
|
function overriddenLookup(lookup, overrides) {
|
|
2031
1939
|
return async (paths) => {
|
|
2032
1940
|
const known = new Map(await lookup(paths.filter((path) => !overrides.has(path))));
|
|
@@ -2044,9 +1952,8 @@ export function composeRuntime(options) {
|
|
|
2044
1952
|
}
|
|
2045
1953
|
function validatorOf(record, confirmedLayer) {
|
|
2046
1954
|
return async (resulting, touched) => {
|
|
2047
|
-
const composition = compositionFor(record);
|
|
2048
1955
|
const local = await openLocal();
|
|
2049
|
-
const
|
|
1956
|
+
const changes = [];
|
|
2050
1957
|
const overrides = new Map();
|
|
2051
1958
|
const pace = paced();
|
|
2052
1959
|
for (const path of new Set([...confirmedLayer.held, ...touched])) {
|
|
@@ -2057,19 +1964,13 @@ export function composeRuntime(options) {
|
|
|
2057
1964
|
continue;
|
|
2058
1965
|
overrides.set(path, now);
|
|
2059
1966
|
if (textOf(was) !== textOf(now))
|
|
2060
|
-
|
|
1967
|
+
changes.push({ path, was: textOf(was), now: textOf(now) });
|
|
2061
1968
|
}
|
|
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);
|
|
1969
|
+
return validatedNeighbourhood(changes, compositionFor(record), {
|
|
1970
|
+
lookup: overriddenLookup(lookupOf(local, record.key, 'confirmed'), overrides),
|
|
1971
|
+
texts: textsOf(local, record.key, 'confirmed'),
|
|
1972
|
+
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
1973
|
+
});
|
|
2073
1974
|
};
|
|
2074
1975
|
}
|
|
2075
1976
|
function textsOf(local, key, view) {
|
|
@@ -2152,7 +2053,7 @@ export function composeRuntime(options) {
|
|
|
2152
2053
|
const target = replicas.get(destination);
|
|
2153
2054
|
const listedRoom = rooms.get(destination);
|
|
2154
2055
|
if (target === undefined || listedRoom === undefined) {
|
|
2155
|
-
throw runtimeError('room
|
|
2056
|
+
throw runtimeError('unknown-room', `Unknown Room ${destination}`);
|
|
2156
2057
|
}
|
|
2157
2058
|
const arriving = await savingIn(target, [], moves.map(({ to }) => to));
|
|
2158
2059
|
const built = await buildMove({
|
|
@@ -2215,10 +2116,10 @@ export function composeRuntime(options) {
|
|
|
2215
2116
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2216
2117
|
const { answer, touched } = committedBoth;
|
|
2217
2118
|
if (answer.fenced !== null) {
|
|
2218
|
-
throw runtimeError('room
|
|
2119
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2219
2120
|
}
|
|
2220
2121
|
if (fences.has(locator) || fences.has(destination)) {
|
|
2221
|
-
throw runtimeError('room
|
|
2122
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2222
2123
|
}
|
|
2223
2124
|
pendings.set(pendingKey(locator, id), {
|
|
2224
2125
|
drafts: saved,
|
|
@@ -2389,10 +2290,10 @@ export function composeRuntime(options) {
|
|
|
2389
2290
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2390
2291
|
const { answer, touched } = done;
|
|
2391
2292
|
if (answer.fenced !== null) {
|
|
2392
|
-
throw runtimeError('room
|
|
2293
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2393
2294
|
}
|
|
2394
2295
|
if (fences.has(locator))
|
|
2395
|
-
throw runtimeError('room
|
|
2296
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2396
2297
|
for (const origin of withdrawn.keys())
|
|
2397
2298
|
pendings.delete(pendingKey(locator, origin));
|
|
2398
2299
|
for (const [id, drafts] of drafted) {
|
|
@@ -2441,7 +2342,7 @@ export function composeRuntime(options) {
|
|
|
2441
2342
|
throw new Error('connection refused');
|
|
2442
2343
|
await local.keepUpload(locator, identifier, upload);
|
|
2443
2344
|
if (fences.has(locator))
|
|
2444
|
-
throw runtimeError('room
|
|
2345
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2445
2346
|
const key = pendingKey(locator, identifier);
|
|
2446
2347
|
pendings.set(key, { ...pendings.get(key), upload });
|
|
2447
2348
|
if (!continuing())
|
|
@@ -2715,13 +2616,6 @@ export function composeRuntime(options) {
|
|
|
2715
2616
|
const freed = await database.ask({ kind: 'opened.clean' });
|
|
2716
2617
|
return freed + ((await options.shell.cleanUp?.()) ?? 0);
|
|
2717
2618
|
}
|
|
2718
|
-
function declaredAgents() {
|
|
2719
|
-
for (const capability of capabilities) {
|
|
2720
|
-
if (capability.conversationAgents !== undefined)
|
|
2721
|
-
return capability.conversationAgents;
|
|
2722
|
-
}
|
|
2723
|
-
return undefined;
|
|
2724
|
-
}
|
|
2725
2619
|
function declaredAction(name) {
|
|
2726
2620
|
for (const capability of capabilities) {
|
|
2727
2621
|
for (const action of capability.actions ?? []) {
|
|
@@ -2773,6 +2667,24 @@ export function composeRuntime(options) {
|
|
|
2773
2667
|
return UNCERTAIN;
|
|
2774
2668
|
}
|
|
2775
2669
|
}
|
|
2670
|
+
async function readConversationHistory(request) {
|
|
2671
|
+
await ready;
|
|
2672
|
+
const stream = options.adapter.conversations;
|
|
2673
|
+
if (closed || refusal !== null || stream?.history === undefined)
|
|
2674
|
+
return UNCERTAIN;
|
|
2675
|
+
journal.record('transfer conversation history');
|
|
2676
|
+
try {
|
|
2677
|
+
const result = await stream.history(request);
|
|
2678
|
+
if (result.kind === 'revoked-authorization')
|
|
2679
|
+
await refuse('revoked-authorization');
|
|
2680
|
+
journal.record(`outcome conversation history ${result.kind}`);
|
|
2681
|
+
return calledResult(result);
|
|
2682
|
+
}
|
|
2683
|
+
catch {
|
|
2684
|
+
journal.record('outcome conversation history transport-uncertainty');
|
|
2685
|
+
return UNCERTAIN;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2776
2688
|
function unsettled(result) {
|
|
2777
2689
|
return result.kind !== 'action' && result.retryable;
|
|
2778
2690
|
}
|
|
@@ -2811,10 +2723,6 @@ export function composeRuntime(options) {
|
|
|
2811
2723
|
continue;
|
|
2812
2724
|
if (closed || signal.aborted)
|
|
2813
2725
|
return 'done';
|
|
2814
|
-
if (pastHorizon(held.operation)) {
|
|
2815
|
-
await local.dropCall(held.operation);
|
|
2816
|
-
continue;
|
|
2817
|
-
}
|
|
2818
2726
|
calling.add(held.operation);
|
|
2819
2727
|
try {
|
|
2820
2728
|
if (unsettled(await invokeAction(held.name, held.input)))
|
|
@@ -2834,6 +2742,33 @@ export function composeRuntime(options) {
|
|
|
2834
2742
|
const held = replicas.get(locator);
|
|
2835
2743
|
return (held !== undefined && options.adapter.reached(locator, held.record.roomVersion, roomVersion));
|
|
2836
2744
|
}
|
|
2745
|
+
function shownAt(locator) {
|
|
2746
|
+
const held = replicas.get(locator)?.record.roomVersion;
|
|
2747
|
+
const raised = highest.get(locator);
|
|
2748
|
+
if (held === undefined || raised === undefined)
|
|
2749
|
+
return held ?? raised;
|
|
2750
|
+
return options.adapter.reached(locator, held, raised) ? held : raised;
|
|
2751
|
+
}
|
|
2752
|
+
function showing(locator, roomVersion) {
|
|
2753
|
+
const at = shownAt(locator);
|
|
2754
|
+
if (at === undefined || !options.adapter.reached(locator, at, roomVersion)) {
|
|
2755
|
+
highest.set(locator, roomVersion);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
function older(locator, roomVersion) {
|
|
2759
|
+
if (beyondHorizon.has(locator))
|
|
2760
|
+
return false;
|
|
2761
|
+
const at = shownAt(locator);
|
|
2762
|
+
return (at !== undefined &&
|
|
2763
|
+
options.adapter.reached(locator, at, roomVersion) &&
|
|
2764
|
+
!options.adapter.reached(locator, roomVersion, at));
|
|
2765
|
+
}
|
|
2766
|
+
function replaced(locators) {
|
|
2767
|
+
for (const locator of locators) {
|
|
2768
|
+
highest.delete(locator);
|
|
2769
|
+
beyondHorizon.add(locator);
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2837
2772
|
async function writtenAfter(locator, roomVersion, path) {
|
|
2838
2773
|
const held = replicas.get(locator);
|
|
2839
2774
|
if (held === undefined)
|
|
@@ -2844,122 +2779,27 @@ export function composeRuntime(options) {
|
|
|
2844
2779
|
options.adapter.reached(locator, written, roomVersion) &&
|
|
2845
2780
|
!options.adapter.reached(locator, roomVersion, written));
|
|
2846
2781
|
}
|
|
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;
|
|
2782
|
+
async function writtenLater(local, locator, key, paths, roomVersion) {
|
|
2783
|
+
if (paths.length === 0 || !older(locator, roomVersion))
|
|
2784
|
+
return new Set();
|
|
2785
|
+
const entries = await local.entries(key, 'confirmed', paths);
|
|
2786
|
+
return new Set(entries.flatMap(({ path, written }) => written !== null &&
|
|
2787
|
+
options.adapter.reached(locator, written, roomVersion) &&
|
|
2788
|
+
!options.adapter.reached(locator, roomVersion, written)
|
|
2789
|
+
? [path]
|
|
2790
|
+
: []));
|
|
2948
2791
|
}
|
|
2949
|
-
function refreshingOf(local, key, composition, liveness, replaying =
|
|
2950
|
-
const write = replaying
|
|
2792
|
+
function refreshingOf(local, locator, key, composition, liveness, replaying = false) {
|
|
2793
|
+
const write = replaying ? finishingWrite : exclusive;
|
|
2951
2794
|
return {
|
|
2952
|
-
|
|
2953
|
-
if (replaying
|
|
2954
|
-
|
|
2955
|
-
installing(replaying);
|
|
2956
|
-
await screened();
|
|
2795
|
+
turn() {
|
|
2796
|
+
if (replaying)
|
|
2797
|
+
installing(locator);
|
|
2957
2798
|
},
|
|
2958
2799
|
prior: async (paths) => new Map((await local.entries(key, 'refreshed', paths)).map((entry) => [entry.path, entry])),
|
|
2800
|
+
later: (paths, roomVersion) => writtenLater(local, locator, key, paths, roomVersion),
|
|
2959
2801
|
async stage(page) {
|
|
2960
2802
|
for (let written = 0;;) {
|
|
2961
|
-
if (replaying !== null)
|
|
2962
|
-
await screened();
|
|
2963
2803
|
const end = batchEnd(page.entries, written);
|
|
2964
2804
|
const last = end === page.entries.length;
|
|
2965
2805
|
const entries = page.entries.slice(written, end);
|
|
@@ -3011,38 +2851,64 @@ export function composeRuntime(options) {
|
|
|
3011
2851
|
}
|
|
3012
2852
|
function installTurn(queue, work) {
|
|
3013
2853
|
return new Promise((resolve, reject) => {
|
|
3014
|
-
queue.push(() => void exclusive(work)
|
|
3015
|
-
|
|
2854
|
+
queue.push(() => void exclusive(work)
|
|
2855
|
+
.then(resolve, reject)
|
|
2856
|
+
.finally(() => {
|
|
2857
|
+
installTurns.busy = false;
|
|
3016
2858
|
nextInstallTurn();
|
|
2859
|
+
}));
|
|
2860
|
+
nextInstallTurn();
|
|
3017
2861
|
});
|
|
3018
2862
|
}
|
|
3019
2863
|
function nextInstallTurn() {
|
|
2864
|
+
if (installTurns.busy || installTurns.ahead > 0)
|
|
2865
|
+
return;
|
|
3020
2866
|
const turn = installTurns.finishing.shift() ?? installTurns.starting.shift();
|
|
3021
|
-
|
|
3022
|
-
|
|
2867
|
+
if (turn === undefined)
|
|
2868
|
+
return;
|
|
2869
|
+
installTurns.busy = true;
|
|
2870
|
+
turn();
|
|
2871
|
+
}
|
|
2872
|
+
async function ahead(work) {
|
|
2873
|
+
let taken = false;
|
|
2874
|
+
const hold = {
|
|
2875
|
+
take() {
|
|
2876
|
+
if (taken)
|
|
2877
|
+
return;
|
|
2878
|
+
taken = true;
|
|
2879
|
+
installTurns.ahead += 1;
|
|
2880
|
+
},
|
|
2881
|
+
release() {
|
|
2882
|
+
if (!taken)
|
|
2883
|
+
return;
|
|
2884
|
+
taken = false;
|
|
2885
|
+
installTurns.ahead -= 1;
|
|
2886
|
+
resumeInstall();
|
|
2887
|
+
},
|
|
2888
|
+
};
|
|
2889
|
+
hold.take();
|
|
2890
|
+
try {
|
|
2891
|
+
return await work(hold);
|
|
2892
|
+
}
|
|
2893
|
+
finally {
|
|
2894
|
+
hold.release();
|
|
2895
|
+
}
|
|
3023
2896
|
}
|
|
3024
|
-
function
|
|
3025
|
-
if (
|
|
3026
|
-
return
|
|
3027
|
-
|
|
3028
|
-
return new Promise((resolve) => shownWaiters.push(resolve));
|
|
2897
|
+
function resumeInstall() {
|
|
2898
|
+
if (installTurns.ahead > 0)
|
|
2899
|
+
return;
|
|
2900
|
+
void yielded().then(nextInstallTurn);
|
|
3029
2901
|
}
|
|
3030
2902
|
async function heldStream() {
|
|
3031
|
-
|
|
3032
|
-
if (streams.generation !== generation)
|
|
3033
|
-
streams = { generation, free: STREAMS_HELD };
|
|
3034
|
-
const held = streams;
|
|
3035
|
-
if (held.free > 0) {
|
|
3036
|
-
held.free -= 1;
|
|
3037
|
-
return once(() => {
|
|
3038
|
-
held.free += 1;
|
|
3039
|
-
});
|
|
3040
|
-
}
|
|
2903
|
+
while (streamsFree === 0)
|
|
3041
2904
|
await new Promise((admit) => installWaiters.push(admit));
|
|
3042
|
-
|
|
2905
|
+
streamsFree -= 1;
|
|
2906
|
+
return once(() => {
|
|
2907
|
+
streamsFree += 1;
|
|
2908
|
+
});
|
|
3043
2909
|
}
|
|
3044
2910
|
async function unqueued() {
|
|
3045
|
-
while (
|
|
2911
|
+
while (queuedText >= QUEUED_TEXT) {
|
|
3046
2912
|
await new Promise((admit) => installWaiters.push(admit));
|
|
3047
2913
|
}
|
|
3048
2914
|
}
|
|
@@ -3067,14 +2933,19 @@ export function composeRuntime(options) {
|
|
|
3067
2933
|
streaming.delete(locator);
|
|
3068
2934
|
underived.delete(locator);
|
|
3069
2935
|
unindexed.delete(locator);
|
|
3070
|
-
|
|
2936
|
+
}
|
|
2937
|
+
function setBack(locator) {
|
|
2938
|
+
const now = bytesOf(locator, true);
|
|
2939
|
+
const earlier = startedOver.get(locator);
|
|
2940
|
+
startedOver.set(locator, {
|
|
2941
|
+
done: Math.max(now.done, earlier?.done ?? 0),
|
|
2942
|
+
total: Math.max(now.total, earlier?.total ?? 0),
|
|
2943
|
+
});
|
|
3071
2944
|
}
|
|
3072
2945
|
function owedIndexes(locator, bytes, text) {
|
|
3073
2946
|
if (bytes <= 0 || text <= 0)
|
|
3074
2947
|
return;
|
|
3075
|
-
unindexed.set(locator, [...(unindexed.get(locator) ?? []), { bytes
|
|
3076
|
-
const check = unchecked.get(locator) ?? { bytes: 0, text: 0, done: 0 };
|
|
3077
|
-
unchecked.set(locator, { ...check, bytes: check.bytes + bytes / 2, text: check.text + text });
|
|
2948
|
+
unindexed.set(locator, [...(unindexed.get(locator) ?? []), { bytes, text }]);
|
|
3078
2949
|
}
|
|
3079
2950
|
function built(locator, { done, text }) {
|
|
3080
2951
|
const owed = unindexed.get(locator) ?? [];
|
|
@@ -3100,41 +2971,34 @@ export function composeRuntime(options) {
|
|
|
3100
2971
|
if (installs.delete(locator))
|
|
3101
2972
|
queueReadiness();
|
|
3102
2973
|
}
|
|
3103
|
-
function
|
|
3104
|
-
if (value === onScreen)
|
|
3105
|
-
return;
|
|
3106
|
-
onScreen = value;
|
|
3107
|
-
journal.record(`installation ${value ? 'shown' : 'hidden'}`);
|
|
3108
|
-
queueReadiness();
|
|
3109
|
-
if (!value)
|
|
3110
|
-
return;
|
|
3111
|
-
for (const resolve of shownWaiters.splice(0))
|
|
3112
|
-
resolve();
|
|
3113
|
-
}
|
|
3114
|
-
async function pullRefresh(authorized, held, liveness) {
|
|
2974
|
+
async function pullRefresh(authorized, held, liveness, hold, refresh) {
|
|
3115
2975
|
if (!liveness())
|
|
3116
2976
|
return unconfirmed;
|
|
3117
|
-
const turn = generation;
|
|
3118
2977
|
const { record } = held;
|
|
3119
|
-
const
|
|
2978
|
+
const from = standingOf(record.locator);
|
|
3120
2979
|
const local = await openLocal();
|
|
3121
2980
|
try {
|
|
3122
2981
|
await exclusive(() => local.unrefresh(record.key));
|
|
3123
|
-
const result = await advancement(
|
|
2982
|
+
const result = await advancement(async (authorization, request) => {
|
|
2983
|
+
hold.release();
|
|
2984
|
+
try {
|
|
2985
|
+
return await refresh(authorization, request);
|
|
2986
|
+
}
|
|
2987
|
+
finally {
|
|
2988
|
+
hold.take();
|
|
2989
|
+
}
|
|
2990
|
+
}, authorized, record, (models, kept) => (liveness() ? remodel(record.locator, models, kept) : kept), liveness, refreshingOf(local, record.locator, record.key, composed(record), liveness));
|
|
3124
2991
|
if (result.kind !== 'advanced') {
|
|
3125
2992
|
journal.record(`transfer refresh ${record.locator} ${result.kind}`);
|
|
3126
2993
|
return result;
|
|
3127
2994
|
}
|
|
3128
|
-
const refused = await validatedRoom(local, result.record, 'refreshed', {
|
|
3129
|
-
record,
|
|
3130
|
-
changed: [...result.changed],
|
|
3131
|
-
});
|
|
3132
|
-
if (refused !== null) {
|
|
3133
|
-
journal.record(`transfer refresh ${record.locator} refused`);
|
|
3134
|
-
return refused;
|
|
3135
|
-
}
|
|
3136
2995
|
journal.record(`transfer refresh ${record.locator} ${result.record.roomVersion}`);
|
|
3137
|
-
return {
|
|
2996
|
+
return {
|
|
2997
|
+
kind: 'advanced',
|
|
2998
|
+
record: result.record,
|
|
2999
|
+
from,
|
|
3000
|
+
remodelled: result.record.model !== record.model,
|
|
3001
|
+
};
|
|
3138
3002
|
}
|
|
3139
3003
|
catch (error) {
|
|
3140
3004
|
if (refusedForSpace(error))
|
|
@@ -3142,9 +3006,6 @@ export function composeRuntime(options) {
|
|
|
3142
3006
|
journal.record(`transfer refresh ${record.locator} transport-uncertainty`);
|
|
3143
3007
|
throw error;
|
|
3144
3008
|
}
|
|
3145
|
-
finally {
|
|
3146
|
-
reads.finished(record.locator, turn);
|
|
3147
|
-
}
|
|
3148
3009
|
}
|
|
3149
3010
|
const heldContents = new Map();
|
|
3150
3011
|
function heldContentOf(local, key) {
|
|
@@ -3162,11 +3023,17 @@ export function composeRuntime(options) {
|
|
|
3162
3023
|
heldContents.set(key, made);
|
|
3163
3024
|
return made;
|
|
3164
3025
|
}
|
|
3026
|
+
async function abandoned(local, locator) {
|
|
3027
|
+
await local.abandon(locator);
|
|
3028
|
+
setBack(locator);
|
|
3029
|
+
untransferred(locator);
|
|
3030
|
+
liveQueries.recover();
|
|
3031
|
+
sweep();
|
|
3032
|
+
}
|
|
3165
3033
|
function staging(local, liveness) {
|
|
3166
3034
|
const derived = async (locator, key, composition) => {
|
|
3167
3035
|
underived.delete(locator);
|
|
3168
3036
|
for (;;) {
|
|
3169
|
-
await screened();
|
|
3170
3037
|
if (!liveness())
|
|
3171
3038
|
return false;
|
|
3172
3039
|
const done = await finishingWrite(async () => liveness()
|
|
@@ -3183,19 +3050,13 @@ export function composeRuntime(options) {
|
|
|
3183
3050
|
held: (locator) => transfers.get(locator) ?? null,
|
|
3184
3051
|
async turn(locator, step) {
|
|
3185
3052
|
installing(locator);
|
|
3186
|
-
|
|
3187
|
-
await screened();
|
|
3188
|
-
const release = step === 'downloading' ? await heldStream() : () => { };
|
|
3189
|
-
if (onScreen || closed)
|
|
3190
|
-
return release;
|
|
3191
|
-
release();
|
|
3192
|
-
}
|
|
3053
|
+
return step === 'downloading' ? heldStream() : () => { };
|
|
3193
3054
|
},
|
|
3194
3055
|
unqueued,
|
|
3195
|
-
queued(
|
|
3196
|
-
|
|
3056
|
+
queued(text) {
|
|
3057
|
+
queuedText += text;
|
|
3197
3058
|
return once(() => {
|
|
3198
|
-
|
|
3059
|
+
queuedText -= text;
|
|
3199
3060
|
});
|
|
3200
3061
|
},
|
|
3201
3062
|
arriving(locator) {
|
|
@@ -3204,7 +3065,7 @@ export function composeRuntime(options) {
|
|
|
3204
3065
|
at(bytes) {
|
|
3205
3066
|
if (ended)
|
|
3206
3067
|
return;
|
|
3207
|
-
arrivals.set(locator,
|
|
3068
|
+
arrivals.set(locator, bytes);
|
|
3208
3069
|
streaming.add(locator);
|
|
3209
3070
|
const now = performance.now();
|
|
3210
3071
|
if (now - arrivalReported < ARRIVAL_REPORT_MS)
|
|
@@ -3212,14 +3073,6 @@ export function composeRuntime(options) {
|
|
|
3212
3073
|
arrivalReported = now;
|
|
3213
3074
|
queueReadiness();
|
|
3214
3075
|
},
|
|
3215
|
-
part() {
|
|
3216
|
-
const arrived = arrivals.get(locator);
|
|
3217
|
-
arrivals.set(locator, {
|
|
3218
|
-
parts: (arrived?.parts ?? 0) + 1,
|
|
3219
|
-
bytes: arrived?.bytes ?? 0,
|
|
3220
|
-
});
|
|
3221
|
-
queueReadiness();
|
|
3222
|
-
},
|
|
3223
3076
|
end() {
|
|
3224
3077
|
if (ended)
|
|
3225
3078
|
return;
|
|
@@ -3229,14 +3082,10 @@ export function composeRuntime(options) {
|
|
|
3229
3082
|
},
|
|
3230
3083
|
};
|
|
3231
3084
|
},
|
|
3232
|
-
|
|
3233
|
-
const later = announced.get(locator);
|
|
3234
|
-
return later === undefined ? [] : [later];
|
|
3235
|
-
},
|
|
3085
|
+
streamed: options.adapter.roomStream !== undefined,
|
|
3236
3086
|
async stage(record, page, composition) {
|
|
3237
3087
|
const { locator } = record;
|
|
3238
3088
|
installing(locator);
|
|
3239
|
-
await screened();
|
|
3240
3089
|
if (!liveness())
|
|
3241
3090
|
return null;
|
|
3242
3091
|
const key = await installWrite(async () => {
|
|
@@ -3265,19 +3114,16 @@ export function composeRuntime(options) {
|
|
|
3265
3114
|
derive: derived,
|
|
3266
3115
|
async refreshing(locator, key, composition) {
|
|
3267
3116
|
await exclusive(() => local.unrefresh(key));
|
|
3268
|
-
return refreshingOf(local, key, composition, liveness,
|
|
3117
|
+
return refreshingOf(local, locator, key, composition, liveness, true);
|
|
3269
3118
|
},
|
|
3270
3119
|
abandon: (locator) => exclusive(async () => {
|
|
3271
|
-
if (
|
|
3272
|
-
|
|
3273
|
-
await local.abandon(locator);
|
|
3274
|
-
untransferred(locator);
|
|
3275
|
-
liveQueries.recover();
|
|
3276
|
-
sweep();
|
|
3120
|
+
if (liveness())
|
|
3121
|
+
await abandoned(local, locator);
|
|
3277
3122
|
}),
|
|
3278
3123
|
setAside: async (locator) => {
|
|
3279
3124
|
if (!liveness())
|
|
3280
3125
|
return;
|
|
3126
|
+
setBack(locator);
|
|
3281
3127
|
untransferred(locator);
|
|
3282
3128
|
liveQueries.recover();
|
|
3283
3129
|
},
|
|
@@ -3286,19 +3132,6 @@ export function composeRuntime(options) {
|
|
|
3286
3132
|
sweep();
|
|
3287
3133
|
}),
|
|
3288
3134
|
content: (key) => heldContentOf(local, key),
|
|
3289
|
-
async validate(record, view) {
|
|
3290
|
-
const { locator } = record;
|
|
3291
|
-
const refused = await validatedRoom(local, record, view, null, null, (text) => {
|
|
3292
|
-
const check = unchecked.get(locator);
|
|
3293
|
-
if (check === undefined)
|
|
3294
|
-
return;
|
|
3295
|
-
unchecked.set(locator, { ...check, done: check.done + text });
|
|
3296
|
-
queueReadiness();
|
|
3297
|
-
});
|
|
3298
|
-
unchecked.delete(locator);
|
|
3299
|
-
queueReadiness();
|
|
3300
|
-
return refused;
|
|
3301
|
-
},
|
|
3302
3135
|
};
|
|
3303
3136
|
}
|
|
3304
3137
|
function reusable(room, local) {
|
|
@@ -3310,15 +3143,21 @@ export function composeRuntime(options) {
|
|
|
3310
3143
|
async function pullBootstrap(authorized, room, local, liveness) {
|
|
3311
3144
|
if (!liveness())
|
|
3312
3145
|
return unconfirmed;
|
|
3313
|
-
const turn = generation;
|
|
3314
3146
|
if (replicas.has(room.locator)) {
|
|
3315
3147
|
catching.add(room.locator);
|
|
3316
3148
|
liveQueries.recover();
|
|
3317
3149
|
}
|
|
3318
3150
|
try {
|
|
3319
|
-
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));
|
|
3151
|
+
const result = await bootstrapRoom(reads, authorized, room, { collection: modelOf(room.locator) }, builtIn, typedPaths, (models, held) => (liveness() ? remodel(room.locator, models, held) : held), liveness, staging(local, liveness), reusable(room, local));
|
|
3320
3152
|
const detail = result.kind === 'staged' ? result.record.roomVersion : result.kind;
|
|
3321
3153
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
3154
|
+
if (result.kind === 'staged' && older(room.locator, result.record.roomVersion)) {
|
|
3155
|
+
caughtUpTo(room.locator);
|
|
3156
|
+
installed(room.locator);
|
|
3157
|
+
await abandoned(local, room.locator);
|
|
3158
|
+
journal.record(`transfer bootstrap ${room.locator} older than shown`);
|
|
3159
|
+
return { kind: 'transport-uncertainty' };
|
|
3160
|
+
}
|
|
3322
3161
|
if (result.kind !== 'staged') {
|
|
3323
3162
|
caughtUpTo(room.locator);
|
|
3324
3163
|
installed(room.locator);
|
|
@@ -3333,9 +3172,6 @@ export function composeRuntime(options) {
|
|
|
3333
3172
|
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
3334
3173
|
throw error;
|
|
3335
3174
|
}
|
|
3336
|
-
finally {
|
|
3337
|
-
reads.finished(room.locator, turn);
|
|
3338
|
-
}
|
|
3339
3175
|
}
|
|
3340
3176
|
function caughtUpTo(locator) {
|
|
3341
3177
|
if (catching.delete(locator))
|
|
@@ -3343,72 +3179,83 @@ export function composeRuntime(options) {
|
|
|
3343
3179
|
}
|
|
3344
3180
|
function behind(room) {
|
|
3345
3181
|
const held = replicas.get(room.locator)?.record.roomVersion;
|
|
3346
|
-
return
|
|
3347
|
-
[announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later)));
|
|
3182
|
+
return held !== undefined && !options.adapter.reached(room.locator, held, room.roomVersion);
|
|
3348
3183
|
}
|
|
3349
|
-
|
|
3350
|
-
const
|
|
3351
|
-
|
|
3352
|
-
if (withheld !== null || reads.carried(room.locator) === carried || !behind(room)) {
|
|
3353
|
-
return withheld;
|
|
3354
|
-
}
|
|
3355
|
-
return advanceOnce(room, local, authorized, live);
|
|
3184
|
+
function standingOf(locator) {
|
|
3185
|
+
const held = replicas.get(locator);
|
|
3186
|
+
return { key: held?.record.key ?? null, confirmed: held?.confirmed ?? 0 };
|
|
3356
3187
|
}
|
|
3357
|
-
|
|
3188
|
+
function stands(locator, from) {
|
|
3189
|
+
const now = standingOf(locator);
|
|
3190
|
+
return now.key === from.key && now.confirmed === from.confirmed;
|
|
3191
|
+
}
|
|
3192
|
+
async function advance(room, local, authorized, running) {
|
|
3358
3193
|
const live = () => running() && !full;
|
|
3359
3194
|
if (!live())
|
|
3360
3195
|
return unconfirmed;
|
|
3361
3196
|
const held = replicas.get(room.locator);
|
|
3362
|
-
if (held !== undefined) {
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
emitRooms();
|
|
3374
|
-
}
|
|
3375
|
-
return null;
|
|
3376
|
-
}
|
|
3377
|
-
if (refreshed.kind === 'advanced') {
|
|
3378
|
-
const landing = { kind: 'refreshed', base: refreshed.base };
|
|
3379
|
-
if (!(await publish(local, room, refreshed.record, landing, live))) {
|
|
3380
|
-
const now = replicas.get(room.locator)?.record.roomVersion;
|
|
3381
|
-
const reached = now !== undefined &&
|
|
3382
|
-
options.adapter.reached(room.locator, now, refreshed.record.roomVersion);
|
|
3383
|
-
return reached ? null : advance(room, local, authorized, live);
|
|
3384
|
-
}
|
|
3385
|
-
await retainModel(local, room, modelOf(room.locator));
|
|
3386
|
-
return null;
|
|
3387
|
-
}
|
|
3388
|
-
if (refreshed.kind !== 'beyond-horizon')
|
|
3197
|
+
if (held !== undefined && options.adapter.roomStream !== undefined) {
|
|
3198
|
+
stale.delete(room.locator);
|
|
3199
|
+
name(room.locator);
|
|
3200
|
+
return null;
|
|
3201
|
+
}
|
|
3202
|
+
const refresh = reads.refresh;
|
|
3203
|
+
if (held !== undefined && refresh !== undefined) {
|
|
3204
|
+
const refreshed = await ahead((hold) => refreshedRoom(room, local, authorized, held, live, hold, refresh));
|
|
3205
|
+
if (refreshed === 'behind')
|
|
3206
|
+
return advance(room, local, authorized, live);
|
|
3207
|
+
if (refreshed !== 'beyond-horizon')
|
|
3389
3208
|
return refreshed;
|
|
3390
|
-
if (
|
|
3391
|
-
owed.add(room.locator);
|
|
3392
|
-
liveQueries.recover();
|
|
3393
|
-
return unconfirmed;
|
|
3394
|
-
}
|
|
3395
|
-
if (reloadsDue.get(room.locator) === generation)
|
|
3209
|
+
if (reloadsDue.has(room.locator))
|
|
3396
3210
|
return unconfirmed;
|
|
3211
|
+
replaced([room.locator]);
|
|
3397
3212
|
}
|
|
3213
|
+
const from = standingOf(room.locator);
|
|
3398
3214
|
const bootstrapped = await pullBootstrap(authorized, room, local, live);
|
|
3399
|
-
if (bootstrapped.kind
|
|
3400
|
-
|
|
3215
|
+
if (bootstrapped.kind !== 'staged')
|
|
3216
|
+
return live() ? bootstrapped : unconfirmed;
|
|
3217
|
+
const landing = { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from };
|
|
3218
|
+
if (await publish(local, room, bootstrapped.record, landing, live)) {
|
|
3401
3219
|
return live() ? null : unconfirmed;
|
|
3402
3220
|
}
|
|
3403
|
-
return
|
|
3221
|
+
return reachedBy(room.locator, bootstrapped.record.roomVersion)
|
|
3222
|
+
? null
|
|
3223
|
+
: advance(room, local, authorized, live);
|
|
3224
|
+
}
|
|
3225
|
+
async function refreshedRoom(room, local, authorized, held, live, hold, refresh) {
|
|
3226
|
+
const refreshed = await pullRefresh(authorized, held, live, hold, refresh);
|
|
3227
|
+
if (!live())
|
|
3228
|
+
return unconfirmed;
|
|
3229
|
+
if (refreshed.kind === 'still-current') {
|
|
3230
|
+
stale.delete(room.locator);
|
|
3231
|
+
if (unreadable.delete(room.locator)) {
|
|
3232
|
+
scopedRefusals.delete(room.locator);
|
|
3233
|
+
list(room, true);
|
|
3234
|
+
emitRooms();
|
|
3235
|
+
}
|
|
3236
|
+
return null;
|
|
3237
|
+
}
|
|
3238
|
+
if (refreshed.kind === 'advanced') {
|
|
3239
|
+
const landing = {
|
|
3240
|
+
kind: 'refreshed',
|
|
3241
|
+
from: refreshed.from,
|
|
3242
|
+
remodelled: refreshed.remodelled,
|
|
3243
|
+
};
|
|
3244
|
+
if (!(await publish(local, room, refreshed.record, landing, live))) {
|
|
3245
|
+
const now = replicas.get(room.locator)?.record.roomVersion;
|
|
3246
|
+
const reached = now !== undefined &&
|
|
3247
|
+
options.adapter.reached(room.locator, now, refreshed.record.roomVersion);
|
|
3248
|
+
return reached ? null : 'behind';
|
|
3249
|
+
}
|
|
3250
|
+
await retainModel(local, room, modelOf(room.locator));
|
|
3251
|
+
return null;
|
|
3252
|
+
}
|
|
3253
|
+
return refreshed.kind === 'beyond-horizon' ? 'beyond-horizon' : refreshed;
|
|
3404
3254
|
}
|
|
3405
3255
|
async function withhold(withheld, room, local, progress) {
|
|
3406
|
-
if (withheld.kind === 'unconfirmed')
|
|
3256
|
+
if (withheld.kind === 'unconfirmed' || isFenced(room.locator))
|
|
3407
3257
|
return;
|
|
3408
|
-
|
|
3409
|
-
stale.delete(room.locator);
|
|
3410
|
-
else
|
|
3411
|
-
stale.add(room.locator);
|
|
3258
|
+
stale.add(room.locator);
|
|
3412
3259
|
if (withheld.kind === 'transport-uncertainty' || withheld.kind === 'retryable-refusal') {
|
|
3413
3260
|
const reset = withheld.kind === 'retryable-refusal' ? withheld.reset : undefined;
|
|
3414
3261
|
repairable.set(room.locator, reset);
|
|
@@ -3430,33 +3277,11 @@ export function composeRuntime(options) {
|
|
|
3430
3277
|
list(room, false);
|
|
3431
3278
|
emitRooms();
|
|
3432
3279
|
}
|
|
3433
|
-
else if (withheld.kind === 'refused') {
|
|
3434
|
-
refusedSource(room, withheld);
|
|
3435
|
-
}
|
|
3436
3280
|
else if (withheld.kind === 'revoked-authorization') {
|
|
3437
3281
|
await refuse('revoked-authorization');
|
|
3438
3282
|
}
|
|
3439
3283
|
}
|
|
3440
|
-
function refusedSource(room, { refusal }) {
|
|
3441
|
-
sourceRefusals.set(room.locator, {
|
|
3442
|
-
code: refusal.rule,
|
|
3443
|
-
retryable: false,
|
|
3444
|
-
fields: {
|
|
3445
|
-
file: refusal.file,
|
|
3446
|
-
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
3447
|
-
...(refusal.referrers === undefined ? {} : { referrers: refusal.referrers }),
|
|
3448
|
-
},
|
|
3449
|
-
});
|
|
3450
|
-
declarationRefusals.delete(room.locator);
|
|
3451
|
-
unreadable.add(room.locator);
|
|
3452
|
-
list(room, false);
|
|
3453
|
-
emitRooms();
|
|
3454
|
-
moveTo({ ...status, ...attention() });
|
|
3455
|
-
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
3456
|
-
}
|
|
3457
3284
|
function leaving(batch, result) {
|
|
3458
|
-
if (result.kind === 'expired')
|
|
3459
|
-
return { batch, returned: returnedBy('expired') };
|
|
3460
3285
|
return {
|
|
3461
3286
|
batch,
|
|
3462
3287
|
returned: {
|
|
@@ -3475,8 +3300,8 @@ export function composeRuntime(options) {
|
|
|
3475
3300
|
function settling(acceptance) {
|
|
3476
3301
|
return acceptance !== null && 'apply' in acceptance;
|
|
3477
3302
|
}
|
|
3478
|
-
function
|
|
3479
|
-
return settling(acceptance) ?
|
|
3303
|
+
function failureOf(acceptance) {
|
|
3304
|
+
return settling(acceptance) ? null : acceptance;
|
|
3480
3305
|
}
|
|
3481
3306
|
async function acceptance(room, local, submitted, accepted) {
|
|
3482
3307
|
if (!continuing())
|
|
@@ -3489,7 +3314,8 @@ export function composeRuntime(options) {
|
|
|
3489
3314
|
logAccepted(room.locator, submitted, accepted);
|
|
3490
3315
|
let record = current.record;
|
|
3491
3316
|
let changes;
|
|
3492
|
-
if (!options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
3317
|
+
if (!options.adapter.reached(room.locator, held, accepted.roomVersion) &&
|
|
3318
|
+
!older(room.locator, accepted.roomVersion)) {
|
|
3493
3319
|
const paths = [...returnedPaths(submitted, accepted)];
|
|
3494
3320
|
const confirmedLayer = await stepped(slicing(await local.entries(record.key, 'confirmed', paths), paths));
|
|
3495
3321
|
const back = await stepped(returning(confirmedLayer, submitted, accepted, accepted.roomVersion));
|
|
@@ -3499,34 +3325,6 @@ export function composeRuntime(options) {
|
|
|
3499
3325
|
? accepted.roomVersion
|
|
3500
3326
|
: held,
|
|
3501
3327
|
};
|
|
3502
|
-
const overrides = new Map([
|
|
3503
|
-
...back.set.map((entry) => [entry.path, entry]),
|
|
3504
|
-
...back.removed.map((path) => [path, null]),
|
|
3505
|
-
]);
|
|
3506
|
-
const refused = await validatedRoom(local, record, 'confirmed', { record: current.record, changed: [...overrides.keys()] }, overrides);
|
|
3507
|
-
if (refused !== null) {
|
|
3508
|
-
const key = pendingKey(room.locator, submitted.id);
|
|
3509
|
-
return {
|
|
3510
|
-
locator: room.locator,
|
|
3511
|
-
left: [],
|
|
3512
|
-
commit: {
|
|
3513
|
-
rooms: [],
|
|
3514
|
-
writes: [
|
|
3515
|
-
{
|
|
3516
|
-
locator: room.locator,
|
|
3517
|
-
accepted: [{ id: submitted.id, roomVersion: accepted.roomVersion }],
|
|
3518
|
-
},
|
|
3519
|
-
],
|
|
3520
|
-
},
|
|
3521
|
-
refused,
|
|
3522
|
-
apply() {
|
|
3523
|
-
const state = pendings.get(key);
|
|
3524
|
-
if (state !== undefined && !fences.has(room.locator))
|
|
3525
|
-
pendings.set(key, { ...state, accepted: accepted.roomVersion });
|
|
3526
|
-
return changeSet(room.locator, { queue: true });
|
|
3527
|
-
},
|
|
3528
|
-
};
|
|
3529
|
-
}
|
|
3530
3328
|
changes = back;
|
|
3531
3329
|
}
|
|
3532
3330
|
const settled = await settlement(local, room.locator, current, record, current.queue.filter((batch) => batch.id !== submitted.id), { room: true, ...(changes === undefined ? {} : { changes }) });
|
|
@@ -3534,10 +3332,9 @@ export function composeRuntime(options) {
|
|
|
3534
3332
|
...settled,
|
|
3535
3333
|
apply(answer) {
|
|
3536
3334
|
const set = settled.apply(answer);
|
|
3335
|
+
showing(room.locator, accepted.roomVersion);
|
|
3537
3336
|
pendings.delete(pendingKey(room.locator, submitted.id));
|
|
3538
3337
|
unreadable.delete(room.locator);
|
|
3539
|
-
sourceRefusals.delete(room.locator);
|
|
3540
|
-
declarationRefusals.delete(room.locator);
|
|
3541
3338
|
list(room, true);
|
|
3542
3339
|
return set;
|
|
3543
3340
|
},
|
|
@@ -3555,13 +3352,9 @@ export function composeRuntime(options) {
|
|
|
3555
3352
|
const failed = await exclusive(async () => {
|
|
3556
3353
|
const adopting = await acceptance(room, local, submitted, accepted);
|
|
3557
3354
|
await adoptedTogether(local, [adopting]);
|
|
3558
|
-
return
|
|
3355
|
+
return failureOf(adopting);
|
|
3559
3356
|
});
|
|
3560
|
-
|
|
3561
|
-
return true;
|
|
3562
|
-
if (failed.kind === 'refused' && continuing())
|
|
3563
|
-
refusedSource(room, failed);
|
|
3564
|
-
return false;
|
|
3357
|
+
return failed === null;
|
|
3565
3358
|
}
|
|
3566
3359
|
async function adoptedMove(room, partner, local, submitted, accepted) {
|
|
3567
3360
|
const other = discovered.get(partner);
|
|
@@ -3575,18 +3368,10 @@ export function composeRuntime(options) {
|
|
|
3575
3368
|
...accepted.destination,
|
|
3576
3369
|
});
|
|
3577
3370
|
await adoptedTogether(local, [left, arrived]);
|
|
3578
|
-
|
|
3579
|
-
if (other !== undefined && refusedArrival?.kind === 'refused' && continuing()) {
|
|
3580
|
-
refusedSource(other, refusedArrival);
|
|
3581
|
-
}
|
|
3582
|
-
return refusalOf(left);
|
|
3371
|
+
return failureOf(left);
|
|
3583
3372
|
});
|
|
3584
3373
|
submitLater(partner);
|
|
3585
|
-
|
|
3586
|
-
return true;
|
|
3587
|
-
if (failed.kind === 'refused' && continuing())
|
|
3588
|
-
refusedSource(room, failed);
|
|
3589
|
-
return false;
|
|
3374
|
+
return failed === null;
|
|
3590
3375
|
}
|
|
3591
3376
|
function queueIn(locator) {
|
|
3592
3377
|
return replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
@@ -3697,20 +3482,13 @@ export function composeRuntime(options) {
|
|
|
3697
3482
|
return;
|
|
3698
3483
|
moveTo({ ...status, ...attention() });
|
|
3699
3484
|
}
|
|
3700
|
-
async function discardHeld(head, room, local, signal) {
|
|
3701
|
-
const partner = partnerOf(head, room.locator);
|
|
3702
|
-
const other = partner === null ? undefined : discovered.get(partner);
|
|
3703
|
-
await discardPending(other === undefined ? [room] : [room, other], local, signal);
|
|
3704
|
-
}
|
|
3705
3485
|
async function submitRoom(room, local, authorized, progress, signal) {
|
|
3706
3486
|
while (continuing() && authorization !== undefined && !signal.aborted) {
|
|
3707
3487
|
const replica = replicas.get(room.locator);
|
|
3708
|
-
const head = replica?.queue
|
|
3488
|
+
const head = replica?.queue.find((batch) => !landedAhead.has(pendingKey(room.locator, batch.id)));
|
|
3709
3489
|
if (replica === undefined || head === undefined)
|
|
3710
3490
|
return 'done';
|
|
3711
3491
|
const state = pendings.get(pendingKey(room.locator, head.id));
|
|
3712
|
-
if (state?.accepted !== undefined)
|
|
3713
|
-
return 'done';
|
|
3714
3492
|
const partner = partnerOf(head, room.locator);
|
|
3715
3493
|
if (partner !== null && !leavingIn(head, room.locator)) {
|
|
3716
3494
|
submitLater(partner);
|
|
@@ -3734,10 +3512,6 @@ export function composeRuntime(options) {
|
|
|
3734
3512
|
return 'done';
|
|
3735
3513
|
}
|
|
3736
3514
|
const lost = earlier || submission.state === 'submitted';
|
|
3737
|
-
if (lost && pastHorizon(head.id)) {
|
|
3738
|
-
await discardHeld(head, room, local, signal);
|
|
3739
|
-
return 'done';
|
|
3740
|
-
}
|
|
3741
3515
|
let result;
|
|
3742
3516
|
journal.record(`submission ${room.locator} ${head.id}`);
|
|
3743
3517
|
await exclusive(() => mark(local, room.locator, head, { state: 'submitted' }, lost));
|
|
@@ -3815,32 +3589,12 @@ export function composeRuntime(options) {
|
|
|
3815
3589
|
const other = partner === null ? undefined : discovered.get(partner);
|
|
3816
3590
|
if (result.kind === 'conflicted' ||
|
|
3817
3591
|
result.kind === 'rejected' ||
|
|
3818
|
-
result.kind === 'expired' ||
|
|
3819
3592
|
result.kind === 'retryable-refusal') {
|
|
3820
|
-
if (result.kind === 'expired' && lost && result.restored !== true) {
|
|
3821
|
-
journal.record(`outcome ${room.locator} ${head.id} expired`);
|
|
3822
|
-
await discardHeld(head, room, local, signal);
|
|
3823
|
-
return 'done';
|
|
3824
|
-
}
|
|
3825
|
-
if (result.kind === 'expired') {
|
|
3826
|
-
await exclusive(() => mark(local, room.locator, head, { state: 'waiting' }, partner !== null && lost));
|
|
3827
|
-
const there = other === undefined ? null : await advance(other, local, authorized, continuing);
|
|
3828
|
-
if (other !== undefined && there !== null) {
|
|
3829
|
-
await withhold(there, other, local, progress);
|
|
3830
|
-
return 'done';
|
|
3831
|
-
}
|
|
3832
|
-
const withheld = await advance(room, local, authorized, continuing);
|
|
3833
|
-
if (withheld !== null) {
|
|
3834
|
-
await withhold(withheld, room, local, progress);
|
|
3835
|
-
return 'done';
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
3593
|
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` +
|
|
3839
3594
|
(result.kind === 'conflicted' && result.roomVersion !== undefined
|
|
3840
3595
|
? ` ${result.roomVersion}`
|
|
3841
3596
|
: ''));
|
|
3842
|
-
|
|
3843
|
-
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
3597
|
+
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
3844
3598
|
}
|
|
3845
3599
|
if (result.kind === 'retryable-refusal') {
|
|
3846
3600
|
const reset = result.reset;
|
|
@@ -3848,11 +3602,11 @@ export function composeRuntime(options) {
|
|
|
3848
3602
|
await exclusive(() => mark(local, room.locator, head, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
3849
3603
|
return reset === undefined ? 'again' : { at: Date.parse(reset) };
|
|
3850
3604
|
}
|
|
3851
|
-
if (result.kind === 'conflicted' || result.kind === 'rejected'
|
|
3605
|
+
if (result.kind === 'conflicted' || result.kind === 'rejected') {
|
|
3852
3606
|
const left = leaving(head, result);
|
|
3853
3607
|
const refreshed = async () => ({
|
|
3854
|
-
there: other === undefined ? null : await
|
|
3855
|
-
here: await
|
|
3608
|
+
there: other === undefined ? null : await advanced(other, local, authorized),
|
|
3609
|
+
here: await advanced(room, local, authorized),
|
|
3856
3610
|
});
|
|
3857
3611
|
const judged = result.kind === 'conflicted' && other !== undefined ? await refreshed() : null;
|
|
3858
3612
|
if (judged !== null && (judged.there !== null || judged.here !== null)) {
|
|
@@ -3872,16 +3626,19 @@ export function composeRuntime(options) {
|
|
|
3872
3626
|
return;
|
|
3873
3627
|
}
|
|
3874
3628
|
const confirmedLayer = await sliceAt(current.record, 'confirmed', [head]);
|
|
3875
|
-
const held =
|
|
3876
|
-
(await landedWhole(room.locator, head, confirmedLayer))
|
|
3877
|
-
|
|
3878
|
-
|
|
3629
|
+
const held = result.kind === 'conflicted' &&
|
|
3630
|
+
(await landedWhole(room.locator, head, confirmedLayer, result.affected));
|
|
3631
|
+
if (held &&
|
|
3632
|
+
result.kind === 'conflicted' &&
|
|
3633
|
+
partner === null &&
|
|
3634
|
+
!landed(head, confirmedLayer)) {
|
|
3635
|
+
landedAhead.set(pendingKey(room.locator, head.id), Object.fromEntries([...touchedBy([head])].map((path) => [path, confirmedLayer.revisions[path]])));
|
|
3636
|
+
return;
|
|
3637
|
+
}
|
|
3879
3638
|
publishChangeSet(await settle(local, room.locator, current, current.record, current.queue.filter((batch) => batch.id !== head.id), { leaving: [held ? { ...left, returned: null } : left] }));
|
|
3880
3639
|
await unpair(local);
|
|
3881
3640
|
moveTo({ ...status, ...attention() });
|
|
3882
3641
|
});
|
|
3883
|
-
if (result.kind === 'expired')
|
|
3884
|
-
continue;
|
|
3885
3642
|
const { there, here } = judged ?? (await refreshed());
|
|
3886
3643
|
if (other !== undefined && there !== null)
|
|
3887
3644
|
await withhold(there, other, local, progress);
|
|
@@ -3901,80 +3658,16 @@ export function composeRuntime(options) {
|
|
|
3901
3658
|
}
|
|
3902
3659
|
return 'done';
|
|
3903
3660
|
}
|
|
3904
|
-
function pastHorizon(identity) {
|
|
3905
|
-
return (outcomeRetention !== null && identityTime(identity) + outcomeRetention * 1000 <= Date.now());
|
|
3906
|
-
}
|
|
3907
|
-
async function discardPending(rooms, local, signal) {
|
|
3908
|
-
const discarded = await exclusive(async () => {
|
|
3909
|
-
const held = rooms
|
|
3910
|
-
.filter(({ locator }) => !fences.has(locator))
|
|
3911
|
-
.map((room) => ({
|
|
3912
|
-
room,
|
|
3913
|
-
before: replicas.get(room.locator),
|
|
3914
|
-
queue: queueIn(room.locator),
|
|
3915
|
-
}));
|
|
3916
|
-
if (held.length === 0)
|
|
3917
|
-
return [];
|
|
3918
|
-
const answer = await local.commit({
|
|
3919
|
-
rooms: held.flatMap(({ room, before }) => before === undefined
|
|
3920
|
-
? []
|
|
3921
|
-
: [
|
|
3922
|
-
{
|
|
3923
|
-
locator: room.locator,
|
|
3924
|
-
key: before.record.key,
|
|
3925
|
-
roots: before.record.typedSelection?.roots ?? [],
|
|
3926
|
-
composed: composedOf(composed(before.record)),
|
|
3927
|
-
overlay: { set: [], cleared: [...before.overlaid] },
|
|
3928
|
-
},
|
|
3929
|
-
]),
|
|
3930
|
-
discards: held.map(({ room }) => room.locator),
|
|
3931
|
-
});
|
|
3932
|
-
for (const { room, before, queue } of held) {
|
|
3933
|
-
const { locator } = room;
|
|
3934
|
-
lastFenced.set(locator, ++fencings);
|
|
3935
|
-
for (const batch of queue)
|
|
3936
|
-
pendings.delete(pendingKey(locator, batch.id));
|
|
3937
|
-
queuesWithoutReplica.delete(locator);
|
|
3938
|
-
if (before !== undefined) {
|
|
3939
|
-
replicas.set(locator, {
|
|
3940
|
-
...before,
|
|
3941
|
-
queue: [],
|
|
3942
|
-
overlaid: new Set(),
|
|
3943
|
-
count: answer.counts[locator] ?? before.count,
|
|
3944
|
-
});
|
|
3945
|
-
}
|
|
3946
|
-
journal.record(`room discarded ${locator}`);
|
|
3947
|
-
}
|
|
3948
|
-
publishChangeSet(mergeChangeSets(held.map(({ room }) => changeSet(room.locator, {
|
|
3949
|
-
documents: answer.changed[room.locator] ?? [],
|
|
3950
|
-
queue: true,
|
|
3951
|
-
}))));
|
|
3952
|
-
moveTo({ ...status, ...attention() });
|
|
3953
|
-
return held.map(({ room }) => room);
|
|
3954
|
-
});
|
|
3955
|
-
for (const room of discarded) {
|
|
3956
|
-
if (signal.aborted)
|
|
3957
|
-
return;
|
|
3958
|
-
const progress = { complete: true, reachable: true };
|
|
3959
|
-
await roomTask(room.locator, async (live) => {
|
|
3960
|
-
await Promise.race([
|
|
3961
|
-
reload(room, local, progress, () => live() && !signal.aborted),
|
|
3962
|
-
abandonedOn(signal),
|
|
3963
|
-
]);
|
|
3964
|
-
});
|
|
3965
|
-
if (signal.aborted)
|
|
3966
|
-
return;
|
|
3967
|
-
await rest(progress);
|
|
3968
|
-
}
|
|
3969
|
-
}
|
|
3970
3661
|
function awaitingAnswer(locator) {
|
|
3662
|
+
answering.get(locator)?.release();
|
|
3971
3663
|
let release = () => { };
|
|
3972
|
-
const
|
|
3664
|
+
const answered = new Promise((resolve) => {
|
|
3973
3665
|
release = resolve;
|
|
3974
3666
|
});
|
|
3975
|
-
|
|
3667
|
+
const awaited = { answered, release };
|
|
3668
|
+
answering.set(locator, awaited);
|
|
3976
3669
|
return () => {
|
|
3977
|
-
if (answering.get(locator)
|
|
3670
|
+
if (answering.get(locator) === awaited)
|
|
3978
3671
|
answering.delete(locator);
|
|
3979
3672
|
release();
|
|
3980
3673
|
};
|
|
@@ -3986,6 +3679,30 @@ export function composeRuntime(options) {
|
|
|
3986
3679
|
JSON.stringify(held.replica) === JSON.stringify(room.replica) &&
|
|
3987
3680
|
JSON.stringify(held.typedSelection) === JSON.stringify(room.typedSelection ?? null)));
|
|
3988
3681
|
}
|
|
3682
|
+
async function keepNaming(local, room) {
|
|
3683
|
+
const held = replicas.get(room.locator)?.record;
|
|
3684
|
+
if (held === undefined || (held.name === room.name && held.handle === room.handle))
|
|
3685
|
+
return;
|
|
3686
|
+
await exclusive(async () => {
|
|
3687
|
+
if (!continuing())
|
|
3688
|
+
return;
|
|
3689
|
+
const replica = replicas.get(room.locator);
|
|
3690
|
+
if (replica === undefined)
|
|
3691
|
+
return;
|
|
3692
|
+
const record = { ...replica.record, name: room.name, handle: room.handle };
|
|
3693
|
+
await local.commit({
|
|
3694
|
+
rooms: [
|
|
3695
|
+
{
|
|
3696
|
+
locator: room.locator,
|
|
3697
|
+
key: record.key,
|
|
3698
|
+
record: storedRecord(record),
|
|
3699
|
+
composed: composedOf(composed(record)),
|
|
3700
|
+
},
|
|
3701
|
+
],
|
|
3702
|
+
});
|
|
3703
|
+
replicas.set(room.locator, { ...replica, record });
|
|
3704
|
+
});
|
|
3705
|
+
}
|
|
3989
3706
|
async function retainModel(local, room, next) {
|
|
3990
3707
|
const key = room.locator;
|
|
3991
3708
|
const previous = modelOf(room.locator);
|
|
@@ -4001,26 +3718,15 @@ export function composeRuntime(options) {
|
|
|
4001
3718
|
const held = replicas.get(room.locator);
|
|
4002
3719
|
let changed = null;
|
|
4003
3720
|
if (held !== undefined) {
|
|
4004
|
-
const
|
|
3721
|
+
const record = {
|
|
4005
3722
|
...held.record,
|
|
3723
|
+
name: room.name,
|
|
3724
|
+
handle: room.handle,
|
|
4006
3725
|
operations: room.operations,
|
|
4007
3726
|
replica: room.replica,
|
|
4008
3727
|
typedSelection: room.typedSelection ?? null,
|
|
4009
3728
|
model: next,
|
|
4010
3729
|
};
|
|
4011
|
-
let read = 'staged';
|
|
4012
|
-
if (!sameMembership(declared, held.record)) {
|
|
4013
|
-
const verdict = await validatedCollection(walkOf(local, held.record.key, 'confirmed'), lookupOf(local, held.record.key, 'confirmed'), composed(declared));
|
|
4014
|
-
read =
|
|
4015
|
-
verdict.verdict !== 'refuse'
|
|
4016
|
-
? 'staged'
|
|
4017
|
-
: verdict.rule === 'model-unavailable'
|
|
4018
|
-
? 'deferred'
|
|
4019
|
-
: { kind: 'refused', refusal: verdict };
|
|
4020
|
-
}
|
|
4021
|
-
const record = read === 'staged'
|
|
4022
|
-
? declared
|
|
4023
|
-
: { ...declared, typedSelection: held.record.typedSelection };
|
|
4024
3730
|
const confirmedLayer = await sliceAt(record, 'confirmed', held.queue, held.overlaid);
|
|
4025
3731
|
const replayedLayer = await replayed(record, confirmedLayer, held.queue);
|
|
4026
3732
|
const diff = await overlayDiff(record, held, replayedLayer.visible, replayedLayer.touched);
|
|
@@ -4042,16 +3748,6 @@ export function composeRuntime(options) {
|
|
|
4042
3748
|
documents: answer.changed[room.locator] ?? [],
|
|
4043
3749
|
room: true,
|
|
4044
3750
|
});
|
|
4045
|
-
if (read === 'staged' || read === 'deferred') {
|
|
4046
|
-
if (declarationRefusals.delete(room.locator)) {
|
|
4047
|
-
sourceRefusals.delete(room.locator);
|
|
4048
|
-
unreadable.delete(room.locator);
|
|
4049
|
-
}
|
|
4050
|
-
}
|
|
4051
|
-
else {
|
|
4052
|
-
refusedSource(room, read);
|
|
4053
|
-
declarationRefusals.add(room.locator);
|
|
4054
|
-
}
|
|
4055
3751
|
}
|
|
4056
3752
|
heldModel(room, next);
|
|
4057
3753
|
if (changed === null)
|
|
@@ -4096,6 +3792,17 @@ export function composeRuntime(options) {
|
|
|
4096
3792
|
emitRooms();
|
|
4097
3793
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
4098
3794
|
}
|
|
3795
|
+
function name(locator) {
|
|
3796
|
+
const stream = options.adapter.roomStream;
|
|
3797
|
+
const record = replicas.get(locator)?.record;
|
|
3798
|
+
const room = discovered.get(locator);
|
|
3799
|
+
if (stream === undefined || record === undefined || room === undefined)
|
|
3800
|
+
return;
|
|
3801
|
+
if (!socketOpen || !handed || record.replica.selection !== room.replica.selection)
|
|
3802
|
+
return;
|
|
3803
|
+
naming += 1;
|
|
3804
|
+
stream.hold([{ locator, roomVersion: record.roomVersion }]);
|
|
3805
|
+
}
|
|
4099
3806
|
function heldRooms(listed) {
|
|
4100
3807
|
return listed.flatMap((room) => {
|
|
4101
3808
|
const record = replicas.get(room.locator)?.record;
|
|
@@ -4113,14 +3820,19 @@ export function composeRuntime(options) {
|
|
|
4113
3820
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
4114
3821
|
});
|
|
4115
3822
|
if (attempt === 'opened') {
|
|
3823
|
+
naming += 1;
|
|
4116
3824
|
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
4117
3825
|
handed = true;
|
|
4118
3826
|
}
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
3827
|
+
for (const room of ready) {
|
|
3828
|
+
if (attempt !== 'unstreamed' && replicas.has(room.locator)) {
|
|
3829
|
+
if (attempt === 'authorized')
|
|
3830
|
+
name(room.locator);
|
|
3831
|
+
continue;
|
|
3832
|
+
}
|
|
3833
|
+
void roomTask(room.locator, (live) => loadRoom(room, local, progress, live)).then(() => rest(progress));
|
|
3834
|
+
}
|
|
3835
|
+
const failed = prepared.find((room) => room.status === 'rejected');
|
|
4124
3836
|
if (failed !== undefined)
|
|
4125
3837
|
throw failed.reason;
|
|
4126
3838
|
}
|
|
@@ -4139,12 +3851,16 @@ export function composeRuntime(options) {
|
|
|
4139
3851
|
}
|
|
4140
3852
|
if (prior !== undefined)
|
|
4141
3853
|
await retainModel(local, room, modelOf(room.locator));
|
|
3854
|
+
if (prior !== undefined)
|
|
3855
|
+
await keepNaming(local, room);
|
|
4142
3856
|
if (discoveryPaused || authorization === undefined)
|
|
4143
3857
|
return false;
|
|
4144
3858
|
return room.replica.admission === 'admitted';
|
|
4145
3859
|
}
|
|
4146
3860
|
async function caughtUp(room, local, progress, live) {
|
|
4147
|
-
if (
|
|
3861
|
+
if (options.adapter.roomStream !== undefined || authorization === undefined)
|
|
3862
|
+
return;
|
|
3863
|
+
if (!live() || !behind(room))
|
|
4148
3864
|
return;
|
|
4149
3865
|
const withheld = await advance(room, local, authorization, live);
|
|
4150
3866
|
if (withheld !== null)
|
|
@@ -4157,6 +3873,8 @@ export function composeRuntime(options) {
|
|
|
4157
3873
|
async function loadRoom(room, local, progress, live) {
|
|
4158
3874
|
if (!live() || authorization === undefined)
|
|
4159
3875
|
return;
|
|
3876
|
+
if (replicas.has(room.locator) && !(await prepareRoom(room, local, progress)))
|
|
3877
|
+
return;
|
|
4160
3878
|
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
4161
3879
|
await reload(room, local, progress, live);
|
|
4162
3880
|
return;
|
|
@@ -4174,20 +3892,53 @@ export function composeRuntime(options) {
|
|
|
4174
3892
|
reloadsDue.delete(room.locator);
|
|
4175
3893
|
if (!live() || authorization === undefined)
|
|
4176
3894
|
return;
|
|
3895
|
+
const from = standingOf(room.locator);
|
|
4177
3896
|
const bootstrapped = await pullBootstrap(authorization, room, local, live);
|
|
4178
3897
|
if (bootstrapped.kind !== 'staged') {
|
|
4179
3898
|
if (live())
|
|
4180
3899
|
await withhold(bootstrapped, room, local, progress);
|
|
4181
3900
|
return;
|
|
4182
3901
|
}
|
|
4183
|
-
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed }, live);
|
|
3902
|
+
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from }, live);
|
|
4184
3903
|
await caughtUp(room, local, progress, live);
|
|
4185
3904
|
}
|
|
4186
3905
|
finally {
|
|
4187
3906
|
caughtUpTo(room.locator);
|
|
4188
3907
|
}
|
|
4189
3908
|
}
|
|
4190
|
-
async function
|
|
3909
|
+
async function asked(authorized) {
|
|
3910
|
+
try {
|
|
3911
|
+
return (await options.adapter.discover?.(authorized)) ?? { kind: 'transport-uncertainty' };
|
|
3912
|
+
}
|
|
3913
|
+
catch {
|
|
3914
|
+
return { kind: 'transport-uncertainty' };
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
function malformed(listed) {
|
|
3918
|
+
for (const room of listed) {
|
|
3919
|
+
if (room.operations === undefined ||
|
|
3920
|
+
!['write', 'delete'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
3921
|
+
room.operations[operation].every((scope) => typeof scope === 'string')) ||
|
|
3922
|
+
room.replica === undefined ||
|
|
3923
|
+
typeof room.replica.selection !== 'string' ||
|
|
3924
|
+
room.replica.selection.length === 0 ||
|
|
3925
|
+
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
3926
|
+
!['locked', 'open'].includes(room.replica.offline)) {
|
|
3927
|
+
return `invalid model or replica declaration ${room.locator}`;
|
|
3928
|
+
}
|
|
3929
|
+
if (!declaredSelection(room))
|
|
3930
|
+
return `invalid typed selection declaration ${room.locator}`;
|
|
3931
|
+
}
|
|
3932
|
+
return null;
|
|
3933
|
+
}
|
|
3934
|
+
async function signIn(local, signedIn) {
|
|
3935
|
+
if (JSON.stringify(signedIn) === JSON.stringify(member))
|
|
3936
|
+
return;
|
|
3937
|
+
await local.fact('member', signedIn);
|
|
3938
|
+
member = signedIn;
|
|
3939
|
+
emitMember();
|
|
3940
|
+
}
|
|
3941
|
+
async function discoverRooms(local, progress, attempt, live, found) {
|
|
4191
3942
|
if (closed || authorization === undefined) {
|
|
4192
3943
|
notifyReady();
|
|
4193
3944
|
return;
|
|
@@ -4195,13 +3946,7 @@ export function composeRuntime(options) {
|
|
|
4195
3946
|
moveTo(transferring(status));
|
|
4196
3947
|
discoveries += 1;
|
|
4197
3948
|
const started = discoveries;
|
|
4198
|
-
|
|
4199
|
-
try {
|
|
4200
|
-
result = await options.adapter.discover(authorization, scope);
|
|
4201
|
-
}
|
|
4202
|
-
catch {
|
|
4203
|
-
result = { kind: 'transport-uncertainty' };
|
|
4204
|
-
}
|
|
3949
|
+
const result = found ?? (await asked(authorization));
|
|
4205
3950
|
if (result.kind === 'discovery')
|
|
4206
3951
|
roomsNamed = true;
|
|
4207
3952
|
if (!live()) {
|
|
@@ -4211,7 +3956,7 @@ export function composeRuntime(options) {
|
|
|
4211
3956
|
}
|
|
4212
3957
|
journal.record(`connection discover ${result.kind}`);
|
|
4213
3958
|
if (result.kind === 'transport-uncertainty') {
|
|
4214
|
-
await
|
|
3959
|
+
await cachedModels(local);
|
|
4215
3960
|
progress.reachable = false;
|
|
4216
3961
|
progress.complete = false;
|
|
4217
3962
|
notifyReady();
|
|
@@ -4232,26 +3977,12 @@ export function composeRuntime(options) {
|
|
|
4232
3977
|
notifyReady();
|
|
4233
3978
|
return;
|
|
4234
3979
|
}
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
room.replica.selection.length === 0 ||
|
|
4242
|
-
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
4243
|
-
!['locked', 'open'].includes(room.replica.offline)) {
|
|
4244
|
-
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
4245
|
-
await refuse('missing-required-guarantee');
|
|
4246
|
-
notifyReady();
|
|
4247
|
-
return;
|
|
4248
|
-
}
|
|
4249
|
-
if (!declaredSelection(room)) {
|
|
4250
|
-
journal.record(`invalid typed selection declaration ${room.locator}`);
|
|
4251
|
-
await refuse('missing-required-guarantee');
|
|
4252
|
-
notifyReady();
|
|
4253
|
-
return;
|
|
4254
|
-
}
|
|
3980
|
+
const invalid = malformed(result.rooms);
|
|
3981
|
+
if (invalid !== null) {
|
|
3982
|
+
journal.record(invalid);
|
|
3983
|
+
await refuse('missing-required-guarantee');
|
|
3984
|
+
notifyReady();
|
|
3985
|
+
return;
|
|
4255
3986
|
}
|
|
4256
3987
|
if (refusal !== null) {
|
|
4257
3988
|
refusal = null;
|
|
@@ -4259,22 +3990,15 @@ export function composeRuntime(options) {
|
|
|
4259
3990
|
}
|
|
4260
3991
|
const previousCapabilities = capabilities;
|
|
4261
3992
|
capabilities = result.capabilities;
|
|
4262
|
-
outcomeRetention = result.retention.outcome;
|
|
4263
3993
|
notifyReady();
|
|
4264
3994
|
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
4265
3995
|
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)) {
|
|
4266
3996
|
limits = result.providerLimits;
|
|
4267
3997
|
await local.fact('providerLimits', limits);
|
|
4268
3998
|
await local.fact('capabilities', capabilities);
|
|
4269
|
-
conversations.declared();
|
|
4270
3999
|
}
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
await local.fact('member', signedIn);
|
|
4274
|
-
member = signedIn;
|
|
4275
|
-
emitMember();
|
|
4276
|
-
}
|
|
4277
|
-
await synchronize(await applyDiscovery(result.rooms, started, result.covered), local, progress, attempt, live);
|
|
4000
|
+
await signIn(local, result.member ?? null);
|
|
4001
|
+
await synchronize(await applyDiscovery(result.rooms, started), local, progress, attempt, live);
|
|
4278
4002
|
}
|
|
4279
4003
|
async function ensureAuthorization() {
|
|
4280
4004
|
if (authorization !== undefined)
|
|
@@ -4289,23 +4013,45 @@ export function composeRuntime(options) {
|
|
|
4289
4013
|
}
|
|
4290
4014
|
}
|
|
4291
4015
|
async function admittedRead(work) {
|
|
4292
|
-
|
|
4293
|
-
|
|
4016
|
+
while (roomReads.free === 0) {
|
|
4017
|
+
await new Promise((admit) => roomReads.waiting.push(admit));
|
|
4294
4018
|
}
|
|
4295
|
-
|
|
4296
|
-
while (reads.free === 0)
|
|
4297
|
-
await new Promise((admit) => reads.waiting.push(admit));
|
|
4298
|
-
reads.free -= 1;
|
|
4019
|
+
roomReads.free -= 1;
|
|
4299
4020
|
try {
|
|
4300
4021
|
await work();
|
|
4301
4022
|
}
|
|
4302
4023
|
finally {
|
|
4303
|
-
|
|
4304
|
-
|
|
4024
|
+
roomReads.free += 1;
|
|
4025
|
+
roomReads.waiting.shift()?.();
|
|
4305
4026
|
}
|
|
4306
4027
|
}
|
|
4028
|
+
function inRoom(locator, work) {
|
|
4029
|
+
roomWork += 1;
|
|
4030
|
+
const ran = (roomLanes.get(locator) ?? Promise.resolve()).then(async () => {
|
|
4031
|
+
try {
|
|
4032
|
+
return await work(continuing);
|
|
4033
|
+
}
|
|
4034
|
+
finally {
|
|
4035
|
+
roomWork -= 1;
|
|
4036
|
+
}
|
|
4037
|
+
});
|
|
4038
|
+
const chain = ran.then(() => undefined, () => undefined);
|
|
4039
|
+
roomLanes.set(locator, chain);
|
|
4040
|
+
working.add(chain);
|
|
4041
|
+
void chain.finally(() => {
|
|
4042
|
+
working.delete(chain);
|
|
4043
|
+
if (roomLanes.get(locator) === chain)
|
|
4044
|
+
roomLanes.delete(locator);
|
|
4045
|
+
});
|
|
4046
|
+
return ran;
|
|
4047
|
+
}
|
|
4307
4048
|
function roomTask(locator, work) {
|
|
4308
|
-
|
|
4049
|
+
if (stopping)
|
|
4050
|
+
return Promise.resolve();
|
|
4051
|
+
return inRoom(locator, (live) => admittedRead(() => work(live))).catch(() => undefined);
|
|
4052
|
+
}
|
|
4053
|
+
function advanced(room, local, authorized) {
|
|
4054
|
+
return inRoom(room.locator, (live) => advance(room, local, authorized, live));
|
|
4309
4055
|
}
|
|
4310
4056
|
function task(lane, work) {
|
|
4311
4057
|
if (stopping)
|
|
@@ -4321,23 +4067,19 @@ export function composeRuntime(options) {
|
|
|
4321
4067
|
void run.finally(() => working.delete(run));
|
|
4322
4068
|
return run;
|
|
4323
4069
|
}
|
|
4324
|
-
function sync(attempt) {
|
|
4070
|
+
function sync(attempt, found) {
|
|
4325
4071
|
return task(OPENING_LANE, async (live) => {
|
|
4326
4072
|
if (!live()) {
|
|
4327
4073
|
readyFromStore();
|
|
4328
4074
|
return;
|
|
4329
4075
|
}
|
|
4330
4076
|
const progress = { complete: true, reachable: true };
|
|
4331
|
-
|
|
4332
|
-
if (attempt === 'opened')
|
|
4333
|
-
opened = generation;
|
|
4334
|
-
underway = attempt === 'opened' ? { generation, progress } : null;
|
|
4077
|
+
underway = attempt === 'opened' ? progress : null;
|
|
4335
4078
|
if (attempt === 'opened')
|
|
4336
4079
|
streamed = progress;
|
|
4337
4080
|
complete = false;
|
|
4338
4081
|
recorded = false;
|
|
4339
4082
|
moveTo(transferring(status));
|
|
4340
|
-
handed = false;
|
|
4341
4083
|
try {
|
|
4342
4084
|
const local = await openLocal();
|
|
4343
4085
|
if (!live())
|
|
@@ -4345,7 +4087,7 @@ export function composeRuntime(options) {
|
|
|
4345
4087
|
await ensureAuthorization();
|
|
4346
4088
|
if (!live())
|
|
4347
4089
|
return;
|
|
4348
|
-
await discoverRooms(local, progress, attempt, live);
|
|
4090
|
+
await discoverRooms(local, progress, attempt, live, found);
|
|
4349
4091
|
}
|
|
4350
4092
|
catch {
|
|
4351
4093
|
progress.complete = false;
|
|
@@ -4353,7 +4095,6 @@ export function composeRuntime(options) {
|
|
|
4353
4095
|
progress.reachable = false;
|
|
4354
4096
|
}
|
|
4355
4097
|
finally {
|
|
4356
|
-
gate.release();
|
|
4357
4098
|
notifyReady();
|
|
4358
4099
|
}
|
|
4359
4100
|
if (!live())
|
|
@@ -4365,35 +4106,59 @@ export function composeRuntime(options) {
|
|
|
4365
4106
|
if (handed || closed || progress.reachable)
|
|
4366
4107
|
return;
|
|
4367
4108
|
underway = null;
|
|
4368
|
-
opened = null;
|
|
4369
|
-
journal.record('connection reopen');
|
|
4370
4109
|
await rest(progress, live);
|
|
4371
|
-
options.adapter.roomStream?.reopen();
|
|
4372
4110
|
});
|
|
4373
4111
|
}
|
|
4374
|
-
function
|
|
4112
|
+
function resynchronize() {
|
|
4375
4113
|
return task(OPENING_LANE, async (live) => {
|
|
4376
|
-
if (!live() || !
|
|
4114
|
+
if (!live() || !announced)
|
|
4377
4115
|
return;
|
|
4378
4116
|
const progress = { complete: true, reachable: true };
|
|
4379
|
-
const gate = discover();
|
|
4380
4117
|
moveTo(transferring(status));
|
|
4381
4118
|
try {
|
|
4382
4119
|
const local = await openLocal();
|
|
4383
4120
|
if (!live())
|
|
4384
4121
|
return;
|
|
4385
|
-
await
|
|
4122
|
+
await synchronize([...discovered.values()], local, progress, 'unstreamed', live);
|
|
4123
|
+
}
|
|
4124
|
+
catch {
|
|
4125
|
+
progress.complete = false;
|
|
4126
|
+
}
|
|
4127
|
+
if (live())
|
|
4128
|
+
await finishOpening(progress, live);
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
4131
|
+
function countAuthorityChange() {
|
|
4132
|
+
authorityChanges += 1;
|
|
4133
|
+
for (const listener of authorityChangeListeners)
|
|
4134
|
+
listener(authorityChanges);
|
|
4135
|
+
}
|
|
4136
|
+
function changeAuthority(change) {
|
|
4137
|
+
return task(OPENING_LANE, async (live) => {
|
|
4138
|
+
if (!live() || !announced)
|
|
4139
|
+
return;
|
|
4140
|
+
countAuthorityChange();
|
|
4141
|
+
const progress = { complete: true, reachable: true };
|
|
4142
|
+
moveTo(transferring(status));
|
|
4143
|
+
try {
|
|
4144
|
+
const local = await openLocal();
|
|
4386
4145
|
if (!live())
|
|
4387
4146
|
return;
|
|
4388
|
-
|
|
4147
|
+
const invalid = malformed(change.rooms);
|
|
4148
|
+
if (invalid !== null) {
|
|
4149
|
+
journal.record(invalid);
|
|
4150
|
+
await refuse('missing-required-guarantee');
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4153
|
+
if (change.member !== undefined)
|
|
4154
|
+
await signIn(local, change.member);
|
|
4155
|
+
discoveries += 1;
|
|
4156
|
+
const listed = await applyDiscovery(change.rooms, discoveries, change.left);
|
|
4157
|
+
await synchronize(listed, local, progress, 'authorized', live);
|
|
4389
4158
|
}
|
|
4390
4159
|
catch {
|
|
4391
4160
|
progress.complete = false;
|
|
4392
4161
|
}
|
|
4393
|
-
finally {
|
|
4394
|
-
gate.release();
|
|
4395
|
-
notifyReady();
|
|
4396
|
-
}
|
|
4397
4162
|
if (!live())
|
|
4398
4163
|
return;
|
|
4399
4164
|
if (!progress.complete)
|
|
@@ -4401,15 +4166,17 @@ export function composeRuntime(options) {
|
|
|
4401
4166
|
await rest(progress, live);
|
|
4402
4167
|
});
|
|
4403
4168
|
}
|
|
4404
|
-
function closedSocket(revoked, lost) {
|
|
4405
|
-
|
|
4406
|
-
const opened =
|
|
4169
|
+
function closedSocket(revoked, lost, authorityChanged) {
|
|
4170
|
+
notifyReady();
|
|
4171
|
+
const opened = announced;
|
|
4407
4172
|
socketOpen = false;
|
|
4173
|
+
announced = false;
|
|
4174
|
+
missedChange ||= authorityChanged;
|
|
4175
|
+
handed = false;
|
|
4176
|
+
naming = 0;
|
|
4408
4177
|
connectionLost();
|
|
4409
|
-
if (lost)
|
|
4410
|
-
reads.lost();
|
|
4178
|
+
if (lost)
|
|
4411
4179
|
conversations.lost();
|
|
4412
|
-
}
|
|
4413
4180
|
if (revoked) {
|
|
4414
4181
|
void refuse('revoked-authorization');
|
|
4415
4182
|
return;
|
|
@@ -4420,18 +4187,32 @@ export function composeRuntime(options) {
|
|
|
4420
4187
|
moveTo({
|
|
4421
4188
|
...status,
|
|
4422
4189
|
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
4423
|
-
providerUnreachable: status.providerUnreachable || !opened,
|
|
4190
|
+
providerUnreachable: status.providerUnreachable || (!opened && lost),
|
|
4191
|
+
});
|
|
4192
|
+
void closing(opened);
|
|
4193
|
+
}
|
|
4194
|
+
function closing(opened) {
|
|
4195
|
+
return task(OPENING_LANE, async (live) => {
|
|
4196
|
+
if (!live())
|
|
4197
|
+
return;
|
|
4198
|
+
const local = await openLocal();
|
|
4199
|
+
if (!opened && !socketOpen)
|
|
4200
|
+
await cachedModels(local);
|
|
4201
|
+
await rest(null, live);
|
|
4424
4202
|
});
|
|
4425
4203
|
}
|
|
4426
|
-
function
|
|
4427
|
-
|
|
4204
|
+
function cachedModels(local) {
|
|
4205
|
+
return settleModels([...rooms.values()].map((room) => discovered.get(room.locator) ?? room), 'cached', local);
|
|
4206
|
+
}
|
|
4207
|
+
function caughtUpRooms(expired) {
|
|
4208
|
+
const progress = underway;
|
|
4209
|
+
underway = null;
|
|
4210
|
+
naming = Math.max(0, naming - 1);
|
|
4428
4211
|
const cycle = progress ?? { complete: true, reachable: true };
|
|
4429
|
-
|
|
4430
|
-
opened = null;
|
|
4212
|
+
replaced(expired);
|
|
4431
4213
|
const reloading = progress === null ? [] : owedReloads(expired);
|
|
4432
4214
|
const replacing = reloadNow((room) => expired.includes(room.locator) || reloading.includes(room.locator), cycle);
|
|
4433
4215
|
return task(OPENING_LANE, async (live) => {
|
|
4434
|
-
underway = null;
|
|
4435
4216
|
if (!live())
|
|
4436
4217
|
return;
|
|
4437
4218
|
if (progress === null) {
|
|
@@ -4439,20 +4220,14 @@ export function composeRuntime(options) {
|
|
|
4439
4220
|
await rest(cycle, live);
|
|
4440
4221
|
return;
|
|
4441
4222
|
}
|
|
4442
|
-
await roomsCarried([...expired, ...reloading]);
|
|
4443
|
-
if (!live())
|
|
4444
|
-
return;
|
|
4445
4223
|
try {
|
|
4446
4224
|
const local = await openLocal();
|
|
4447
|
-
|
|
4225
|
+
await replacing;
|
|
4448
4226
|
if (progress.reachable) {
|
|
4449
4227
|
await settleModels([...discovered.values()].filter((room) => replicas.has(room.locator) &&
|
|
4450
4228
|
!expired.includes(room.locator) &&
|
|
4451
4229
|
!reloading.includes(room.locator)), 'current', local);
|
|
4452
4230
|
}
|
|
4453
|
-
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
4454
|
-
if (failed !== undefined)
|
|
4455
|
-
throw failed.reason;
|
|
4456
4231
|
}
|
|
4457
4232
|
catch {
|
|
4458
4233
|
progress.complete = false;
|
|
@@ -4464,11 +4239,8 @@ export function composeRuntime(options) {
|
|
|
4464
4239
|
}
|
|
4465
4240
|
function owedReloads(expired) {
|
|
4466
4241
|
const reloading = [...discovered.values()]
|
|
4467
|
-
.filter((room) => replicas.has(room.locator) &&
|
|
4468
|
-
!expired.includes(room.locator) &&
|
|
4469
|
-
(owed.has(room.locator) || revoked(room.locator)))
|
|
4242
|
+
.filter((room) => replicas.has(room.locator) && !expired.includes(room.locator) && revoked(room.locator))
|
|
4470
4243
|
.map((room) => room.locator);
|
|
4471
|
-
owed.clear();
|
|
4472
4244
|
for (const locator of reloading)
|
|
4473
4245
|
catching.add(locator);
|
|
4474
4246
|
liveQueries.recover();
|
|
@@ -4477,16 +4249,17 @@ export function composeRuntime(options) {
|
|
|
4477
4249
|
async function reloadNow(replaced, progress) {
|
|
4478
4250
|
const reloaded = [...discovered.values()].filter((room) => replicas.has(room.locator) && replaced(room));
|
|
4479
4251
|
for (const room of reloaded)
|
|
4480
|
-
reloadsDue.
|
|
4252
|
+
reloadsDue.add(room.locator);
|
|
4481
4253
|
try {
|
|
4482
4254
|
const local = await openLocal();
|
|
4483
|
-
|
|
4255
|
+
for (const room of reloaded) {
|
|
4256
|
+
void roomTask(room.locator, (live) => reload(room, local, progress, live)).then(() => rest(progress));
|
|
4257
|
+
}
|
|
4484
4258
|
}
|
|
4485
4259
|
catch {
|
|
4486
4260
|
for (const room of reloaded)
|
|
4487
4261
|
reloadsDue.delete(room.locator);
|
|
4488
4262
|
progress.reachable = false;
|
|
4489
|
-
return [];
|
|
4490
4263
|
}
|
|
4491
4264
|
}
|
|
4492
4265
|
async function settleModels(held, status, local) {
|
|
@@ -4497,7 +4270,7 @@ export function composeRuntime(options) {
|
|
|
4497
4270
|
const settled = await exclusive(async () => {
|
|
4498
4271
|
if (!continuing())
|
|
4499
4272
|
return [];
|
|
4500
|
-
const unchanged = held.filter((room) => owed(room) &&
|
|
4273
|
+
const unchanged = held.filter((room) => owed(room) && declaredAsHeld(room));
|
|
4501
4274
|
for (const room of unchanged) {
|
|
4502
4275
|
const next = { ...modelOf(room.locator), status };
|
|
4503
4276
|
const replica = replicas.get(room.locator);
|
|
@@ -4557,14 +4330,10 @@ export function composeRuntime(options) {
|
|
|
4557
4330
|
try {
|
|
4558
4331
|
const local = await openLocal();
|
|
4559
4332
|
await roomTask(locator, async (live) => {
|
|
4560
|
-
|
|
4561
|
-
if (!current() || authorization === undefined)
|
|
4333
|
+
if (!live() || authorization === undefined)
|
|
4562
4334
|
return;
|
|
4563
|
-
const withheld = await
|
|
4564
|
-
|
|
4565
|
-
abandonedOn(signal),
|
|
4566
|
-
]);
|
|
4567
|
-
if (withheld !== null && current())
|
|
4335
|
+
const withheld = await advance(room, local, authorization, live);
|
|
4336
|
+
if (withheld !== null && live())
|
|
4568
4337
|
await withhold(withheld, room, local, progress);
|
|
4569
4338
|
});
|
|
4570
4339
|
}
|
|
@@ -4619,7 +4388,6 @@ export function composeRuntime(options) {
|
|
|
4619
4388
|
progress.complete = false;
|
|
4620
4389
|
return;
|
|
4621
4390
|
}
|
|
4622
|
-
reads.applied();
|
|
4623
4391
|
submitAll();
|
|
4624
4392
|
for (const locator of stale)
|
|
4625
4393
|
if (repairable.has(locator))
|
|
@@ -4650,7 +4418,7 @@ export function composeRuntime(options) {
|
|
|
4650
4418
|
if (!live())
|
|
4651
4419
|
return;
|
|
4652
4420
|
let lastCompleted = status.lastCompleted;
|
|
4653
|
-
let synced = complete && stale.size === 0 && !pending();
|
|
4421
|
+
let synced = complete && roomWork === 0 && naming === 0 && stale.size === 0 && !pending();
|
|
4654
4422
|
if (synced && !recorded) {
|
|
4655
4423
|
const completedAt = new Date().toISOString();
|
|
4656
4424
|
try {
|
|
@@ -4665,74 +4433,69 @@ export function composeRuntime(options) {
|
|
|
4665
4433
|
}
|
|
4666
4434
|
}
|
|
4667
4435
|
moveTo({
|
|
4668
|
-
state: pending()
|
|
4436
|
+
state: pending()
|
|
4437
|
+
? 'pending'
|
|
4438
|
+
: synced && complete && roomWork === 0 && naming === 0
|
|
4439
|
+
? 'synced'
|
|
4440
|
+
: 'syncing',
|
|
4669
4441
|
lastCompleted,
|
|
4670
|
-
providerUnreachable: !progress.reachable,
|
|
4442
|
+
providerUnreachable: progress === null ? status.providerUnreachable : !progress.reachable,
|
|
4671
4443
|
...attention(),
|
|
4672
4444
|
});
|
|
4673
4445
|
}
|
|
4674
|
-
function
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
}
|
|
4682
|
-
function applyRoom(locator, roomVersion) {
|
|
4683
|
-
announced.set(locator, roomVersion);
|
|
4684
|
-
return task(roomLane(locator), async (live) => {
|
|
4685
|
-
if (discovering?.generation === generation)
|
|
4686
|
-
await discovering.done;
|
|
4687
|
-
const saving = answering.get(locator);
|
|
4688
|
-
if (saving?.generation === generation)
|
|
4689
|
-
await saving.answered;
|
|
4690
|
-
if (!live() || authorization === undefined)
|
|
4691
|
-
return;
|
|
4692
|
-
const room = discovered.get(locator);
|
|
4693
|
-
const held = replicas.get(locator);
|
|
4694
|
-
if (room === undefined || room.replica.admission !== 'admitted')
|
|
4695
|
-
return;
|
|
4696
|
-
if (held !== undefined &&
|
|
4697
|
-
(held.record.replica.selection !== room.replica.selection ||
|
|
4698
|
-
options.adapter.reached(locator, held.record.roomVersion, roomVersion))) {
|
|
4699
|
-
return;
|
|
4700
|
-
}
|
|
4701
|
-
const during = underway?.generation === generation ? underway.progress : null;
|
|
4702
|
-
const progress = during ?? { complete: true, reachable: true };
|
|
4703
|
-
if (during === null)
|
|
4704
|
-
moveTo(transferring(status));
|
|
4446
|
+
function applyRoom(change) {
|
|
4447
|
+
const { locator } = change;
|
|
4448
|
+
if (stopping)
|
|
4449
|
+
return Promise.resolve();
|
|
4450
|
+
return inRoom(locator, async (live) => {
|
|
4451
|
+
await answering.get(locator)?.answered;
|
|
4452
|
+
const progress = underway ?? { complete: true, reachable: true };
|
|
4705
4453
|
try {
|
|
4706
4454
|
const local = await openLocal();
|
|
4707
|
-
|
|
4708
|
-
if (!live()
|
|
4709
|
-
return;
|
|
4710
|
-
const
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4455
|
+
for (;;) {
|
|
4456
|
+
if (!live())
|
|
4457
|
+
return null;
|
|
4458
|
+
const room = discovered.get(locator);
|
|
4459
|
+
const held = replicas.get(locator);
|
|
4460
|
+
if (room === undefined ||
|
|
4461
|
+
held === undefined ||
|
|
4462
|
+
room.replica.admission !== 'admitted' ||
|
|
4463
|
+
held.record.replica.selection !== room.replica.selection) {
|
|
4464
|
+
return null;
|
|
4465
|
+
}
|
|
4466
|
+
const at = held.record.roomVersion;
|
|
4467
|
+
if (options.adapter.reached(locator, at, change.roomVersion))
|
|
4468
|
+
return progress;
|
|
4469
|
+
if (!options.adapter.reached(locator, at, change.from)) {
|
|
4470
|
+
name(locator);
|
|
4471
|
+
return progress;
|
|
4472
|
+
}
|
|
4473
|
+
if (underway === null)
|
|
4474
|
+
moveTo(transferring(status));
|
|
4475
|
+
const writing = () => live() && !full;
|
|
4476
|
+
const refreshed = await ahead((hold) => pullRefresh(authorization ?? {}, held, writing, hold, () => change.read()));
|
|
4477
|
+
if (refreshed.kind !== 'advanced')
|
|
4478
|
+
return progress;
|
|
4479
|
+
const landing = {
|
|
4480
|
+
kind: 'refreshed',
|
|
4481
|
+
from: refreshed.from,
|
|
4482
|
+
remodelled: refreshed.remodelled,
|
|
4483
|
+
};
|
|
4484
|
+
if (await publish(local, room, refreshed.record, landing, writing)) {
|
|
4485
|
+
stale.delete(locator);
|
|
4486
|
+
await retainModel(local, room, modelOf(locator));
|
|
4487
|
+
return progress;
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4714
4490
|
}
|
|
4715
4491
|
catch {
|
|
4492
|
+
if (!stale.has(locator))
|
|
4493
|
+
name(locator);
|
|
4716
4494
|
stale.add(locator);
|
|
4495
|
+
return progress;
|
|
4717
4496
|
}
|
|
4718
|
-
|
|
4719
|
-
if (during === null)
|
|
4720
|
-
await rest(progress, live);
|
|
4721
|
-
}
|
|
4722
|
-
});
|
|
4497
|
+
}).then((progress) => rest(progress), () => undefined);
|
|
4723
4498
|
}
|
|
4724
|
-
function roomsCarried(replacing) {
|
|
4725
|
-
const replaced = new Set(replacing.map(roomLane));
|
|
4726
|
-
return Promise.all([...lanes]
|
|
4727
|
-
.filter(([lane, held]) => lane !== OPENING_LANE && !replaced.has(lane) && held.generation === generation)
|
|
4728
|
-
.map(([, held]) => held.chain));
|
|
4729
|
-
}
|
|
4730
|
-
const receipts = {
|
|
4731
|
-
heads: async () => (await openLocal()).receivedHeads(),
|
|
4732
|
-
parts: async (key, count) => (await openLocal()).receivedParts(key, count),
|
|
4733
|
-
keep: async (key, head, part) => (await openLocal()).keepReceived(key, head, part),
|
|
4734
|
-
drop: async (keys) => (await openLocal()).dropReceived(keys),
|
|
4735
|
-
};
|
|
4736
4499
|
function observeStream() {
|
|
4737
4500
|
const stream = options.adapter.roomStream;
|
|
4738
4501
|
if (stream === undefined || withdrawStream !== undefined)
|
|
@@ -4742,31 +4505,33 @@ export function composeRuntime(options) {
|
|
|
4742
4505
|
return;
|
|
4743
4506
|
if (event.kind === 'opened') {
|
|
4744
4507
|
socketOpen = true;
|
|
4508
|
+
announced = false;
|
|
4509
|
+
handed = false;
|
|
4510
|
+
naming = 0;
|
|
4745
4511
|
connectionLive();
|
|
4746
4512
|
generation += 1;
|
|
4747
|
-
for (const admit of installWaiters.splice(0))
|
|
4748
|
-
admit();
|
|
4749
|
-
reads.opened();
|
|
4750
4513
|
conversations.reopened();
|
|
4751
4514
|
recovery.resume();
|
|
4752
|
-
|
|
4515
|
+
}
|
|
4516
|
+
else if (event.kind === 'discovered') {
|
|
4517
|
+
if (!socketOpen)
|
|
4518
|
+
return;
|
|
4519
|
+
announced = true;
|
|
4520
|
+
if (missedChange) {
|
|
4521
|
+
missedChange = false;
|
|
4522
|
+
countAuthorityChange();
|
|
4523
|
+
}
|
|
4524
|
+
void sync('opened', event.discovery);
|
|
4753
4525
|
}
|
|
4754
4526
|
else if (event.kind === 'closed')
|
|
4755
|
-
closedSocket(event.revoked, event.lost);
|
|
4756
|
-
else if (event.kind === '
|
|
4757
|
-
void
|
|
4527
|
+
closedSocket(event.revoked, event.lost, event.authorityChanged);
|
|
4528
|
+
else if (event.kind === 'caught-up')
|
|
4529
|
+
void caughtUpRooms(event.expired);
|
|
4758
4530
|
else if (event.kind === 'authority-changed')
|
|
4759
|
-
void
|
|
4531
|
+
void changeAuthority(event);
|
|
4760
4532
|
else
|
|
4761
|
-
void applyRoom(event
|
|
4762
|
-
}
|
|
4763
|
-
}
|
|
4764
|
-
async function heldRevisions(locator, paths) {
|
|
4765
|
-
const held = replicas.get(locator);
|
|
4766
|
-
if (held === undefined || paths.length === 0)
|
|
4767
|
-
return new Map();
|
|
4768
|
-
const entries = await (await openLocal()).entries(held.record.key, 'confirmed', paths);
|
|
4769
|
-
return new Map(entries.flatMap(({ path, revision }) => revision === null ? [] : [[path, revision]]));
|
|
4533
|
+
void applyRoom(event);
|
|
4534
|
+
});
|
|
4770
4535
|
}
|
|
4771
4536
|
function writesHeld() {
|
|
4772
4537
|
return serviceMode === 'write-halted' || haltedRefusal;
|
|
@@ -4839,8 +4604,6 @@ export function composeRuntime(options) {
|
|
|
4839
4604
|
notifyDiscovered();
|
|
4840
4605
|
if (probing !== null)
|
|
4841
4606
|
clearInterval(probing);
|
|
4842
|
-
for (const resolve of shownWaiters.splice(0))
|
|
4843
|
-
resolve();
|
|
4844
4607
|
connectionLive();
|
|
4845
4608
|
lockListeners.clear();
|
|
4846
4609
|
readinessWatches.clear();
|
|
@@ -4858,6 +4621,7 @@ export function composeRuntime(options) {
|
|
|
4858
4621
|
statusListeners.clear();
|
|
4859
4622
|
serviceModeListeners.clear();
|
|
4860
4623
|
memberListeners.clear();
|
|
4624
|
+
authorityChangeListeners.clear();
|
|
4861
4625
|
liveQueries.close();
|
|
4862
4626
|
queueSubscriptions.clear();
|
|
4863
4627
|
roomDraftListeners.clear();
|
|
@@ -4865,6 +4629,7 @@ export function composeRuntime(options) {
|
|
|
4865
4629
|
}
|
|
4866
4630
|
async function stop() {
|
|
4867
4631
|
stopping = true;
|
|
4632
|
+
inFlight.abort();
|
|
4868
4633
|
await recovery.retire();
|
|
4869
4634
|
await Promise.all(working);
|
|
4870
4635
|
await mutations;
|
|
@@ -4883,15 +4648,15 @@ export function composeRuntime(options) {
|
|
|
4883
4648
|
readyFromStore();
|
|
4884
4649
|
});
|
|
4885
4650
|
if (options.adapter.roomStream === undefined)
|
|
4886
|
-
void sync('unstreamed');
|
|
4651
|
+
void sync('unstreamed', null);
|
|
4887
4652
|
function planned(input, reach) {
|
|
4888
|
-
const plan =
|
|
4653
|
+
const plan = planWithin(input, roomFacts(reach), planReach(reach));
|
|
4889
4654
|
const named = plan.paths !== null &&
|
|
4890
4655
|
plan.rooms !== null &&
|
|
4891
4656
|
plan.paths.length * plan.rooms.length <= MAXIMUM_WINDOW;
|
|
4892
4657
|
return plan.limit === null && named ? plan : { ...plan, limit: capped(plan.limit) };
|
|
4893
4658
|
}
|
|
4894
|
-
function watchPlan(plan, deliver) {
|
|
4659
|
+
function watchPlan(plan, deliver, fail) {
|
|
4895
4660
|
let seeded = false;
|
|
4896
4661
|
let pending = true;
|
|
4897
4662
|
const shown = () => {
|
|
@@ -4917,7 +4682,7 @@ export function composeRuntime(options) {
|
|
|
4917
4682
|
}
|
|
4918
4683
|
seeded = false;
|
|
4919
4684
|
deliver(message);
|
|
4920
|
-
});
|
|
4685
|
+
}, fail);
|
|
4921
4686
|
return () => {
|
|
4922
4687
|
watchers.delete(shown);
|
|
4923
4688
|
if (watchers.size === 0)
|
|
@@ -4925,61 +4690,59 @@ export function composeRuntime(options) {
|
|
|
4925
4690
|
stop();
|
|
4926
4691
|
};
|
|
4927
4692
|
}
|
|
4928
|
-
async function searched(input, reach
|
|
4693
|
+
async function searched(input, reach) {
|
|
4929
4694
|
const asked = searchPlanOf(input);
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
const cover = covered(filter);
|
|
4939
|
-
awaitPreparation(cover);
|
|
4940
|
-
const pending = [...unlocking, ...('pending' in cover ? cover.pending : [])];
|
|
4941
|
-
const complete = pending.length === 0 && !('pending' in cover);
|
|
4942
|
-
const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
|
|
4943
|
-
const answering = complete
|
|
4944
|
-
? filter
|
|
4945
|
-
: { ...filter, rooms: reachedRooms(filter).filter((room) => !awaited.has(room)) };
|
|
4946
|
-
const answered = covered(answering);
|
|
4947
|
-
if ('pending' in answered)
|
|
4948
|
-
continue;
|
|
4949
|
-
const listed = conversations.searchable();
|
|
4950
|
-
const named = reach !== null && !reach.conversations
|
|
4951
|
-
? []
|
|
4952
|
-
: (asked.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
|
|
4953
|
-
const hits = await (await openLocal()).search({
|
|
4954
|
-
text: asked.text,
|
|
4955
|
-
limit: capped(asked.limit),
|
|
4956
|
-
after: asked.after,
|
|
4957
|
-
total: asked.total,
|
|
4958
|
-
documents: {
|
|
4959
|
-
plan: answering,
|
|
4960
|
-
rooms: answered.rooms,
|
|
4961
|
-
beneath: asked.collection
|
|
4962
|
-
? answered.rooms.map(({ locator, key }) => ({
|
|
4963
|
-
key,
|
|
4964
|
-
roots: replicas.get(locator)?.record.typedSelection?.roots ?? [],
|
|
4965
|
-
}))
|
|
4966
|
-
: [],
|
|
4967
|
-
},
|
|
4968
|
-
conversations: named,
|
|
4969
|
-
});
|
|
4970
|
-
if (JSON.stringify(covered(answering)) !== JSON.stringify(answered))
|
|
4971
|
-
continue;
|
|
4972
|
-
const coverage = {
|
|
4973
|
-
rooms: answered.coverage.rooms,
|
|
4974
|
-
conversations: named,
|
|
4975
|
-
overlay: answered.coverage.overlay,
|
|
4695
|
+
await ready;
|
|
4696
|
+
const search = options.adapter.search;
|
|
4697
|
+
if (search === undefined) {
|
|
4698
|
+
return {
|
|
4699
|
+
kind: 'undeclared',
|
|
4700
|
+
code: 'action-undeclared',
|
|
4701
|
+
fields: { name: 'search' },
|
|
4702
|
+
retryable: false,
|
|
4976
4703
|
};
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4704
|
+
}
|
|
4705
|
+
if (closed || refusal !== null || connectionOf() !== 'connected')
|
|
4706
|
+
return OFFLINE;
|
|
4707
|
+
const reached = reach === null ? null : new Set(reach.rooms);
|
|
4708
|
+
const named = asked.rooms === null
|
|
4709
|
+
? reached === null
|
|
4710
|
+
? null
|
|
4711
|
+
: [...reached]
|
|
4712
|
+
: asked.rooms.filter((room) => reached === null || reached.has(room));
|
|
4713
|
+
const rooms = named === null ? null : named.filter((room) => replicas.has(room)).sort();
|
|
4714
|
+
if (rooms !== null && rooms.length === 0)
|
|
4715
|
+
return { kind: 'found', documents: [] };
|
|
4716
|
+
try {
|
|
4717
|
+
await ensureAuthorization();
|
|
4718
|
+
}
|
|
4719
|
+
catch {
|
|
4720
|
+
return UNCERTAIN;
|
|
4721
|
+
}
|
|
4722
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
4723
|
+
return UNCERTAIN;
|
|
4724
|
+
journal.record('transfer search');
|
|
4725
|
+
try {
|
|
4726
|
+
const result = await search(authorization, { text: asked.text, rooms });
|
|
4727
|
+
if (result.kind === 'revoked-authorization')
|
|
4728
|
+
await refuse('revoked-authorization');
|
|
4729
|
+
journal.record(`outcome search ${result.kind}`);
|
|
4730
|
+
if (result.kind === 'rejected')
|
|
4731
|
+
return result;
|
|
4732
|
+
if (result.kind !== 'found')
|
|
4733
|
+
return failure(result);
|
|
4734
|
+
const kept = rooms === null ? null : new Set(rooms);
|
|
4735
|
+
return {
|
|
4736
|
+
kind: 'found',
|
|
4737
|
+
documents: result.documents.filter((hit) => kept === null || kept.has(hit.room)),
|
|
4738
|
+
};
|
|
4739
|
+
}
|
|
4740
|
+
catch {
|
|
4741
|
+
journal.record('outcome search transport-uncertainty');
|
|
4742
|
+
return UNCERTAIN;
|
|
4980
4743
|
}
|
|
4981
4744
|
}
|
|
4982
|
-
const watch = (topic, input, deliver) => {
|
|
4745
|
+
const watch = (topic, input, deliver, fail) => {
|
|
4983
4746
|
if (topic === 'room-drafts') {
|
|
4984
4747
|
const listener = { notify: deliver, ready: false };
|
|
4985
4748
|
roomDraftListeners.add(listener);
|
|
@@ -5028,7 +4791,7 @@ export function composeRuntime(options) {
|
|
|
5028
4791
|
};
|
|
5029
4792
|
}
|
|
5030
4793
|
if (topic === 'readiness') {
|
|
5031
|
-
return watchReadiness({ target: '', code: 'stored'
|
|
4794
|
+
return watchReadiness({ target: '', code: 'stored' }, null, deliver);
|
|
5032
4795
|
}
|
|
5033
4796
|
if (topic === 'rooms') {
|
|
5034
4797
|
unlistedRooms.add(deliver);
|
|
@@ -5073,6 +4836,13 @@ export function composeRuntime(options) {
|
|
|
5073
4836
|
memberListeners.delete(deliver);
|
|
5074
4837
|
};
|
|
5075
4838
|
}
|
|
4839
|
+
if (topic === 'authority-changes') {
|
|
4840
|
+
authorityChangeListeners.add(deliver);
|
|
4841
|
+
deliver(authorityChanges);
|
|
4842
|
+
return () => {
|
|
4843
|
+
authorityChangeListeners.delete(deliver);
|
|
4844
|
+
};
|
|
4845
|
+
}
|
|
5076
4846
|
if (topic === 'queue') {
|
|
5077
4847
|
const queue = { locator: input[0], notify: deliver };
|
|
5078
4848
|
queueSubscriptions.add(queue);
|
|
@@ -5081,7 +4851,7 @@ export function composeRuntime(options) {
|
|
|
5081
4851
|
queueSubscriptions.delete(queue);
|
|
5082
4852
|
};
|
|
5083
4853
|
}
|
|
5084
|
-
return watchPlan(planned(input[0], null), deliver);
|
|
4854
|
+
return watchPlan(planned(input[0], null), deliver, fail);
|
|
5085
4855
|
};
|
|
5086
4856
|
const md = {
|
|
5087
4857
|
loaded() {
|
|
@@ -5101,7 +4871,7 @@ export function composeRuntime(options) {
|
|
|
5101
4871
|
});
|
|
5102
4872
|
},
|
|
5103
4873
|
query: ((input) => rowsQuery(planned(input, null))),
|
|
5104
|
-
search: (input) => searched(input, null
|
|
4874
|
+
search: (input) => searched(input, null),
|
|
5105
4875
|
types(locator) {
|
|
5106
4876
|
return {
|
|
5107
4877
|
collection: modelTypes(modelOf(locator)),
|
|
@@ -5204,6 +4974,8 @@ export function composeRuntime(options) {
|
|
|
5204
4974
|
return journal.export();
|
|
5205
4975
|
},
|
|
5206
4976
|
cleanUp,
|
|
4977
|
+
unsent: () => database.ask({ kind: 'authored.unsent' }),
|
|
4978
|
+
reset: () => options.shell.reset(),
|
|
5207
4979
|
persistence() {
|
|
5208
4980
|
return persistence === 'granted';
|
|
5209
4981
|
},
|
|
@@ -5226,7 +4998,7 @@ export function composeRuntime(options) {
|
|
|
5226
4998
|
authorization = undefined;
|
|
5227
4999
|
discoveryPaused = false;
|
|
5228
5000
|
if (options.adapter.roomStream === undefined)
|
|
5229
|
-
await sync('unstreamed');
|
|
5001
|
+
await sync('unstreamed', null);
|
|
5230
5002
|
else
|
|
5231
5003
|
await task(OPENING_LANE, ensureAuthorization);
|
|
5232
5004
|
},
|
|
@@ -5249,7 +5021,6 @@ export function composeRuntime(options) {
|
|
|
5249
5021
|
md,
|
|
5250
5022
|
seed,
|
|
5251
5023
|
stop,
|
|
5252
|
-
lose: loseRooms,
|
|
5253
5024
|
watch,
|
|
5254
5025
|
persisted(answer) {
|
|
5255
5026
|
if (answer === persistence)
|
|
@@ -5258,7 +5029,6 @@ export function composeRuntime(options) {
|
|
|
5258
5029
|
journal.record(`persistence ${answer}`);
|
|
5259
5030
|
queueReadiness();
|
|
5260
5031
|
},
|
|
5261
|
-
shown: screen,
|
|
5262
5032
|
within: (reach, port) => {
|
|
5263
5033
|
if (full)
|
|
5264
5034
|
void probed();
|