@agentshouse/mdruntime 0.1.0-c08b314dc5105 → 0.1.0-c09bf842921ff
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/adapter.d.ts +72 -56
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +3 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +29 -47
- package/dist/bootstrap.d.ts +9 -16
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +85 -109
- 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 -270
- package/dist/connection.d.ts +9 -9
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +965 -1050
- 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 +16 -13
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +229 -155
- package/dist/durability.d.ts +6 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +3 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +17 -8
- 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 +27 -14
- package/dist/memory.d.ts +11 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +166 -83
- package/dist/messages.d.ts +2 -2
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +15 -16
- package/dist/model.d.ts +2 -8
- 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/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -0
- 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 +9 -8
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +37 -48
- package/dist/react.d.ts +3 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/readiness.d.ts +3 -17
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +4 -10
- package/dist/recovery.d.ts +1 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +26 -0
- package/dist/refresh.d.ts +7 -5
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +29 -5
- package/dist/replica.d.ts +9 -7
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +27 -13
- package/dist/room-draft.d.ts +21 -5
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +98 -51
- package/dist/rows.d.ts +14 -21
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +48 -184
- package/dist/save.d.ts +2 -0
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +12 -6
- 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 +4 -3
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -1
- package/dist/store.d.ts +15 -36
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +26 -81
- package/dist/stored.d.ts +7 -30
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +142 -189
- package/dist/tables.d.ts +19 -46
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +29 -66
- 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 +42 -100
- 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 -368
- package/dist/reads.d.ts +0 -11
- package/dist/reads.d.ts.map +0 -1
- package/dist/reads.js +0 -100
- package/dist/words.d.ts +0 -19
- package/dist/words.d.ts.map +0 -1
- package/dist/words.js +0 -150
package/dist/connection.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { attachedModel, builtInModel, roomTypesOf } from './attached-model.js';
|
|
1
|
+
import { identityOf, parseFrontmatter, plain, queryKey, roomTarget, target, } from '@agentshouse/mdmodel';
|
|
2
|
+
import { attachedModel, builtInModel, roomTypesOf, schemaTypes } from './attached-model.js';
|
|
3
3
|
import { cached, modelTypes, NO_MODEL, sameModel, } from './model.js';
|
|
4
4
|
import { observing, whole } from './observation.js';
|
|
5
|
-
import {
|
|
5
|
+
import { planWithin, } from './query.js';
|
|
6
6
|
import { createLiveQueries } from './live.js';
|
|
7
7
|
import { seededResult } from './seed.js';
|
|
8
8
|
import { documentKey, roomOfKey } from './rows.js';
|
|
9
9
|
import { searchPlanOf } from './search.js';
|
|
10
10
|
import { capped, MAXIMUM_WINDOW } from './windows.js';
|
|
11
|
-
import { bootstrapRoom
|
|
11
|
+
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,21 +20,20 @@ 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,
|
|
27
|
-
import { binaryBase, changedParts, changesOf, gatheredParts, held, heldFile, joinedDraft, moveTarget, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
|
|
25
|
+
import { compositionOf, entryAt, landed, movedBy, overlayOf, replaying, returning, returnedPaths, slicing, slicingWith, standing, storedRecord, touchedBy, } from './replica.js';
|
|
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';
|
|
30
29
|
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 {
|
|
35
|
-
import { beneath, normalize } from './paths.js';
|
|
33
|
+
import { roomDraftRevision } from './identity.js';
|
|
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,34 +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
|
-
|
|
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.
|
|
62
|
+
const DERIVE_LONGEST_MS = 1_500;
|
|
63
63
|
const QUERY_BYTES = 1 << 20;
|
|
64
64
|
const STAGE_BYTES = 1 << 20;
|
|
65
65
|
const STAGE_FLOOR = 1 << 16;
|
|
66
66
|
// A batch aims at this much database work, so a Save or a query waits behind at most one short write.
|
|
67
67
|
const STAGE_AIM_MS = 50;
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
68
|
+
const STREAMS_HELD = 4;
|
|
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.
|
|
71
72
|
const ARRIVAL_REPORT_MS = 250;
|
|
72
|
-
const CLEAN_CHANGES = 2_000;
|
|
73
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.
|
|
74
74
|
const LOCK_GRACE_MS = 120_000;
|
|
75
75
|
function operationOf(input) {
|
|
76
76
|
const operation = input?.operation_id;
|
|
77
77
|
return typeof operation === 'string' ? operation : null;
|
|
78
78
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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));
|
|
85
86
|
});
|
|
86
87
|
}
|
|
87
|
-
function roomLane(locator) {
|
|
88
|
-
return `room ${locator}`;
|
|
89
|
-
}
|
|
90
88
|
function pendingKey(locator, id) {
|
|
91
89
|
return `${locator}\u0000${id}`;
|
|
92
90
|
}
|
|
@@ -106,6 +104,7 @@ export function composeRuntime(options) {
|
|
|
106
104
|
const usableWhenHeld = options.usableWhenHeld ?? [];
|
|
107
105
|
const queuesWithoutReplica = new FencedMap(isFenced);
|
|
108
106
|
const pendings = new Map();
|
|
107
|
+
const landedAhead = new Map();
|
|
109
108
|
const seeds = new Map();
|
|
110
109
|
const seedWatchers = new Map();
|
|
111
110
|
const roomDrafts = new FencedMap(isFenced);
|
|
@@ -117,15 +116,24 @@ export function composeRuntime(options) {
|
|
|
117
116
|
const counted = new Map();
|
|
118
117
|
const unreadable = new Set();
|
|
119
118
|
const builtIn = builtInModel(roomTypes);
|
|
119
|
+
const typedPaths = options.adapter.typedPaths ?? [];
|
|
120
120
|
const models = new FencedMap(isFenced);
|
|
121
121
|
const liveModels = new FencedMap(isFenced);
|
|
122
122
|
const invalidModels = new Set();
|
|
123
123
|
const discovered = new Map();
|
|
124
|
-
|
|
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
|
+
};
|
|
125
135
|
let roomsListed = false;
|
|
126
136
|
const scopedRefusals = new Map();
|
|
127
|
-
const sourceRefusals = new Map();
|
|
128
|
-
const declarationRefusals = new Set();
|
|
129
137
|
function modelOf(locator) {
|
|
130
138
|
return models.get(locator) ?? NO_MODEL;
|
|
131
139
|
}
|
|
@@ -135,7 +143,7 @@ export function composeRuntime(options) {
|
|
|
135
143
|
: modelOf(locator);
|
|
136
144
|
}
|
|
137
145
|
function composed(record) {
|
|
138
|
-
return compositionOf(record, builtIn);
|
|
146
|
+
return compositionOf(record, builtIn, typedPaths);
|
|
139
147
|
}
|
|
140
148
|
function compositionFor(record) {
|
|
141
149
|
return composed({ ...record, model: savedModel(record.locator) });
|
|
@@ -148,30 +156,31 @@ export function composeRuntime(options) {
|
|
|
148
156
|
let closed = false;
|
|
149
157
|
let stopping = false;
|
|
150
158
|
let withdrawStream;
|
|
151
|
-
const announced = new Map();
|
|
152
159
|
let underway = null;
|
|
153
160
|
let streamed = null;
|
|
154
161
|
let handed = false;
|
|
162
|
+
let naming = 0;
|
|
155
163
|
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
156
164
|
const repairing = new Set();
|
|
157
165
|
const calling = new Set();
|
|
158
166
|
const repairable = new Map();
|
|
159
|
-
let outcomeRetention = null;
|
|
160
167
|
const answering = new Map();
|
|
161
168
|
let complete = false;
|
|
162
169
|
let recorded = false;
|
|
163
170
|
const stale = new Set();
|
|
164
171
|
let generation = 0;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
const reloadsDue = new Map();
|
|
172
|
+
const reloadsDue = new Set();
|
|
173
|
+
const highest = new Map();
|
|
174
|
+
const beyondHorizon = new Set();
|
|
169
175
|
const lanes = new Map();
|
|
170
|
-
|
|
176
|
+
const roomLanes = new Map();
|
|
177
|
+
let roomWork = 0;
|
|
178
|
+
const roomReads = { free: ROOM_READS, waiting: [] };
|
|
171
179
|
const working = new Set();
|
|
172
180
|
let mutations = Promise.resolve();
|
|
173
181
|
let gathering = null;
|
|
174
182
|
const roomListeners = new Set();
|
|
183
|
+
const unlistedRooms = new Set();
|
|
175
184
|
const refusalListeners = new Set();
|
|
176
185
|
const storageLossListeners = new Set();
|
|
177
186
|
const statusListeners = new Set();
|
|
@@ -182,6 +191,8 @@ export function composeRuntime(options) {
|
|
|
182
191
|
let withdrawConversations;
|
|
183
192
|
let currencyQueued = null;
|
|
184
193
|
let socketOpen = false;
|
|
194
|
+
let announced = false;
|
|
195
|
+
let missedChange = false;
|
|
185
196
|
const confirmed = new Set();
|
|
186
197
|
let lockGrace = null;
|
|
187
198
|
const lockListeners = new Set();
|
|
@@ -191,6 +202,8 @@ export function composeRuntime(options) {
|
|
|
191
202
|
const serviceModeListeners = new Set();
|
|
192
203
|
let member = null;
|
|
193
204
|
const memberListeners = new Set();
|
|
205
|
+
let authorityChanges = 0;
|
|
206
|
+
const authorityChangeListeners = new Set();
|
|
194
207
|
const unpreparedDemand = new Set();
|
|
195
208
|
const unpreparable = new Set();
|
|
196
209
|
let preparing = false;
|
|
@@ -215,16 +228,16 @@ export function composeRuntime(options) {
|
|
|
215
228
|
let readinessQueued = false;
|
|
216
229
|
const catching = new Set();
|
|
217
230
|
const transfers = new FencedMap(isFenced);
|
|
218
|
-
const installs = new
|
|
219
|
-
const keptParts = new Map();
|
|
231
|
+
const installs = new Set();
|
|
220
232
|
const arrivals = new Map();
|
|
233
|
+
const streaming = new Set();
|
|
221
234
|
const underived = new Map();
|
|
235
|
+
const unindexed = new Map();
|
|
236
|
+
const startedOver = new Map();
|
|
222
237
|
let arrivalReported = 0;
|
|
223
|
-
let
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
let queuedBytes = 0;
|
|
227
|
-
const partWaiters = [];
|
|
238
|
+
let streamsFree = STREAMS_HELD;
|
|
239
|
+
let queuedText = 0;
|
|
240
|
+
const installWaiters = [];
|
|
228
241
|
const stageBudget = {
|
|
229
242
|
bytes: STAGE_FLOOR * 4,
|
|
230
243
|
floor: STAGE_FLOOR,
|
|
@@ -237,23 +250,10 @@ export function composeRuntime(options) {
|
|
|
237
250
|
ceiling: DERIVE_BYTES,
|
|
238
251
|
aim: DERIVE_AIM_MS,
|
|
239
252
|
};
|
|
240
|
-
|
|
253
|
+
const installTurns = { busy: false, ahead: 0, finishing: [], starting: [] };
|
|
241
254
|
const conversations = createConversationStore({
|
|
242
|
-
agents: declaredAgents,
|
|
243
|
-
documents: (declaration, after) => rowsQuery(planOf({
|
|
244
|
-
rooms: [declaration.locator],
|
|
245
|
-
types: [declaration.type],
|
|
246
|
-
select: {
|
|
247
|
-
fields: [
|
|
248
|
-
declaration.identity,
|
|
249
|
-
declaration.name,
|
|
250
|
-
...(declaration.retired === undefined ? [] : [declaration.retired]),
|
|
251
|
-
].map((field) => `/${escapePointer(field)}`),
|
|
252
|
-
},
|
|
253
|
-
limit: MAXIMUM_WINDOW,
|
|
254
|
-
...(after === undefined ? {} : { after }),
|
|
255
|
-
}, roomFacts())),
|
|
256
255
|
call: (name, input) => invokeAction(name, input),
|
|
256
|
+
history: (request) => readConversationHistory(request),
|
|
257
257
|
durably,
|
|
258
258
|
stream: () => options.adapter.conversations,
|
|
259
259
|
publish: publishChangeSet,
|
|
@@ -261,14 +261,22 @@ export function composeRuntime(options) {
|
|
|
261
261
|
full: () => full,
|
|
262
262
|
closed: () => closed,
|
|
263
263
|
unanswered: resendLater,
|
|
264
|
-
expired: (operation) => pastHorizon(operation),
|
|
265
264
|
});
|
|
266
265
|
const journal = createLog();
|
|
267
266
|
const keptFiles = openedFiles(database, isFenced);
|
|
268
|
-
let
|
|
267
|
+
let readyFromStore = () => { };
|
|
269
268
|
const ready = new Promise((resolve) => {
|
|
270
|
-
|
|
269
|
+
readyFromStore = resolve;
|
|
270
|
+
});
|
|
271
|
+
let notifyDiscovered = () => { };
|
|
272
|
+
const firstDiscovery = new Promise((resolve) => {
|
|
273
|
+
notifyDiscovered = resolve;
|
|
271
274
|
});
|
|
275
|
+
let roomsNamed = false;
|
|
276
|
+
function notifyReady() {
|
|
277
|
+
notifyDiscovered();
|
|
278
|
+
readyFromStore();
|
|
279
|
+
}
|
|
272
280
|
function rememberRoomDraft(record) {
|
|
273
281
|
const room = roomDrafts.get(record.locator) ?? new Map();
|
|
274
282
|
room.set(record.path, record);
|
|
@@ -345,9 +353,9 @@ export function composeRuntime(options) {
|
|
|
345
353
|
return value === undefined ? null : structuredClone(value);
|
|
346
354
|
}
|
|
347
355
|
async function writtenRoomDraft(room, path, revision, input, next) {
|
|
348
|
-
const parts = readParts(input);
|
|
356
|
+
const parts = readParts(input, path);
|
|
349
357
|
if (path === '' || normalize(path) !== path)
|
|
350
|
-
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`);
|
|
351
359
|
const local = await openLocal();
|
|
352
360
|
const current = roomDrafts.get(room)?.get(path);
|
|
353
361
|
if ((current?.revision ?? null) !== revision) {
|
|
@@ -355,7 +363,9 @@ export function composeRuntime(options) {
|
|
|
355
363
|
}
|
|
356
364
|
const file = parts.file === undefined
|
|
357
365
|
? undefined
|
|
358
|
-
: await heldFile(parts.file.blob,
|
|
366
|
+
: await heldFile(parts.file.blob, parts.file.base === undefined
|
|
367
|
+
? binaryBase(await visibleOf(room, [path]), path)
|
|
368
|
+
: parts.file.base);
|
|
359
369
|
const record = {
|
|
360
370
|
locator: room,
|
|
361
371
|
path,
|
|
@@ -390,6 +400,20 @@ export function composeRuntime(options) {
|
|
|
390
400
|
(queuesWithoutReplica.size > 0 ||
|
|
391
401
|
[...replicas.values()].some((replica) => replica.queue.length > 0)));
|
|
392
402
|
}
|
|
403
|
+
function unsent() {
|
|
404
|
+
const changed = new Map();
|
|
405
|
+
for (const room of [...replicas.keys(), ...queuesWithoutReplica.keys()]) {
|
|
406
|
+
for (const { batch, state } of entriesOf(room)) {
|
|
407
|
+
if (state === 'local')
|
|
408
|
+
continue;
|
|
409
|
+
for (const { path } of batch.changes)
|
|
410
|
+
changed.set(`${room}\u0000${path}`, { room, path });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return [...changed]
|
|
414
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
415
|
+
.map(([, change]) => change);
|
|
416
|
+
}
|
|
393
417
|
function attention() {
|
|
394
418
|
const attentionRequired = [];
|
|
395
419
|
for (const record of [...roomDrafts.values()].flatMap((room) => [...room.values()])) {
|
|
@@ -401,16 +425,9 @@ export function composeRuntime(options) {
|
|
|
401
425
|
returned: structuredClone(record.returned),
|
|
402
426
|
});
|
|
403
427
|
}
|
|
404
|
-
for (const [room, refusal] of sourceRefusals)
|
|
405
|
-
attentionRequired.push({ room, refusal: structuredClone(refusal) });
|
|
406
428
|
attentionRequired.sort((left, right) => {
|
|
407
429
|
if (left.room !== right.room)
|
|
408
430
|
return left.room < right.room ? -1 : 1;
|
|
409
|
-
if ('refusal' in left || 'refusal' in right) {
|
|
410
|
-
if ('refusal' in left && 'refusal' in right)
|
|
411
|
-
return 0;
|
|
412
|
-
return 'refusal' in left ? -1 : 1;
|
|
413
|
-
}
|
|
414
431
|
if (left.path !== right.path)
|
|
415
432
|
return left.path < right.path ? -1 : 1;
|
|
416
433
|
return 0;
|
|
@@ -422,7 +439,7 @@ export function composeRuntime(options) {
|
|
|
422
439
|
if (reset === null || submission.reset < reset)
|
|
423
440
|
reset = submission.reset;
|
|
424
441
|
}
|
|
425
|
-
return { attentionRequired, reset };
|
|
442
|
+
return { attentionRequired, unsent: unsent(), reset };
|
|
426
443
|
}
|
|
427
444
|
function documentsIn(locator) {
|
|
428
445
|
return replicas.get(locator)?.count ?? 0;
|
|
@@ -511,11 +528,26 @@ export function composeRuntime(options) {
|
|
|
511
528
|
const value = listed();
|
|
512
529
|
for (const listener of roomListeners)
|
|
513
530
|
listener(value);
|
|
531
|
+
emitKnownRooms();
|
|
532
|
+
}
|
|
533
|
+
function emitKnownRooms() {
|
|
534
|
+
conversations.learned();
|
|
535
|
+
if (unlistedRooms.size === 0)
|
|
536
|
+
return;
|
|
537
|
+
if (!roomsListed && rooms.size === 0 && refusal === null && !status.providerUnreachable)
|
|
538
|
+
return;
|
|
539
|
+
const value = listed();
|
|
540
|
+
for (const listener of [...unlistedRooms]) {
|
|
541
|
+
unlistedRooms.delete(listener);
|
|
542
|
+
roomListeners.add(listener);
|
|
543
|
+
listener(value);
|
|
544
|
+
}
|
|
514
545
|
}
|
|
515
546
|
function emitRefusal() {
|
|
516
547
|
queueReadiness();
|
|
517
548
|
for (const listener of refusalListeners)
|
|
518
549
|
listener(refusal);
|
|
550
|
+
emitKnownRooms();
|
|
519
551
|
}
|
|
520
552
|
function emitStorageLoss() {
|
|
521
553
|
queueReadiness();
|
|
@@ -536,6 +568,7 @@ export function composeRuntime(options) {
|
|
|
536
568
|
if (closed)
|
|
537
569
|
return;
|
|
538
570
|
queueReadiness();
|
|
571
|
+
emitKnownRooms();
|
|
539
572
|
const next = status.state === 'synced' && !conversations.current()
|
|
540
573
|
? { ...status, state: 'syncing' }
|
|
541
574
|
: status;
|
|
@@ -549,9 +582,6 @@ export function composeRuntime(options) {
|
|
|
549
582
|
if (localSaves)
|
|
550
583
|
return Object.freeze({ batch, state: 'local' });
|
|
551
584
|
const state = pendings.get(pendingKey(locator, batch.id));
|
|
552
|
-
if (state?.accepted !== undefined) {
|
|
553
|
-
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
554
|
-
}
|
|
555
585
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
556
586
|
}
|
|
557
587
|
function entriesOf(locator) {
|
|
@@ -581,12 +611,12 @@ export function composeRuntime(options) {
|
|
|
581
611
|
const key = held.record.key;
|
|
582
612
|
const done = await exclusive(async () => {
|
|
583
613
|
const local = await openLocal();
|
|
584
|
-
return timed(deriveBudget, () => local.rederive(key, composed(held.record), deriveBudget.bytes));
|
|
614
|
+
return timed(deriveBudget, () => local.rederive(key, composed(held.record), deriveBudget.bytes, DERIVE_LONGEST_MS, DERIVE_FLOOR));
|
|
585
615
|
});
|
|
586
616
|
const now = replicas.get(locator);
|
|
587
617
|
if (now?.record.key !== key)
|
|
588
618
|
continue;
|
|
589
|
-
if (!done)
|
|
619
|
+
if (!done.done)
|
|
590
620
|
continue;
|
|
591
621
|
replicas.set(locator, { ...now, indexed: true });
|
|
592
622
|
publishChangeSet(changeSet(locator, { documents: null }));
|
|
@@ -625,11 +655,7 @@ export function composeRuntime(options) {
|
|
|
625
655
|
})();
|
|
626
656
|
}
|
|
627
657
|
function roomsOfReach(reach) {
|
|
628
|
-
return new Set([
|
|
629
|
-
...reach.rooms,
|
|
630
|
-
...reach.documents.map(roomOfKey),
|
|
631
|
-
...reach.areas.flatMap((area) => (area.types.length === 0 ? [] : [area.room])),
|
|
632
|
-
]);
|
|
658
|
+
return new Set([...reach.rooms, ...reach.documents.map(roomOfKey)]);
|
|
633
659
|
}
|
|
634
660
|
function reachedRooms(plan) {
|
|
635
661
|
const reached = plan.reach === null ? null : roomsOfReach(plan.reach);
|
|
@@ -668,7 +694,7 @@ export function composeRuntime(options) {
|
|
|
668
694
|
coverage.push({
|
|
669
695
|
room: locator,
|
|
670
696
|
generation: replica.record.transfer,
|
|
671
|
-
current: !(catching.has(locator) ||
|
|
697
|
+
current: !(catching.has(locator) || transfers.has(locator)),
|
|
672
698
|
});
|
|
673
699
|
read.push({ locator, key: replica.record.key });
|
|
674
700
|
for (const batch of replica.queue)
|
|
@@ -695,8 +721,6 @@ export function composeRuntime(options) {
|
|
|
695
721
|
return { listed: named.every((room) => replicas.has(room)), rooms: named };
|
|
696
722
|
}
|
|
697
723
|
function roomState(locator) {
|
|
698
|
-
if (sourceRefusals.has(locator))
|
|
699
|
-
return 'refused';
|
|
700
724
|
const listedRoom = discovered.get(locator);
|
|
701
725
|
if (roomsListed &&
|
|
702
726
|
(listedRoom === undefined ||
|
|
@@ -711,7 +735,7 @@ export function composeRuntime(options) {
|
|
|
711
735
|
}
|
|
712
736
|
function conversationCoverage(reach) {
|
|
713
737
|
const declared = reach === null || reach.conversations;
|
|
714
|
-
if (!declared || options.adapter.conversations === undefined
|
|
738
|
+
if (!declared || options.adapter.conversations === undefined)
|
|
715
739
|
return 'undeclared';
|
|
716
740
|
if (!conversations.known() || !conversations.current())
|
|
717
741
|
return 'acquiring';
|
|
@@ -726,83 +750,50 @@ export function composeRuntime(options) {
|
|
|
726
750
|
? 'connected'
|
|
727
751
|
: 'connecting';
|
|
728
752
|
}
|
|
729
|
-
function
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
rule: held.code,
|
|
741
|
-
location: typeof location === 'string' ? location : null,
|
|
742
|
-
},
|
|
743
|
-
];
|
|
744
|
-
});
|
|
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 };
|
|
745
764
|
}
|
|
746
765
|
function installationOf(coverage) {
|
|
747
766
|
const rooms = { done: 0, total: 0 };
|
|
748
|
-
const parts = { done: 0, total: 0 };
|
|
749
767
|
const bytes = { done: 0, total: 0 };
|
|
750
|
-
const received = { done: 0, total: 0 };
|
|
751
768
|
let installing = 0;
|
|
752
|
-
let
|
|
753
|
-
let listed = false;
|
|
754
|
-
let waiting = false;
|
|
769
|
+
let downloading = false;
|
|
755
770
|
for (const { room, state } of coverage) {
|
|
756
771
|
if (state === 'unadmitted')
|
|
757
772
|
continue;
|
|
758
773
|
rooms.total += 1;
|
|
759
774
|
if (state === 'complete')
|
|
760
775
|
rooms.done += 1;
|
|
761
|
-
const transfer = transfers.get(room);
|
|
762
|
-
const replica = replicas.get(room)?.record.inventory ?? null;
|
|
763
|
-
const inventory = transfer?.inventory ?? replica ?? installs.get(room) ?? null;
|
|
764
776
|
const underway = state === 'acquiring' || (installs.has(room) && state !== 'complete');
|
|
765
777
|
if (underway)
|
|
766
778
|
installing += 1;
|
|
767
|
-
if (underway &&
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
bytes.total += inventory.bytes;
|
|
774
|
-
parts.done += done.parts;
|
|
775
|
-
bytes.done += done.bytes;
|
|
776
|
-
received.total += inventory.bytes;
|
|
777
|
-
received.done += done.bytes;
|
|
778
|
-
for (const held of [keptParts.get(room), arrivals.get(room)]) {
|
|
779
|
-
for (const arrived of held?.values() ?? [])
|
|
780
|
-
received.done += arrived;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
waiting ||= underway;
|
|
779
|
+
if (underway && (streaming.has(room) || !arrivals.has(room)))
|
|
780
|
+
downloading = true;
|
|
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);
|
|
784
785
|
}
|
|
785
786
|
const step = installing === 0
|
|
786
787
|
? null
|
|
787
|
-
:
|
|
788
|
-
? '
|
|
789
|
-
:
|
|
790
|
-
? '
|
|
791
|
-
:
|
|
792
|
-
|
|
793
|
-
: 'replaying';
|
|
794
|
-
return {
|
|
795
|
-
step,
|
|
796
|
-
paused: waiting && !onScreen ? 'hidden' : null,
|
|
797
|
-
rooms,
|
|
798
|
-
parts,
|
|
799
|
-
bytes,
|
|
800
|
-
received,
|
|
801
|
-
};
|
|
788
|
+
: downloading
|
|
789
|
+
? 'downloading'
|
|
790
|
+
: bytes.done < bytes.total
|
|
791
|
+
? 'writing'
|
|
792
|
+
: 'replaying';
|
|
793
|
+
return { step, rooms, bytes };
|
|
802
794
|
}
|
|
803
795
|
function readinessFor(port, reach) {
|
|
804
796
|
const required = requiredRooms(reach);
|
|
805
|
-
const search = port.search === 'required';
|
|
806
797
|
const coverage = required.rooms.map((room) => ({ room, state: roomState(room) }));
|
|
807
798
|
const preparing = coverage
|
|
808
799
|
.filter((room) => room.state === 'preparing' && !unpreparable.has(room.room))
|
|
@@ -815,14 +806,12 @@ export function composeRuntime(options) {
|
|
|
815
806
|
listed: required.listed,
|
|
816
807
|
rooms: coverage,
|
|
817
808
|
conversations: conversationCoverage(reach),
|
|
818
|
-
search: search ? 'required' : 'unused',
|
|
819
809
|
},
|
|
820
810
|
persistence,
|
|
821
811
|
connection: connectionOf(),
|
|
822
812
|
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
823
|
-
space: full ? 'full' :
|
|
813
|
+
space: full ? 'full' : null,
|
|
824
814
|
installation: installationOf(coverage),
|
|
825
|
-
refused: refusedIn(required.rooms),
|
|
826
815
|
lost: storageLoss !== null,
|
|
827
816
|
established: targets[port.target]?.established === true ||
|
|
828
817
|
(usableWhenHeld.includes(port.target) && required.rooms.some((room) => reopened.has(room))),
|
|
@@ -910,8 +899,12 @@ export function composeRuntime(options) {
|
|
|
910
899
|
unpreparable.clear();
|
|
911
900
|
journal.record('space freed');
|
|
912
901
|
queueReadiness();
|
|
913
|
-
if (
|
|
914
|
-
|
|
902
|
+
if (closed || authorization === undefined)
|
|
903
|
+
return;
|
|
904
|
+
if (options.adapter.roomStream === undefined)
|
|
905
|
+
void sync('unstreamed', null);
|
|
906
|
+
else
|
|
907
|
+
void resynchronize();
|
|
915
908
|
}
|
|
916
909
|
function seedRoomHeld(room, version) {
|
|
917
910
|
return indexed(room) && reachedBy(room, version);
|
|
@@ -1059,6 +1052,8 @@ export function composeRuntime(options) {
|
|
|
1059
1052
|
}
|
|
1060
1053
|
async function refuse(reason, provider = null) {
|
|
1061
1054
|
discoveryPaused = true;
|
|
1055
|
+
inFlight.abort();
|
|
1056
|
+
inFlight = new AbortController();
|
|
1062
1057
|
try {
|
|
1063
1058
|
if (reason === 'revoked-authorization') {
|
|
1064
1059
|
await exclusive(async () => {
|
|
@@ -1143,10 +1138,10 @@ export function composeRuntime(options) {
|
|
|
1143
1138
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
1144
1139
|
roomVersion: replicas.get(room.locator)?.record.roomVersion ?? null,
|
|
1145
1140
|
synchronizationAvailable,
|
|
1146
|
-
refusal: scopedRefusals.get(room.locator) ??
|
|
1141
|
+
refusal: scopedRefusals.get(room.locator) ?? null,
|
|
1147
1142
|
});
|
|
1148
1143
|
}
|
|
1149
|
-
async function applyDiscovery(found, started,
|
|
1144
|
+
async function applyDiscovery(found, started, left) {
|
|
1150
1145
|
const lockedBefore = lockedRooms();
|
|
1151
1146
|
for (const room of found) {
|
|
1152
1147
|
const fence = fences.get(room.locator);
|
|
@@ -1160,18 +1155,11 @@ export function composeRuntime(options) {
|
|
|
1160
1155
|
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
1161
1156
|
for (const locator of listedLocators)
|
|
1162
1157
|
scopedRefusals.delete(locator);
|
|
1163
|
-
if (
|
|
1158
|
+
if (left === undefined)
|
|
1164
1159
|
discovered.clear();
|
|
1165
|
-
else
|
|
1166
|
-
for (const locator of covered)
|
|
1167
|
-
discovered.delete(locator);
|
|
1168
1160
|
for (const room of listed)
|
|
1169
1161
|
discovered.set(room.locator, room);
|
|
1170
|
-
|
|
1171
|
-
if (options.adapter.exhaustiveDiscovery === true)
|
|
1172
|
-
await loseRooms(unlisted);
|
|
1173
|
-
else
|
|
1174
|
-
unlistedKept(unlisted);
|
|
1162
|
+
await loseRooms(left ?? unlistedHeld(new Set(found.map((room) => room.locator))));
|
|
1175
1163
|
for (const room of listed) {
|
|
1176
1164
|
const held = replicas.get(room.locator)?.record;
|
|
1177
1165
|
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
@@ -1202,15 +1190,6 @@ export function composeRuntime(options) {
|
|
|
1202
1190
|
]),
|
|
1203
1191
|
].filter((locator) => !listed.has(locator));
|
|
1204
1192
|
}
|
|
1205
|
-
function unlistedKept(unlisted) {
|
|
1206
|
-
for (const locator of unlisted) {
|
|
1207
|
-
const held = replicas.get(locator)?.record;
|
|
1208
|
-
if (held !== undefined)
|
|
1209
|
-
list(held, false);
|
|
1210
|
-
else
|
|
1211
|
-
rooms.delete(locator);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
1193
|
function fence(locators) {
|
|
1215
1194
|
const fresh = [...new Set(locators)].filter((locator) => !fences.has(locator));
|
|
1216
1195
|
const removed = fresh.map((locator) => ({
|
|
@@ -1221,24 +1200,26 @@ export function composeRuntime(options) {
|
|
|
1221
1200
|
fences.set(locator, { at: discoveries, erased: false });
|
|
1222
1201
|
lastFenced.set(locator, ++fencings);
|
|
1223
1202
|
confirmed.delete(locator);
|
|
1224
|
-
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
1203
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []) {
|
|
1225
1204
|
pendings.delete(pendingKey(locator, batch.id));
|
|
1205
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1206
|
+
}
|
|
1226
1207
|
replicas.delete(locator);
|
|
1208
|
+
highest.delete(locator);
|
|
1209
|
+
beyondHorizon.delete(locator);
|
|
1227
1210
|
queuesWithoutReplica.delete(locator);
|
|
1228
1211
|
rooms.delete(locator);
|
|
1229
1212
|
roomDrafts.delete(locator);
|
|
1230
1213
|
attachedFiles.delete(locator);
|
|
1231
1214
|
untransferred(locator);
|
|
1215
|
+
startedOver.delete(locator);
|
|
1232
1216
|
catching.delete(locator);
|
|
1233
1217
|
discovered.delete(locator);
|
|
1234
|
-
announced.delete(locator);
|
|
1235
1218
|
unreadable.delete(locator);
|
|
1236
1219
|
stale.delete(locator);
|
|
1237
1220
|
repairable.delete(locator);
|
|
1238
1221
|
unpreparable.delete(locator);
|
|
1239
1222
|
scopedRefusals.delete(locator);
|
|
1240
|
-
sourceRefusals.delete(locator);
|
|
1241
|
-
declarationRefusals.delete(locator);
|
|
1242
1223
|
models.delete(locator);
|
|
1243
1224
|
liveModels.delete(locator);
|
|
1244
1225
|
invalidModels.delete(locator);
|
|
@@ -1292,8 +1273,7 @@ export function composeRuntime(options) {
|
|
|
1292
1273
|
function retyped(persisted) {
|
|
1293
1274
|
if (persisted.status === 'unavailable')
|
|
1294
1275
|
return persisted;
|
|
1295
|
-
|
|
1296
|
-
return state.status === 'unavailable' ? state : { ...persisted, types: state.types };
|
|
1276
|
+
return { ...persisted, types: schemaTypes(persisted.snapshot.schema, roomTypes) };
|
|
1297
1277
|
}
|
|
1298
1278
|
function restored(key, held) {
|
|
1299
1279
|
const persisted = retyped(held);
|
|
@@ -1327,16 +1307,9 @@ export function composeRuntime(options) {
|
|
|
1327
1307
|
const queuedBatches = new Map(queues.map((queue) => [queue.locator, queue.pending.map(({ batch }) => batch)]));
|
|
1328
1308
|
targets = held.targets;
|
|
1329
1309
|
transfers.clear();
|
|
1330
|
-
for (const [locator, record] of held.transfers) {
|
|
1331
|
-
transfers.set(locator, {
|
|
1332
|
-
...record,
|
|
1333
|
-
models: { collection: retyped(record.models.collection) },
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
1310
|
arrivals.clear();
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
keptParts.set(locator, new Map(parts));
|
|
1311
|
+
streaming.clear();
|
|
1312
|
+
startedOver.clear();
|
|
1340
1313
|
limits = held.providerLimits;
|
|
1341
1314
|
capabilities = held.capabilities;
|
|
1342
1315
|
member = held.member;
|
|
@@ -1346,8 +1319,9 @@ export function composeRuntime(options) {
|
|
|
1346
1319
|
presentations.clear();
|
|
1347
1320
|
for (const record of held.presentation)
|
|
1348
1321
|
presentations.set(record.app, record.value);
|
|
1349
|
-
conversations.hydrate(heldConversations, chatDrafts);
|
|
1322
|
+
conversations.hydrate(heldConversations, chatDrafts, held.conversationList, held.conversationCursor);
|
|
1350
1323
|
pendings.clear();
|
|
1324
|
+
landedAhead.clear();
|
|
1351
1325
|
for (const queue of queues) {
|
|
1352
1326
|
for (const { batch, state } of queue.pending) {
|
|
1353
1327
|
pendings.set(pendingKey(queue.locator, batch.id), state);
|
|
@@ -1375,7 +1349,7 @@ export function composeRuntime(options) {
|
|
|
1375
1349
|
});
|
|
1376
1350
|
const existing = rooms.get(record.locator);
|
|
1377
1351
|
if (existing === undefined)
|
|
1378
|
-
list(record,
|
|
1352
|
+
list(record, record.roomVersion !== null);
|
|
1379
1353
|
else {
|
|
1380
1354
|
rooms.set(record.locator, {
|
|
1381
1355
|
...existing,
|
|
@@ -1389,8 +1363,14 @@ export function composeRuntime(options) {
|
|
|
1389
1363
|
});
|
|
1390
1364
|
}
|
|
1391
1365
|
});
|
|
1392
|
-
|
|
1393
|
-
|
|
1366
|
+
try {
|
|
1367
|
+
for (const key of held.orphans)
|
|
1368
|
+
await opened.release(key);
|
|
1369
|
+
}
|
|
1370
|
+
catch (error) {
|
|
1371
|
+
if (!refusedForSpace(error))
|
|
1372
|
+
throw error;
|
|
1373
|
+
}
|
|
1394
1374
|
if (held.erasing || held.orphans.length > 0)
|
|
1395
1375
|
queueMicrotask(sweep);
|
|
1396
1376
|
return {
|
|
@@ -1490,14 +1470,12 @@ export function composeRuntime(options) {
|
|
|
1490
1470
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
1491
1471
|
};
|
|
1492
1472
|
}
|
|
1493
|
-
const REWRITE_LINKS = ['field', 'body'];
|
|
1494
|
-
const CHECKED_LINKS = ['field', 'section'];
|
|
1495
1473
|
async function sliceAt(record, view, queue, extra = [], moved = []) {
|
|
1496
1474
|
const local = await openLocal();
|
|
1497
1475
|
const needed = new Set([...touchedBy(queue), ...extra]);
|
|
1498
1476
|
const movedPaths = [...new Set([...movedBy(queue, record.locator), ...moved])];
|
|
1499
1477
|
if (movedPaths.length > 0) {
|
|
1500
|
-
for (const { path } of await local.referrers(record.key, view, movedPaths
|
|
1478
|
+
for (const { path } of await local.referrers(record.key, view, movedPaths)) {
|
|
1501
1479
|
needed.add(path);
|
|
1502
1480
|
}
|
|
1503
1481
|
}
|
|
@@ -1592,8 +1570,6 @@ export function composeRuntime(options) {
|
|
|
1592
1570
|
function unpairedIn(locator, batch) {
|
|
1593
1571
|
if (!queueIn(locator).some((held) => held.id === batch.id))
|
|
1594
1572
|
return false;
|
|
1595
|
-
if (pendings.get(pendingKey(locator, batch.id))?.accepted !== undefined)
|
|
1596
|
-
return false;
|
|
1597
1573
|
return !leavingIn(batch, locator) || withdrawable(locator)(batch);
|
|
1598
1574
|
}
|
|
1599
1575
|
async function together(local, settlements) {
|
|
@@ -1636,10 +1612,16 @@ export function composeRuntime(options) {
|
|
|
1636
1612
|
? read
|
|
1637
1613
|
: await stepped(slicingWith(read, overrides.set, overrides.removed));
|
|
1638
1614
|
const composition = composed(record);
|
|
1639
|
-
const
|
|
1640
|
-
|
|
1615
|
+
const shown = queue.filter((batch) => {
|
|
1616
|
+
const held = landedAhead.get(pendingKey(locator, batch.id));
|
|
1617
|
+
return (held !== undefined &&
|
|
1618
|
+
Object.entries(held).some(([path, revision]) => confirmedLayer.revisions[path] !== revision));
|
|
1619
|
+
});
|
|
1620
|
+
const unshown = queue.filter((batch) => !shown.includes(batch));
|
|
1621
|
+
const { withdrawn, kept } = await stepped(standing(confirmedLayer, unshown, withdrawable(locator), localSaves, locator, composition));
|
|
1622
|
+
const founded = kept.filter((batch) => !unshown.includes(batch));
|
|
1641
1623
|
const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
|
|
1642
|
-
const unqueued =
|
|
1624
|
+
const unqueued = shown.map((batch) => ({ batch, landed: true }));
|
|
1643
1625
|
for (const batch of withdrawn) {
|
|
1644
1626
|
unqueued.push({ batch, landed: await landedWhole(locator, batch, confirmedLayer) });
|
|
1645
1627
|
}
|
|
@@ -1704,8 +1686,10 @@ export function composeRuntime(options) {
|
|
|
1704
1686
|
commit,
|
|
1705
1687
|
apply: (answer) => {
|
|
1706
1688
|
for (const batch of previousQueue) {
|
|
1707
|
-
if (
|
|
1708
|
-
|
|
1689
|
+
if (staying.has(batch.id))
|
|
1690
|
+
continue;
|
|
1691
|
+
pendings.delete(pendingKey(locator, batch.id));
|
|
1692
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1709
1693
|
}
|
|
1710
1694
|
for (const draft of written.values())
|
|
1711
1695
|
rememberRoomDraft(draft);
|
|
@@ -1754,36 +1738,38 @@ export function composeRuntime(options) {
|
|
|
1754
1738
|
const answer = answers?.collection;
|
|
1755
1739
|
if (answer === undefined)
|
|
1756
1740
|
return held;
|
|
1757
|
-
const
|
|
1758
|
-
if (refused !== null) {
|
|
1759
|
-
const where = refused.location === undefined ? '' : ` ${refused.location}`;
|
|
1760
|
-
journal.record(`model invalid ${locator} collection ${refused.identity} ${refused.rule}${where}`);
|
|
1761
|
-
}
|
|
1741
|
+
const state = attachedModel(answer, roomTypes);
|
|
1762
1742
|
models.set(locator, state);
|
|
1763
1743
|
return { collection: state };
|
|
1764
1744
|
}
|
|
1765
|
-
function publish(local, room,
|
|
1745
|
+
function publish(local, room, reached, landing, live) {
|
|
1766
1746
|
const landed = exclusive(async () => {
|
|
1767
1747
|
if (!live()) {
|
|
1768
1748
|
caughtUpTo(room.locator);
|
|
1769
1749
|
return true;
|
|
1770
1750
|
}
|
|
1771
1751
|
const prior = replicas.get(room.locator);
|
|
1772
|
-
if (landing.
|
|
1773
|
-
(
|
|
1774
|
-
|
|
1752
|
+
if (!stands(room.locator, landing.from)) {
|
|
1753
|
+
if (landing.kind === 'refreshed') {
|
|
1754
|
+
await local.unrefresh(reached.key);
|
|
1755
|
+
return false;
|
|
1756
|
+
}
|
|
1757
|
+
caughtUpTo(room.locator);
|
|
1758
|
+
await abandoned(local, room.locator);
|
|
1775
1759
|
return false;
|
|
1776
1760
|
}
|
|
1761
|
+
const record = landing.kind === 'refreshed' && prior !== undefined
|
|
1762
|
+
? {
|
|
1763
|
+
...prior.record,
|
|
1764
|
+
roomVersion: reached.roomVersion,
|
|
1765
|
+
...(landing.remodelled ? { model: reached.model } : {}),
|
|
1766
|
+
}
|
|
1767
|
+
: reached;
|
|
1777
1768
|
adoptModels(room.locator, record);
|
|
1778
1769
|
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
1779
|
-
const roomVersion = record.roomVersion;
|
|
1780
|
-
const accepted = queue.filter((batch) => {
|
|
1781
|
-
const at = pendings.get(pendingKey(room.locator, batch.id))?.accepted;
|
|
1782
|
-
return at !== undefined && options.adapter.reached(room.locator, roomVersion, at);
|
|
1783
|
-
});
|
|
1784
1770
|
let set;
|
|
1785
1771
|
try {
|
|
1786
|
-
set = await settle(local, room.locator, prior, record, queue
|
|
1772
|
+
set = await settle(local, room.locator, prior, record, queue, landing.kind === 'refreshed'
|
|
1787
1773
|
? {
|
|
1788
1774
|
room: true,
|
|
1789
1775
|
view: 'refreshed',
|
|
@@ -1805,24 +1791,24 @@ export function composeRuntime(options) {
|
|
|
1805
1791
|
return true;
|
|
1806
1792
|
}
|
|
1807
1793
|
if (landing.kind === 'bootstrapped') {
|
|
1794
|
+
highest.delete(room.locator);
|
|
1795
|
+
beyondHorizon.delete(room.locator);
|
|
1808
1796
|
untransferred(room.locator);
|
|
1797
|
+
startedOver.delete(room.locator);
|
|
1809
1798
|
catching.delete(room.locator);
|
|
1810
|
-
|
|
1799
|
+
if (prior !== undefined && prior.record.key !== record.key)
|
|
1800
|
+
sweep();
|
|
1801
|
+
name(room.locator);
|
|
1811
1802
|
}
|
|
1812
|
-
for (const batch of accepted)
|
|
1813
|
-
pendings.delete(pendingKey(room.locator, batch.id));
|
|
1814
1803
|
stale.delete(room.locator);
|
|
1815
1804
|
unpreparable.delete(room.locator);
|
|
1816
1805
|
if (unreadable.delete(room.locator))
|
|
1817
1806
|
scopedRefusals.delete(room.locator);
|
|
1818
|
-
sourceRefusals.delete(room.locator);
|
|
1819
|
-
declarationRefusals.delete(room.locator);
|
|
1820
1807
|
list(room, true);
|
|
1821
1808
|
publishChangeSet(set);
|
|
1822
1809
|
await unpair(local);
|
|
1823
1810
|
moveTo({ ...status, ...attention() });
|
|
1824
|
-
|
|
1825
|
-
if (accepted.length > 0 && (replicas.get(room.locator)?.queue.length ?? 0) > 0) {
|
|
1811
|
+
if (landing.kind === 'bootstrapped' && (replicas.get(room.locator)?.queue.length ?? 0) > 0) {
|
|
1826
1812
|
submitLater(room.locator);
|
|
1827
1813
|
}
|
|
1828
1814
|
return true;
|
|
@@ -1834,7 +1820,7 @@ export function composeRuntime(options) {
|
|
|
1834
1820
|
const replica = replicas.get(locator);
|
|
1835
1821
|
const room = rooms.get(locator);
|
|
1836
1822
|
if (replica === undefined || room === undefined) {
|
|
1837
|
-
throw runtimeError('room
|
|
1823
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
1838
1824
|
}
|
|
1839
1825
|
return { local, replica, room };
|
|
1840
1826
|
}
|
|
@@ -1852,12 +1838,7 @@ export function composeRuntime(options) {
|
|
|
1852
1838
|
return committed(locator, target, saved, new Set(saved.map(({ path }) => path)), writtenBy);
|
|
1853
1839
|
}
|
|
1854
1840
|
async function saveParts(locator, named, writtenBy) {
|
|
1855
|
-
const read = named
|
|
1856
|
-
if (read.some(({ path }, at) => path === '' ||
|
|
1857
|
-
normalize(path) !== path ||
|
|
1858
|
-
read.findIndex((one) => one.path === path) !== at)) {
|
|
1859
|
-
throw runtimeError('room-draft-malformed');
|
|
1860
|
-
}
|
|
1841
|
+
const read = readSavedParts(named);
|
|
1861
1842
|
const target = await savedRoom(locator);
|
|
1862
1843
|
const saved = [];
|
|
1863
1844
|
const files = [];
|
|
@@ -1866,7 +1847,7 @@ export function composeRuntime(options) {
|
|
|
1866
1847
|
for (const { path, parts } of read) {
|
|
1867
1848
|
const file = parts.file === undefined
|
|
1868
1849
|
? undefined
|
|
1869
|
-
: await heldFile(parts.file.blob, binaryBase(visible, path));
|
|
1850
|
+
: await heldFile(parts.file.blob, parts.file.base === undefined ? binaryBase(visible, path) : parts.file.base);
|
|
1870
1851
|
if (parts.file !== undefined && file !== undefined) {
|
|
1871
1852
|
files.push({ key: file.localId, blob: parts.file.blob });
|
|
1872
1853
|
}
|
|
@@ -1900,14 +1881,15 @@ export function composeRuntime(options) {
|
|
|
1900
1881
|
const removed = changes.flatMap((change) => (change.kind === 'remove' ? [change.path] : []));
|
|
1901
1882
|
const referring = removed.length === 0
|
|
1902
1883
|
? []
|
|
1903
|
-
: (await local.referrers(record.key, 'confirmed', removed
|
|
1904
|
-
const confirmedLayer = await sliceAt(record, 'confirmed', replica.queue, [...paths, ...referring], moved);
|
|
1884
|
+
: (await local.referrers(record.key, 'confirmed', removed)).map(({ path }) => path);
|
|
1885
|
+
const confirmedLayer = await sliceAt(record, 'confirmed', replica.queue, [...paths, ...paths.flatMap(ancestors), ...referring], moved);
|
|
1905
1886
|
const { visible } = await replayed(record, confirmedLayer, replica.queue);
|
|
1906
1887
|
const composition = composed(record);
|
|
1907
1888
|
return {
|
|
1908
1889
|
record,
|
|
1909
1890
|
confirmed: confirmedLayer,
|
|
1910
1891
|
visible,
|
|
1892
|
+
folders: new Set(await local.folders(record.key, 'visible', paths)),
|
|
1911
1893
|
*visibleOver(queue) {
|
|
1912
1894
|
return (yield* replaying(confirmedLayer, queue, record.locator, composition)).visible;
|
|
1913
1895
|
},
|
|
@@ -1930,18 +1912,6 @@ export function composeRuntime(options) {
|
|
|
1930
1912
|
return undefined;
|
|
1931
1913
|
return entry.binary !== null ? null : entry.content;
|
|
1932
1914
|
}
|
|
1933
|
-
function walkOf(local, key, view) {
|
|
1934
|
-
return async (after) => {
|
|
1935
|
-
const page = await local.walk(key, view, after, WALK_BYTES);
|
|
1936
|
-
return {
|
|
1937
|
-
entries: page.entries.map(({ path, content, binary }) => ({
|
|
1938
|
-
path,
|
|
1939
|
-
content: binary !== null ? null : content,
|
|
1940
|
-
})),
|
|
1941
|
-
next: page.next,
|
|
1942
|
-
};
|
|
1943
|
-
};
|
|
1944
|
-
}
|
|
1945
1915
|
function lookupOf(local, key, view) {
|
|
1946
1916
|
return async (paths) => {
|
|
1947
1917
|
if (paths.length === 0)
|
|
@@ -1958,37 +1928,6 @@ export function composeRuntime(options) {
|
|
|
1958
1928
|
const read = parseFrontmatter(entry.content ?? '');
|
|
1959
1929
|
return { type: read.ok ? (read.frontmatter.type ?? 'any') : undefined, binary: false };
|
|
1960
1930
|
}
|
|
1961
|
-
function overriddenWalk(walk, overrides) {
|
|
1962
|
-
const sorted = [...overrides.keys()].sort();
|
|
1963
|
-
const beyond = (path) => {
|
|
1964
|
-
if (path === null)
|
|
1965
|
-
return 0;
|
|
1966
|
-
let low = 0;
|
|
1967
|
-
let high = sorted.length;
|
|
1968
|
-
while (low < high) {
|
|
1969
|
-
const middle = (low + high) >>> 1;
|
|
1970
|
-
if (sorted[middle] <= path)
|
|
1971
|
-
low = middle + 1;
|
|
1972
|
-
else
|
|
1973
|
-
high = middle;
|
|
1974
|
-
}
|
|
1975
|
-
return low;
|
|
1976
|
-
};
|
|
1977
|
-
return async (after) => {
|
|
1978
|
-
const page = await walk(after);
|
|
1979
|
-
const end = page.next;
|
|
1980
|
-
const added = sorted
|
|
1981
|
-
.slice(beyond(after), end === null ? sorted.length : beyond(end))
|
|
1982
|
-
.flatMap((path) => {
|
|
1983
|
-
const entry = overrides.get(path);
|
|
1984
|
-
return entry === null || entry === undefined
|
|
1985
|
-
? []
|
|
1986
|
-
: [{ path, content: entry.binary !== null ? null : entry.content }];
|
|
1987
|
-
});
|
|
1988
|
-
const entries = [...page.entries.filter(({ path }) => !overrides.has(path)), ...added].sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
1989
|
-
return { entries, next: end };
|
|
1990
|
-
};
|
|
1991
|
-
}
|
|
1992
1931
|
function overriddenLookup(lookup, overrides) {
|
|
1993
1932
|
return async (paths) => {
|
|
1994
1933
|
const known = new Map(await lookup(paths.filter((path) => !overrides.has(path))));
|
|
@@ -2006,9 +1945,8 @@ export function composeRuntime(options) {
|
|
|
2006
1945
|
}
|
|
2007
1946
|
function validatorOf(record, confirmedLayer) {
|
|
2008
1947
|
return async (resulting, touched) => {
|
|
2009
|
-
const composition = compositionFor(record);
|
|
2010
1948
|
const local = await openLocal();
|
|
2011
|
-
const
|
|
1949
|
+
const changes = [];
|
|
2012
1950
|
const overrides = new Map();
|
|
2013
1951
|
const pace = paced();
|
|
2014
1952
|
for (const path of new Set([...confirmedLayer.held, ...touched])) {
|
|
@@ -2019,19 +1957,13 @@ export function composeRuntime(options) {
|
|
|
2019
1957
|
continue;
|
|
2020
1958
|
overrides.set(path, now);
|
|
2021
1959
|
if (textOf(was) !== textOf(now))
|
|
2022
|
-
|
|
1960
|
+
changes.push({ path, was: textOf(was), now: textOf(now) });
|
|
2023
1961
|
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
texts: textsOf(local, record.key, 'confirmed'),
|
|
2030
|
-
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
2031
|
-
}));
|
|
2032
|
-
if (clean)
|
|
2033
|
-
return { verdict: 'accept' };
|
|
2034
|
-
return validatedCollection(overriddenWalk(walkOf(local, record.key, 'confirmed'), overrides), lookup, composition);
|
|
1962
|
+
return validatedNeighbourhood(changes, compositionFor(record), {
|
|
1963
|
+
lookup: overriddenLookup(lookupOf(local, record.key, 'confirmed'), overrides),
|
|
1964
|
+
texts: textsOf(local, record.key, 'confirmed'),
|
|
1965
|
+
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
1966
|
+
});
|
|
2035
1967
|
};
|
|
2036
1968
|
}
|
|
2037
1969
|
function textsOf(local, key, view) {
|
|
@@ -2042,7 +1974,7 @@ export function composeRuntime(options) {
|
|
|
2042
1974
|
}
|
|
2043
1975
|
function referrersOf(local, key, view) {
|
|
2044
1976
|
return async (targets) => [
|
|
2045
|
-
...new Set((await local.referrers(key, view, targets
|
|
1977
|
+
...new Set((await local.referrers(key, view, targets)).map(({ path }) => path)),
|
|
2046
1978
|
];
|
|
2047
1979
|
}
|
|
2048
1980
|
async function sideOf(locator, replica, room, saving) {
|
|
@@ -2055,6 +1987,7 @@ export function composeRuntime(options) {
|
|
|
2055
1987
|
operations: room.operations,
|
|
2056
1988
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
2057
1989
|
visible: saving.visible,
|
|
1990
|
+
folders: saving.folders,
|
|
2058
1991
|
queued: queuedDocuments(replica.queue),
|
|
2059
1992
|
unmodeled: await unmodeledIn(record),
|
|
2060
1993
|
validate: validatorOf(record, saving.confirmed),
|
|
@@ -2107,13 +2040,13 @@ export function composeRuntime(options) {
|
|
|
2107
2040
|
}
|
|
2108
2041
|
const moves = saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
|
|
2109
2042
|
const sourceSaving = await savingIn(replica, moves.map(({ path }) => ({ kind: 'remove', path })), saved.map(({ path }) => path), moves.map(({ path }) => path));
|
|
2110
|
-
const changed = saved.flatMap(({ path, parts }) => changedParts(path,
|
|
2043
|
+
const changed = saved.flatMap(({ path, parts }) => changedParts(path, parts, sourceSaving.visible));
|
|
2111
2044
|
if (changed.length > 0)
|
|
2112
2045
|
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
2113
2046
|
const target = replicas.get(destination);
|
|
2114
2047
|
const listedRoom = rooms.get(destination);
|
|
2115
2048
|
if (target === undefined || listedRoom === undefined) {
|
|
2116
|
-
throw runtimeError('room
|
|
2049
|
+
throw runtimeError('unknown-room', `Unknown Room ${destination}`);
|
|
2117
2050
|
}
|
|
2118
2051
|
const arriving = await savingIn(target, [], moves.map(({ to }) => to));
|
|
2119
2052
|
const built = await buildMove({
|
|
@@ -2176,10 +2109,10 @@ export function composeRuntime(options) {
|
|
|
2176
2109
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2177
2110
|
const { answer, touched } = committedBoth;
|
|
2178
2111
|
if (answer.fenced !== null) {
|
|
2179
|
-
throw runtimeError('room
|
|
2112
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2180
2113
|
}
|
|
2181
2114
|
if (fences.has(locator) || fences.has(destination)) {
|
|
2182
|
-
throw runtimeError('room
|
|
2115
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2183
2116
|
}
|
|
2184
2117
|
pendings.set(pendingKey(locator, id), {
|
|
2185
2118
|
drafts: saved,
|
|
@@ -2232,7 +2165,7 @@ export function composeRuntime(options) {
|
|
|
2232
2165
|
...saved.map(({ path }) => path),
|
|
2233
2166
|
...parts.flatMap(({ bases }) => bases.map(({ path }) => path)),
|
|
2234
2167
|
]);
|
|
2235
|
-
const changed = saved.flatMap(({ path, parts: held }) => changedParts(path,
|
|
2168
|
+
const changed = saved.flatMap(({ path, parts: held }) => changedParts(path, held, saving.visible));
|
|
2236
2169
|
if (changed.length > 0)
|
|
2237
2170
|
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
2238
2171
|
const requeue = await stepped(requeuing(replica.queue, changes, saving.visible, withdrawable(locator), saving.visibleOver));
|
|
@@ -2260,6 +2193,7 @@ export function composeRuntime(options) {
|
|
|
2260
2193
|
providerLimits: limits,
|
|
2261
2194
|
handle: room.handle,
|
|
2262
2195
|
visible: saving.visible,
|
|
2196
|
+
folders: saving.folders,
|
|
2263
2197
|
changes,
|
|
2264
2198
|
bases: parts.flatMap(({ bases }) => bases),
|
|
2265
2199
|
queued: queuedDocuments(replica.queue),
|
|
@@ -2349,10 +2283,10 @@ export function composeRuntime(options) {
|
|
|
2349
2283
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2350
2284
|
const { answer, touched } = done;
|
|
2351
2285
|
if (answer.fenced !== null) {
|
|
2352
|
-
throw runtimeError('room
|
|
2286
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2353
2287
|
}
|
|
2354
2288
|
if (fences.has(locator))
|
|
2355
|
-
throw runtimeError('room
|
|
2289
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2356
2290
|
for (const origin of withdrawn.keys())
|
|
2357
2291
|
pendings.delete(pendingKey(locator, origin));
|
|
2358
2292
|
for (const [id, drafts] of drafted) {
|
|
@@ -2401,7 +2335,7 @@ export function composeRuntime(options) {
|
|
|
2401
2335
|
throw new Error('connection refused');
|
|
2402
2336
|
await local.keepUpload(locator, identifier, upload);
|
|
2403
2337
|
if (fences.has(locator))
|
|
2404
|
-
throw runtimeError('room
|
|
2338
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2405
2339
|
const key = pendingKey(locator, identifier);
|
|
2406
2340
|
pendings.set(key, { ...pendings.get(key), upload });
|
|
2407
2341
|
if (!continuing())
|
|
@@ -2410,6 +2344,9 @@ export function composeRuntime(options) {
|
|
|
2410
2344
|
async function readHistory(locator, path, query = {}) {
|
|
2411
2345
|
if (closed || refusal !== null)
|
|
2412
2346
|
return UNCERTAIN;
|
|
2347
|
+
await firstDiscovery;
|
|
2348
|
+
if (!roomsNamed)
|
|
2349
|
+
return UNCERTAIN;
|
|
2413
2350
|
try {
|
|
2414
2351
|
await ensureAuthorization();
|
|
2415
2352
|
}
|
|
@@ -2502,6 +2439,9 @@ export function composeRuntime(options) {
|
|
|
2502
2439
|
async function transferBinary(request, transfer, answer) {
|
|
2503
2440
|
if (closed || refusal !== null)
|
|
2504
2441
|
return UNCERTAIN;
|
|
2442
|
+
await firstDiscovery;
|
|
2443
|
+
if (!roomsNamed)
|
|
2444
|
+
return UNCERTAIN;
|
|
2505
2445
|
try {
|
|
2506
2446
|
await ensureAuthorization();
|
|
2507
2447
|
}
|
|
@@ -2648,7 +2588,8 @@ export function composeRuntime(options) {
|
|
|
2648
2588
|
}
|
|
2649
2589
|
async function attachBinary(locator, file) {
|
|
2650
2590
|
await ready;
|
|
2651
|
-
|
|
2591
|
+
await firstDiscovery;
|
|
2592
|
+
if (closed || !roomsNamed || connectionOf() === 'unreachable' || refusal !== null)
|
|
2652
2593
|
return OFFLINE;
|
|
2653
2594
|
const request = await attachedFile(locator, file);
|
|
2654
2595
|
return transferBinary({ locator, path: file.name }, async (held) => {
|
|
@@ -2668,13 +2609,6 @@ export function composeRuntime(options) {
|
|
|
2668
2609
|
const freed = await database.ask({ kind: 'opened.clean' });
|
|
2669
2610
|
return freed + ((await options.shell.cleanUp?.()) ?? 0);
|
|
2670
2611
|
}
|
|
2671
|
-
function declaredAgents() {
|
|
2672
|
-
for (const capability of capabilities) {
|
|
2673
|
-
if (capability.conversationAgents !== undefined)
|
|
2674
|
-
return capability.conversationAgents;
|
|
2675
|
-
}
|
|
2676
|
-
return undefined;
|
|
2677
|
-
}
|
|
2678
2612
|
function declaredAction(name) {
|
|
2679
2613
|
for (const capability of capabilities) {
|
|
2680
2614
|
for (const action of capability.actions ?? []) {
|
|
@@ -2726,6 +2660,24 @@ export function composeRuntime(options) {
|
|
|
2726
2660
|
return UNCERTAIN;
|
|
2727
2661
|
}
|
|
2728
2662
|
}
|
|
2663
|
+
async function readConversationHistory(request) {
|
|
2664
|
+
await ready;
|
|
2665
|
+
const stream = options.adapter.conversations;
|
|
2666
|
+
if (closed || refusal !== null || stream?.history === undefined)
|
|
2667
|
+
return UNCERTAIN;
|
|
2668
|
+
journal.record('transfer conversation history');
|
|
2669
|
+
try {
|
|
2670
|
+
const result = await stream.history(request);
|
|
2671
|
+
if (result.kind === 'revoked-authorization')
|
|
2672
|
+
await refuse('revoked-authorization');
|
|
2673
|
+
journal.record(`outcome conversation history ${result.kind}`);
|
|
2674
|
+
return calledResult(result);
|
|
2675
|
+
}
|
|
2676
|
+
catch {
|
|
2677
|
+
journal.record('outcome conversation history transport-uncertainty');
|
|
2678
|
+
return UNCERTAIN;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2729
2681
|
function unsettled(result) {
|
|
2730
2682
|
return result.kind !== 'action' && result.retryable;
|
|
2731
2683
|
}
|
|
@@ -2764,10 +2716,6 @@ export function composeRuntime(options) {
|
|
|
2764
2716
|
continue;
|
|
2765
2717
|
if (closed || signal.aborted)
|
|
2766
2718
|
return 'done';
|
|
2767
|
-
if (pastHorizon(held.operation)) {
|
|
2768
|
-
await local.dropCall(held.operation);
|
|
2769
|
-
continue;
|
|
2770
|
-
}
|
|
2771
2719
|
calling.add(held.operation);
|
|
2772
2720
|
try {
|
|
2773
2721
|
if (unsettled(await invokeAction(held.name, held.input)))
|
|
@@ -2787,6 +2735,33 @@ export function composeRuntime(options) {
|
|
|
2787
2735
|
const held = replicas.get(locator);
|
|
2788
2736
|
return (held !== undefined && options.adapter.reached(locator, held.record.roomVersion, roomVersion));
|
|
2789
2737
|
}
|
|
2738
|
+
function shownAt(locator) {
|
|
2739
|
+
const held = replicas.get(locator)?.record.roomVersion;
|
|
2740
|
+
const raised = highest.get(locator);
|
|
2741
|
+
if (held === undefined || raised === undefined)
|
|
2742
|
+
return held ?? raised;
|
|
2743
|
+
return options.adapter.reached(locator, held, raised) ? held : raised;
|
|
2744
|
+
}
|
|
2745
|
+
function showing(locator, roomVersion) {
|
|
2746
|
+
const at = shownAt(locator);
|
|
2747
|
+
if (at === undefined || !options.adapter.reached(locator, at, roomVersion)) {
|
|
2748
|
+
highest.set(locator, roomVersion);
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
function older(locator, roomVersion) {
|
|
2752
|
+
if (beyondHorizon.has(locator))
|
|
2753
|
+
return false;
|
|
2754
|
+
const at = shownAt(locator);
|
|
2755
|
+
return (at !== undefined &&
|
|
2756
|
+
options.adapter.reached(locator, at, roomVersion) &&
|
|
2757
|
+
!options.adapter.reached(locator, roomVersion, at));
|
|
2758
|
+
}
|
|
2759
|
+
function replaced(locators) {
|
|
2760
|
+
for (const locator of locators) {
|
|
2761
|
+
highest.delete(locator);
|
|
2762
|
+
beyondHorizon.add(locator);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2790
2765
|
async function writtenAfter(locator, roomVersion, path) {
|
|
2791
2766
|
const held = replicas.get(locator);
|
|
2792
2767
|
if (held === undefined)
|
|
@@ -2797,115 +2772,27 @@ export function composeRuntime(options) {
|
|
|
2797
2772
|
options.adapter.reached(locator, written, roomVersion) &&
|
|
2798
2773
|
!options.adapter.reached(locator, roomVersion, written));
|
|
2799
2774
|
}
|
|
2800
|
-
function
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
else
|
|
2810
|
-
known.set(path, entry.binary !== null ? null : entry.content);
|
|
2811
|
-
}
|
|
2812
|
-
return known;
|
|
2813
|
-
};
|
|
2814
|
-
}
|
|
2815
|
-
async function unreadableRoom(texts, walk, paths) {
|
|
2816
|
-
if (paths !== null) {
|
|
2817
|
-
const text = {};
|
|
2818
|
-
for (const [path, content] of await texts(paths))
|
|
2819
|
-
if (content !== null)
|
|
2820
|
-
text[path] = content;
|
|
2821
|
-
return stepped(unreadableIn(text, Object.keys(text)));
|
|
2822
|
-
}
|
|
2823
|
-
for (let after = null;;) {
|
|
2824
|
-
const page = await walk(after);
|
|
2825
|
-
const text = {};
|
|
2826
|
-
for (const { path, content } of page.entries)
|
|
2827
|
-
if (content !== null)
|
|
2828
|
-
text[path] = content;
|
|
2829
|
-
const refused = await stepped(unreadableIn(text, Object.keys(text)));
|
|
2830
|
-
if (refused !== null)
|
|
2831
|
-
return refused;
|
|
2832
|
-
if (page.next === null)
|
|
2833
|
-
return null;
|
|
2834
|
-
after = page.next;
|
|
2835
|
-
}
|
|
2836
|
-
}
|
|
2837
|
-
async function validatedRoom(local, record, view, previous, overrides = null) {
|
|
2838
|
-
const composition = composed(record);
|
|
2839
|
-
const modeled = record.model.status !== 'unavailable' || record.typedSelection !== null;
|
|
2840
|
-
const lookup = overrides === null
|
|
2841
|
-
? lookupOf(local, record.key, view)
|
|
2842
|
-
: overriddenLookup(lookupOf(local, record.key, view), overrides);
|
|
2843
|
-
const texts = overrides === null
|
|
2844
|
-
? textsOf(local, record.key, view)
|
|
2845
|
-
: overriddenTexts(textsOf(local, record.key, view), overrides);
|
|
2846
|
-
const walk = overrides === null
|
|
2847
|
-
? walkOf(local, record.key, view)
|
|
2848
|
-
: overriddenWalk(walkOf(local, record.key, view), overrides);
|
|
2849
|
-
let changes = null;
|
|
2850
|
-
let reshaped = false;
|
|
2851
|
-
if (previous !== null && previous.changed.length <= CLEAN_CHANGES) {
|
|
2852
|
-
const [was, now] = await Promise.all([
|
|
2853
|
-
local.entries(record.key, 'confirmed', previous.changed),
|
|
2854
|
-
overrides === null
|
|
2855
|
-
? local.entries(record.key, view, previous.changed)
|
|
2856
|
-
: Promise.resolve([]),
|
|
2857
|
-
]);
|
|
2858
|
-
const before = new Map(was.map((entry) => [entry.path, entry]));
|
|
2859
|
-
const after = overrides === null
|
|
2860
|
-
? new Map(now.map((entry) => [entry.path, entry]))
|
|
2861
|
-
: overrides;
|
|
2862
|
-
changes = previous.changed.flatMap((path) => {
|
|
2863
|
-
const one = before.get(path) ?? null;
|
|
2864
|
-
const other = after.get(path) ?? null;
|
|
2865
|
-
reshaped ||= textDiffers(one, other);
|
|
2866
|
-
const held = textOf(one);
|
|
2867
|
-
const next = textOf(other);
|
|
2868
|
-
return held === next ? [] : [{ path, was: held, now: next }];
|
|
2869
|
-
});
|
|
2870
|
-
}
|
|
2871
|
-
const named = changes === null ? null : changes.map(({ path }) => path);
|
|
2872
|
-
const textChanged = changes === null || changes.length > 0 || reshaped;
|
|
2873
|
-
const modelsChanged = previous === null || !sameComposition(composed(previous.record), composition);
|
|
2874
|
-
if (modeled && (textChanged || modelsChanged)) {
|
|
2875
|
-
const clean = previous !== null &&
|
|
2876
|
-
changes !== null &&
|
|
2877
|
-
(await changedCleanly(changes, composed(previous.record), composition, {
|
|
2878
|
-
lookup,
|
|
2879
|
-
texts,
|
|
2880
|
-
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
2881
|
-
}));
|
|
2882
|
-
if (clean)
|
|
2883
|
-
return null;
|
|
2884
|
-
const verdict = await validatedCollection(walk, lookup, composition);
|
|
2885
|
-
if (verdict.verdict === 'accept')
|
|
2886
|
-
return null;
|
|
2887
|
-
if (verdict.rule !== 'model-unavailable')
|
|
2888
|
-
return { kind: 'refused', refusal: verdict };
|
|
2889
|
-
return unreadableRoom(texts, walk, named);
|
|
2890
|
-
}
|
|
2891
|
-
if (textChanged)
|
|
2892
|
-
return unreadableRoom(texts, walk, named);
|
|
2893
|
-
return null;
|
|
2775
|
+
async function writtenLater(local, locator, key, paths, roomVersion) {
|
|
2776
|
+
if (paths.length === 0 || !older(locator, roomVersion))
|
|
2777
|
+
return new Set();
|
|
2778
|
+
const entries = await local.entries(key, 'confirmed', paths);
|
|
2779
|
+
return new Set(entries.flatMap(({ path, written }) => written !== null &&
|
|
2780
|
+
options.adapter.reached(locator, written, roomVersion) &&
|
|
2781
|
+
!options.adapter.reached(locator, roomVersion, written)
|
|
2782
|
+
? [path]
|
|
2783
|
+
: []));
|
|
2894
2784
|
}
|
|
2895
|
-
function refreshingOf(local, key, composition, liveness, replaying =
|
|
2896
|
-
const write = replaying
|
|
2785
|
+
function refreshingOf(local, locator, key, composition, liveness, replaying = false) {
|
|
2786
|
+
const write = replaying ? finishingWrite : exclusive;
|
|
2897
2787
|
return {
|
|
2898
|
-
|
|
2899
|
-
if (replaying
|
|
2900
|
-
|
|
2901
|
-
installing(replaying);
|
|
2902
|
-
await screened();
|
|
2788
|
+
turn() {
|
|
2789
|
+
if (replaying)
|
|
2790
|
+
installing(locator);
|
|
2903
2791
|
},
|
|
2904
2792
|
prior: async (paths) => new Map((await local.entries(key, 'refreshed', paths)).map((entry) => [entry.path, entry])),
|
|
2793
|
+
later: (paths, roomVersion) => writtenLater(local, locator, key, paths, roomVersion),
|
|
2905
2794
|
async stage(page) {
|
|
2906
2795
|
for (let written = 0;;) {
|
|
2907
|
-
if (replaying !== null)
|
|
2908
|
-
await screened();
|
|
2909
2796
|
const end = batchEnd(page.entries, written);
|
|
2910
2797
|
const last = end === page.entries.length;
|
|
2911
2798
|
const entries = page.entries.slice(written, end);
|
|
@@ -2950,25 +2837,74 @@ export function composeRuntime(options) {
|
|
|
2950
2837
|
return done;
|
|
2951
2838
|
}
|
|
2952
2839
|
function installWrite(work) {
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2840
|
+
return installTurn(installTurns.starting, work);
|
|
2841
|
+
}
|
|
2842
|
+
function finishingWrite(work) {
|
|
2843
|
+
return installTurn(installTurns.finishing, work);
|
|
2844
|
+
}
|
|
2845
|
+
function installTurn(queue, work) {
|
|
2846
|
+
return new Promise((resolve, reject) => {
|
|
2847
|
+
queue.push(() => void exclusive(work)
|
|
2848
|
+
.then(resolve, reject)
|
|
2849
|
+
.finally(() => {
|
|
2850
|
+
installTurns.busy = false;
|
|
2851
|
+
nextInstallTurn();
|
|
2852
|
+
}));
|
|
2853
|
+
nextInstallTurn();
|
|
2854
|
+
});
|
|
2962
2855
|
}
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2856
|
+
function nextInstallTurn() {
|
|
2857
|
+
if (installTurns.busy || installTurns.ahead > 0)
|
|
2858
|
+
return;
|
|
2859
|
+
const turn = installTurns.finishing.shift() ?? installTurns.starting.shift();
|
|
2860
|
+
if (turn === undefined)
|
|
2861
|
+
return;
|
|
2862
|
+
installTurns.busy = true;
|
|
2863
|
+
turn();
|
|
2864
|
+
}
|
|
2865
|
+
async function ahead(work) {
|
|
2866
|
+
let taken = false;
|
|
2867
|
+
const hold = {
|
|
2868
|
+
take() {
|
|
2869
|
+
if (taken)
|
|
2870
|
+
return;
|
|
2871
|
+
taken = true;
|
|
2872
|
+
installTurns.ahead += 1;
|
|
2873
|
+
},
|
|
2874
|
+
release() {
|
|
2875
|
+
if (!taken)
|
|
2876
|
+
return;
|
|
2877
|
+
taken = false;
|
|
2878
|
+
installTurns.ahead -= 1;
|
|
2879
|
+
resumeInstall();
|
|
2880
|
+
},
|
|
2881
|
+
};
|
|
2882
|
+
hold.take();
|
|
2883
|
+
try {
|
|
2884
|
+
return await work(hold);
|
|
2966
2885
|
}
|
|
2967
|
-
|
|
2886
|
+
finally {
|
|
2887
|
+
hold.release();
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
function resumeInstall() {
|
|
2891
|
+
if (installTurns.ahead > 0)
|
|
2892
|
+
return;
|
|
2893
|
+
void yielded().then(nextInstallTurn);
|
|
2894
|
+
}
|
|
2895
|
+
async function heldStream() {
|
|
2896
|
+
while (streamsFree === 0)
|
|
2897
|
+
await new Promise((admit) => installWaiters.push(admit));
|
|
2898
|
+
streamsFree -= 1;
|
|
2968
2899
|
return once(() => {
|
|
2969
|
-
|
|
2900
|
+
streamsFree += 1;
|
|
2970
2901
|
});
|
|
2971
2902
|
}
|
|
2903
|
+
async function unqueued() {
|
|
2904
|
+
while (queuedText >= QUEUED_TEXT) {
|
|
2905
|
+
await new Promise((admit) => installWaiters.push(admit));
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2972
2908
|
function once(release) {
|
|
2973
2909
|
let released = false;
|
|
2974
2910
|
return () => {
|
|
@@ -2976,58 +2912,86 @@ export function composeRuntime(options) {
|
|
|
2976
2912
|
return;
|
|
2977
2913
|
released = true;
|
|
2978
2914
|
release();
|
|
2979
|
-
for (const admit of
|
|
2915
|
+
for (const admit of installWaiters.splice(0))
|
|
2980
2916
|
admit();
|
|
2981
2917
|
};
|
|
2982
2918
|
}
|
|
2983
|
-
function installing(locator
|
|
2984
|
-
installs.
|
|
2919
|
+
function installing(locator) {
|
|
2920
|
+
installs.add(locator);
|
|
2985
2921
|
queueReadiness();
|
|
2986
2922
|
}
|
|
2987
2923
|
function untransferred(locator) {
|
|
2988
2924
|
transfers.delete(locator);
|
|
2989
|
-
|
|
2925
|
+
arrivals.delete(locator);
|
|
2926
|
+
streaming.delete(locator);
|
|
2990
2927
|
underived.delete(locator);
|
|
2928
|
+
unindexed.delete(locator);
|
|
2929
|
+
}
|
|
2930
|
+
function setBack(locator) {
|
|
2931
|
+
const now = bytesOf(locator, true);
|
|
2932
|
+
const earlier = startedOver.get(locator);
|
|
2933
|
+
startedOver.set(locator, {
|
|
2934
|
+
done: Math.max(now.done, earlier?.done ?? 0),
|
|
2935
|
+
total: Math.max(now.total, earlier?.total ?? 0),
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
function owedIndexes(locator, bytes, text) {
|
|
2939
|
+
if (bytes <= 0 || text <= 0)
|
|
2940
|
+
return;
|
|
2941
|
+
unindexed.set(locator, [...(unindexed.get(locator) ?? []), { bytes, text }]);
|
|
2942
|
+
}
|
|
2943
|
+
function built(locator, { done, text }) {
|
|
2944
|
+
const owed = unindexed.get(locator) ?? [];
|
|
2945
|
+
let left = text;
|
|
2946
|
+
while (!done && left > 0 && owed.length > 0) {
|
|
2947
|
+
const [first] = owed;
|
|
2948
|
+
if (left >= first.text) {
|
|
2949
|
+
left -= first.text;
|
|
2950
|
+
owed.shift();
|
|
2951
|
+
continue;
|
|
2952
|
+
}
|
|
2953
|
+
owed[0] = {
|
|
2954
|
+
bytes: (first.bytes * (first.text - left)) / first.text,
|
|
2955
|
+
text: first.text - left,
|
|
2956
|
+
};
|
|
2957
|
+
left = 0;
|
|
2958
|
+
}
|
|
2959
|
+
if (done || owed.length === 0)
|
|
2960
|
+
unindexed.delete(locator);
|
|
2961
|
+
queueReadiness();
|
|
2991
2962
|
}
|
|
2992
2963
|
function installed(locator) {
|
|
2993
2964
|
if (installs.delete(locator))
|
|
2994
2965
|
queueReadiness();
|
|
2995
2966
|
}
|
|
2996
|
-
function
|
|
2997
|
-
if (value === onScreen)
|
|
2998
|
-
return;
|
|
2999
|
-
onScreen = value;
|
|
3000
|
-
journal.record(`installation ${value ? 'shown' : 'hidden'}`);
|
|
3001
|
-
queueReadiness();
|
|
3002
|
-
if (!value)
|
|
3003
|
-
return;
|
|
3004
|
-
for (const resolve of shownWaiters.splice(0))
|
|
3005
|
-
resolve();
|
|
3006
|
-
}
|
|
3007
|
-
async function pullRefresh(authorized, held, liveness) {
|
|
2967
|
+
async function pullRefresh(authorized, held, liveness, hold, refresh) {
|
|
3008
2968
|
if (!liveness())
|
|
3009
2969
|
return unconfirmed;
|
|
3010
|
-
const turn = generation;
|
|
3011
2970
|
const { record } = held;
|
|
3012
|
-
const
|
|
2971
|
+
const from = standingOf(record.locator);
|
|
3013
2972
|
const local = await openLocal();
|
|
3014
2973
|
try {
|
|
3015
2974
|
await exclusive(() => local.unrefresh(record.key));
|
|
3016
|
-
const result = await advancement(
|
|
2975
|
+
const result = await advancement(async (authorization, request) => {
|
|
2976
|
+
hold.release();
|
|
2977
|
+
try {
|
|
2978
|
+
return await refresh(authorization, request);
|
|
2979
|
+
}
|
|
2980
|
+
finally {
|
|
2981
|
+
hold.take();
|
|
2982
|
+
}
|
|
2983
|
+
}, authorized, record, (models, kept) => (liveness() ? remodel(record.locator, models, kept) : kept), liveness, refreshingOf(local, record.locator, record.key, composed(record), liveness));
|
|
3017
2984
|
if (result.kind !== 'advanced') {
|
|
3018
2985
|
journal.record(`transfer refresh ${record.locator} ${result.kind}`);
|
|
3019
2986
|
return result;
|
|
3020
2987
|
}
|
|
3021
|
-
const refused = await validatedRoom(local, result.record, 'refreshed', {
|
|
3022
|
-
record,
|
|
3023
|
-
changed: [...result.changed],
|
|
3024
|
-
});
|
|
3025
|
-
if (refused !== null) {
|
|
3026
|
-
journal.record(`transfer refresh ${record.locator} refused`);
|
|
3027
|
-
return refused;
|
|
3028
|
-
}
|
|
3029
2988
|
journal.record(`transfer refresh ${record.locator} ${result.record.roomVersion}`);
|
|
3030
|
-
return {
|
|
2989
|
+
return {
|
|
2990
|
+
kind: 'advanced',
|
|
2991
|
+
record: result.record,
|
|
2992
|
+
from,
|
|
2993
|
+
remodelled: result.record.model !== record.model,
|
|
2994
|
+
};
|
|
3031
2995
|
}
|
|
3032
2996
|
catch (error) {
|
|
3033
2997
|
if (refusedForSpace(error))
|
|
@@ -3035,9 +2999,6 @@ export function composeRuntime(options) {
|
|
|
3035
2999
|
journal.record(`transfer refresh ${record.locator} transport-uncertainty`);
|
|
3036
3000
|
throw error;
|
|
3037
3001
|
}
|
|
3038
|
-
finally {
|
|
3039
|
-
reads.finished(record.locator, turn);
|
|
3040
|
-
}
|
|
3041
3002
|
}
|
|
3042
3003
|
const heldContents = new Map();
|
|
3043
3004
|
function heldContentOf(local, key) {
|
|
@@ -3055,29 +3016,26 @@ export function composeRuntime(options) {
|
|
|
3055
3016
|
heldContents.set(key, made);
|
|
3056
3017
|
return made;
|
|
3057
3018
|
}
|
|
3058
|
-
async function
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
arrivals.get(locator)?.delete(token);
|
|
3065
|
-
}
|
|
3066
|
-
queueReadiness();
|
|
3019
|
+
async function abandoned(local, locator) {
|
|
3020
|
+
await local.abandon(locator);
|
|
3021
|
+
setBack(locator);
|
|
3022
|
+
untransferred(locator);
|
|
3023
|
+
liveQueries.recover();
|
|
3024
|
+
sweep();
|
|
3067
3025
|
}
|
|
3068
3026
|
function staging(local, liveness) {
|
|
3069
3027
|
const derived = async (locator, key, composition) => {
|
|
3070
3028
|
underived.delete(locator);
|
|
3071
3029
|
for (;;) {
|
|
3072
|
-
await screened();
|
|
3073
3030
|
if (!liveness())
|
|
3074
3031
|
return false;
|
|
3075
|
-
const done = await
|
|
3076
|
-
? timed(deriveBudget, () => local.rederive(key, composition, deriveBudget.bytes))
|
|
3032
|
+
const done = await finishingWrite(async () => liveness()
|
|
3033
|
+
? timed(deriveBudget, () => local.rederive(key, composition, deriveBudget.bytes, DERIVE_LONGEST_MS, DERIVE_FLOOR))
|
|
3077
3034
|
: null);
|
|
3078
3035
|
if (done === null)
|
|
3079
3036
|
return false;
|
|
3080
|
-
|
|
3037
|
+
built(locator, done);
|
|
3038
|
+
if (done.done)
|
|
3081
3039
|
return true;
|
|
3082
3040
|
}
|
|
3083
3041
|
};
|
|
@@ -3085,28 +3043,23 @@ export function composeRuntime(options) {
|
|
|
3085
3043
|
held: (locator) => transfers.get(locator) ?? null,
|
|
3086
3044
|
async turn(locator, step) {
|
|
3087
3045
|
installing(locator);
|
|
3088
|
-
|
|
3089
|
-
await screened();
|
|
3090
|
-
const release = step === 'downloading' ? await heldPart() : () => { };
|
|
3091
|
-
if (onScreen || closed)
|
|
3092
|
-
return release;
|
|
3093
|
-
release();
|
|
3094
|
-
}
|
|
3046
|
+
return step === 'downloading' ? heldStream() : () => { };
|
|
3095
3047
|
},
|
|
3096
|
-
|
|
3097
|
-
|
|
3048
|
+
unqueued,
|
|
3049
|
+
queued(text) {
|
|
3050
|
+
queuedText += text;
|
|
3098
3051
|
return once(() => {
|
|
3099
|
-
|
|
3052
|
+
queuedText -= text;
|
|
3100
3053
|
});
|
|
3101
3054
|
},
|
|
3102
3055
|
arriving(locator) {
|
|
3103
3056
|
let ended = false;
|
|
3104
3057
|
return {
|
|
3105
|
-
at(
|
|
3106
|
-
if (ended
|
|
3058
|
+
at(bytes) {
|
|
3059
|
+
if (ended)
|
|
3107
3060
|
return;
|
|
3108
|
-
|
|
3109
|
-
|
|
3061
|
+
arrivals.set(locator, bytes);
|
|
3062
|
+
streaming.add(locator);
|
|
3110
3063
|
const now = performance.now();
|
|
3111
3064
|
if (now - arrivalReported < ARRIVAL_REPORT_MS)
|
|
3112
3065
|
return;
|
|
@@ -3114,45 +3067,38 @@ export function composeRuntime(options) {
|
|
|
3114
3067
|
queueReadiness();
|
|
3115
3068
|
},
|
|
3116
3069
|
end() {
|
|
3070
|
+
if (ended)
|
|
3071
|
+
return;
|
|
3117
3072
|
ended = true;
|
|
3118
|
-
if (
|
|
3073
|
+
if (streaming.delete(locator))
|
|
3119
3074
|
queueReadiness();
|
|
3120
3075
|
},
|
|
3121
3076
|
};
|
|
3122
3077
|
},
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
? { local: earlier.local, parts: [...earlier.parts, ...later.parts] }
|
|
3126
|
-
: null, keptTogether),
|
|
3127
|
-
announced(locator) {
|
|
3128
|
-
const later = announced.get(locator);
|
|
3129
|
-
return later === undefined ? [] : [later];
|
|
3130
|
-
},
|
|
3131
|
-
async stage(record, page, composition, written) {
|
|
3078
|
+
streamed: options.adapter.roomStream !== undefined,
|
|
3079
|
+
async stage(record, page, composition) {
|
|
3132
3080
|
const { locator } = record;
|
|
3133
|
-
installing(locator
|
|
3134
|
-
await screened();
|
|
3081
|
+
installing(locator);
|
|
3135
3082
|
if (!liveness())
|
|
3136
3083
|
return null;
|
|
3137
3084
|
const key = await installWrite(async () => {
|
|
3138
3085
|
if (!liveness())
|
|
3139
3086
|
return null;
|
|
3140
|
-
return local.stage(record, composition, page
|
|
3087
|
+
return local.stage(record, composition, page);
|
|
3141
3088
|
});
|
|
3142
3089
|
if (key === null || !liveness())
|
|
3143
3090
|
return null;
|
|
3144
3091
|
const arrived = !transfers.has(locator);
|
|
3092
|
+
const prior = transfers.get(locator)?.done.bytes ?? 0;
|
|
3145
3093
|
const held = { ...record, key };
|
|
3146
3094
|
transfers.set(locator, held);
|
|
3147
|
-
if (written !== null) {
|
|
3148
|
-
keptParts.get(locator)?.delete(written);
|
|
3149
|
-
arrivals.get(locator)?.delete(written);
|
|
3150
|
-
}
|
|
3151
3095
|
if (arrived)
|
|
3152
3096
|
liveQueries.recover();
|
|
3153
3097
|
installing(locator);
|
|
3154
3098
|
liveQueries.recover();
|
|
3155
|
-
const
|
|
3099
|
+
const text = page.entries.reduce((sum, entry) => sum + (entry.content?.length ?? 0), 0);
|
|
3100
|
+
owedIndexes(locator, record.done.bytes - prior, text);
|
|
3101
|
+
const owed = text + (underived.get(locator) ?? 0);
|
|
3156
3102
|
underived.set(locator, owed);
|
|
3157
3103
|
if (owed < deriveBudget.bytes)
|
|
3158
3104
|
return held;
|
|
@@ -3161,29 +3107,24 @@ export function composeRuntime(options) {
|
|
|
3161
3107
|
derive: derived,
|
|
3162
3108
|
async refreshing(locator, key, composition) {
|
|
3163
3109
|
await exclusive(() => local.unrefresh(key));
|
|
3164
|
-
return refreshingOf(local, key, composition, liveness,
|
|
3110
|
+
return refreshingOf(local, locator, key, composition, liveness, true);
|
|
3165
3111
|
},
|
|
3166
3112
|
abandon: (locator) => exclusive(async () => {
|
|
3167
|
-
if (
|
|
3168
|
-
|
|
3169
|
-
await local.abandon(locator);
|
|
3170
|
-
untransferred(locator);
|
|
3171
|
-
liveQueries.recover();
|
|
3172
|
-
sweep();
|
|
3113
|
+
if (liveness())
|
|
3114
|
+
await abandoned(local, locator);
|
|
3173
3115
|
}),
|
|
3174
|
-
setAside:
|
|
3116
|
+
setAside: async (locator) => {
|
|
3175
3117
|
if (!liveness())
|
|
3176
3118
|
return;
|
|
3177
|
-
|
|
3119
|
+
setBack(locator);
|
|
3178
3120
|
untransferred(locator);
|
|
3179
3121
|
liveQueries.recover();
|
|
3180
|
-
}
|
|
3122
|
+
},
|
|
3181
3123
|
release: (key) => exclusive(async () => {
|
|
3182
3124
|
await local.release(key);
|
|
3183
3125
|
sweep();
|
|
3184
3126
|
}),
|
|
3185
3127
|
content: (key) => heldContentOf(local, key),
|
|
3186
|
-
validate: (record, view) => validatedRoom(local, record, view, null),
|
|
3187
3128
|
};
|
|
3188
3129
|
}
|
|
3189
3130
|
function reusable(room, local) {
|
|
@@ -3195,15 +3136,21 @@ export function composeRuntime(options) {
|
|
|
3195
3136
|
async function pullBootstrap(authorized, room, local, liveness) {
|
|
3196
3137
|
if (!liveness())
|
|
3197
3138
|
return unconfirmed;
|
|
3198
|
-
const turn = generation;
|
|
3199
3139
|
if (replicas.has(room.locator)) {
|
|
3200
3140
|
catching.add(room.locator);
|
|
3201
3141
|
liveQueries.recover();
|
|
3202
3142
|
}
|
|
3203
3143
|
try {
|
|
3204
|
-
const result = await bootstrapRoom(reads, authorized, room, { collection: modelOf(room.locator) }, builtIn, (models, held) => (liveness() ? remodel(room.locator, models, held) : held), liveness, staging(local, liveness), reusable(room, local));
|
|
3144
|
+
const result = await bootstrapRoom(reads, authorized, room, { collection: modelOf(room.locator) }, builtIn, typedPaths, (models, held) => (liveness() ? remodel(room.locator, models, held) : held), liveness, staging(local, liveness), reusable(room, local));
|
|
3205
3145
|
const detail = result.kind === 'staged' ? result.record.roomVersion : result.kind;
|
|
3206
3146
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
3147
|
+
if (result.kind === 'staged' && older(room.locator, result.record.roomVersion)) {
|
|
3148
|
+
caughtUpTo(room.locator);
|
|
3149
|
+
installed(room.locator);
|
|
3150
|
+
await abandoned(local, room.locator);
|
|
3151
|
+
journal.record(`transfer bootstrap ${room.locator} older than shown`);
|
|
3152
|
+
return { kind: 'transport-uncertainty' };
|
|
3153
|
+
}
|
|
3207
3154
|
if (result.kind !== 'staged') {
|
|
3208
3155
|
caughtUpTo(room.locator);
|
|
3209
3156
|
installed(room.locator);
|
|
@@ -3218,9 +3165,6 @@ export function composeRuntime(options) {
|
|
|
3218
3165
|
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
3219
3166
|
throw error;
|
|
3220
3167
|
}
|
|
3221
|
-
finally {
|
|
3222
|
-
reads.finished(room.locator, turn);
|
|
3223
|
-
}
|
|
3224
3168
|
}
|
|
3225
3169
|
function caughtUpTo(locator) {
|
|
3226
3170
|
if (catching.delete(locator))
|
|
@@ -3228,72 +3172,83 @@ export function composeRuntime(options) {
|
|
|
3228
3172
|
}
|
|
3229
3173
|
function behind(room) {
|
|
3230
3174
|
const held = replicas.get(room.locator)?.record.roomVersion;
|
|
3231
|
-
return
|
|
3232
|
-
[announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later)));
|
|
3175
|
+
return held !== undefined && !options.adapter.reached(room.locator, held, room.roomVersion);
|
|
3233
3176
|
}
|
|
3234
|
-
|
|
3235
|
-
const
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
return
|
|
3177
|
+
function standingOf(locator) {
|
|
3178
|
+
const held = replicas.get(locator);
|
|
3179
|
+
return { key: held?.record.key ?? null, confirmed: held?.confirmed ?? 0 };
|
|
3180
|
+
}
|
|
3181
|
+
function stands(locator, from) {
|
|
3182
|
+
const now = standingOf(locator);
|
|
3183
|
+
return now.key === from.key && now.confirmed === from.confirmed;
|
|
3241
3184
|
}
|
|
3242
|
-
async function
|
|
3185
|
+
async function advance(room, local, authorized, running) {
|
|
3243
3186
|
const live = () => running() && !full;
|
|
3244
3187
|
if (!live())
|
|
3245
3188
|
return unconfirmed;
|
|
3246
3189
|
const held = replicas.get(room.locator);
|
|
3247
|
-
if (held !== undefined) {
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
emitRooms();
|
|
3259
|
-
}
|
|
3260
|
-
return null;
|
|
3261
|
-
}
|
|
3262
|
-
if (refreshed.kind === 'advanced') {
|
|
3263
|
-
const landing = { kind: 'refreshed', base: refreshed.base };
|
|
3264
|
-
if (!(await publish(local, room, refreshed.record, landing, live))) {
|
|
3265
|
-
const now = replicas.get(room.locator)?.record.roomVersion;
|
|
3266
|
-
const reached = now !== undefined &&
|
|
3267
|
-
options.adapter.reached(room.locator, now, refreshed.record.roomVersion);
|
|
3268
|
-
return reached ? null : advance(room, local, authorized, live);
|
|
3269
|
-
}
|
|
3270
|
-
await retainModel(local, room, modelOf(room.locator));
|
|
3271
|
-
return null;
|
|
3272
|
-
}
|
|
3273
|
-
if (refreshed.kind !== 'beyond-horizon')
|
|
3190
|
+
if (held !== undefined && options.adapter.roomStream !== undefined) {
|
|
3191
|
+
stale.delete(room.locator);
|
|
3192
|
+
name(room.locator);
|
|
3193
|
+
return null;
|
|
3194
|
+
}
|
|
3195
|
+
const refresh = reads.refresh;
|
|
3196
|
+
if (held !== undefined && refresh !== undefined) {
|
|
3197
|
+
const refreshed = await ahead((hold) => refreshedRoom(room, local, authorized, held, live, hold, refresh));
|
|
3198
|
+
if (refreshed === 'behind')
|
|
3199
|
+
return advance(room, local, authorized, live);
|
|
3200
|
+
if (refreshed !== 'beyond-horizon')
|
|
3274
3201
|
return refreshed;
|
|
3275
|
-
if (
|
|
3276
|
-
owed.add(room.locator);
|
|
3277
|
-
liveQueries.recover();
|
|
3278
|
-
return unconfirmed;
|
|
3279
|
-
}
|
|
3280
|
-
if (reloadsDue.get(room.locator) === generation)
|
|
3202
|
+
if (reloadsDue.has(room.locator))
|
|
3281
3203
|
return unconfirmed;
|
|
3204
|
+
replaced([room.locator]);
|
|
3282
3205
|
}
|
|
3206
|
+
const from = standingOf(room.locator);
|
|
3283
3207
|
const bootstrapped = await pullBootstrap(authorized, room, local, live);
|
|
3284
|
-
if (bootstrapped.kind
|
|
3285
|
-
|
|
3208
|
+
if (bootstrapped.kind !== 'staged')
|
|
3209
|
+
return live() ? bootstrapped : unconfirmed;
|
|
3210
|
+
const landing = { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from };
|
|
3211
|
+
if (await publish(local, room, bootstrapped.record, landing, live)) {
|
|
3286
3212
|
return live() ? null : unconfirmed;
|
|
3287
3213
|
}
|
|
3288
|
-
return
|
|
3214
|
+
return reachedBy(room.locator, bootstrapped.record.roomVersion)
|
|
3215
|
+
? null
|
|
3216
|
+
: advance(room, local, authorized, live);
|
|
3217
|
+
}
|
|
3218
|
+
async function refreshedRoom(room, local, authorized, held, live, hold, refresh) {
|
|
3219
|
+
const refreshed = await pullRefresh(authorized, held, live, hold, refresh);
|
|
3220
|
+
if (!live())
|
|
3221
|
+
return unconfirmed;
|
|
3222
|
+
if (refreshed.kind === 'still-current') {
|
|
3223
|
+
stale.delete(room.locator);
|
|
3224
|
+
if (unreadable.delete(room.locator)) {
|
|
3225
|
+
scopedRefusals.delete(room.locator);
|
|
3226
|
+
list(room, true);
|
|
3227
|
+
emitRooms();
|
|
3228
|
+
}
|
|
3229
|
+
return null;
|
|
3230
|
+
}
|
|
3231
|
+
if (refreshed.kind === 'advanced') {
|
|
3232
|
+
const landing = {
|
|
3233
|
+
kind: 'refreshed',
|
|
3234
|
+
from: refreshed.from,
|
|
3235
|
+
remodelled: refreshed.remodelled,
|
|
3236
|
+
};
|
|
3237
|
+
if (!(await publish(local, room, refreshed.record, landing, live))) {
|
|
3238
|
+
const now = replicas.get(room.locator)?.record.roomVersion;
|
|
3239
|
+
const reached = now !== undefined &&
|
|
3240
|
+
options.adapter.reached(room.locator, now, refreshed.record.roomVersion);
|
|
3241
|
+
return reached ? null : 'behind';
|
|
3242
|
+
}
|
|
3243
|
+
await retainModel(local, room, modelOf(room.locator));
|
|
3244
|
+
return null;
|
|
3245
|
+
}
|
|
3246
|
+
return refreshed.kind === 'beyond-horizon' ? 'beyond-horizon' : refreshed;
|
|
3289
3247
|
}
|
|
3290
3248
|
async function withhold(withheld, room, local, progress) {
|
|
3291
|
-
if (withheld.kind === 'unconfirmed')
|
|
3249
|
+
if (withheld.kind === 'unconfirmed' || isFenced(room.locator))
|
|
3292
3250
|
return;
|
|
3293
|
-
|
|
3294
|
-
stale.delete(room.locator);
|
|
3295
|
-
else
|
|
3296
|
-
stale.add(room.locator);
|
|
3251
|
+
stale.add(room.locator);
|
|
3297
3252
|
if (withheld.kind === 'transport-uncertainty' || withheld.kind === 'retryable-refusal') {
|
|
3298
3253
|
const reset = withheld.kind === 'retryable-refusal' ? withheld.reset : undefined;
|
|
3299
3254
|
repairable.set(room.locator, reset);
|
|
@@ -3315,33 +3270,11 @@ export function composeRuntime(options) {
|
|
|
3315
3270
|
list(room, false);
|
|
3316
3271
|
emitRooms();
|
|
3317
3272
|
}
|
|
3318
|
-
else if (withheld.kind === 'refused') {
|
|
3319
|
-
refusedSource(room, withheld);
|
|
3320
|
-
}
|
|
3321
3273
|
else if (withheld.kind === 'revoked-authorization') {
|
|
3322
3274
|
await refuse('revoked-authorization');
|
|
3323
3275
|
}
|
|
3324
3276
|
}
|
|
3325
|
-
function refusedSource(room, { refusal }) {
|
|
3326
|
-
sourceRefusals.set(room.locator, {
|
|
3327
|
-
code: refusal.rule,
|
|
3328
|
-
retryable: false,
|
|
3329
|
-
fields: {
|
|
3330
|
-
file: refusal.file,
|
|
3331
|
-
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
3332
|
-
...(refusal.referrers === undefined ? {} : { referrers: refusal.referrers }),
|
|
3333
|
-
},
|
|
3334
|
-
});
|
|
3335
|
-
declarationRefusals.delete(room.locator);
|
|
3336
|
-
unreadable.add(room.locator);
|
|
3337
|
-
list(room, false);
|
|
3338
|
-
emitRooms();
|
|
3339
|
-
moveTo({ ...status, ...attention() });
|
|
3340
|
-
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
3341
|
-
}
|
|
3342
3277
|
function leaving(batch, result) {
|
|
3343
|
-
if (result.kind === 'expired')
|
|
3344
|
-
return { batch, returned: returnedBy('expired') };
|
|
3345
3278
|
return {
|
|
3346
3279
|
batch,
|
|
3347
3280
|
returned: {
|
|
@@ -3360,8 +3293,8 @@ export function composeRuntime(options) {
|
|
|
3360
3293
|
function settling(acceptance) {
|
|
3361
3294
|
return acceptance !== null && 'apply' in acceptance;
|
|
3362
3295
|
}
|
|
3363
|
-
function
|
|
3364
|
-
return settling(acceptance) ?
|
|
3296
|
+
function failureOf(acceptance) {
|
|
3297
|
+
return settling(acceptance) ? null : acceptance;
|
|
3365
3298
|
}
|
|
3366
3299
|
async function acceptance(room, local, submitted, accepted) {
|
|
3367
3300
|
if (!continuing())
|
|
@@ -3374,44 +3307,17 @@ export function composeRuntime(options) {
|
|
|
3374
3307
|
logAccepted(room.locator, submitted, accepted);
|
|
3375
3308
|
let record = current.record;
|
|
3376
3309
|
let changes;
|
|
3377
|
-
if (!options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
3310
|
+
if (!options.adapter.reached(room.locator, held, accepted.roomVersion) &&
|
|
3311
|
+
!older(room.locator, accepted.roomVersion)) {
|
|
3378
3312
|
const paths = [...returnedPaths(submitted, accepted)];
|
|
3379
3313
|
const confirmedLayer = await stepped(slicing(await local.entries(record.key, 'confirmed', paths), paths));
|
|
3380
|
-
const back = await stepped(returning(confirmedLayer, submitted, accepted, accepted.roomVersion));
|
|
3314
|
+
const back = await stepped(returning(confirmedLayer, submitted, accepted, accepted.roomVersion, composed(record)));
|
|
3381
3315
|
record = {
|
|
3382
3316
|
...record,
|
|
3383
3317
|
roomVersion: options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
3384
3318
|
? accepted.roomVersion
|
|
3385
3319
|
: held,
|
|
3386
3320
|
};
|
|
3387
|
-
const overrides = new Map([
|
|
3388
|
-
...back.set.map((entry) => [entry.path, entry]),
|
|
3389
|
-
...back.removed.map((path) => [path, null]),
|
|
3390
|
-
]);
|
|
3391
|
-
const refused = await validatedRoom(local, record, 'confirmed', { record: current.record, changed: [...overrides.keys()] }, overrides);
|
|
3392
|
-
if (refused !== null) {
|
|
3393
|
-
const key = pendingKey(room.locator, submitted.id);
|
|
3394
|
-
return {
|
|
3395
|
-
locator: room.locator,
|
|
3396
|
-
left: [],
|
|
3397
|
-
commit: {
|
|
3398
|
-
rooms: [],
|
|
3399
|
-
writes: [
|
|
3400
|
-
{
|
|
3401
|
-
locator: room.locator,
|
|
3402
|
-
accepted: [{ id: submitted.id, roomVersion: accepted.roomVersion }],
|
|
3403
|
-
},
|
|
3404
|
-
],
|
|
3405
|
-
},
|
|
3406
|
-
refused,
|
|
3407
|
-
apply() {
|
|
3408
|
-
const state = pendings.get(key);
|
|
3409
|
-
if (state !== undefined && !fences.has(room.locator))
|
|
3410
|
-
pendings.set(key, { ...state, accepted: accepted.roomVersion });
|
|
3411
|
-
return changeSet(room.locator, { queue: true });
|
|
3412
|
-
},
|
|
3413
|
-
};
|
|
3414
|
-
}
|
|
3415
3321
|
changes = back;
|
|
3416
3322
|
}
|
|
3417
3323
|
const settled = await settlement(local, room.locator, current, record, current.queue.filter((batch) => batch.id !== submitted.id), { room: true, ...(changes === undefined ? {} : { changes }) });
|
|
@@ -3419,10 +3325,9 @@ export function composeRuntime(options) {
|
|
|
3419
3325
|
...settled,
|
|
3420
3326
|
apply(answer) {
|
|
3421
3327
|
const set = settled.apply(answer);
|
|
3328
|
+
showing(room.locator, accepted.roomVersion);
|
|
3422
3329
|
pendings.delete(pendingKey(room.locator, submitted.id));
|
|
3423
3330
|
unreadable.delete(room.locator);
|
|
3424
|
-
sourceRefusals.delete(room.locator);
|
|
3425
|
-
declarationRefusals.delete(room.locator);
|
|
3426
3331
|
list(room, true);
|
|
3427
3332
|
return set;
|
|
3428
3333
|
},
|
|
@@ -3440,13 +3345,9 @@ export function composeRuntime(options) {
|
|
|
3440
3345
|
const failed = await exclusive(async () => {
|
|
3441
3346
|
const adopting = await acceptance(room, local, submitted, accepted);
|
|
3442
3347
|
await adoptedTogether(local, [adopting]);
|
|
3443
|
-
return
|
|
3348
|
+
return failureOf(adopting);
|
|
3444
3349
|
});
|
|
3445
|
-
|
|
3446
|
-
return true;
|
|
3447
|
-
if (failed.kind === 'refused' && continuing())
|
|
3448
|
-
refusedSource(room, failed);
|
|
3449
|
-
return false;
|
|
3350
|
+
return failed === null;
|
|
3450
3351
|
}
|
|
3451
3352
|
async function adoptedMove(room, partner, local, submitted, accepted) {
|
|
3452
3353
|
const other = discovered.get(partner);
|
|
@@ -3460,18 +3361,10 @@ export function composeRuntime(options) {
|
|
|
3460
3361
|
...accepted.destination,
|
|
3461
3362
|
});
|
|
3462
3363
|
await adoptedTogether(local, [left, arrived]);
|
|
3463
|
-
|
|
3464
|
-
if (other !== undefined && refusedArrival?.kind === 'refused' && continuing()) {
|
|
3465
|
-
refusedSource(other, refusedArrival);
|
|
3466
|
-
}
|
|
3467
|
-
return refusalOf(left);
|
|
3364
|
+
return failureOf(left);
|
|
3468
3365
|
});
|
|
3469
3366
|
submitLater(partner);
|
|
3470
|
-
|
|
3471
|
-
return true;
|
|
3472
|
-
if (failed.kind === 'refused' && continuing())
|
|
3473
|
-
refusedSource(room, failed);
|
|
3474
|
-
return false;
|
|
3367
|
+
return failed === null;
|
|
3475
3368
|
}
|
|
3476
3369
|
function queueIn(locator) {
|
|
3477
3370
|
return replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
@@ -3582,20 +3475,13 @@ export function composeRuntime(options) {
|
|
|
3582
3475
|
return;
|
|
3583
3476
|
moveTo({ ...status, ...attention() });
|
|
3584
3477
|
}
|
|
3585
|
-
async function discardHeld(head, room, local, signal) {
|
|
3586
|
-
const partner = partnerOf(head, room.locator);
|
|
3587
|
-
const other = partner === null ? undefined : discovered.get(partner);
|
|
3588
|
-
await discardPending(other === undefined ? [room] : [room, other], local, signal);
|
|
3589
|
-
}
|
|
3590
3478
|
async function submitRoom(room, local, authorized, progress, signal) {
|
|
3591
3479
|
while (continuing() && authorization !== undefined && !signal.aborted) {
|
|
3592
3480
|
const replica = replicas.get(room.locator);
|
|
3593
|
-
const head = replica?.queue
|
|
3481
|
+
const head = replica?.queue.find((batch) => !landedAhead.has(pendingKey(room.locator, batch.id)));
|
|
3594
3482
|
if (replica === undefined || head === undefined)
|
|
3595
3483
|
return 'done';
|
|
3596
3484
|
const state = pendings.get(pendingKey(room.locator, head.id));
|
|
3597
|
-
if (state?.accepted !== undefined)
|
|
3598
|
-
return 'done';
|
|
3599
3485
|
const partner = partnerOf(head, room.locator);
|
|
3600
3486
|
if (partner !== null && !leavingIn(head, room.locator)) {
|
|
3601
3487
|
submitLater(partner);
|
|
@@ -3619,10 +3505,6 @@ export function composeRuntime(options) {
|
|
|
3619
3505
|
return 'done';
|
|
3620
3506
|
}
|
|
3621
3507
|
const lost = earlier || submission.state === 'submitted';
|
|
3622
|
-
if (lost && pastHorizon(head.id)) {
|
|
3623
|
-
await discardHeld(head, room, local, signal);
|
|
3624
|
-
return 'done';
|
|
3625
|
-
}
|
|
3626
3508
|
let result;
|
|
3627
3509
|
journal.record(`submission ${room.locator} ${head.id}`);
|
|
3628
3510
|
await exclusive(() => mark(local, room.locator, head, { state: 'submitted' }, lost));
|
|
@@ -3700,32 +3582,12 @@ export function composeRuntime(options) {
|
|
|
3700
3582
|
const other = partner === null ? undefined : discovered.get(partner);
|
|
3701
3583
|
if (result.kind === 'conflicted' ||
|
|
3702
3584
|
result.kind === 'rejected' ||
|
|
3703
|
-
result.kind === 'expired' ||
|
|
3704
3585
|
result.kind === 'retryable-refusal') {
|
|
3705
|
-
if (result.kind === 'expired' && lost && result.restored !== true) {
|
|
3706
|
-
journal.record(`outcome ${room.locator} ${head.id} expired`);
|
|
3707
|
-
await discardHeld(head, room, local, signal);
|
|
3708
|
-
return 'done';
|
|
3709
|
-
}
|
|
3710
|
-
if (result.kind === 'expired') {
|
|
3711
|
-
await exclusive(() => mark(local, room.locator, head, { state: 'waiting' }, partner !== null && lost));
|
|
3712
|
-
const there = other === undefined ? null : await advance(other, local, authorized, continuing);
|
|
3713
|
-
if (other !== undefined && there !== null) {
|
|
3714
|
-
await withhold(there, other, local, progress);
|
|
3715
|
-
return 'done';
|
|
3716
|
-
}
|
|
3717
|
-
const withheld = await advance(room, local, authorized, continuing);
|
|
3718
|
-
if (withheld !== null) {
|
|
3719
|
-
await withhold(withheld, room, local, progress);
|
|
3720
|
-
return 'done';
|
|
3721
|
-
}
|
|
3722
|
-
}
|
|
3723
3586
|
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` +
|
|
3724
3587
|
(result.kind === 'conflicted' && result.roomVersion !== undefined
|
|
3725
3588
|
? ` ${result.roomVersion}`
|
|
3726
3589
|
: ''));
|
|
3727
|
-
|
|
3728
|
-
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
3590
|
+
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
3729
3591
|
}
|
|
3730
3592
|
if (result.kind === 'retryable-refusal') {
|
|
3731
3593
|
const reset = result.reset;
|
|
@@ -3733,11 +3595,11 @@ export function composeRuntime(options) {
|
|
|
3733
3595
|
await exclusive(() => mark(local, room.locator, head, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
3734
3596
|
return reset === undefined ? 'again' : { at: Date.parse(reset) };
|
|
3735
3597
|
}
|
|
3736
|
-
if (result.kind === 'conflicted' || result.kind === 'rejected'
|
|
3598
|
+
if (result.kind === 'conflicted' || result.kind === 'rejected') {
|
|
3737
3599
|
const left = leaving(head, result);
|
|
3738
3600
|
const refreshed = async () => ({
|
|
3739
|
-
there: other === undefined ? null : await
|
|
3740
|
-
here: await
|
|
3601
|
+
there: other === undefined ? null : await advanced(other, local, authorized),
|
|
3602
|
+
here: await advanced(room, local, authorized),
|
|
3741
3603
|
});
|
|
3742
3604
|
const judged = result.kind === 'conflicted' && other !== undefined ? await refreshed() : null;
|
|
3743
3605
|
if (judged !== null && (judged.there !== null || judged.here !== null)) {
|
|
@@ -3757,16 +3619,19 @@ export function composeRuntime(options) {
|
|
|
3757
3619
|
return;
|
|
3758
3620
|
}
|
|
3759
3621
|
const confirmedLayer = await sliceAt(current.record, 'confirmed', [head]);
|
|
3760
|
-
const held =
|
|
3761
|
-
(await landedWhole(room.locator, head, confirmedLayer))
|
|
3762
|
-
|
|
3763
|
-
|
|
3622
|
+
const held = result.kind === 'conflicted' &&
|
|
3623
|
+
(await landedWhole(room.locator, head, confirmedLayer, result.affected));
|
|
3624
|
+
if (held &&
|
|
3625
|
+
result.kind === 'conflicted' &&
|
|
3626
|
+
partner === null &&
|
|
3627
|
+
!landed(head, confirmedLayer)) {
|
|
3628
|
+
landedAhead.set(pendingKey(room.locator, head.id), Object.fromEntries([...touchedBy([head])].map((path) => [path, confirmedLayer.revisions[path]])));
|
|
3629
|
+
return;
|
|
3630
|
+
}
|
|
3764
3631
|
publishChangeSet(await settle(local, room.locator, current, current.record, current.queue.filter((batch) => batch.id !== head.id), { leaving: [held ? { ...left, returned: null } : left] }));
|
|
3765
3632
|
await unpair(local);
|
|
3766
3633
|
moveTo({ ...status, ...attention() });
|
|
3767
3634
|
});
|
|
3768
|
-
if (result.kind === 'expired')
|
|
3769
|
-
continue;
|
|
3770
3635
|
const { there, here } = judged ?? (await refreshed());
|
|
3771
3636
|
if (other !== undefined && there !== null)
|
|
3772
3637
|
await withhold(there, other, local, progress);
|
|
@@ -3786,94 +3651,55 @@ export function composeRuntime(options) {
|
|
|
3786
3651
|
}
|
|
3787
3652
|
return 'done';
|
|
3788
3653
|
}
|
|
3789
|
-
function pastHorizon(identity) {
|
|
3790
|
-
return (outcomeRetention !== null && identityTime(identity) + outcomeRetention * 1000 <= Date.now());
|
|
3791
|
-
}
|
|
3792
|
-
async function discardPending(rooms, local, signal) {
|
|
3793
|
-
const discarded = await exclusive(async () => {
|
|
3794
|
-
const held = rooms
|
|
3795
|
-
.filter(({ locator }) => !fences.has(locator))
|
|
3796
|
-
.map((room) => ({
|
|
3797
|
-
room,
|
|
3798
|
-
before: replicas.get(room.locator),
|
|
3799
|
-
queue: queueIn(room.locator),
|
|
3800
|
-
}));
|
|
3801
|
-
if (held.length === 0)
|
|
3802
|
-
return [];
|
|
3803
|
-
const answer = await local.commit({
|
|
3804
|
-
rooms: held.flatMap(({ room, before }) => before === undefined
|
|
3805
|
-
? []
|
|
3806
|
-
: [
|
|
3807
|
-
{
|
|
3808
|
-
locator: room.locator,
|
|
3809
|
-
key: before.record.key,
|
|
3810
|
-
roots: before.record.typedSelection?.roots ?? [],
|
|
3811
|
-
composed: composedOf(composed(before.record)),
|
|
3812
|
-
overlay: { set: [], cleared: [...before.overlaid] },
|
|
3813
|
-
},
|
|
3814
|
-
]),
|
|
3815
|
-
discards: held.map(({ room }) => room.locator),
|
|
3816
|
-
});
|
|
3817
|
-
for (const { room, before, queue } of held) {
|
|
3818
|
-
const { locator } = room;
|
|
3819
|
-
lastFenced.set(locator, ++fencings);
|
|
3820
|
-
for (const batch of queue)
|
|
3821
|
-
pendings.delete(pendingKey(locator, batch.id));
|
|
3822
|
-
queuesWithoutReplica.delete(locator);
|
|
3823
|
-
if (before !== undefined) {
|
|
3824
|
-
replicas.set(locator, {
|
|
3825
|
-
...before,
|
|
3826
|
-
queue: [],
|
|
3827
|
-
overlaid: new Set(),
|
|
3828
|
-
count: answer.counts[locator] ?? before.count,
|
|
3829
|
-
});
|
|
3830
|
-
}
|
|
3831
|
-
journal.record(`room discarded ${locator}`);
|
|
3832
|
-
}
|
|
3833
|
-
publishChangeSet(mergeChangeSets(held.map(({ room }) => changeSet(room.locator, {
|
|
3834
|
-
documents: answer.changed[room.locator] ?? [],
|
|
3835
|
-
queue: true,
|
|
3836
|
-
}))));
|
|
3837
|
-
moveTo({ ...status, ...attention() });
|
|
3838
|
-
return held.map(({ room }) => room);
|
|
3839
|
-
});
|
|
3840
|
-
for (const room of discarded) {
|
|
3841
|
-
if (signal.aborted)
|
|
3842
|
-
return;
|
|
3843
|
-
const progress = { complete: true, reachable: true };
|
|
3844
|
-
await roomTask(room.locator, async (live) => {
|
|
3845
|
-
await Promise.race([
|
|
3846
|
-
reload(room, local, progress, () => live() && !signal.aborted),
|
|
3847
|
-
abandonedOn(signal),
|
|
3848
|
-
]);
|
|
3849
|
-
});
|
|
3850
|
-
if (signal.aborted)
|
|
3851
|
-
return;
|
|
3852
|
-
await rest(progress);
|
|
3853
|
-
}
|
|
3854
|
-
}
|
|
3855
3654
|
function awaitingAnswer(locator) {
|
|
3655
|
+
answering.get(locator)?.release();
|
|
3856
3656
|
let release = () => { };
|
|
3857
|
-
const
|
|
3657
|
+
const answered = new Promise((resolve) => {
|
|
3858
3658
|
release = resolve;
|
|
3859
3659
|
});
|
|
3860
|
-
|
|
3660
|
+
const awaited = { answered, release };
|
|
3661
|
+
answering.set(locator, awaited);
|
|
3861
3662
|
return () => {
|
|
3862
|
-
if (answering.get(locator)
|
|
3663
|
+
if (answering.get(locator) === awaited)
|
|
3863
3664
|
answering.delete(locator);
|
|
3864
3665
|
release();
|
|
3865
3666
|
};
|
|
3866
3667
|
}
|
|
3668
|
+
function declaredAsHeld(room) {
|
|
3669
|
+
const held = replicas.get(room.locator)?.record;
|
|
3670
|
+
return (held === undefined ||
|
|
3671
|
+
(JSON.stringify(held.operations) === JSON.stringify(room.operations) &&
|
|
3672
|
+
JSON.stringify(held.replica) === JSON.stringify(room.replica) &&
|
|
3673
|
+
JSON.stringify(held.typedSelection) === JSON.stringify(room.typedSelection ?? null)));
|
|
3674
|
+
}
|
|
3675
|
+
async function keepNaming(local, room) {
|
|
3676
|
+
const held = replicas.get(room.locator)?.record;
|
|
3677
|
+
if (held === undefined || (held.name === room.name && held.handle === room.handle))
|
|
3678
|
+
return;
|
|
3679
|
+
await exclusive(async () => {
|
|
3680
|
+
if (!continuing())
|
|
3681
|
+
return;
|
|
3682
|
+
const replica = replicas.get(room.locator);
|
|
3683
|
+
if (replica === undefined)
|
|
3684
|
+
return;
|
|
3685
|
+
const record = { ...replica.record, name: room.name, handle: room.handle };
|
|
3686
|
+
await local.commit({
|
|
3687
|
+
rooms: [
|
|
3688
|
+
{
|
|
3689
|
+
locator: room.locator,
|
|
3690
|
+
key: record.key,
|
|
3691
|
+
record: storedRecord(record),
|
|
3692
|
+
composed: composedOf(composed(record)),
|
|
3693
|
+
},
|
|
3694
|
+
],
|
|
3695
|
+
});
|
|
3696
|
+
replicas.set(room.locator, { ...replica, record });
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3867
3699
|
async function retainModel(local, room, next) {
|
|
3868
3700
|
const key = room.locator;
|
|
3869
3701
|
const previous = modelOf(room.locator);
|
|
3870
|
-
|
|
3871
|
-
if (JSON.stringify(previous) === JSON.stringify(next) &&
|
|
3872
|
-
(heldBefore === undefined ||
|
|
3873
|
-
(JSON.stringify(heldBefore.operations) === JSON.stringify(room.operations) &&
|
|
3874
|
-
JSON.stringify(heldBefore.replica) === JSON.stringify(room.replica) &&
|
|
3875
|
-
JSON.stringify(heldBefore.typedSelection) ===
|
|
3876
|
-
JSON.stringify(room.typedSelection ?? null))))
|
|
3702
|
+
if (JSON.stringify(previous) === JSON.stringify(next) && declaredAsHeld(room))
|
|
3877
3703
|
return;
|
|
3878
3704
|
if (next.status === 'unavailable') {
|
|
3879
3705
|
invalidModels.add(key);
|
|
@@ -3885,26 +3711,15 @@ export function composeRuntime(options) {
|
|
|
3885
3711
|
const held = replicas.get(room.locator);
|
|
3886
3712
|
let changed = null;
|
|
3887
3713
|
if (held !== undefined) {
|
|
3888
|
-
const
|
|
3714
|
+
const record = {
|
|
3889
3715
|
...held.record,
|
|
3716
|
+
name: room.name,
|
|
3717
|
+
handle: room.handle,
|
|
3890
3718
|
operations: room.operations,
|
|
3891
3719
|
replica: room.replica,
|
|
3892
3720
|
typedSelection: room.typedSelection ?? null,
|
|
3893
3721
|
model: next,
|
|
3894
3722
|
};
|
|
3895
|
-
let read = 'staged';
|
|
3896
|
-
if (!sameMembership(declared, held.record)) {
|
|
3897
|
-
const verdict = await validatedCollection(walkOf(local, held.record.key, 'confirmed'), lookupOf(local, held.record.key, 'confirmed'), composed(declared));
|
|
3898
|
-
read =
|
|
3899
|
-
verdict.verdict !== 'refuse'
|
|
3900
|
-
? 'staged'
|
|
3901
|
-
: verdict.rule === 'model-unavailable'
|
|
3902
|
-
? 'deferred'
|
|
3903
|
-
: { kind: 'refused', refusal: verdict };
|
|
3904
|
-
}
|
|
3905
|
-
const record = read === 'staged'
|
|
3906
|
-
? declared
|
|
3907
|
-
: { ...declared, typedSelection: held.record.typedSelection };
|
|
3908
3723
|
const confirmedLayer = await sliceAt(record, 'confirmed', held.queue, held.overlaid);
|
|
3909
3724
|
const replayedLayer = await replayed(record, confirmedLayer, held.queue);
|
|
3910
3725
|
const diff = await overlayDiff(record, held, replayedLayer.visible, replayedLayer.touched);
|
|
@@ -3926,29 +3741,22 @@ export function composeRuntime(options) {
|
|
|
3926
3741
|
documents: answer.changed[room.locator] ?? [],
|
|
3927
3742
|
room: true,
|
|
3928
3743
|
});
|
|
3929
|
-
if (read === 'staged' || read === 'deferred') {
|
|
3930
|
-
if (declarationRefusals.delete(room.locator)) {
|
|
3931
|
-
sourceRefusals.delete(room.locator);
|
|
3932
|
-
unreadable.delete(room.locator);
|
|
3933
|
-
}
|
|
3934
|
-
}
|
|
3935
|
-
else {
|
|
3936
|
-
refusedSource(room, read);
|
|
3937
|
-
declarationRefusals.add(room.locator);
|
|
3938
|
-
}
|
|
3939
|
-
}
|
|
3940
|
-
models.set(key, next);
|
|
3941
|
-
if (next.status === 'current') {
|
|
3942
|
-
liveModels.set(key, JSON.stringify(next.snapshot));
|
|
3943
|
-
invalidModels.delete(key);
|
|
3944
3744
|
}
|
|
3945
|
-
|
|
3745
|
+
heldModel(room, next);
|
|
3946
3746
|
if (changed === null)
|
|
3947
3747
|
emitRooms();
|
|
3948
3748
|
else
|
|
3949
3749
|
publishChangeSet(changed);
|
|
3950
3750
|
});
|
|
3951
3751
|
}
|
|
3752
|
+
function heldModel(room, next) {
|
|
3753
|
+
models.set(room.locator, next);
|
|
3754
|
+
if (next.status === 'current') {
|
|
3755
|
+
liveModels.set(room.locator, JSON.stringify(next.snapshot));
|
|
3756
|
+
invalidModels.delete(room.locator);
|
|
3757
|
+
}
|
|
3758
|
+
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
3759
|
+
}
|
|
3952
3760
|
async function scoped(result, room, local) {
|
|
3953
3761
|
if (result.scope === 'content' && !result.retryable) {
|
|
3954
3762
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
@@ -3977,6 +3785,17 @@ export function composeRuntime(options) {
|
|
|
3977
3785
|
emitRooms();
|
|
3978
3786
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
3979
3787
|
}
|
|
3788
|
+
function name(locator) {
|
|
3789
|
+
const stream = options.adapter.roomStream;
|
|
3790
|
+
const record = replicas.get(locator)?.record;
|
|
3791
|
+
const room = discovered.get(locator);
|
|
3792
|
+
if (stream === undefined || record === undefined || room === undefined)
|
|
3793
|
+
return;
|
|
3794
|
+
if (!socketOpen || !handed || record.replica.selection !== room.replica.selection)
|
|
3795
|
+
return;
|
|
3796
|
+
naming += 1;
|
|
3797
|
+
stream.hold([{ locator, roomVersion: record.roomVersion }]);
|
|
3798
|
+
}
|
|
3980
3799
|
function heldRooms(listed) {
|
|
3981
3800
|
return listed.flatMap((room) => {
|
|
3982
3801
|
const record = replicas.get(room.locator)?.record;
|
|
@@ -3994,14 +3813,19 @@ export function composeRuntime(options) {
|
|
|
3994
3813
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
3995
3814
|
});
|
|
3996
3815
|
if (attempt === 'opened') {
|
|
3816
|
+
naming += 1;
|
|
3997
3817
|
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
3998
3818
|
handed = true;
|
|
3999
3819
|
}
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
3820
|
+
for (const room of ready) {
|
|
3821
|
+
if (attempt !== 'unstreamed' && replicas.has(room.locator)) {
|
|
3822
|
+
if (attempt === 'authorized')
|
|
3823
|
+
name(room.locator);
|
|
3824
|
+
continue;
|
|
3825
|
+
}
|
|
3826
|
+
void roomTask(room.locator, (live) => loadRoom(room, local, progress, live)).then(() => rest(progress));
|
|
3827
|
+
}
|
|
3828
|
+
const failed = prepared.find((room) => room.status === 'rejected');
|
|
4005
3829
|
if (failed !== undefined)
|
|
4006
3830
|
throw failed.reason;
|
|
4007
3831
|
}
|
|
@@ -4020,12 +3844,16 @@ export function composeRuntime(options) {
|
|
|
4020
3844
|
}
|
|
4021
3845
|
if (prior !== undefined)
|
|
4022
3846
|
await retainModel(local, room, modelOf(room.locator));
|
|
3847
|
+
if (prior !== undefined)
|
|
3848
|
+
await keepNaming(local, room);
|
|
4023
3849
|
if (discoveryPaused || authorization === undefined)
|
|
4024
3850
|
return false;
|
|
4025
3851
|
return room.replica.admission === 'admitted';
|
|
4026
3852
|
}
|
|
4027
3853
|
async function caughtUp(room, local, progress, live) {
|
|
4028
|
-
if (
|
|
3854
|
+
if (options.adapter.roomStream !== undefined || authorization === undefined)
|
|
3855
|
+
return;
|
|
3856
|
+
if (!live() || !behind(room))
|
|
4029
3857
|
return;
|
|
4030
3858
|
const withheld = await advance(room, local, authorization, live);
|
|
4031
3859
|
if (withheld !== null)
|
|
@@ -4038,6 +3866,8 @@ export function composeRuntime(options) {
|
|
|
4038
3866
|
async function loadRoom(room, local, progress, live) {
|
|
4039
3867
|
if (!live() || authorization === undefined)
|
|
4040
3868
|
return;
|
|
3869
|
+
if (replicas.has(room.locator) && !(await prepareRoom(room, local, progress)))
|
|
3870
|
+
return;
|
|
4041
3871
|
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
4042
3872
|
await reload(room, local, progress, live);
|
|
4043
3873
|
return;
|
|
@@ -4055,20 +3885,53 @@ export function composeRuntime(options) {
|
|
|
4055
3885
|
reloadsDue.delete(room.locator);
|
|
4056
3886
|
if (!live() || authorization === undefined)
|
|
4057
3887
|
return;
|
|
3888
|
+
const from = standingOf(room.locator);
|
|
4058
3889
|
const bootstrapped = await pullBootstrap(authorization, room, local, live);
|
|
4059
3890
|
if (bootstrapped.kind !== 'staged') {
|
|
4060
3891
|
if (live())
|
|
4061
3892
|
await withhold(bootstrapped, room, local, progress);
|
|
4062
3893
|
return;
|
|
4063
3894
|
}
|
|
4064
|
-
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed }, live);
|
|
3895
|
+
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from }, live);
|
|
4065
3896
|
await caughtUp(room, local, progress, live);
|
|
4066
3897
|
}
|
|
4067
3898
|
finally {
|
|
4068
3899
|
caughtUpTo(room.locator);
|
|
4069
3900
|
}
|
|
4070
3901
|
}
|
|
4071
|
-
async function
|
|
3902
|
+
async function asked(authorized) {
|
|
3903
|
+
try {
|
|
3904
|
+
return (await options.adapter.discover?.(authorized)) ?? { kind: 'transport-uncertainty' };
|
|
3905
|
+
}
|
|
3906
|
+
catch {
|
|
3907
|
+
return { kind: 'transport-uncertainty' };
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
function malformed(listed) {
|
|
3911
|
+
for (const room of listed) {
|
|
3912
|
+
if (room.operations === undefined ||
|
|
3913
|
+
!['write', 'delete'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
3914
|
+
room.operations[operation].every((scope) => typeof scope === 'string')) ||
|
|
3915
|
+
room.replica === undefined ||
|
|
3916
|
+
typeof room.replica.selection !== 'string' ||
|
|
3917
|
+
room.replica.selection.length === 0 ||
|
|
3918
|
+
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
3919
|
+
!['locked', 'open'].includes(room.replica.offline)) {
|
|
3920
|
+
return `invalid model or replica declaration ${room.locator}`;
|
|
3921
|
+
}
|
|
3922
|
+
if (!declaredSelection(room))
|
|
3923
|
+
return `invalid typed selection declaration ${room.locator}`;
|
|
3924
|
+
}
|
|
3925
|
+
return null;
|
|
3926
|
+
}
|
|
3927
|
+
async function signIn(local, signedIn) {
|
|
3928
|
+
if (JSON.stringify(signedIn) === JSON.stringify(member))
|
|
3929
|
+
return;
|
|
3930
|
+
await local.fact('member', signedIn);
|
|
3931
|
+
member = signedIn;
|
|
3932
|
+
emitMember();
|
|
3933
|
+
}
|
|
3934
|
+
async function discoverRooms(local, progress, attempt, live, found) {
|
|
4072
3935
|
if (closed || authorization === undefined) {
|
|
4073
3936
|
notifyReady();
|
|
4074
3937
|
return;
|
|
@@ -4076,13 +3939,9 @@ export function composeRuntime(options) {
|
|
|
4076
3939
|
moveTo(transferring(status));
|
|
4077
3940
|
discoveries += 1;
|
|
4078
3941
|
const started = discoveries;
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
}
|
|
4083
|
-
catch {
|
|
4084
|
-
result = { kind: 'transport-uncertainty' };
|
|
4085
|
-
}
|
|
3942
|
+
const result = found ?? (await asked(authorization));
|
|
3943
|
+
if (result.kind === 'discovery')
|
|
3944
|
+
roomsNamed = true;
|
|
4086
3945
|
if (!live()) {
|
|
4087
3946
|
progress.complete = false;
|
|
4088
3947
|
notifyReady();
|
|
@@ -4090,9 +3949,7 @@ export function composeRuntime(options) {
|
|
|
4090
3949
|
}
|
|
4091
3950
|
journal.record(`connection discover ${result.kind}`);
|
|
4092
3951
|
if (result.kind === 'transport-uncertainty') {
|
|
4093
|
-
|
|
4094
|
-
await retainModel(local, room, cached(modelOf(room.locator)));
|
|
4095
|
-
}
|
|
3952
|
+
await cachedModels(local);
|
|
4096
3953
|
progress.reachable = false;
|
|
4097
3954
|
progress.complete = false;
|
|
4098
3955
|
notifyReady();
|
|
@@ -4113,26 +3970,12 @@ export function composeRuntime(options) {
|
|
|
4113
3970
|
notifyReady();
|
|
4114
3971
|
return;
|
|
4115
3972
|
}
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
room.replica.selection.length === 0 ||
|
|
4123
|
-
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
4124
|
-
!['locked', 'open'].includes(room.replica.offline)) {
|
|
4125
|
-
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
4126
|
-
await refuse('missing-required-guarantee');
|
|
4127
|
-
notifyReady();
|
|
4128
|
-
return;
|
|
4129
|
-
}
|
|
4130
|
-
if (!declaredSelection(room)) {
|
|
4131
|
-
journal.record(`invalid typed selection declaration ${room.locator}`);
|
|
4132
|
-
await refuse('missing-required-guarantee');
|
|
4133
|
-
notifyReady();
|
|
4134
|
-
return;
|
|
4135
|
-
}
|
|
3973
|
+
const invalid = malformed(result.rooms);
|
|
3974
|
+
if (invalid !== null) {
|
|
3975
|
+
journal.record(invalid);
|
|
3976
|
+
await refuse('missing-required-guarantee');
|
|
3977
|
+
notifyReady();
|
|
3978
|
+
return;
|
|
4136
3979
|
}
|
|
4137
3980
|
if (refusal !== null) {
|
|
4138
3981
|
refusal = null;
|
|
@@ -4140,7 +3983,6 @@ export function composeRuntime(options) {
|
|
|
4140
3983
|
}
|
|
4141
3984
|
const previousCapabilities = capabilities;
|
|
4142
3985
|
capabilities = result.capabilities;
|
|
4143
|
-
outcomeRetention = result.retention.outcome;
|
|
4144
3986
|
notifyReady();
|
|
4145
3987
|
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
4146
3988
|
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)) {
|
|
@@ -4148,13 +3990,8 @@ export function composeRuntime(options) {
|
|
|
4148
3990
|
await local.fact('providerLimits', limits);
|
|
4149
3991
|
await local.fact('capabilities', capabilities);
|
|
4150
3992
|
}
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
await local.fact('member', signedIn);
|
|
4154
|
-
member = signedIn;
|
|
4155
|
-
emitMember();
|
|
4156
|
-
}
|
|
4157
|
-
await synchronize(await applyDiscovery(result.rooms, started, result.covered), local, progress, attempt, live);
|
|
3993
|
+
await signIn(local, result.member ?? null);
|
|
3994
|
+
await synchronize(await applyDiscovery(result.rooms, started), local, progress, attempt, live);
|
|
4158
3995
|
}
|
|
4159
3996
|
async function ensureAuthorization() {
|
|
4160
3997
|
if (authorization !== undefined)
|
|
@@ -4169,23 +4006,45 @@ export function composeRuntime(options) {
|
|
|
4169
4006
|
}
|
|
4170
4007
|
}
|
|
4171
4008
|
async function admittedRead(work) {
|
|
4172
|
-
|
|
4173
|
-
|
|
4009
|
+
while (roomReads.free === 0) {
|
|
4010
|
+
await new Promise((admit) => roomReads.waiting.push(admit));
|
|
4174
4011
|
}
|
|
4175
|
-
|
|
4176
|
-
while (reads.free === 0)
|
|
4177
|
-
await new Promise((admit) => reads.waiting.push(admit));
|
|
4178
|
-
reads.free -= 1;
|
|
4012
|
+
roomReads.free -= 1;
|
|
4179
4013
|
try {
|
|
4180
4014
|
await work();
|
|
4181
4015
|
}
|
|
4182
4016
|
finally {
|
|
4183
|
-
|
|
4184
|
-
|
|
4017
|
+
roomReads.free += 1;
|
|
4018
|
+
roomReads.waiting.shift()?.();
|
|
4185
4019
|
}
|
|
4186
4020
|
}
|
|
4021
|
+
function inRoom(locator, work) {
|
|
4022
|
+
roomWork += 1;
|
|
4023
|
+
const ran = (roomLanes.get(locator) ?? Promise.resolve()).then(async () => {
|
|
4024
|
+
try {
|
|
4025
|
+
return await work(continuing);
|
|
4026
|
+
}
|
|
4027
|
+
finally {
|
|
4028
|
+
roomWork -= 1;
|
|
4029
|
+
}
|
|
4030
|
+
});
|
|
4031
|
+
const chain = ran.then(() => undefined, () => undefined);
|
|
4032
|
+
roomLanes.set(locator, chain);
|
|
4033
|
+
working.add(chain);
|
|
4034
|
+
void chain.finally(() => {
|
|
4035
|
+
working.delete(chain);
|
|
4036
|
+
if (roomLanes.get(locator) === chain)
|
|
4037
|
+
roomLanes.delete(locator);
|
|
4038
|
+
});
|
|
4039
|
+
return ran;
|
|
4040
|
+
}
|
|
4187
4041
|
function roomTask(locator, work) {
|
|
4188
|
-
|
|
4042
|
+
if (stopping)
|
|
4043
|
+
return Promise.resolve();
|
|
4044
|
+
return inRoom(locator, (live) => admittedRead(() => work(live))).catch(() => undefined);
|
|
4045
|
+
}
|
|
4046
|
+
function advanced(room, local, authorized) {
|
|
4047
|
+
return inRoom(room.locator, (live) => advance(room, local, authorized, live));
|
|
4189
4048
|
}
|
|
4190
4049
|
function task(lane, work) {
|
|
4191
4050
|
if (stopping)
|
|
@@ -4201,23 +4060,19 @@ export function composeRuntime(options) {
|
|
|
4201
4060
|
void run.finally(() => working.delete(run));
|
|
4202
4061
|
return run;
|
|
4203
4062
|
}
|
|
4204
|
-
function sync(attempt) {
|
|
4063
|
+
function sync(attempt, found) {
|
|
4205
4064
|
return task(OPENING_LANE, async (live) => {
|
|
4206
4065
|
if (!live()) {
|
|
4207
|
-
|
|
4066
|
+
readyFromStore();
|
|
4208
4067
|
return;
|
|
4209
4068
|
}
|
|
4210
4069
|
const progress = { complete: true, reachable: true };
|
|
4211
|
-
|
|
4212
|
-
if (attempt === 'opened')
|
|
4213
|
-
opened = generation;
|
|
4214
|
-
underway = attempt === 'opened' ? { generation, progress } : null;
|
|
4070
|
+
underway = attempt === 'opened' ? progress : null;
|
|
4215
4071
|
if (attempt === 'opened')
|
|
4216
4072
|
streamed = progress;
|
|
4217
4073
|
complete = false;
|
|
4218
4074
|
recorded = false;
|
|
4219
4075
|
moveTo(transferring(status));
|
|
4220
|
-
handed = false;
|
|
4221
4076
|
try {
|
|
4222
4077
|
const local = await openLocal();
|
|
4223
4078
|
if (!live())
|
|
@@ -4225,7 +4080,7 @@ export function composeRuntime(options) {
|
|
|
4225
4080
|
await ensureAuthorization();
|
|
4226
4081
|
if (!live())
|
|
4227
4082
|
return;
|
|
4228
|
-
await discoverRooms(local, progress, attempt, live);
|
|
4083
|
+
await discoverRooms(local, progress, attempt, live, found);
|
|
4229
4084
|
}
|
|
4230
4085
|
catch {
|
|
4231
4086
|
progress.complete = false;
|
|
@@ -4233,7 +4088,6 @@ export function composeRuntime(options) {
|
|
|
4233
4088
|
progress.reachable = false;
|
|
4234
4089
|
}
|
|
4235
4090
|
finally {
|
|
4236
|
-
gate.release();
|
|
4237
4091
|
notifyReady();
|
|
4238
4092
|
}
|
|
4239
4093
|
if (!live())
|
|
@@ -4245,35 +4099,59 @@ export function composeRuntime(options) {
|
|
|
4245
4099
|
if (handed || closed || progress.reachable)
|
|
4246
4100
|
return;
|
|
4247
4101
|
underway = null;
|
|
4248
|
-
opened = null;
|
|
4249
|
-
journal.record('connection reopen');
|
|
4250
4102
|
await rest(progress, live);
|
|
4251
|
-
options.adapter.roomStream?.reopen();
|
|
4252
4103
|
});
|
|
4253
4104
|
}
|
|
4254
|
-
function
|
|
4105
|
+
function resynchronize() {
|
|
4255
4106
|
return task(OPENING_LANE, async (live) => {
|
|
4256
|
-
if (!live() || !
|
|
4107
|
+
if (!live() || !announced)
|
|
4257
4108
|
return;
|
|
4258
4109
|
const progress = { complete: true, reachable: true };
|
|
4259
|
-
const gate = discover();
|
|
4260
4110
|
moveTo(transferring(status));
|
|
4261
4111
|
try {
|
|
4262
4112
|
const local = await openLocal();
|
|
4263
4113
|
if (!live())
|
|
4264
4114
|
return;
|
|
4265
|
-
await
|
|
4115
|
+
await synchronize([...discovered.values()], local, progress, 'unstreamed', live);
|
|
4116
|
+
}
|
|
4117
|
+
catch {
|
|
4118
|
+
progress.complete = false;
|
|
4119
|
+
}
|
|
4120
|
+
if (live())
|
|
4121
|
+
await finishOpening(progress, live);
|
|
4122
|
+
});
|
|
4123
|
+
}
|
|
4124
|
+
function countAuthorityChange() {
|
|
4125
|
+
authorityChanges += 1;
|
|
4126
|
+
for (const listener of authorityChangeListeners)
|
|
4127
|
+
listener(authorityChanges);
|
|
4128
|
+
}
|
|
4129
|
+
function changeAuthority(change) {
|
|
4130
|
+
return task(OPENING_LANE, async (live) => {
|
|
4131
|
+
if (!live() || !announced)
|
|
4132
|
+
return;
|
|
4133
|
+
countAuthorityChange();
|
|
4134
|
+
const progress = { complete: true, reachable: true };
|
|
4135
|
+
moveTo(transferring(status));
|
|
4136
|
+
try {
|
|
4137
|
+
const local = await openLocal();
|
|
4266
4138
|
if (!live())
|
|
4267
4139
|
return;
|
|
4268
|
-
|
|
4140
|
+
const invalid = malformed(change.rooms);
|
|
4141
|
+
if (invalid !== null) {
|
|
4142
|
+
journal.record(invalid);
|
|
4143
|
+
await refuse('missing-required-guarantee');
|
|
4144
|
+
return;
|
|
4145
|
+
}
|
|
4146
|
+
if (change.member !== undefined)
|
|
4147
|
+
await signIn(local, change.member);
|
|
4148
|
+
discoveries += 1;
|
|
4149
|
+
const listed = await applyDiscovery(change.rooms, discoveries, change.left);
|
|
4150
|
+
await synchronize(listed, local, progress, 'authorized', live);
|
|
4269
4151
|
}
|
|
4270
4152
|
catch {
|
|
4271
4153
|
progress.complete = false;
|
|
4272
4154
|
}
|
|
4273
|
-
finally {
|
|
4274
|
-
gate.release();
|
|
4275
|
-
notifyReady();
|
|
4276
|
-
}
|
|
4277
4155
|
if (!live())
|
|
4278
4156
|
return;
|
|
4279
4157
|
if (!progress.complete)
|
|
@@ -4281,14 +4159,17 @@ export function composeRuntime(options) {
|
|
|
4281
4159
|
await rest(progress, live);
|
|
4282
4160
|
});
|
|
4283
4161
|
}
|
|
4284
|
-
function closedSocket(revoked, lost) {
|
|
4285
|
-
|
|
4162
|
+
function closedSocket(revoked, lost, authorityChanged) {
|
|
4163
|
+
notifyReady();
|
|
4164
|
+
const opened = announced;
|
|
4286
4165
|
socketOpen = false;
|
|
4166
|
+
announced = false;
|
|
4167
|
+
missedChange ||= authorityChanged;
|
|
4168
|
+
handed = false;
|
|
4169
|
+
naming = 0;
|
|
4287
4170
|
connectionLost();
|
|
4288
|
-
if (lost)
|
|
4289
|
-
reads.lost();
|
|
4171
|
+
if (lost)
|
|
4290
4172
|
conversations.lost();
|
|
4291
|
-
}
|
|
4292
4173
|
if (revoked) {
|
|
4293
4174
|
void refuse('revoked-authorization');
|
|
4294
4175
|
return;
|
|
@@ -4299,18 +4180,32 @@ export function composeRuntime(options) {
|
|
|
4299
4180
|
moveTo({
|
|
4300
4181
|
...status,
|
|
4301
4182
|
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
4302
|
-
providerUnreachable: status.providerUnreachable || !opened,
|
|
4183
|
+
providerUnreachable: status.providerUnreachable || (!opened && lost),
|
|
4303
4184
|
});
|
|
4185
|
+
void closing(opened);
|
|
4304
4186
|
}
|
|
4305
|
-
function
|
|
4306
|
-
|
|
4187
|
+
function closing(opened) {
|
|
4188
|
+
return task(OPENING_LANE, async (live) => {
|
|
4189
|
+
if (!live())
|
|
4190
|
+
return;
|
|
4191
|
+
const local = await openLocal();
|
|
4192
|
+
if (!opened && !socketOpen)
|
|
4193
|
+
await cachedModels(local);
|
|
4194
|
+
await rest(null, live);
|
|
4195
|
+
});
|
|
4196
|
+
}
|
|
4197
|
+
function cachedModels(local) {
|
|
4198
|
+
return settleModels([...rooms.values()].map((room) => discovered.get(room.locator) ?? room), 'cached', local);
|
|
4199
|
+
}
|
|
4200
|
+
function caughtUpRooms(expired) {
|
|
4201
|
+
const progress = underway;
|
|
4202
|
+
underway = null;
|
|
4203
|
+
naming = Math.max(0, naming - 1);
|
|
4307
4204
|
const cycle = progress ?? { complete: true, reachable: true };
|
|
4308
|
-
|
|
4309
|
-
opened = null;
|
|
4205
|
+
replaced(expired);
|
|
4310
4206
|
const reloading = progress === null ? [] : owedReloads(expired);
|
|
4311
4207
|
const replacing = reloadNow((room) => expired.includes(room.locator) || reloading.includes(room.locator), cycle);
|
|
4312
4208
|
return task(OPENING_LANE, async (live) => {
|
|
4313
|
-
underway = null;
|
|
4314
4209
|
if (!live())
|
|
4315
4210
|
return;
|
|
4316
4211
|
if (progress === null) {
|
|
@@ -4318,24 +4213,14 @@ export function composeRuntime(options) {
|
|
|
4318
4213
|
await rest(cycle, live);
|
|
4319
4214
|
return;
|
|
4320
4215
|
}
|
|
4321
|
-
await roomsCarried([...expired, ...reloading]);
|
|
4322
|
-
if (!live())
|
|
4323
|
-
return;
|
|
4324
4216
|
try {
|
|
4325
4217
|
const local = await openLocal();
|
|
4326
|
-
|
|
4218
|
+
await replacing;
|
|
4327
4219
|
if (progress.reachable) {
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
!reloading.includes(room.locator)) {
|
|
4332
|
-
await confirmModels(room, local);
|
|
4333
|
-
}
|
|
4334
|
-
}
|
|
4220
|
+
await settleModels([...discovered.values()].filter((room) => replicas.has(room.locator) &&
|
|
4221
|
+
!expired.includes(room.locator) &&
|
|
4222
|
+
!reloading.includes(room.locator)), 'current', local);
|
|
4335
4223
|
}
|
|
4336
|
-
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
4337
|
-
if (failed !== undefined)
|
|
4338
|
-
throw failed.reason;
|
|
4339
4224
|
}
|
|
4340
4225
|
catch {
|
|
4341
4226
|
progress.complete = false;
|
|
@@ -4347,11 +4232,8 @@ export function composeRuntime(options) {
|
|
|
4347
4232
|
}
|
|
4348
4233
|
function owedReloads(expired) {
|
|
4349
4234
|
const reloading = [...discovered.values()]
|
|
4350
|
-
.filter((room) => replicas.has(room.locator) &&
|
|
4351
|
-
!expired.includes(room.locator) &&
|
|
4352
|
-
(owed.has(room.locator) || revoked(room.locator)))
|
|
4235
|
+
.filter((room) => replicas.has(room.locator) && !expired.includes(room.locator) && revoked(room.locator))
|
|
4353
4236
|
.map((room) => room.locator);
|
|
4354
|
-
owed.clear();
|
|
4355
4237
|
for (const locator of reloading)
|
|
4356
4238
|
catching.add(locator);
|
|
4357
4239
|
liveQueries.recover();
|
|
@@ -4360,22 +4242,44 @@ export function composeRuntime(options) {
|
|
|
4360
4242
|
async function reloadNow(replaced, progress) {
|
|
4361
4243
|
const reloaded = [...discovered.values()].filter((room) => replicas.has(room.locator) && replaced(room));
|
|
4362
4244
|
for (const room of reloaded)
|
|
4363
|
-
reloadsDue.
|
|
4245
|
+
reloadsDue.add(room.locator);
|
|
4364
4246
|
try {
|
|
4365
4247
|
const local = await openLocal();
|
|
4366
|
-
|
|
4248
|
+
for (const room of reloaded) {
|
|
4249
|
+
void roomTask(room.locator, (live) => reload(room, local, progress, live)).then(() => rest(progress));
|
|
4250
|
+
}
|
|
4367
4251
|
}
|
|
4368
4252
|
catch {
|
|
4369
4253
|
for (const room of reloaded)
|
|
4370
4254
|
reloadsDue.delete(room.locator);
|
|
4371
4255
|
progress.reachable = false;
|
|
4372
|
-
return [];
|
|
4373
4256
|
}
|
|
4374
4257
|
}
|
|
4375
|
-
async function
|
|
4376
|
-
const
|
|
4377
|
-
|
|
4378
|
-
|
|
4258
|
+
async function settleModels(held, status, local) {
|
|
4259
|
+
const owed = (room) => {
|
|
4260
|
+
const model = modelOf(room.locator);
|
|
4261
|
+
return model.status !== 'unavailable' && model.status !== status;
|
|
4262
|
+
};
|
|
4263
|
+
const settled = await exclusive(async () => {
|
|
4264
|
+
if (!continuing())
|
|
4265
|
+
return [];
|
|
4266
|
+
const unchanged = held.filter((room) => owed(room) && declaredAsHeld(room));
|
|
4267
|
+
for (const room of unchanged) {
|
|
4268
|
+
const next = { ...modelOf(room.locator), status };
|
|
4269
|
+
const replica = replicas.get(room.locator);
|
|
4270
|
+
if (replica !== undefined)
|
|
4271
|
+
replicas.set(room.locator, { ...replica, record: { ...replica.record, model: next } });
|
|
4272
|
+
heldModel(room, next);
|
|
4273
|
+
}
|
|
4274
|
+
if (unchanged.length > 0)
|
|
4275
|
+
emitRooms();
|
|
4276
|
+
return unchanged;
|
|
4277
|
+
});
|
|
4278
|
+
for (const room of held) {
|
|
4279
|
+
if (settled.includes(room) || !owed(room))
|
|
4280
|
+
continue;
|
|
4281
|
+
await retainModel(local, room, { ...modelOf(room.locator), status });
|
|
4282
|
+
}
|
|
4379
4283
|
}
|
|
4380
4284
|
function submitLater(locator) {
|
|
4381
4285
|
if (localSaves)
|
|
@@ -4419,14 +4323,10 @@ export function composeRuntime(options) {
|
|
|
4419
4323
|
try {
|
|
4420
4324
|
const local = await openLocal();
|
|
4421
4325
|
await roomTask(locator, async (live) => {
|
|
4422
|
-
|
|
4423
|
-
if (!current() || authorization === undefined)
|
|
4326
|
+
if (!live() || authorization === undefined)
|
|
4424
4327
|
return;
|
|
4425
|
-
const withheld = await
|
|
4426
|
-
|
|
4427
|
-
abandonedOn(signal),
|
|
4428
|
-
]);
|
|
4429
|
-
if (withheld !== null && current())
|
|
4328
|
+
const withheld = await advance(room, local, authorization, live);
|
|
4329
|
+
if (withheld !== null && live())
|
|
4430
4330
|
await withhold(withheld, room, local, progress);
|
|
4431
4331
|
});
|
|
4432
4332
|
}
|
|
@@ -4481,14 +4381,11 @@ export function composeRuntime(options) {
|
|
|
4481
4381
|
progress.complete = false;
|
|
4482
4382
|
return;
|
|
4483
4383
|
}
|
|
4484
|
-
reads.applied();
|
|
4485
4384
|
submitAll();
|
|
4486
4385
|
for (const locator of stale)
|
|
4487
4386
|
if (repairable.has(locator))
|
|
4488
4387
|
repairLater(locator, {});
|
|
4489
4388
|
await conversations.reconcile();
|
|
4490
|
-
if (status.providerUnreachable)
|
|
4491
|
-
conversations.reconnected();
|
|
4492
4389
|
const reading = { complete: true, reachable: true };
|
|
4493
4390
|
await conversations.catchUp(reading);
|
|
4494
4391
|
if (progress.reachable && reading.reachable)
|
|
@@ -4514,7 +4411,7 @@ export function composeRuntime(options) {
|
|
|
4514
4411
|
if (!live())
|
|
4515
4412
|
return;
|
|
4516
4413
|
let lastCompleted = status.lastCompleted;
|
|
4517
|
-
let synced = complete && stale.size === 0 && !pending();
|
|
4414
|
+
let synced = complete && roomWork === 0 && naming === 0 && stale.size === 0 && !pending();
|
|
4518
4415
|
if (synced && !recorded) {
|
|
4519
4416
|
const completedAt = new Date().toISOString();
|
|
4520
4417
|
try {
|
|
@@ -4529,74 +4426,69 @@ export function composeRuntime(options) {
|
|
|
4529
4426
|
}
|
|
4530
4427
|
}
|
|
4531
4428
|
moveTo({
|
|
4532
|
-
state: pending()
|
|
4429
|
+
state: pending()
|
|
4430
|
+
? 'pending'
|
|
4431
|
+
: synced && complete && roomWork === 0 && naming === 0
|
|
4432
|
+
? 'synced'
|
|
4433
|
+
: 'syncing',
|
|
4533
4434
|
lastCompleted,
|
|
4534
|
-
providerUnreachable: !progress.reachable,
|
|
4435
|
+
providerUnreachable: progress === null ? status.providerUnreachable : !progress.reachable,
|
|
4535
4436
|
...attention(),
|
|
4536
4437
|
});
|
|
4537
4438
|
}
|
|
4538
|
-
function
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
}
|
|
4546
|
-
function applyRoom(locator, roomVersion) {
|
|
4547
|
-
announced.set(locator, roomVersion);
|
|
4548
|
-
return task(roomLane(locator), async (live) => {
|
|
4549
|
-
if (discovering?.generation === generation)
|
|
4550
|
-
await discovering.done;
|
|
4551
|
-
const saving = answering.get(locator);
|
|
4552
|
-
if (saving?.generation === generation)
|
|
4553
|
-
await saving.answered;
|
|
4554
|
-
if (!live() || authorization === undefined)
|
|
4555
|
-
return;
|
|
4556
|
-
const room = discovered.get(locator);
|
|
4557
|
-
const held = replicas.get(locator);
|
|
4558
|
-
if (room === undefined || room.replica.admission !== 'admitted')
|
|
4559
|
-
return;
|
|
4560
|
-
if (held !== undefined &&
|
|
4561
|
-
(held.record.replica.selection !== room.replica.selection ||
|
|
4562
|
-
options.adapter.reached(locator, held.record.roomVersion, roomVersion))) {
|
|
4563
|
-
return;
|
|
4564
|
-
}
|
|
4565
|
-
const during = underway?.generation === generation ? underway.progress : null;
|
|
4566
|
-
const progress = during ?? { complete: true, reachable: true };
|
|
4567
|
-
if (during === null)
|
|
4568
|
-
moveTo(transferring(status));
|
|
4439
|
+
function applyRoom(change) {
|
|
4440
|
+
const { locator } = change;
|
|
4441
|
+
if (stopping)
|
|
4442
|
+
return Promise.resolve();
|
|
4443
|
+
return inRoom(locator, async (live) => {
|
|
4444
|
+
await answering.get(locator)?.answered;
|
|
4445
|
+
const progress = underway ?? { complete: true, reachable: true };
|
|
4569
4446
|
try {
|
|
4570
4447
|
const local = await openLocal();
|
|
4571
|
-
|
|
4572
|
-
if (!live()
|
|
4573
|
-
return;
|
|
4574
|
-
const
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4448
|
+
for (;;) {
|
|
4449
|
+
if (!live())
|
|
4450
|
+
return null;
|
|
4451
|
+
const room = discovered.get(locator);
|
|
4452
|
+
const held = replicas.get(locator);
|
|
4453
|
+
if (room === undefined ||
|
|
4454
|
+
held === undefined ||
|
|
4455
|
+
room.replica.admission !== 'admitted' ||
|
|
4456
|
+
held.record.replica.selection !== room.replica.selection) {
|
|
4457
|
+
return null;
|
|
4458
|
+
}
|
|
4459
|
+
const at = held.record.roomVersion;
|
|
4460
|
+
if (options.adapter.reached(locator, at, change.roomVersion))
|
|
4461
|
+
return progress;
|
|
4462
|
+
if (!options.adapter.reached(locator, at, change.from)) {
|
|
4463
|
+
name(locator);
|
|
4464
|
+
return progress;
|
|
4465
|
+
}
|
|
4466
|
+
if (underway === null)
|
|
4467
|
+
moveTo(transferring(status));
|
|
4468
|
+
const writing = () => live() && !full;
|
|
4469
|
+
const refreshed = await ahead((hold) => pullRefresh(authorization ?? {}, held, writing, hold, () => change.read()));
|
|
4470
|
+
if (refreshed.kind !== 'advanced')
|
|
4471
|
+
return progress;
|
|
4472
|
+
const landing = {
|
|
4473
|
+
kind: 'refreshed',
|
|
4474
|
+
from: refreshed.from,
|
|
4475
|
+
remodelled: refreshed.remodelled,
|
|
4476
|
+
};
|
|
4477
|
+
if (await publish(local, room, refreshed.record, landing, writing)) {
|
|
4478
|
+
stale.delete(locator);
|
|
4479
|
+
await retainModel(local, room, modelOf(locator));
|
|
4480
|
+
return progress;
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4578
4483
|
}
|
|
4579
4484
|
catch {
|
|
4485
|
+
if (!stale.has(locator))
|
|
4486
|
+
name(locator);
|
|
4580
4487
|
stale.add(locator);
|
|
4488
|
+
return progress;
|
|
4581
4489
|
}
|
|
4582
|
-
|
|
4583
|
-
if (during === null)
|
|
4584
|
-
await rest(progress, live);
|
|
4585
|
-
}
|
|
4586
|
-
});
|
|
4490
|
+
}).then((progress) => rest(progress), () => undefined);
|
|
4587
4491
|
}
|
|
4588
|
-
function roomsCarried(replacing) {
|
|
4589
|
-
const replaced = new Set(replacing.map(roomLane));
|
|
4590
|
-
return Promise.all([...lanes]
|
|
4591
|
-
.filter(([lane, held]) => lane !== OPENING_LANE && !replaced.has(lane) && held.generation === generation)
|
|
4592
|
-
.map(([, held]) => held.chain));
|
|
4593
|
-
}
|
|
4594
|
-
const receipts = {
|
|
4595
|
-
heads: async () => (await openLocal()).receivedHeads(),
|
|
4596
|
-
parts: async (key, count) => (await openLocal()).receivedParts(key, count),
|
|
4597
|
-
keep: async (key, head, part) => (await openLocal()).keepReceived(key, head, part),
|
|
4598
|
-
drop: async (keys) => (await openLocal()).dropReceived(keys),
|
|
4599
|
-
};
|
|
4600
4492
|
function observeStream() {
|
|
4601
4493
|
const stream = options.adapter.roomStream;
|
|
4602
4494
|
if (stream === undefined || withdrawStream !== undefined)
|
|
@@ -4606,29 +4498,33 @@ export function composeRuntime(options) {
|
|
|
4606
4498
|
return;
|
|
4607
4499
|
if (event.kind === 'opened') {
|
|
4608
4500
|
socketOpen = true;
|
|
4501
|
+
announced = false;
|
|
4502
|
+
handed = false;
|
|
4503
|
+
naming = 0;
|
|
4609
4504
|
connectionLive();
|
|
4610
4505
|
generation += 1;
|
|
4611
|
-
reads.opened();
|
|
4612
4506
|
conversations.reopened();
|
|
4613
4507
|
recovery.resume();
|
|
4614
|
-
|
|
4508
|
+
}
|
|
4509
|
+
else if (event.kind === 'discovered') {
|
|
4510
|
+
if (!socketOpen)
|
|
4511
|
+
return;
|
|
4512
|
+
announced = true;
|
|
4513
|
+
if (missedChange) {
|
|
4514
|
+
missedChange = false;
|
|
4515
|
+
countAuthorityChange();
|
|
4516
|
+
}
|
|
4517
|
+
void sync('opened', event.discovery);
|
|
4615
4518
|
}
|
|
4616
4519
|
else if (event.kind === 'closed')
|
|
4617
|
-
closedSocket(event.revoked, event.lost);
|
|
4618
|
-
else if (event.kind === '
|
|
4619
|
-
void
|
|
4520
|
+
closedSocket(event.revoked, event.lost, event.authorityChanged);
|
|
4521
|
+
else if (event.kind === 'caught-up')
|
|
4522
|
+
void caughtUpRooms(event.expired);
|
|
4620
4523
|
else if (event.kind === 'authority-changed')
|
|
4621
|
-
void
|
|
4524
|
+
void changeAuthority(event);
|
|
4622
4525
|
else
|
|
4623
|
-
void applyRoom(event
|
|
4624
|
-
}
|
|
4625
|
-
}
|
|
4626
|
-
async function heldRevisions(locator, paths) {
|
|
4627
|
-
const held = replicas.get(locator);
|
|
4628
|
-
if (held === undefined || paths.length === 0)
|
|
4629
|
-
return new Map();
|
|
4630
|
-
const entries = await (await openLocal()).entries(held.record.key, 'confirmed', paths);
|
|
4631
|
-
return new Map(entries.flatMap(({ path, revision }) => revision === null ? [] : [[path, revision]]));
|
|
4526
|
+
void applyRoom(event);
|
|
4527
|
+
});
|
|
4632
4528
|
}
|
|
4633
4529
|
function writesHeld() {
|
|
4634
4530
|
return serviceMode === 'write-halted' || haltedRefusal;
|
|
@@ -4698,10 +4594,9 @@ export function composeRuntime(options) {
|
|
|
4698
4594
|
if (closed)
|
|
4699
4595
|
return;
|
|
4700
4596
|
closed = true;
|
|
4597
|
+
notifyDiscovered();
|
|
4701
4598
|
if (probing !== null)
|
|
4702
4599
|
clearInterval(probing);
|
|
4703
|
-
for (const resolve of shownWaiters.splice(0))
|
|
4704
|
-
resolve();
|
|
4705
4600
|
connectionLive();
|
|
4706
4601
|
lockListeners.clear();
|
|
4707
4602
|
readinessWatches.clear();
|
|
@@ -4713,11 +4608,13 @@ export function composeRuntime(options) {
|
|
|
4713
4608
|
conversations.close();
|
|
4714
4609
|
void recovery.retire();
|
|
4715
4610
|
roomListeners.clear();
|
|
4611
|
+
unlistedRooms.clear();
|
|
4716
4612
|
refusalListeners.clear();
|
|
4717
4613
|
storageLossListeners.clear();
|
|
4718
4614
|
statusListeners.clear();
|
|
4719
4615
|
serviceModeListeners.clear();
|
|
4720
4616
|
memberListeners.clear();
|
|
4617
|
+
authorityChangeListeners.clear();
|
|
4721
4618
|
liveQueries.close();
|
|
4722
4619
|
queueSubscriptions.clear();
|
|
4723
4620
|
roomDraftListeners.clear();
|
|
@@ -4725,6 +4622,7 @@ export function composeRuntime(options) {
|
|
|
4725
4622
|
}
|
|
4726
4623
|
async function stop() {
|
|
4727
4624
|
stopping = true;
|
|
4625
|
+
inFlight.abort();
|
|
4728
4626
|
await recovery.retire();
|
|
4729
4627
|
await Promise.all(working);
|
|
4730
4628
|
await mutations;
|
|
@@ -4738,20 +4636,20 @@ export function composeRuntime(options) {
|
|
|
4738
4636
|
observeStream();
|
|
4739
4637
|
void openLocal().then(() => {
|
|
4740
4638
|
if (capabilities.length > 0)
|
|
4741
|
-
|
|
4639
|
+
readyFromStore();
|
|
4742
4640
|
}, () => {
|
|
4743
|
-
|
|
4641
|
+
readyFromStore();
|
|
4744
4642
|
});
|
|
4745
4643
|
if (options.adapter.roomStream === undefined)
|
|
4746
|
-
void sync('unstreamed');
|
|
4644
|
+
void sync('unstreamed', null);
|
|
4747
4645
|
function planned(input, reach) {
|
|
4748
|
-
const plan =
|
|
4646
|
+
const plan = planWithin(input, roomFacts(reach), planReach(reach));
|
|
4749
4647
|
const named = plan.paths !== null &&
|
|
4750
4648
|
plan.rooms !== null &&
|
|
4751
4649
|
plan.paths.length * plan.rooms.length <= MAXIMUM_WINDOW;
|
|
4752
4650
|
return plan.limit === null && named ? plan : { ...plan, limit: capped(plan.limit) };
|
|
4753
4651
|
}
|
|
4754
|
-
function watchPlan(plan, deliver) {
|
|
4652
|
+
function watchPlan(plan, deliver, fail) {
|
|
4755
4653
|
let seeded = false;
|
|
4756
4654
|
let pending = true;
|
|
4757
4655
|
const shown = () => {
|
|
@@ -4777,7 +4675,7 @@ export function composeRuntime(options) {
|
|
|
4777
4675
|
}
|
|
4778
4676
|
seeded = false;
|
|
4779
4677
|
deliver(message);
|
|
4780
|
-
});
|
|
4678
|
+
}, fail);
|
|
4781
4679
|
return () => {
|
|
4782
4680
|
watchers.delete(shown);
|
|
4783
4681
|
if (watchers.size === 0)
|
|
@@ -4785,61 +4683,59 @@ export function composeRuntime(options) {
|
|
|
4785
4683
|
stop();
|
|
4786
4684
|
};
|
|
4787
4685
|
}
|
|
4788
|
-
async function searched(input, reach
|
|
4686
|
+
async function searched(input, reach) {
|
|
4789
4687
|
const asked = searchPlanOf(input);
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
rooms: answered.coverage.rooms,
|
|
4834
|
-
conversations: named,
|
|
4835
|
-
overlay: answered.coverage.overlay,
|
|
4688
|
+
await ready;
|
|
4689
|
+
const search = options.adapter.search;
|
|
4690
|
+
if (search === undefined) {
|
|
4691
|
+
return {
|
|
4692
|
+
kind: 'undeclared',
|
|
4693
|
+
code: 'action-undeclared',
|
|
4694
|
+
fields: { name: 'search' },
|
|
4695
|
+
retryable: false,
|
|
4696
|
+
};
|
|
4697
|
+
}
|
|
4698
|
+
if (closed || refusal !== null || connectionOf() !== 'connected')
|
|
4699
|
+
return OFFLINE;
|
|
4700
|
+
const reached = reach === null ? null : new Set(reach.rooms);
|
|
4701
|
+
const named = asked.rooms === null
|
|
4702
|
+
? reached === null
|
|
4703
|
+
? null
|
|
4704
|
+
: [...reached]
|
|
4705
|
+
: asked.rooms.filter((room) => reached === null || reached.has(room));
|
|
4706
|
+
const rooms = named === null ? null : named.filter((room) => replicas.has(room)).sort();
|
|
4707
|
+
if (rooms !== null && rooms.length === 0)
|
|
4708
|
+
return { kind: 'found', documents: [] };
|
|
4709
|
+
try {
|
|
4710
|
+
await ensureAuthorization();
|
|
4711
|
+
}
|
|
4712
|
+
catch {
|
|
4713
|
+
return UNCERTAIN;
|
|
4714
|
+
}
|
|
4715
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
4716
|
+
return UNCERTAIN;
|
|
4717
|
+
journal.record('transfer search');
|
|
4718
|
+
try {
|
|
4719
|
+
const result = await search(authorization, { text: asked.text, rooms });
|
|
4720
|
+
if (result.kind === 'revoked-authorization')
|
|
4721
|
+
await refuse('revoked-authorization');
|
|
4722
|
+
journal.record(`outcome search ${result.kind}`);
|
|
4723
|
+
if (result.kind === 'rejected')
|
|
4724
|
+
return result;
|
|
4725
|
+
if (result.kind !== 'found')
|
|
4726
|
+
return failure(result);
|
|
4727
|
+
const kept = rooms === null ? null : new Set(rooms);
|
|
4728
|
+
return {
|
|
4729
|
+
kind: 'found',
|
|
4730
|
+
documents: result.documents.filter((hit) => kept === null || kept.has(hit.room)),
|
|
4836
4731
|
};
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4732
|
+
}
|
|
4733
|
+
catch {
|
|
4734
|
+
journal.record('outcome search transport-uncertainty');
|
|
4735
|
+
return UNCERTAIN;
|
|
4840
4736
|
}
|
|
4841
4737
|
}
|
|
4842
|
-
const watch = (topic, input, deliver) => {
|
|
4738
|
+
const watch = (topic, input, deliver, fail) => {
|
|
4843
4739
|
if (topic === 'room-drafts') {
|
|
4844
4740
|
const listener = { notify: deliver, ready: false };
|
|
4845
4741
|
roomDraftListeners.add(listener);
|
|
@@ -4888,12 +4784,13 @@ export function composeRuntime(options) {
|
|
|
4888
4784
|
};
|
|
4889
4785
|
}
|
|
4890
4786
|
if (topic === 'readiness') {
|
|
4891
|
-
return watchReadiness({ target: '', code: 'stored'
|
|
4787
|
+
return watchReadiness({ target: '', code: 'stored' }, null, deliver);
|
|
4892
4788
|
}
|
|
4893
4789
|
if (topic === 'rooms') {
|
|
4894
|
-
|
|
4895
|
-
|
|
4790
|
+
unlistedRooms.add(deliver);
|
|
4791
|
+
emitKnownRooms();
|
|
4896
4792
|
return () => {
|
|
4793
|
+
unlistedRooms.delete(deliver);
|
|
4897
4794
|
roomListeners.delete(deliver);
|
|
4898
4795
|
};
|
|
4899
4796
|
}
|
|
@@ -4932,6 +4829,13 @@ export function composeRuntime(options) {
|
|
|
4932
4829
|
memberListeners.delete(deliver);
|
|
4933
4830
|
};
|
|
4934
4831
|
}
|
|
4832
|
+
if (topic === 'authority-changes') {
|
|
4833
|
+
authorityChangeListeners.add(deliver);
|
|
4834
|
+
deliver(authorityChanges);
|
|
4835
|
+
return () => {
|
|
4836
|
+
authorityChangeListeners.delete(deliver);
|
|
4837
|
+
};
|
|
4838
|
+
}
|
|
4935
4839
|
if (topic === 'queue') {
|
|
4936
4840
|
const queue = { locator: input[0], notify: deliver };
|
|
4937
4841
|
queueSubscriptions.add(queue);
|
|
@@ -4940,17 +4844,27 @@ export function composeRuntime(options) {
|
|
|
4940
4844
|
queueSubscriptions.delete(queue);
|
|
4941
4845
|
};
|
|
4942
4846
|
}
|
|
4943
|
-
return watchPlan(planned(input[0], null), deliver);
|
|
4847
|
+
return watchPlan(planned(input[0], null), deliver, fail);
|
|
4944
4848
|
};
|
|
4945
4849
|
const md = {
|
|
4946
4850
|
loaded() {
|
|
4947
4851
|
return openLocal().then(() => undefined);
|
|
4948
4852
|
},
|
|
4949
4853
|
rooms() {
|
|
4950
|
-
|
|
4854
|
+
if (closed)
|
|
4855
|
+
return Promise.resolve([]);
|
|
4856
|
+
return new Promise((settle) => {
|
|
4857
|
+
const answer = (value) => {
|
|
4858
|
+
unlistedRooms.delete(answer);
|
|
4859
|
+
roomListeners.delete(answer);
|
|
4860
|
+
settle(value);
|
|
4861
|
+
};
|
|
4862
|
+
unlistedRooms.add(answer);
|
|
4863
|
+
emitKnownRooms();
|
|
4864
|
+
});
|
|
4951
4865
|
},
|
|
4952
4866
|
query: ((input) => rowsQuery(planned(input, null))),
|
|
4953
|
-
search: (input) => searched(input, null
|
|
4867
|
+
search: (input) => searched(input, null),
|
|
4954
4868
|
types(locator) {
|
|
4955
4869
|
return {
|
|
4956
4870
|
collection: modelTypes(modelOf(locator)),
|
|
@@ -5053,6 +4967,8 @@ export function composeRuntime(options) {
|
|
|
5053
4967
|
return journal.export();
|
|
5054
4968
|
},
|
|
5055
4969
|
cleanUp,
|
|
4970
|
+
unsent: () => database.ask({ kind: 'authored.unsent' }),
|
|
4971
|
+
reset: () => options.shell.reset(),
|
|
5056
4972
|
persistence() {
|
|
5057
4973
|
return persistence === 'granted';
|
|
5058
4974
|
},
|
|
@@ -5075,7 +4991,7 @@ export function composeRuntime(options) {
|
|
|
5075
4991
|
authorization = undefined;
|
|
5076
4992
|
discoveryPaused = false;
|
|
5077
4993
|
if (options.adapter.roomStream === undefined)
|
|
5078
|
-
await sync('unstreamed');
|
|
4994
|
+
await sync('unstreamed', null);
|
|
5079
4995
|
else
|
|
5080
4996
|
await task(OPENING_LANE, ensureAuthorization);
|
|
5081
4997
|
},
|
|
@@ -5092,12 +5008,12 @@ export function composeRuntime(options) {
|
|
|
5092
5008
|
lockedRooms,
|
|
5093
5009
|
watchLocks,
|
|
5094
5010
|
located,
|
|
5011
|
+
room: (locator) => listed().find((one) => one.locator === locator),
|
|
5095
5012
|
};
|
|
5096
5013
|
return {
|
|
5097
5014
|
md,
|
|
5098
5015
|
seed,
|
|
5099
5016
|
stop,
|
|
5100
|
-
lose: loseRooms,
|
|
5101
5017
|
watch,
|
|
5102
5018
|
persisted(answer) {
|
|
5103
5019
|
if (answer === persistence)
|
|
@@ -5106,7 +5022,6 @@ export function composeRuntime(options) {
|
|
|
5106
5022
|
journal.record(`persistence ${answer}`);
|
|
5107
5023
|
queueReadiness();
|
|
5108
5024
|
},
|
|
5109
|
-
shown: screen,
|
|
5110
5025
|
within: (reach, port) => {
|
|
5111
5026
|
if (full)
|
|
5112
5027
|
void probed();
|