@agentshouse/mdruntime 0.1.0-ccbb89b0ca151 → 0.1.0-ccc0e3407f107
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 +74 -57
- 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 +11 -18
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +85 -108
- 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 +7 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +23 -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 +10 -9
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1009 -1063
- 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 -4
- package/dist/memory.d.ts +11 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +167 -84
- 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 +8 -8
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +6 -12
- package/dist/observation.d.ts +3 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +8 -3
- package/dist/outcome.d.ts +0 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +3 -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 +10 -8
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +29 -14
- 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 +20 -12
- 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 +21 -34
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +34 -77
- package/dist/stored.d.ts +11 -34
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +158 -241
- package/dist/tables.d.ts +23 -48
- 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,
|
|
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
|
}
|
|
@@ -102,11 +100,15 @@ export function composeRuntime(options) {
|
|
|
102
100
|
const fencedSince = (locator, since) => fences.has(locator) || (lastFenced.get(locator) ?? 0) > since;
|
|
103
101
|
let discoveries = 0;
|
|
104
102
|
const replicas = new FencedMap(isFenced);
|
|
103
|
+
const reopened = new Set();
|
|
104
|
+
const usableWhenHeld = options.usableWhenHeld ?? [];
|
|
105
105
|
const queuesWithoutReplica = new FencedMap(isFenced);
|
|
106
106
|
const pendings = new Map();
|
|
107
|
+
const landedAhead = new Map();
|
|
107
108
|
const seeds = new Map();
|
|
108
109
|
const seedWatchers = new Map();
|
|
109
110
|
const roomDrafts = new FencedMap(isFenced);
|
|
111
|
+
const attachedFiles = new FencedMap(isFenced);
|
|
110
112
|
const presentations = new Map();
|
|
111
113
|
let limits = [];
|
|
112
114
|
let capabilities = [];
|
|
@@ -114,15 +116,24 @@ export function composeRuntime(options) {
|
|
|
114
116
|
const counted = new Map();
|
|
115
117
|
const unreadable = new Set();
|
|
116
118
|
const builtIn = builtInModel(roomTypes);
|
|
119
|
+
const typedPaths = options.adapter.typedPaths ?? [];
|
|
117
120
|
const models = new FencedMap(isFenced);
|
|
118
121
|
const liveModels = new FencedMap(isFenced);
|
|
119
122
|
const invalidModels = new Set();
|
|
120
123
|
const discovered = new Map();
|
|
121
|
-
|
|
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
|
+
};
|
|
122
135
|
let roomsListed = false;
|
|
123
136
|
const scopedRefusals = new Map();
|
|
124
|
-
const sourceRefusals = new Map();
|
|
125
|
-
const declarationRefusals = new Set();
|
|
126
137
|
function modelOf(locator) {
|
|
127
138
|
return models.get(locator) ?? NO_MODEL;
|
|
128
139
|
}
|
|
@@ -132,7 +143,7 @@ export function composeRuntime(options) {
|
|
|
132
143
|
: modelOf(locator);
|
|
133
144
|
}
|
|
134
145
|
function composed(record) {
|
|
135
|
-
return compositionOf(record, builtIn);
|
|
146
|
+
return compositionOf(record, builtIn, typedPaths);
|
|
136
147
|
}
|
|
137
148
|
function compositionFor(record) {
|
|
138
149
|
return composed({ ...record, model: savedModel(record.locator) });
|
|
@@ -145,30 +156,31 @@ export function composeRuntime(options) {
|
|
|
145
156
|
let closed = false;
|
|
146
157
|
let stopping = false;
|
|
147
158
|
let withdrawStream;
|
|
148
|
-
const announced = new Map();
|
|
149
159
|
let underway = null;
|
|
150
160
|
let streamed = null;
|
|
151
161
|
let handed = false;
|
|
162
|
+
let naming = 0;
|
|
152
163
|
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
153
164
|
const repairing = new Set();
|
|
154
165
|
const calling = new Set();
|
|
155
166
|
const repairable = new Map();
|
|
156
|
-
let outcomeRetention = null;
|
|
157
167
|
const answering = new Map();
|
|
158
168
|
let complete = false;
|
|
159
169
|
let recorded = false;
|
|
160
170
|
const stale = new Set();
|
|
161
171
|
let generation = 0;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
const reloadsDue = new Map();
|
|
172
|
+
const reloadsDue = new Set();
|
|
173
|
+
const highest = new Map();
|
|
174
|
+
const beyondHorizon = new Set();
|
|
166
175
|
const lanes = new Map();
|
|
167
|
-
|
|
176
|
+
const roomLanes = new Map();
|
|
177
|
+
let roomWork = 0;
|
|
178
|
+
const roomReads = { free: ROOM_READS, waiting: [] };
|
|
168
179
|
const working = new Set();
|
|
169
180
|
let mutations = Promise.resolve();
|
|
170
181
|
let gathering = null;
|
|
171
182
|
const roomListeners = new Set();
|
|
183
|
+
const unlistedRooms = new Set();
|
|
172
184
|
const refusalListeners = new Set();
|
|
173
185
|
const storageLossListeners = new Set();
|
|
174
186
|
const statusListeners = new Set();
|
|
@@ -179,6 +191,8 @@ export function composeRuntime(options) {
|
|
|
179
191
|
let withdrawConversations;
|
|
180
192
|
let currencyQueued = null;
|
|
181
193
|
let socketOpen = false;
|
|
194
|
+
let announced = false;
|
|
195
|
+
let missedChange = false;
|
|
182
196
|
const confirmed = new Set();
|
|
183
197
|
let lockGrace = null;
|
|
184
198
|
const lockListeners = new Set();
|
|
@@ -188,6 +202,8 @@ export function composeRuntime(options) {
|
|
|
188
202
|
const serviceModeListeners = new Set();
|
|
189
203
|
let member = null;
|
|
190
204
|
const memberListeners = new Set();
|
|
205
|
+
let authorityChanges = 0;
|
|
206
|
+
const authorityChangeListeners = new Set();
|
|
191
207
|
const unpreparedDemand = new Set();
|
|
192
208
|
const unpreparable = new Set();
|
|
193
209
|
let preparing = false;
|
|
@@ -212,16 +228,16 @@ export function composeRuntime(options) {
|
|
|
212
228
|
let readinessQueued = false;
|
|
213
229
|
const catching = new Set();
|
|
214
230
|
const transfers = new FencedMap(isFenced);
|
|
215
|
-
const installs = new
|
|
216
|
-
const keptParts = new Map();
|
|
231
|
+
const installs = new Set();
|
|
217
232
|
const arrivals = new Map();
|
|
233
|
+
const streaming = new Set();
|
|
218
234
|
const underived = new Map();
|
|
235
|
+
const unindexed = new Map();
|
|
236
|
+
const startedOver = new Map();
|
|
219
237
|
let arrivalReported = 0;
|
|
220
|
-
let
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
let queuedBytes = 0;
|
|
224
|
-
const partWaiters = [];
|
|
238
|
+
let streamsFree = STREAMS_HELD;
|
|
239
|
+
let queuedText = 0;
|
|
240
|
+
const installWaiters = [];
|
|
225
241
|
const stageBudget = {
|
|
226
242
|
bytes: STAGE_FLOOR * 4,
|
|
227
243
|
floor: STAGE_FLOOR,
|
|
@@ -234,23 +250,10 @@ export function composeRuntime(options) {
|
|
|
234
250
|
ceiling: DERIVE_BYTES,
|
|
235
251
|
aim: DERIVE_AIM_MS,
|
|
236
252
|
};
|
|
237
|
-
|
|
253
|
+
const installTurns = { busy: false, ahead: 0, finishing: [], starting: [] };
|
|
238
254
|
const conversations = createConversationStore({
|
|
239
|
-
agents: declaredAgents,
|
|
240
|
-
documents: (declaration, after) => rowsQuery(planOf({
|
|
241
|
-
rooms: [declaration.locator],
|
|
242
|
-
types: [declaration.type],
|
|
243
|
-
select: {
|
|
244
|
-
fields: [
|
|
245
|
-
declaration.identity,
|
|
246
|
-
declaration.name,
|
|
247
|
-
...(declaration.retired === undefined ? [] : [declaration.retired]),
|
|
248
|
-
].map((field) => `/${escapePointer(field)}`),
|
|
249
|
-
},
|
|
250
|
-
limit: MAXIMUM_WINDOW,
|
|
251
|
-
...(after === undefined ? {} : { after }),
|
|
252
|
-
}, roomFacts())),
|
|
253
255
|
call: (name, input) => invokeAction(name, input),
|
|
256
|
+
history: (request) => readConversationHistory(request),
|
|
254
257
|
durably,
|
|
255
258
|
stream: () => options.adapter.conversations,
|
|
256
259
|
publish: publishChangeSet,
|
|
@@ -258,14 +261,22 @@ export function composeRuntime(options) {
|
|
|
258
261
|
full: () => full,
|
|
259
262
|
closed: () => closed,
|
|
260
263
|
unanswered: resendLater,
|
|
261
|
-
expired: (operation) => pastHorizon(operation),
|
|
262
264
|
});
|
|
263
265
|
const journal = createLog();
|
|
264
266
|
const keptFiles = openedFiles(database, isFenced);
|
|
265
|
-
let
|
|
267
|
+
let readyFromStore = () => { };
|
|
266
268
|
const ready = new Promise((resolve) => {
|
|
267
|
-
|
|
269
|
+
readyFromStore = resolve;
|
|
270
|
+
});
|
|
271
|
+
let notifyDiscovered = () => { };
|
|
272
|
+
const firstDiscovery = new Promise((resolve) => {
|
|
273
|
+
notifyDiscovered = resolve;
|
|
268
274
|
});
|
|
275
|
+
let roomsNamed = false;
|
|
276
|
+
function notifyReady() {
|
|
277
|
+
notifyDiscovered();
|
|
278
|
+
readyFromStore();
|
|
279
|
+
}
|
|
269
280
|
function rememberRoomDraft(record) {
|
|
270
281
|
const room = roomDrafts.get(record.locator) ?? new Map();
|
|
271
282
|
room.set(record.path, record);
|
|
@@ -342,9 +353,9 @@ export function composeRuntime(options) {
|
|
|
342
353
|
return value === undefined ? null : structuredClone(value);
|
|
343
354
|
}
|
|
344
355
|
async function writtenRoomDraft(room, path, revision, input, next) {
|
|
345
|
-
const parts = readParts(input);
|
|
356
|
+
const parts = readParts(input, path);
|
|
346
357
|
if (path === '' || normalize(path) !== path)
|
|
347
|
-
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`);
|
|
348
359
|
const local = await openLocal();
|
|
349
360
|
const current = roomDrafts.get(room)?.get(path);
|
|
350
361
|
if ((current?.revision ?? null) !== revision) {
|
|
@@ -352,7 +363,9 @@ export function composeRuntime(options) {
|
|
|
352
363
|
}
|
|
353
364
|
const file = parts.file === undefined
|
|
354
365
|
? undefined
|
|
355
|
-
: 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);
|
|
356
369
|
const record = {
|
|
357
370
|
locator: room,
|
|
358
371
|
path,
|
|
@@ -387,6 +400,20 @@ export function composeRuntime(options) {
|
|
|
387
400
|
(queuesWithoutReplica.size > 0 ||
|
|
388
401
|
[...replicas.values()].some((replica) => replica.queue.length > 0)));
|
|
389
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
|
+
}
|
|
390
417
|
function attention() {
|
|
391
418
|
const attentionRequired = [];
|
|
392
419
|
for (const record of [...roomDrafts.values()].flatMap((room) => [...room.values()])) {
|
|
@@ -398,16 +425,9 @@ export function composeRuntime(options) {
|
|
|
398
425
|
returned: structuredClone(record.returned),
|
|
399
426
|
});
|
|
400
427
|
}
|
|
401
|
-
for (const [room, refusal] of sourceRefusals)
|
|
402
|
-
attentionRequired.push({ room, refusal: structuredClone(refusal) });
|
|
403
428
|
attentionRequired.sort((left, right) => {
|
|
404
429
|
if (left.room !== right.room)
|
|
405
430
|
return left.room < right.room ? -1 : 1;
|
|
406
|
-
if ('refusal' in left || 'refusal' in right) {
|
|
407
|
-
if ('refusal' in left && 'refusal' in right)
|
|
408
|
-
return 0;
|
|
409
|
-
return 'refusal' in left ? -1 : 1;
|
|
410
|
-
}
|
|
411
431
|
if (left.path !== right.path)
|
|
412
432
|
return left.path < right.path ? -1 : 1;
|
|
413
433
|
return 0;
|
|
@@ -419,7 +439,7 @@ export function composeRuntime(options) {
|
|
|
419
439
|
if (reset === null || submission.reset < reset)
|
|
420
440
|
reset = submission.reset;
|
|
421
441
|
}
|
|
422
|
-
return { attentionRequired, reset };
|
|
442
|
+
return { attentionRequired, unsent: unsent(), reset };
|
|
423
443
|
}
|
|
424
444
|
function documentsIn(locator) {
|
|
425
445
|
return replicas.get(locator)?.count ?? 0;
|
|
@@ -508,11 +528,26 @@ export function composeRuntime(options) {
|
|
|
508
528
|
const value = listed();
|
|
509
529
|
for (const listener of roomListeners)
|
|
510
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
|
+
}
|
|
511
545
|
}
|
|
512
546
|
function emitRefusal() {
|
|
513
547
|
queueReadiness();
|
|
514
548
|
for (const listener of refusalListeners)
|
|
515
549
|
listener(refusal);
|
|
550
|
+
emitKnownRooms();
|
|
516
551
|
}
|
|
517
552
|
function emitStorageLoss() {
|
|
518
553
|
queueReadiness();
|
|
@@ -533,6 +568,7 @@ export function composeRuntime(options) {
|
|
|
533
568
|
if (closed)
|
|
534
569
|
return;
|
|
535
570
|
queueReadiness();
|
|
571
|
+
emitKnownRooms();
|
|
536
572
|
const next = status.state === 'synced' && !conversations.current()
|
|
537
573
|
? { ...status, state: 'syncing' }
|
|
538
574
|
: status;
|
|
@@ -546,9 +582,6 @@ export function composeRuntime(options) {
|
|
|
546
582
|
if (localSaves)
|
|
547
583
|
return Object.freeze({ batch, state: 'local' });
|
|
548
584
|
const state = pendings.get(pendingKey(locator, batch.id));
|
|
549
|
-
if (state?.accepted !== undefined) {
|
|
550
|
-
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
551
|
-
}
|
|
552
585
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
553
586
|
}
|
|
554
587
|
function entriesOf(locator) {
|
|
@@ -578,12 +611,12 @@ export function composeRuntime(options) {
|
|
|
578
611
|
const key = held.record.key;
|
|
579
612
|
const done = await exclusive(async () => {
|
|
580
613
|
const local = await openLocal();
|
|
581
|
-
return timed(deriveBudget, () => local.rederive(key, deriveBudget.bytes));
|
|
614
|
+
return timed(deriveBudget, () => local.rederive(key, composed(held.record), deriveBudget.bytes, DERIVE_LONGEST_MS, DERIVE_FLOOR));
|
|
582
615
|
});
|
|
583
616
|
const now = replicas.get(locator);
|
|
584
617
|
if (now?.record.key !== key)
|
|
585
618
|
continue;
|
|
586
|
-
if (!done)
|
|
619
|
+
if (!done.done)
|
|
587
620
|
continue;
|
|
588
621
|
replicas.set(locator, { ...now, indexed: true });
|
|
589
622
|
publishChangeSet(changeSet(locator, { documents: null }));
|
|
@@ -622,11 +655,7 @@ export function composeRuntime(options) {
|
|
|
622
655
|
})();
|
|
623
656
|
}
|
|
624
657
|
function roomsOfReach(reach) {
|
|
625
|
-
return new Set([
|
|
626
|
-
...reach.rooms,
|
|
627
|
-
...reach.documents.map(roomOfKey),
|
|
628
|
-
...reach.areas.flatMap((area) => (area.types.length === 0 ? [] : [area.room])),
|
|
629
|
-
]);
|
|
658
|
+
return new Set([...reach.rooms, ...reach.documents.map(roomOfKey)]);
|
|
630
659
|
}
|
|
631
660
|
function reachedRooms(plan) {
|
|
632
661
|
const reached = plan.reach === null ? null : roomsOfReach(plan.reach);
|
|
@@ -665,7 +694,7 @@ export function composeRuntime(options) {
|
|
|
665
694
|
coverage.push({
|
|
666
695
|
room: locator,
|
|
667
696
|
generation: replica.record.transfer,
|
|
668
|
-
current: !(catching.has(locator) ||
|
|
697
|
+
current: !(catching.has(locator) || transfers.has(locator)),
|
|
669
698
|
});
|
|
670
699
|
read.push({ locator, key: replica.record.key });
|
|
671
700
|
for (const batch of replica.queue)
|
|
@@ -692,8 +721,6 @@ export function composeRuntime(options) {
|
|
|
692
721
|
return { listed: named.every((room) => replicas.has(room)), rooms: named };
|
|
693
722
|
}
|
|
694
723
|
function roomState(locator) {
|
|
695
|
-
if (sourceRefusals.has(locator))
|
|
696
|
-
return 'refused';
|
|
697
724
|
const listedRoom = discovered.get(locator);
|
|
698
725
|
if (roomsListed &&
|
|
699
726
|
(listedRoom === undefined ||
|
|
@@ -708,7 +735,7 @@ export function composeRuntime(options) {
|
|
|
708
735
|
}
|
|
709
736
|
function conversationCoverage(reach) {
|
|
710
737
|
const declared = reach === null || reach.conversations;
|
|
711
|
-
if (!declared || options.adapter.conversations === undefined
|
|
738
|
+
if (!declared || options.adapter.conversations === undefined)
|
|
712
739
|
return 'undeclared';
|
|
713
740
|
if (!conversations.known() || !conversations.current())
|
|
714
741
|
return 'acquiring';
|
|
@@ -723,83 +750,50 @@ export function composeRuntime(options) {
|
|
|
723
750
|
? 'connected'
|
|
724
751
|
: 'connecting';
|
|
725
752
|
}
|
|
726
|
-
function
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
rule: held.code,
|
|
738
|
-
location: typeof location === 'string' ? location : null,
|
|
739
|
-
},
|
|
740
|
-
];
|
|
741
|
-
});
|
|
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 };
|
|
742
764
|
}
|
|
743
765
|
function installationOf(coverage) {
|
|
744
766
|
const rooms = { done: 0, total: 0 };
|
|
745
|
-
const parts = { done: 0, total: 0 };
|
|
746
767
|
const bytes = { done: 0, total: 0 };
|
|
747
|
-
const received = { done: 0, total: 0 };
|
|
748
768
|
let installing = 0;
|
|
749
|
-
let
|
|
750
|
-
let listed = false;
|
|
751
|
-
let waiting = false;
|
|
769
|
+
let downloading = false;
|
|
752
770
|
for (const { room, state } of coverage) {
|
|
753
771
|
if (state === 'unadmitted')
|
|
754
772
|
continue;
|
|
755
773
|
rooms.total += 1;
|
|
756
774
|
if (state === 'complete')
|
|
757
775
|
rooms.done += 1;
|
|
758
|
-
const transfer = transfers.get(room);
|
|
759
|
-
const replica = replicas.get(room)?.record.inventory ?? null;
|
|
760
|
-
const inventory = transfer?.inventory ?? replica ?? installs.get(room) ?? null;
|
|
761
776
|
const underway = state === 'acquiring' || (installs.has(room) && state !== 'complete');
|
|
762
777
|
if (underway)
|
|
763
778
|
installing += 1;
|
|
764
|
-
if (underway &&
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
bytes.total += inventory.bytes;
|
|
771
|
-
parts.done += done.parts;
|
|
772
|
-
bytes.done += done.bytes;
|
|
773
|
-
received.total += inventory.bytes;
|
|
774
|
-
received.done += done.bytes;
|
|
775
|
-
for (const held of [keptParts.get(room), arrivals.get(room)]) {
|
|
776
|
-
for (const arrived of held?.values() ?? [])
|
|
777
|
-
received.done += arrived;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
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);
|
|
781
785
|
}
|
|
782
786
|
const step = installing === 0
|
|
783
787
|
? null
|
|
784
|
-
:
|
|
785
|
-
? '
|
|
786
|
-
:
|
|
787
|
-
? '
|
|
788
|
-
:
|
|
789
|
-
|
|
790
|
-
: 'replaying';
|
|
791
|
-
return {
|
|
792
|
-
step,
|
|
793
|
-
paused: waiting && !onScreen ? 'hidden' : null,
|
|
794
|
-
rooms,
|
|
795
|
-
parts,
|
|
796
|
-
bytes,
|
|
797
|
-
received,
|
|
798
|
-
};
|
|
788
|
+
: downloading
|
|
789
|
+
? 'downloading'
|
|
790
|
+
: bytes.done < bytes.total
|
|
791
|
+
? 'writing'
|
|
792
|
+
: 'replaying';
|
|
793
|
+
return { step, rooms, bytes };
|
|
799
794
|
}
|
|
800
795
|
function readinessFor(port, reach) {
|
|
801
796
|
const required = requiredRooms(reach);
|
|
802
|
-
const search = port.search === 'required';
|
|
803
797
|
const coverage = required.rooms.map((room) => ({ room, state: roomState(room) }));
|
|
804
798
|
const preparing = coverage
|
|
805
799
|
.filter((room) => room.state === 'preparing' && !unpreparable.has(room.room))
|
|
@@ -812,16 +806,15 @@ export function composeRuntime(options) {
|
|
|
812
806
|
listed: required.listed,
|
|
813
807
|
rooms: coverage,
|
|
814
808
|
conversations: conversationCoverage(reach),
|
|
815
|
-
search: search ? 'required' : 'unused',
|
|
816
809
|
},
|
|
817
810
|
persistence,
|
|
818
811
|
connection: connectionOf(),
|
|
819
812
|
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
820
|
-
space: full ? 'full' :
|
|
813
|
+
space: full ? 'full' : null,
|
|
821
814
|
installation: installationOf(coverage),
|
|
822
|
-
refused: refusedIn(required.rooms),
|
|
823
815
|
lost: storageLoss !== null,
|
|
824
|
-
established: targets[port.target]?.established === true
|
|
816
|
+
established: targets[port.target]?.established === true ||
|
|
817
|
+
(usableWhenHeld.includes(port.target) && required.rooms.some((room) => reopened.has(room))),
|
|
825
818
|
});
|
|
826
819
|
}
|
|
827
820
|
function establish(name) {
|
|
@@ -874,7 +867,8 @@ export function composeRuntime(options) {
|
|
|
874
867
|
probeEachMinute();
|
|
875
868
|
}
|
|
876
869
|
function installedTarget() {
|
|
877
|
-
return
|
|
870
|
+
return ((usableWhenHeld.length > 0 && reopened.size > 0) ||
|
|
871
|
+
Object.values(targets).some((target) => target.established));
|
|
878
872
|
}
|
|
879
873
|
function probeEachMinute() {
|
|
880
874
|
if (!full || probing !== null || closed || !installedTarget())
|
|
@@ -905,8 +899,12 @@ export function composeRuntime(options) {
|
|
|
905
899
|
unpreparable.clear();
|
|
906
900
|
journal.record('space freed');
|
|
907
901
|
queueReadiness();
|
|
908
|
-
if (
|
|
909
|
-
|
|
902
|
+
if (closed || authorization === undefined)
|
|
903
|
+
return;
|
|
904
|
+
if (options.adapter.roomStream === undefined)
|
|
905
|
+
void sync('unstreamed', null);
|
|
906
|
+
else
|
|
907
|
+
void resynchronize();
|
|
910
908
|
}
|
|
911
909
|
function seedRoomHeld(room, version) {
|
|
912
910
|
return indexed(room) && reachedBy(room, version);
|
|
@@ -1032,6 +1030,10 @@ export function composeRuntime(options) {
|
|
|
1032
1030
|
}
|
|
1033
1031
|
function publishChangeSet(set) {
|
|
1034
1032
|
applyChangeSet(set);
|
|
1033
|
+
for (const { locator, path } of set.documents) {
|
|
1034
|
+
if (path !== null)
|
|
1035
|
+
attachedFiles.get(locator)?.delete(path);
|
|
1036
|
+
}
|
|
1035
1037
|
if (set.rooms.length > 0 ||
|
|
1036
1038
|
[...new Set(set.documents.map(({ locator }) => locator))].some((locator) => rooms.has(locator) && counted.get(locator) !== documentsIn(locator)))
|
|
1037
1039
|
emitRooms();
|
|
@@ -1050,6 +1052,8 @@ export function composeRuntime(options) {
|
|
|
1050
1052
|
}
|
|
1051
1053
|
async function refuse(reason, provider = null) {
|
|
1052
1054
|
discoveryPaused = true;
|
|
1055
|
+
inFlight.abort();
|
|
1056
|
+
inFlight = new AbortController();
|
|
1053
1057
|
try {
|
|
1054
1058
|
if (reason === 'revoked-authorization') {
|
|
1055
1059
|
await exclusive(async () => {
|
|
@@ -1067,7 +1071,7 @@ export function composeRuntime(options) {
|
|
|
1067
1071
|
locator,
|
|
1068
1072
|
key: record.key,
|
|
1069
1073
|
record: storedRecord(record),
|
|
1070
|
-
|
|
1074
|
+
composed: composedOf(composed(record)),
|
|
1071
1075
|
})),
|
|
1072
1076
|
});
|
|
1073
1077
|
}
|
|
@@ -1134,10 +1138,10 @@ export function composeRuntime(options) {
|
|
|
1134
1138
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
1135
1139
|
roomVersion: replicas.get(room.locator)?.record.roomVersion ?? null,
|
|
1136
1140
|
synchronizationAvailable,
|
|
1137
|
-
refusal: scopedRefusals.get(room.locator) ??
|
|
1141
|
+
refusal: scopedRefusals.get(room.locator) ?? null,
|
|
1138
1142
|
});
|
|
1139
1143
|
}
|
|
1140
|
-
async function applyDiscovery(found, started,
|
|
1144
|
+
async function applyDiscovery(found, started, left) {
|
|
1141
1145
|
const lockedBefore = lockedRooms();
|
|
1142
1146
|
for (const room of found) {
|
|
1143
1147
|
const fence = fences.get(room.locator);
|
|
@@ -1151,18 +1155,11 @@ export function composeRuntime(options) {
|
|
|
1151
1155
|
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
1152
1156
|
for (const locator of listedLocators)
|
|
1153
1157
|
scopedRefusals.delete(locator);
|
|
1154
|
-
if (
|
|
1158
|
+
if (left === undefined)
|
|
1155
1159
|
discovered.clear();
|
|
1156
|
-
else
|
|
1157
|
-
for (const locator of covered)
|
|
1158
|
-
discovered.delete(locator);
|
|
1159
1160
|
for (const room of listed)
|
|
1160
1161
|
discovered.set(room.locator, room);
|
|
1161
|
-
|
|
1162
|
-
if (options.adapter.exhaustiveDiscovery === true)
|
|
1163
|
-
await loseRooms(unlisted);
|
|
1164
|
-
else
|
|
1165
|
-
unlistedKept(unlisted);
|
|
1162
|
+
await loseRooms(left ?? unlistedHeld(new Set(found.map((room) => room.locator))));
|
|
1166
1163
|
for (const room of listed) {
|
|
1167
1164
|
const held = replicas.get(room.locator)?.record;
|
|
1168
1165
|
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
@@ -1193,15 +1190,6 @@ export function composeRuntime(options) {
|
|
|
1193
1190
|
]),
|
|
1194
1191
|
].filter((locator) => !listed.has(locator));
|
|
1195
1192
|
}
|
|
1196
|
-
function unlistedKept(unlisted) {
|
|
1197
|
-
for (const locator of unlisted) {
|
|
1198
|
-
const held = replicas.get(locator)?.record;
|
|
1199
|
-
if (held !== undefined)
|
|
1200
|
-
list(held, false);
|
|
1201
|
-
else
|
|
1202
|
-
rooms.delete(locator);
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
1193
|
function fence(locators) {
|
|
1206
1194
|
const fresh = [...new Set(locators)].filter((locator) => !fences.has(locator));
|
|
1207
1195
|
const removed = fresh.map((locator) => ({
|
|
@@ -1212,23 +1200,26 @@ export function composeRuntime(options) {
|
|
|
1212
1200
|
fences.set(locator, { at: discoveries, erased: false });
|
|
1213
1201
|
lastFenced.set(locator, ++fencings);
|
|
1214
1202
|
confirmed.delete(locator);
|
|
1215
|
-
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
1203
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []) {
|
|
1216
1204
|
pendings.delete(pendingKey(locator, batch.id));
|
|
1205
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1206
|
+
}
|
|
1217
1207
|
replicas.delete(locator);
|
|
1208
|
+
highest.delete(locator);
|
|
1209
|
+
beyondHorizon.delete(locator);
|
|
1218
1210
|
queuesWithoutReplica.delete(locator);
|
|
1219
1211
|
rooms.delete(locator);
|
|
1220
1212
|
roomDrafts.delete(locator);
|
|
1213
|
+
attachedFiles.delete(locator);
|
|
1221
1214
|
untransferred(locator);
|
|
1215
|
+
startedOver.delete(locator);
|
|
1222
1216
|
catching.delete(locator);
|
|
1223
1217
|
discovered.delete(locator);
|
|
1224
|
-
announced.delete(locator);
|
|
1225
1218
|
unreadable.delete(locator);
|
|
1226
1219
|
stale.delete(locator);
|
|
1227
1220
|
repairable.delete(locator);
|
|
1228
1221
|
unpreparable.delete(locator);
|
|
1229
1222
|
scopedRefusals.delete(locator);
|
|
1230
|
-
sourceRefusals.delete(locator);
|
|
1231
|
-
declarationRefusals.delete(locator);
|
|
1232
1223
|
models.delete(locator);
|
|
1233
1224
|
liveModels.delete(locator);
|
|
1234
1225
|
invalidModels.delete(locator);
|
|
@@ -1282,8 +1273,7 @@ export function composeRuntime(options) {
|
|
|
1282
1273
|
function retyped(persisted) {
|
|
1283
1274
|
if (persisted.status === 'unavailable')
|
|
1284
1275
|
return persisted;
|
|
1285
|
-
|
|
1286
|
-
return state.status === 'unavailable' ? state : { ...persisted, types: state.types };
|
|
1276
|
+
return { ...persisted, types: schemaTypes(persisted.snapshot.schema, roomTypes) };
|
|
1287
1277
|
}
|
|
1288
1278
|
function restored(key, held) {
|
|
1289
1279
|
const persisted = retyped(held);
|
|
@@ -1317,12 +1307,9 @@ export function composeRuntime(options) {
|
|
|
1317
1307
|
const queuedBatches = new Map(queues.map((queue) => [queue.locator, queue.pending.map(({ batch }) => batch)]));
|
|
1318
1308
|
targets = held.targets;
|
|
1319
1309
|
transfers.clear();
|
|
1320
|
-
for (const [locator, record] of held.transfers)
|
|
1321
|
-
transfers.set(locator, record);
|
|
1322
1310
|
arrivals.clear();
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
keptParts.set(locator, new Map(parts));
|
|
1311
|
+
streaming.clear();
|
|
1312
|
+
startedOver.clear();
|
|
1326
1313
|
limits = held.providerLimits;
|
|
1327
1314
|
capabilities = held.capabilities;
|
|
1328
1315
|
member = held.member;
|
|
@@ -1332,14 +1319,16 @@ export function composeRuntime(options) {
|
|
|
1332
1319
|
presentations.clear();
|
|
1333
1320
|
for (const record of held.presentation)
|
|
1334
1321
|
presentations.set(record.app, record.value);
|
|
1335
|
-
conversations.hydrate(heldConversations, chatDrafts);
|
|
1322
|
+
conversations.hydrate(heldConversations, chatDrafts, held.conversationList, held.conversationCursor);
|
|
1336
1323
|
pendings.clear();
|
|
1324
|
+
landedAhead.clear();
|
|
1337
1325
|
for (const queue of queues) {
|
|
1338
1326
|
for (const { batch, state } of queue.pending) {
|
|
1339
1327
|
pendings.set(pendingKey(queue.locator, batch.id), state);
|
|
1340
1328
|
}
|
|
1341
1329
|
}
|
|
1342
1330
|
replicas.clear();
|
|
1331
|
+
reopened.clear();
|
|
1343
1332
|
queuesWithoutReplica.clear();
|
|
1344
1333
|
for (const [locator, queue] of queuedBatches) {
|
|
1345
1334
|
if (!held.rooms.has(locator))
|
|
@@ -1349,6 +1338,7 @@ export function composeRuntime(options) {
|
|
|
1349
1338
|
if (fences.has(stored.locator))
|
|
1350
1339
|
return;
|
|
1351
1340
|
const record = { ...stored, model: restored(stored.locator, stored.model) };
|
|
1341
|
+
reopened.add(record.locator);
|
|
1352
1342
|
replicas.set(record.locator, {
|
|
1353
1343
|
record,
|
|
1354
1344
|
queue: queuedBatches.get(record.locator) ?? [],
|
|
@@ -1359,7 +1349,7 @@ export function composeRuntime(options) {
|
|
|
1359
1349
|
});
|
|
1360
1350
|
const existing = rooms.get(record.locator);
|
|
1361
1351
|
if (existing === undefined)
|
|
1362
|
-
list(record,
|
|
1352
|
+
list(record, record.roomVersion !== null);
|
|
1363
1353
|
else {
|
|
1364
1354
|
rooms.set(record.locator, {
|
|
1365
1355
|
...existing,
|
|
@@ -1373,8 +1363,14 @@ export function composeRuntime(options) {
|
|
|
1373
1363
|
});
|
|
1374
1364
|
}
|
|
1375
1365
|
});
|
|
1376
|
-
|
|
1377
|
-
|
|
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
|
+
}
|
|
1378
1374
|
if (held.erasing || held.orphans.length > 0)
|
|
1379
1375
|
queueMicrotask(sweep);
|
|
1380
1376
|
return {
|
|
@@ -1474,14 +1470,12 @@ export function composeRuntime(options) {
|
|
|
1474
1470
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
1475
1471
|
};
|
|
1476
1472
|
}
|
|
1477
|
-
const REWRITE_LINKS = ['field', 'body'];
|
|
1478
|
-
const CHECKED_LINKS = ['field', 'section'];
|
|
1479
1473
|
async function sliceAt(record, view, queue, extra = [], moved = []) {
|
|
1480
1474
|
const local = await openLocal();
|
|
1481
1475
|
const needed = new Set([...touchedBy(queue), ...extra]);
|
|
1482
1476
|
const movedPaths = [...new Set([...movedBy(queue, record.locator), ...moved])];
|
|
1483
1477
|
if (movedPaths.length > 0) {
|
|
1484
|
-
for (const { path } of await local.referrers(record.key, view, movedPaths
|
|
1478
|
+
for (const { path } of await local.referrers(record.key, view, movedPaths)) {
|
|
1485
1479
|
needed.add(path);
|
|
1486
1480
|
}
|
|
1487
1481
|
}
|
|
@@ -1576,8 +1570,6 @@ export function composeRuntime(options) {
|
|
|
1576
1570
|
function unpairedIn(locator, batch) {
|
|
1577
1571
|
if (!queueIn(locator).some((held) => held.id === batch.id))
|
|
1578
1572
|
return false;
|
|
1579
|
-
if (pendings.get(pendingKey(locator, batch.id))?.accepted !== undefined)
|
|
1580
|
-
return false;
|
|
1581
1573
|
return !leavingIn(batch, locator) || withdrawable(locator)(batch);
|
|
1582
1574
|
}
|
|
1583
1575
|
async function together(local, settlements) {
|
|
@@ -1620,10 +1612,16 @@ export function composeRuntime(options) {
|
|
|
1620
1612
|
? read
|
|
1621
1613
|
: await stepped(slicingWith(read, overrides.set, overrides.removed));
|
|
1622
1614
|
const composition = composed(record);
|
|
1623
|
-
const
|
|
1624
|
-
|
|
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));
|
|
1625
1623
|
const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
|
|
1626
|
-
const unqueued =
|
|
1624
|
+
const unqueued = shown.map((batch) => ({ batch, landed: true }));
|
|
1627
1625
|
for (const batch of withdrawn) {
|
|
1628
1626
|
unqueued.push({ batch, landed: await landedWhole(locator, batch, confirmedLayer) });
|
|
1629
1627
|
}
|
|
@@ -1662,6 +1660,7 @@ export function composeRuntime(options) {
|
|
|
1662
1660
|
key: record.key,
|
|
1663
1661
|
record: storedRecord(record),
|
|
1664
1662
|
roots: record.typedSelection?.roots ?? [],
|
|
1663
|
+
composed: composedOf(composition),
|
|
1665
1664
|
...settling.commit,
|
|
1666
1665
|
...(overrides === undefined
|
|
1667
1666
|
? {}
|
|
@@ -1687,8 +1686,10 @@ export function composeRuntime(options) {
|
|
|
1687
1686
|
commit,
|
|
1688
1687
|
apply: (answer) => {
|
|
1689
1688
|
for (const batch of previousQueue) {
|
|
1690
|
-
if (
|
|
1691
|
-
|
|
1689
|
+
if (staying.has(batch.id))
|
|
1690
|
+
continue;
|
|
1691
|
+
pendings.delete(pendingKey(locator, batch.id));
|
|
1692
|
+
landedAhead.delete(pendingKey(locator, batch.id));
|
|
1692
1693
|
}
|
|
1693
1694
|
for (const draft of written.values())
|
|
1694
1695
|
rememberRoomDraft(draft);
|
|
@@ -1737,47 +1738,48 @@ export function composeRuntime(options) {
|
|
|
1737
1738
|
const answer = answers?.collection;
|
|
1738
1739
|
if (answer === undefined)
|
|
1739
1740
|
return held;
|
|
1740
|
-
const
|
|
1741
|
-
if (refused !== null) {
|
|
1742
|
-
const where = refused.location === undefined ? '' : ` ${refused.location}`;
|
|
1743
|
-
journal.record(`model invalid ${locator} collection ${refused.identity} ${refused.rule}${where}`);
|
|
1744
|
-
}
|
|
1741
|
+
const state = attachedModel(answer, roomTypes);
|
|
1745
1742
|
models.set(locator, state);
|
|
1746
1743
|
return { collection: state };
|
|
1747
1744
|
}
|
|
1748
|
-
function publish(local, room,
|
|
1745
|
+
function publish(local, room, reached, landing, live) {
|
|
1749
1746
|
const landed = exclusive(async () => {
|
|
1750
1747
|
if (!live()) {
|
|
1751
1748
|
caughtUpTo(room.locator);
|
|
1752
1749
|
return true;
|
|
1753
1750
|
}
|
|
1754
1751
|
const prior = replicas.get(room.locator);
|
|
1755
|
-
if (landing.
|
|
1756
|
-
(
|
|
1757
|
-
|
|
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);
|
|
1758
1759
|
return false;
|
|
1759
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;
|
|
1760
1768
|
adoptModels(room.locator, record);
|
|
1761
1769
|
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
1762
|
-
const roomVersion = record.roomVersion;
|
|
1763
|
-
const accepted = queue.filter((batch) => {
|
|
1764
|
-
const at = pendings.get(pendingKey(room.locator, batch.id))?.accepted;
|
|
1765
|
-
return at !== undefined && options.adapter.reached(room.locator, roomVersion, at);
|
|
1766
|
-
});
|
|
1767
1770
|
let set;
|
|
1768
1771
|
try {
|
|
1769
|
-
set = await settle(local, room.locator, prior, record, queue
|
|
1772
|
+
set = await settle(local, room.locator, prior, record, queue, landing.kind === 'refreshed'
|
|
1770
1773
|
? {
|
|
1771
1774
|
room: true,
|
|
1772
1775
|
view: 'refreshed',
|
|
1773
|
-
commit: { refreshed: true
|
|
1776
|
+
commit: { refreshed: true },
|
|
1774
1777
|
}
|
|
1775
1778
|
: {
|
|
1776
1779
|
room: true,
|
|
1777
1780
|
view: landing.refreshed ? 'refreshed' : 'confirmed',
|
|
1778
1781
|
commit: {
|
|
1779
1782
|
published: { from: prior?.record.key ?? null },
|
|
1780
|
-
composition: composed(record),
|
|
1781
1783
|
...(landing.refreshed ? { refreshed: true } : {}),
|
|
1782
1784
|
},
|
|
1783
1785
|
});
|
|
@@ -1789,24 +1791,24 @@ export function composeRuntime(options) {
|
|
|
1789
1791
|
return true;
|
|
1790
1792
|
}
|
|
1791
1793
|
if (landing.kind === 'bootstrapped') {
|
|
1794
|
+
highest.delete(room.locator);
|
|
1795
|
+
beyondHorizon.delete(room.locator);
|
|
1792
1796
|
untransferred(room.locator);
|
|
1797
|
+
startedOver.delete(room.locator);
|
|
1793
1798
|
catching.delete(room.locator);
|
|
1794
|
-
|
|
1799
|
+
if (prior !== undefined && prior.record.key !== record.key)
|
|
1800
|
+
sweep();
|
|
1801
|
+
name(room.locator);
|
|
1795
1802
|
}
|
|
1796
|
-
for (const batch of accepted)
|
|
1797
|
-
pendings.delete(pendingKey(room.locator, batch.id));
|
|
1798
1803
|
stale.delete(room.locator);
|
|
1799
1804
|
unpreparable.delete(room.locator);
|
|
1800
1805
|
if (unreadable.delete(room.locator))
|
|
1801
1806
|
scopedRefusals.delete(room.locator);
|
|
1802
|
-
sourceRefusals.delete(room.locator);
|
|
1803
|
-
declarationRefusals.delete(room.locator);
|
|
1804
1807
|
list(room, true);
|
|
1805
1808
|
publishChangeSet(set);
|
|
1806
1809
|
await unpair(local);
|
|
1807
1810
|
moveTo({ ...status, ...attention() });
|
|
1808
|
-
|
|
1809
|
-
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) {
|
|
1810
1812
|
submitLater(room.locator);
|
|
1811
1813
|
}
|
|
1812
1814
|
return true;
|
|
@@ -1818,7 +1820,7 @@ export function composeRuntime(options) {
|
|
|
1818
1820
|
const replica = replicas.get(locator);
|
|
1819
1821
|
const room = rooms.get(locator);
|
|
1820
1822
|
if (replica === undefined || room === undefined) {
|
|
1821
|
-
throw runtimeError('room
|
|
1823
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
1822
1824
|
}
|
|
1823
1825
|
return { local, replica, room };
|
|
1824
1826
|
}
|
|
@@ -1836,12 +1838,7 @@ export function composeRuntime(options) {
|
|
|
1836
1838
|
return committed(locator, target, saved, new Set(saved.map(({ path }) => path)), writtenBy);
|
|
1837
1839
|
}
|
|
1838
1840
|
async function saveParts(locator, named, writtenBy) {
|
|
1839
|
-
const read = named
|
|
1840
|
-
if (read.some(({ path }, at) => path === '' ||
|
|
1841
|
-
normalize(path) !== path ||
|
|
1842
|
-
read.findIndex((one) => one.path === path) !== at)) {
|
|
1843
|
-
throw runtimeError('room-draft-malformed');
|
|
1844
|
-
}
|
|
1841
|
+
const read = readSavedParts(named);
|
|
1845
1842
|
const target = await savedRoom(locator);
|
|
1846
1843
|
const saved = [];
|
|
1847
1844
|
const files = [];
|
|
@@ -1850,7 +1847,7 @@ export function composeRuntime(options) {
|
|
|
1850
1847
|
for (const { path, parts } of read) {
|
|
1851
1848
|
const file = parts.file === undefined
|
|
1852
1849
|
? undefined
|
|
1853
|
-
: await heldFile(parts.file.blob, binaryBase(visible, path));
|
|
1850
|
+
: await heldFile(parts.file.blob, parts.file.base === undefined ? binaryBase(visible, path) : parts.file.base);
|
|
1854
1851
|
if (parts.file !== undefined && file !== undefined) {
|
|
1855
1852
|
files.push({ key: file.localId, blob: parts.file.blob });
|
|
1856
1853
|
}
|
|
@@ -1884,14 +1881,15 @@ export function composeRuntime(options) {
|
|
|
1884
1881
|
const removed = changes.flatMap((change) => (change.kind === 'remove' ? [change.path] : []));
|
|
1885
1882
|
const referring = removed.length === 0
|
|
1886
1883
|
? []
|
|
1887
|
-
: (await local.referrers(record.key, 'confirmed', removed
|
|
1888
|
-
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);
|
|
1889
1886
|
const { visible } = await replayed(record, confirmedLayer, replica.queue);
|
|
1890
1887
|
const composition = composed(record);
|
|
1891
1888
|
return {
|
|
1892
1889
|
record,
|
|
1893
1890
|
confirmed: confirmedLayer,
|
|
1894
1891
|
visible,
|
|
1892
|
+
folders: new Set(await local.folders(record.key, 'visible', paths)),
|
|
1895
1893
|
*visibleOver(queue) {
|
|
1896
1894
|
return (yield* replaying(confirmedLayer, queue, record.locator, composition)).visible;
|
|
1897
1895
|
},
|
|
@@ -1914,18 +1912,6 @@ export function composeRuntime(options) {
|
|
|
1914
1912
|
return undefined;
|
|
1915
1913
|
return entry.binary !== null ? null : entry.content;
|
|
1916
1914
|
}
|
|
1917
|
-
function walkOf(local, key, view) {
|
|
1918
|
-
return async (after) => {
|
|
1919
|
-
const page = await local.walk(key, view, after, WALK_BYTES);
|
|
1920
|
-
return {
|
|
1921
|
-
entries: page.entries.map(({ path, content, binary }) => ({
|
|
1922
|
-
path,
|
|
1923
|
-
content: binary !== null ? null : content,
|
|
1924
|
-
})),
|
|
1925
|
-
next: page.next,
|
|
1926
|
-
};
|
|
1927
|
-
};
|
|
1928
|
-
}
|
|
1929
1915
|
function lookupOf(local, key, view) {
|
|
1930
1916
|
return async (paths) => {
|
|
1931
1917
|
if (paths.length === 0)
|
|
@@ -1942,37 +1928,6 @@ export function composeRuntime(options) {
|
|
|
1942
1928
|
const read = parseFrontmatter(entry.content ?? '');
|
|
1943
1929
|
return { type: read.ok ? (read.frontmatter.type ?? 'any') : undefined, binary: false };
|
|
1944
1930
|
}
|
|
1945
|
-
function overriddenWalk(walk, overrides) {
|
|
1946
|
-
const sorted = [...overrides.keys()].sort();
|
|
1947
|
-
const beyond = (path) => {
|
|
1948
|
-
if (path === null)
|
|
1949
|
-
return 0;
|
|
1950
|
-
let low = 0;
|
|
1951
|
-
let high = sorted.length;
|
|
1952
|
-
while (low < high) {
|
|
1953
|
-
const middle = (low + high) >>> 1;
|
|
1954
|
-
if (sorted[middle] <= path)
|
|
1955
|
-
low = middle + 1;
|
|
1956
|
-
else
|
|
1957
|
-
high = middle;
|
|
1958
|
-
}
|
|
1959
|
-
return low;
|
|
1960
|
-
};
|
|
1961
|
-
return async (after) => {
|
|
1962
|
-
const page = await walk(after);
|
|
1963
|
-
const end = page.next;
|
|
1964
|
-
const added = sorted
|
|
1965
|
-
.slice(beyond(after), end === null ? sorted.length : beyond(end))
|
|
1966
|
-
.flatMap((path) => {
|
|
1967
|
-
const entry = overrides.get(path);
|
|
1968
|
-
return entry === null || entry === undefined
|
|
1969
|
-
? []
|
|
1970
|
-
: [{ path, content: entry.binary !== null ? null : entry.content }];
|
|
1971
|
-
});
|
|
1972
|
-
const entries = [...page.entries.filter(({ path }) => !overrides.has(path)), ...added].sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
1973
|
-
return { entries, next: end };
|
|
1974
|
-
};
|
|
1975
|
-
}
|
|
1976
1931
|
function overriddenLookup(lookup, overrides) {
|
|
1977
1932
|
return async (paths) => {
|
|
1978
1933
|
const known = new Map(await lookup(paths.filter((path) => !overrides.has(path))));
|
|
@@ -1990,9 +1945,8 @@ export function composeRuntime(options) {
|
|
|
1990
1945
|
}
|
|
1991
1946
|
function validatorOf(record, confirmedLayer) {
|
|
1992
1947
|
return async (resulting, touched) => {
|
|
1993
|
-
const composition = compositionFor(record);
|
|
1994
1948
|
const local = await openLocal();
|
|
1995
|
-
const
|
|
1949
|
+
const changes = [];
|
|
1996
1950
|
const overrides = new Map();
|
|
1997
1951
|
const pace = paced();
|
|
1998
1952
|
for (const path of new Set([...confirmedLayer.held, ...touched])) {
|
|
@@ -2003,19 +1957,13 @@ export function composeRuntime(options) {
|
|
|
2003
1957
|
continue;
|
|
2004
1958
|
overrides.set(path, now);
|
|
2005
1959
|
if (textOf(was) !== textOf(now))
|
|
2006
|
-
|
|
1960
|
+
changes.push({ path, was: textOf(was), now: textOf(now) });
|
|
2007
1961
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
texts: textsOf(local, record.key, 'confirmed'),
|
|
2014
|
-
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
2015
|
-
}));
|
|
2016
|
-
if (clean)
|
|
2017
|
-
return { verdict: 'accept' };
|
|
2018
|
-
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
|
+
});
|
|
2019
1967
|
};
|
|
2020
1968
|
}
|
|
2021
1969
|
function textsOf(local, key, view) {
|
|
@@ -2026,7 +1974,7 @@ export function composeRuntime(options) {
|
|
|
2026
1974
|
}
|
|
2027
1975
|
function referrersOf(local, key, view) {
|
|
2028
1976
|
return async (targets) => [
|
|
2029
|
-
...new Set((await local.referrers(key, view, targets
|
|
1977
|
+
...new Set((await local.referrers(key, view, targets)).map(({ path }) => path)),
|
|
2030
1978
|
];
|
|
2031
1979
|
}
|
|
2032
1980
|
async function sideOf(locator, replica, room, saving) {
|
|
@@ -2039,6 +1987,7 @@ export function composeRuntime(options) {
|
|
|
2039
1987
|
operations: room.operations,
|
|
2040
1988
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
2041
1989
|
visible: saving.visible,
|
|
1990
|
+
folders: saving.folders,
|
|
2042
1991
|
queued: queuedDocuments(replica.queue),
|
|
2043
1992
|
unmodeled: await unmodeledIn(record),
|
|
2044
1993
|
validate: validatorOf(record, saving.confirmed),
|
|
@@ -2059,6 +2008,7 @@ export function composeRuntime(options) {
|
|
|
2059
2008
|
locator,
|
|
2060
2009
|
key: saving.record.key,
|
|
2061
2010
|
roots: saving.record.typedSelection?.roots ?? [],
|
|
2011
|
+
composed: composedOf(composed(saving.record)),
|
|
2062
2012
|
overlay: diff.overlay,
|
|
2063
2013
|
});
|
|
2064
2014
|
}
|
|
@@ -2090,13 +2040,13 @@ export function composeRuntime(options) {
|
|
|
2090
2040
|
}
|
|
2091
2041
|
const moves = saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
|
|
2092
2042
|
const sourceSaving = await savingIn(replica, moves.map(({ path }) => ({ kind: 'remove', path })), saved.map(({ path }) => path), moves.map(({ path }) => path));
|
|
2093
|
-
const changed = saved.flatMap(({ path, parts }) => changedParts(path,
|
|
2043
|
+
const changed = saved.flatMap(({ path, parts }) => changedParts(path, parts, sourceSaving.visible));
|
|
2094
2044
|
if (changed.length > 0)
|
|
2095
2045
|
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
2096
2046
|
const target = replicas.get(destination);
|
|
2097
2047
|
const listedRoom = rooms.get(destination);
|
|
2098
2048
|
if (target === undefined || listedRoom === undefined) {
|
|
2099
|
-
throw runtimeError('room
|
|
2049
|
+
throw runtimeError('unknown-room', `Unknown Room ${destination}`);
|
|
2100
2050
|
}
|
|
2101
2051
|
const arriving = await savingIn(target, [], moves.map(({ to }) => to));
|
|
2102
2052
|
const built = await buildMove({
|
|
@@ -2159,10 +2109,10 @@ export function composeRuntime(options) {
|
|
|
2159
2109
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2160
2110
|
const { answer, touched } = committedBoth;
|
|
2161
2111
|
if (answer.fenced !== null) {
|
|
2162
|
-
throw runtimeError('room
|
|
2112
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2163
2113
|
}
|
|
2164
2114
|
if (fences.has(locator) || fences.has(destination)) {
|
|
2165
|
-
throw runtimeError('room
|
|
2115
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2166
2116
|
}
|
|
2167
2117
|
pendings.set(pendingKey(locator, id), {
|
|
2168
2118
|
drafts: saved,
|
|
@@ -2215,7 +2165,7 @@ export function composeRuntime(options) {
|
|
|
2215
2165
|
...saved.map(({ path }) => path),
|
|
2216
2166
|
...parts.flatMap(({ bases }) => bases.map(({ path }) => path)),
|
|
2217
2167
|
]);
|
|
2218
|
-
const changed = saved.flatMap(({ path, parts: held }) => changedParts(path,
|
|
2168
|
+
const changed = saved.flatMap(({ path, parts: held }) => changedParts(path, held, saving.visible));
|
|
2219
2169
|
if (changed.length > 0)
|
|
2220
2170
|
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
2221
2171
|
const requeue = await stepped(requeuing(replica.queue, changes, saving.visible, withdrawable(locator), saving.visibleOver));
|
|
@@ -2243,6 +2193,7 @@ export function composeRuntime(options) {
|
|
|
2243
2193
|
providerLimits: limits,
|
|
2244
2194
|
handle: room.handle,
|
|
2245
2195
|
visible: saving.visible,
|
|
2196
|
+
folders: saving.folders,
|
|
2246
2197
|
changes,
|
|
2247
2198
|
bases: parts.flatMap(({ bases }) => bases),
|
|
2248
2199
|
queued: queuedDocuments(replica.queue),
|
|
@@ -2332,10 +2283,10 @@ export function composeRuntime(options) {
|
|
|
2332
2283
|
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2333
2284
|
const { answer, touched } = done;
|
|
2334
2285
|
if (answer.fenced !== null) {
|
|
2335
|
-
throw runtimeError('room
|
|
2286
|
+
throw runtimeError('unknown-room', `Unknown Room ${answer.fenced}`);
|
|
2336
2287
|
}
|
|
2337
2288
|
if (fences.has(locator))
|
|
2338
|
-
throw runtimeError('room
|
|
2289
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2339
2290
|
for (const origin of withdrawn.keys())
|
|
2340
2291
|
pendings.delete(pendingKey(locator, origin));
|
|
2341
2292
|
for (const [id, drafts] of drafted) {
|
|
@@ -2384,7 +2335,7 @@ export function composeRuntime(options) {
|
|
|
2384
2335
|
throw new Error('connection refused');
|
|
2385
2336
|
await local.keepUpload(locator, identifier, upload);
|
|
2386
2337
|
if (fences.has(locator))
|
|
2387
|
-
throw runtimeError('room
|
|
2338
|
+
throw runtimeError('unknown-room', `Unknown Room ${locator}`);
|
|
2388
2339
|
const key = pendingKey(locator, identifier);
|
|
2389
2340
|
pendings.set(key, { ...pendings.get(key), upload });
|
|
2390
2341
|
if (!continuing())
|
|
@@ -2393,6 +2344,9 @@ export function composeRuntime(options) {
|
|
|
2393
2344
|
async function readHistory(locator, path, query = {}) {
|
|
2394
2345
|
if (closed || refusal !== null)
|
|
2395
2346
|
return UNCERTAIN;
|
|
2347
|
+
await firstDiscovery;
|
|
2348
|
+
if (!roomsNamed)
|
|
2349
|
+
return UNCERTAIN;
|
|
2396
2350
|
try {
|
|
2397
2351
|
await ensureAuthorization();
|
|
2398
2352
|
}
|
|
@@ -2485,6 +2439,9 @@ export function composeRuntime(options) {
|
|
|
2485
2439
|
async function transferBinary(request, transfer, answer) {
|
|
2486
2440
|
if (closed || refusal !== null)
|
|
2487
2441
|
return UNCERTAIN;
|
|
2442
|
+
await firstDiscovery;
|
|
2443
|
+
if (!roomsNamed)
|
|
2444
|
+
return UNCERTAIN;
|
|
2488
2445
|
try {
|
|
2489
2446
|
await ensureAuthorization();
|
|
2490
2447
|
}
|
|
@@ -2530,11 +2487,12 @@ export function composeRuntime(options) {
|
|
|
2530
2487
|
if (revision !== undefined)
|
|
2531
2488
|
return { kind: 'path', locator, path, revision };
|
|
2532
2489
|
const replica = replicas.get(locator);
|
|
2533
|
-
if (replica === undefined)
|
|
2534
|
-
return null;
|
|
2535
2490
|
const local = await openLocal();
|
|
2536
|
-
const [held] = await local.entries(replica.record.key, 'visible', [path]);
|
|
2537
|
-
|
|
2491
|
+
const [held] = replica === undefined ? [] : await local.entries(replica.record.key, 'visible', [path]);
|
|
2492
|
+
const attached = held === undefined ? attachedFiles.get(locator)?.get(path) : undefined;
|
|
2493
|
+
if (attached !== undefined)
|
|
2494
|
+
return { kind: 'path', locator, path, revision: attached.revision };
|
|
2495
|
+
if (replica === undefined || held?.binary === undefined || held.binary === null)
|
|
2538
2496
|
return null;
|
|
2539
2497
|
if (held.binary.state === 'pending')
|
|
2540
2498
|
return { kind: 'pending', localId: held.binary.localId };
|
|
@@ -2594,10 +2552,12 @@ export function composeRuntime(options) {
|
|
|
2594
2552
|
}
|
|
2595
2553
|
async function heldMediaType(locator, path) {
|
|
2596
2554
|
const replica = replicas.get(locator);
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
return held
|
|
2555
|
+
const [held] = replica === undefined
|
|
2556
|
+
? []
|
|
2557
|
+
: await (await openLocal()).entries(replica.record.key, 'visible', [path]);
|
|
2558
|
+
return held === undefined
|
|
2559
|
+
? attachedFiles.get(locator)?.get(path)?.mediaType
|
|
2560
|
+
: held.binary?.mediaType;
|
|
2601
2561
|
}
|
|
2602
2562
|
function linkOf(asked, held) {
|
|
2603
2563
|
return transferBinary(asked, (authorization) => options.adapter.link(authorization, asked), (result) => binaryLinkAnswer(result, held));
|
|
@@ -2628,10 +2588,19 @@ export function composeRuntime(options) {
|
|
|
2628
2588
|
}
|
|
2629
2589
|
async function attachBinary(locator, file) {
|
|
2630
2590
|
await ready;
|
|
2631
|
-
|
|
2591
|
+
await firstDiscovery;
|
|
2592
|
+
if (closed || !roomsNamed || connectionOf() === 'unreachable' || refusal !== null)
|
|
2632
2593
|
return OFFLINE;
|
|
2633
2594
|
const request = await attachedFile(locator, file);
|
|
2634
|
-
return transferBinary({ locator, path: file.name }, (held) =>
|
|
2595
|
+
return transferBinary({ locator, path: file.name }, async (held) => {
|
|
2596
|
+
const result = await options.adapter.attach(held, request);
|
|
2597
|
+
if (result.kind === 'attached') {
|
|
2598
|
+
const landed = attachedFiles.get(locator) ?? new Map();
|
|
2599
|
+
landed.set(result.path, { revision: result.revision, mediaType: request.mediaType });
|
|
2600
|
+
attachedFiles.set(locator, landed);
|
|
2601
|
+
}
|
|
2602
|
+
return result;
|
|
2603
|
+
}, attachAnswer);
|
|
2635
2604
|
}
|
|
2636
2605
|
function readPart(file, part) {
|
|
2637
2606
|
return database.ask({ kind: 'file.part', key: file, part });
|
|
@@ -2640,13 +2609,6 @@ export function composeRuntime(options) {
|
|
|
2640
2609
|
const freed = await database.ask({ kind: 'opened.clean' });
|
|
2641
2610
|
return freed + ((await options.shell.cleanUp?.()) ?? 0);
|
|
2642
2611
|
}
|
|
2643
|
-
function declaredAgents() {
|
|
2644
|
-
for (const capability of capabilities) {
|
|
2645
|
-
if (capability.conversationAgents !== undefined)
|
|
2646
|
-
return capability.conversationAgents;
|
|
2647
|
-
}
|
|
2648
|
-
return undefined;
|
|
2649
|
-
}
|
|
2650
2612
|
function declaredAction(name) {
|
|
2651
2613
|
for (const capability of capabilities) {
|
|
2652
2614
|
for (const action of capability.actions ?? []) {
|
|
@@ -2698,6 +2660,24 @@ export function composeRuntime(options) {
|
|
|
2698
2660
|
return UNCERTAIN;
|
|
2699
2661
|
}
|
|
2700
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
|
+
}
|
|
2701
2681
|
function unsettled(result) {
|
|
2702
2682
|
return result.kind !== 'action' && result.retryable;
|
|
2703
2683
|
}
|
|
@@ -2736,10 +2716,6 @@ export function composeRuntime(options) {
|
|
|
2736
2716
|
continue;
|
|
2737
2717
|
if (closed || signal.aborted)
|
|
2738
2718
|
return 'done';
|
|
2739
|
-
if (pastHorizon(held.operation)) {
|
|
2740
|
-
await local.dropCall(held.operation);
|
|
2741
|
-
continue;
|
|
2742
|
-
}
|
|
2743
2719
|
calling.add(held.operation);
|
|
2744
2720
|
try {
|
|
2745
2721
|
if (unsettled(await invokeAction(held.name, held.input)))
|
|
@@ -2759,6 +2735,33 @@ export function composeRuntime(options) {
|
|
|
2759
2735
|
const held = replicas.get(locator);
|
|
2760
2736
|
return (held !== undefined && options.adapter.reached(locator, held.record.roomVersion, roomVersion));
|
|
2761
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
|
+
}
|
|
2762
2765
|
async function writtenAfter(locator, roomVersion, path) {
|
|
2763
2766
|
const held = replicas.get(locator);
|
|
2764
2767
|
if (held === undefined)
|
|
@@ -2769,122 +2772,34 @@ export function composeRuntime(options) {
|
|
|
2769
2772
|
options.adapter.reached(locator, written, roomVersion) &&
|
|
2770
2773
|
!options.adapter.reached(locator, roomVersion, written));
|
|
2771
2774
|
}
|
|
2772
|
-
function
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
else
|
|
2782
|
-
known.set(path, entry.binary !== null ? null : entry.content);
|
|
2783
|
-
}
|
|
2784
|
-
return known;
|
|
2785
|
-
};
|
|
2786
|
-
}
|
|
2787
|
-
async function unreadableRoom(texts, walk, paths) {
|
|
2788
|
-
if (paths !== null) {
|
|
2789
|
-
const text = {};
|
|
2790
|
-
for (const [path, content] of await texts(paths))
|
|
2791
|
-
if (content !== null)
|
|
2792
|
-
text[path] = content;
|
|
2793
|
-
return stepped(unreadableIn(text, Object.keys(text)));
|
|
2794
|
-
}
|
|
2795
|
-
for (let after = null;;) {
|
|
2796
|
-
const page = await walk(after);
|
|
2797
|
-
const text = {};
|
|
2798
|
-
for (const { path, content } of page.entries)
|
|
2799
|
-
if (content !== null)
|
|
2800
|
-
text[path] = content;
|
|
2801
|
-
const refused = await stepped(unreadableIn(text, Object.keys(text)));
|
|
2802
|
-
if (refused !== null)
|
|
2803
|
-
return refused;
|
|
2804
|
-
if (page.next === null)
|
|
2805
|
-
return null;
|
|
2806
|
-
after = page.next;
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
async function validatedRoom(local, record, view, previous, overrides = null) {
|
|
2810
|
-
const composition = composed(record);
|
|
2811
|
-
const modeled = record.model.status !== 'unavailable' || record.typedSelection !== null;
|
|
2812
|
-
const lookup = overrides === null
|
|
2813
|
-
? lookupOf(local, record.key, view)
|
|
2814
|
-
: overriddenLookup(lookupOf(local, record.key, view), overrides);
|
|
2815
|
-
const texts = overrides === null
|
|
2816
|
-
? textsOf(local, record.key, view)
|
|
2817
|
-
: overriddenTexts(textsOf(local, record.key, view), overrides);
|
|
2818
|
-
const walk = overrides === null
|
|
2819
|
-
? walkOf(local, record.key, view)
|
|
2820
|
-
: overriddenWalk(walkOf(local, record.key, view), overrides);
|
|
2821
|
-
let changes = null;
|
|
2822
|
-
let reshaped = false;
|
|
2823
|
-
if (previous !== null && previous.changed.length <= CLEAN_CHANGES) {
|
|
2824
|
-
const [was, now] = await Promise.all([
|
|
2825
|
-
local.entries(record.key, 'confirmed', previous.changed),
|
|
2826
|
-
overrides === null
|
|
2827
|
-
? local.entries(record.key, view, previous.changed)
|
|
2828
|
-
: Promise.resolve([]),
|
|
2829
|
-
]);
|
|
2830
|
-
const before = new Map(was.map((entry) => [entry.path, entry]));
|
|
2831
|
-
const after = overrides === null
|
|
2832
|
-
? new Map(now.map((entry) => [entry.path, entry]))
|
|
2833
|
-
: overrides;
|
|
2834
|
-
changes = previous.changed.flatMap((path) => {
|
|
2835
|
-
const one = before.get(path) ?? null;
|
|
2836
|
-
const other = after.get(path) ?? null;
|
|
2837
|
-
reshaped ||= textDiffers(one, other);
|
|
2838
|
-
const held = textOf(one);
|
|
2839
|
-
const next = textOf(other);
|
|
2840
|
-
return held === next ? [] : [{ path, was: held, now: next }];
|
|
2841
|
-
});
|
|
2842
|
-
}
|
|
2843
|
-
const named = changes === null ? null : changes.map(({ path }) => path);
|
|
2844
|
-
const textChanged = changes === null || changes.length > 0 || reshaped;
|
|
2845
|
-
const modelsChanged = previous === null || !sameComposition(composed(previous.record), composition);
|
|
2846
|
-
if (modeled && (textChanged || modelsChanged)) {
|
|
2847
|
-
const clean = previous !== null &&
|
|
2848
|
-
changes !== null &&
|
|
2849
|
-
(await changedCleanly(changes, composed(previous.record), composition, {
|
|
2850
|
-
lookup,
|
|
2851
|
-
texts,
|
|
2852
|
-
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
2853
|
-
}));
|
|
2854
|
-
if (clean)
|
|
2855
|
-
return null;
|
|
2856
|
-
const verdict = await validatedCollection(walk, lookup, composition);
|
|
2857
|
-
if (verdict.verdict === 'accept')
|
|
2858
|
-
return null;
|
|
2859
|
-
if (verdict.rule !== 'model-unavailable')
|
|
2860
|
-
return { kind: 'refused', refusal: verdict };
|
|
2861
|
-
return unreadableRoom(texts, walk, named);
|
|
2862
|
-
}
|
|
2863
|
-
if (textChanged)
|
|
2864
|
-
return unreadableRoom(texts, walk, named);
|
|
2865
|
-
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
|
+
: []));
|
|
2866
2784
|
}
|
|
2867
|
-
function refreshingOf(local, key, liveness, replaying =
|
|
2868
|
-
const write = replaying
|
|
2785
|
+
function refreshingOf(local, locator, key, composition, liveness, replaying = false) {
|
|
2786
|
+
const write = replaying ? finishingWrite : exclusive;
|
|
2869
2787
|
return {
|
|
2870
|
-
|
|
2871
|
-
if (replaying
|
|
2872
|
-
|
|
2873
|
-
installing(replaying);
|
|
2874
|
-
await screened();
|
|
2788
|
+
turn() {
|
|
2789
|
+
if (replaying)
|
|
2790
|
+
installing(locator);
|
|
2875
2791
|
},
|
|
2876
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),
|
|
2877
2794
|
async stage(page) {
|
|
2878
2795
|
for (let written = 0;;) {
|
|
2879
|
-
if (replaying !== null)
|
|
2880
|
-
await screened();
|
|
2881
2796
|
const end = batchEnd(page.entries, written);
|
|
2882
2797
|
const last = end === page.entries.length;
|
|
2883
2798
|
const entries = page.entries.slice(written, end);
|
|
2884
2799
|
const staged = await write(async () => {
|
|
2885
2800
|
if (!liveness())
|
|
2886
2801
|
return false;
|
|
2887
|
-
await timed(stageBudget, () => local.refresh(key, last
|
|
2802
|
+
await timed(stageBudget, () => local.refresh(key, composition, last
|
|
2888
2803
|
? { ...page, entries }
|
|
2889
2804
|
: { entries, removals: [], folders: [], folderRemovals: [] }));
|
|
2890
2805
|
return true;
|
|
@@ -2922,25 +2837,74 @@ export function composeRuntime(options) {
|
|
|
2922
2837
|
return done;
|
|
2923
2838
|
}
|
|
2924
2839
|
function installWrite(work) {
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
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
|
+
});
|
|
2934
2855
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
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);
|
|
2885
|
+
}
|
|
2886
|
+
finally {
|
|
2887
|
+
hold.release();
|
|
2938
2888
|
}
|
|
2939
|
-
|
|
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;
|
|
2940
2899
|
return once(() => {
|
|
2941
|
-
|
|
2900
|
+
streamsFree += 1;
|
|
2942
2901
|
});
|
|
2943
2902
|
}
|
|
2903
|
+
async function unqueued() {
|
|
2904
|
+
while (queuedText >= QUEUED_TEXT) {
|
|
2905
|
+
await new Promise((admit) => installWaiters.push(admit));
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2944
2908
|
function once(release) {
|
|
2945
2909
|
let released = false;
|
|
2946
2910
|
return () => {
|
|
@@ -2948,58 +2912,86 @@ export function composeRuntime(options) {
|
|
|
2948
2912
|
return;
|
|
2949
2913
|
released = true;
|
|
2950
2914
|
release();
|
|
2951
|
-
for (const admit of
|
|
2915
|
+
for (const admit of installWaiters.splice(0))
|
|
2952
2916
|
admit();
|
|
2953
2917
|
};
|
|
2954
2918
|
}
|
|
2955
|
-
function installing(locator
|
|
2956
|
-
installs.
|
|
2919
|
+
function installing(locator) {
|
|
2920
|
+
installs.add(locator);
|
|
2957
2921
|
queueReadiness();
|
|
2958
2922
|
}
|
|
2959
2923
|
function untransferred(locator) {
|
|
2960
2924
|
transfers.delete(locator);
|
|
2961
|
-
|
|
2925
|
+
arrivals.delete(locator);
|
|
2926
|
+
streaming.delete(locator);
|
|
2962
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();
|
|
2963
2962
|
}
|
|
2964
2963
|
function installed(locator) {
|
|
2965
2964
|
if (installs.delete(locator))
|
|
2966
2965
|
queueReadiness();
|
|
2967
2966
|
}
|
|
2968
|
-
function
|
|
2969
|
-
if (value === onScreen)
|
|
2970
|
-
return;
|
|
2971
|
-
onScreen = value;
|
|
2972
|
-
journal.record(`installation ${value ? 'shown' : 'hidden'}`);
|
|
2973
|
-
queueReadiness();
|
|
2974
|
-
if (!value)
|
|
2975
|
-
return;
|
|
2976
|
-
for (const resolve of shownWaiters.splice(0))
|
|
2977
|
-
resolve();
|
|
2978
|
-
}
|
|
2979
|
-
async function pullRefresh(authorized, held, liveness) {
|
|
2967
|
+
async function pullRefresh(authorized, held, liveness, hold, refresh) {
|
|
2980
2968
|
if (!liveness())
|
|
2981
2969
|
return unconfirmed;
|
|
2982
|
-
const turn = generation;
|
|
2983
2970
|
const { record } = held;
|
|
2984
|
-
const
|
|
2971
|
+
const from = standingOf(record.locator);
|
|
2985
2972
|
const local = await openLocal();
|
|
2986
2973
|
try {
|
|
2987
2974
|
await exclusive(() => local.unrefresh(record.key));
|
|
2988
|
-
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));
|
|
2989
2984
|
if (result.kind !== 'advanced') {
|
|
2990
2985
|
journal.record(`transfer refresh ${record.locator} ${result.kind}`);
|
|
2991
2986
|
return result;
|
|
2992
2987
|
}
|
|
2993
|
-
const refused = await validatedRoom(local, result.record, 'refreshed', {
|
|
2994
|
-
record,
|
|
2995
|
-
changed: [...result.changed],
|
|
2996
|
-
});
|
|
2997
|
-
if (refused !== null) {
|
|
2998
|
-
journal.record(`transfer refresh ${record.locator} refused`);
|
|
2999
|
-
return refused;
|
|
3000
|
-
}
|
|
3001
2988
|
journal.record(`transfer refresh ${record.locator} ${result.record.roomVersion}`);
|
|
3002
|
-
return {
|
|
2989
|
+
return {
|
|
2990
|
+
kind: 'advanced',
|
|
2991
|
+
record: result.record,
|
|
2992
|
+
from,
|
|
2993
|
+
remodelled: result.record.model !== record.model,
|
|
2994
|
+
};
|
|
3003
2995
|
}
|
|
3004
2996
|
catch (error) {
|
|
3005
2997
|
if (refusedForSpace(error))
|
|
@@ -3007,9 +2999,6 @@ export function composeRuntime(options) {
|
|
|
3007
2999
|
journal.record(`transfer refresh ${record.locator} transport-uncertainty`);
|
|
3008
3000
|
throw error;
|
|
3009
3001
|
}
|
|
3010
|
-
finally {
|
|
3011
|
-
reads.finished(record.locator, turn);
|
|
3012
|
-
}
|
|
3013
3002
|
}
|
|
3014
3003
|
const heldContents = new Map();
|
|
3015
3004
|
function heldContentOf(local, key) {
|
|
@@ -3027,27 +3016,26 @@ export function composeRuntime(options) {
|
|
|
3027
3016
|
heldContents.set(key, made);
|
|
3028
3017
|
return made;
|
|
3029
3018
|
}
|
|
3030
|
-
async function
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
arrivals.get(locator)?.delete(token);
|
|
3037
|
-
}
|
|
3038
|
-
queueReadiness();
|
|
3019
|
+
async function abandoned(local, locator) {
|
|
3020
|
+
await local.abandon(locator);
|
|
3021
|
+
setBack(locator);
|
|
3022
|
+
untransferred(locator);
|
|
3023
|
+
liveQueries.recover();
|
|
3024
|
+
sweep();
|
|
3039
3025
|
}
|
|
3040
3026
|
function staging(local, liveness) {
|
|
3041
|
-
const derived = async (locator, key) => {
|
|
3027
|
+
const derived = async (locator, key, composition) => {
|
|
3042
3028
|
underived.delete(locator);
|
|
3043
3029
|
for (;;) {
|
|
3044
|
-
await screened();
|
|
3045
3030
|
if (!liveness())
|
|
3046
3031
|
return false;
|
|
3047
|
-
const done = await
|
|
3032
|
+
const done = await finishingWrite(async () => liveness()
|
|
3033
|
+
? timed(deriveBudget, () => local.rederive(key, composition, deriveBudget.bytes, DERIVE_LONGEST_MS, DERIVE_FLOOR))
|
|
3034
|
+
: null);
|
|
3048
3035
|
if (done === null)
|
|
3049
3036
|
return false;
|
|
3050
|
-
|
|
3037
|
+
built(locator, done);
|
|
3038
|
+
if (done.done)
|
|
3051
3039
|
return true;
|
|
3052
3040
|
}
|
|
3053
3041
|
};
|
|
@@ -3055,28 +3043,23 @@ export function composeRuntime(options) {
|
|
|
3055
3043
|
held: (locator) => transfers.get(locator) ?? null,
|
|
3056
3044
|
async turn(locator, step) {
|
|
3057
3045
|
installing(locator);
|
|
3058
|
-
|
|
3059
|
-
await screened();
|
|
3060
|
-
const release = step === 'downloading' ? await heldPart() : () => { };
|
|
3061
|
-
if (onScreen || closed)
|
|
3062
|
-
return release;
|
|
3063
|
-
release();
|
|
3064
|
-
}
|
|
3046
|
+
return step === 'downloading' ? heldStream() : () => { };
|
|
3065
3047
|
},
|
|
3066
|
-
|
|
3067
|
-
|
|
3048
|
+
unqueued,
|
|
3049
|
+
queued(text) {
|
|
3050
|
+
queuedText += text;
|
|
3068
3051
|
return once(() => {
|
|
3069
|
-
|
|
3052
|
+
queuedText -= text;
|
|
3070
3053
|
});
|
|
3071
3054
|
},
|
|
3072
3055
|
arriving(locator) {
|
|
3073
3056
|
let ended = false;
|
|
3074
3057
|
return {
|
|
3075
|
-
at(
|
|
3076
|
-
if (ended
|
|
3058
|
+
at(bytes) {
|
|
3059
|
+
if (ended)
|
|
3077
3060
|
return;
|
|
3078
|
-
|
|
3079
|
-
|
|
3061
|
+
arrivals.set(locator, bytes);
|
|
3062
|
+
streaming.add(locator);
|
|
3080
3063
|
const now = performance.now();
|
|
3081
3064
|
if (now - arrivalReported < ARRIVAL_REPORT_MS)
|
|
3082
3065
|
return;
|
|
@@ -3084,76 +3067,64 @@ export function composeRuntime(options) {
|
|
|
3084
3067
|
queueReadiness();
|
|
3085
3068
|
},
|
|
3086
3069
|
end() {
|
|
3070
|
+
if (ended)
|
|
3071
|
+
return;
|
|
3087
3072
|
ended = true;
|
|
3088
|
-
if (
|
|
3073
|
+
if (streaming.delete(locator))
|
|
3089
3074
|
queueReadiness();
|
|
3090
3075
|
},
|
|
3091
3076
|
};
|
|
3092
3077
|
},
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
? { local: earlier.local, parts: [...earlier.parts, ...later.parts] }
|
|
3096
|
-
: null, keptTogether),
|
|
3097
|
-
announced(locator) {
|
|
3098
|
-
const later = announced.get(locator);
|
|
3099
|
-
return later === undefined ? [] : [later];
|
|
3100
|
-
},
|
|
3101
|
-
async stage(record, page, composition, written) {
|
|
3078
|
+
streamed: options.adapter.roomStream !== undefined,
|
|
3079
|
+
async stage(record, page, composition) {
|
|
3102
3080
|
const { locator } = record;
|
|
3103
|
-
installing(locator
|
|
3104
|
-
await screened();
|
|
3081
|
+
installing(locator);
|
|
3105
3082
|
if (!liveness())
|
|
3106
3083
|
return null;
|
|
3107
3084
|
const key = await installWrite(async () => {
|
|
3108
3085
|
if (!liveness())
|
|
3109
3086
|
return null;
|
|
3110
|
-
return local.stage(record, composition, page
|
|
3087
|
+
return local.stage(record, composition, page);
|
|
3111
3088
|
});
|
|
3112
3089
|
if (key === null || !liveness())
|
|
3113
3090
|
return null;
|
|
3114
3091
|
const arrived = !transfers.has(locator);
|
|
3092
|
+
const prior = transfers.get(locator)?.done.bytes ?? 0;
|
|
3115
3093
|
const held = { ...record, key };
|
|
3116
3094
|
transfers.set(locator, held);
|
|
3117
|
-
if (written !== null) {
|
|
3118
|
-
keptParts.get(locator)?.delete(written);
|
|
3119
|
-
arrivals.get(locator)?.delete(written);
|
|
3120
|
-
}
|
|
3121
3095
|
if (arrived)
|
|
3122
3096
|
liveQueries.recover();
|
|
3123
3097
|
installing(locator);
|
|
3124
3098
|
liveQueries.recover();
|
|
3125
|
-
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);
|
|
3126
3102
|
underived.set(locator, owed);
|
|
3127
3103
|
if (owed < deriveBudget.bytes)
|
|
3128
3104
|
return held;
|
|
3129
|
-
return (await derived(locator, key)) ? held : null;
|
|
3105
|
+
return (await derived(locator, key, composition)) ? held : null;
|
|
3130
3106
|
},
|
|
3131
3107
|
derive: derived,
|
|
3132
|
-
async refreshing(locator, key) {
|
|
3108
|
+
async refreshing(locator, key, composition) {
|
|
3133
3109
|
await exclusive(() => local.unrefresh(key));
|
|
3134
|
-
return refreshingOf(local, key, liveness,
|
|
3110
|
+
return refreshingOf(local, locator, key, composition, liveness, true);
|
|
3135
3111
|
},
|
|
3136
3112
|
abandon: (locator) => exclusive(async () => {
|
|
3137
|
-
if (
|
|
3138
|
-
|
|
3139
|
-
await local.abandon(locator);
|
|
3140
|
-
untransferred(locator);
|
|
3141
|
-
liveQueries.recover();
|
|
3142
|
-
sweep();
|
|
3113
|
+
if (liveness())
|
|
3114
|
+
await abandoned(local, locator);
|
|
3143
3115
|
}),
|
|
3144
|
-
setAside:
|
|
3116
|
+
setAside: async (locator) => {
|
|
3145
3117
|
if (!liveness())
|
|
3146
3118
|
return;
|
|
3147
|
-
|
|
3119
|
+
setBack(locator);
|
|
3148
3120
|
untransferred(locator);
|
|
3149
3121
|
liveQueries.recover();
|
|
3150
|
-
}
|
|
3122
|
+
},
|
|
3151
3123
|
release: (key) => exclusive(async () => {
|
|
3152
3124
|
await local.release(key);
|
|
3153
3125
|
sweep();
|
|
3154
3126
|
}),
|
|
3155
3127
|
content: (key) => heldContentOf(local, key),
|
|
3156
|
-
validate: (record, view) => validatedRoom(local, record, view, null),
|
|
3157
3128
|
};
|
|
3158
3129
|
}
|
|
3159
3130
|
function reusable(room, local) {
|
|
@@ -3165,15 +3136,21 @@ export function composeRuntime(options) {
|
|
|
3165
3136
|
async function pullBootstrap(authorized, room, local, liveness) {
|
|
3166
3137
|
if (!liveness())
|
|
3167
3138
|
return unconfirmed;
|
|
3168
|
-
const turn = generation;
|
|
3169
3139
|
if (replicas.has(room.locator)) {
|
|
3170
3140
|
catching.add(room.locator);
|
|
3171
3141
|
liveQueries.recover();
|
|
3172
3142
|
}
|
|
3173
3143
|
try {
|
|
3174
|
-
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));
|
|
3175
3145
|
const detail = result.kind === 'staged' ? result.record.roomVersion : result.kind;
|
|
3176
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
|
+
}
|
|
3177
3154
|
if (result.kind !== 'staged') {
|
|
3178
3155
|
caughtUpTo(room.locator);
|
|
3179
3156
|
installed(room.locator);
|
|
@@ -3188,9 +3165,6 @@ export function composeRuntime(options) {
|
|
|
3188
3165
|
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
3189
3166
|
throw error;
|
|
3190
3167
|
}
|
|
3191
|
-
finally {
|
|
3192
|
-
reads.finished(room.locator, turn);
|
|
3193
|
-
}
|
|
3194
3168
|
}
|
|
3195
3169
|
function caughtUpTo(locator) {
|
|
3196
3170
|
if (catching.delete(locator))
|
|
@@ -3198,72 +3172,83 @@ export function composeRuntime(options) {
|
|
|
3198
3172
|
}
|
|
3199
3173
|
function behind(room) {
|
|
3200
3174
|
const held = replicas.get(room.locator)?.record.roomVersion;
|
|
3201
|
-
return
|
|
3202
|
-
[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);
|
|
3203
3176
|
}
|
|
3204
|
-
|
|
3205
|
-
const
|
|
3206
|
-
|
|
3207
|
-
if (withheld !== null || reads.carried(room.locator) === carried || !behind(room)) {
|
|
3208
|
-
return withheld;
|
|
3209
|
-
}
|
|
3210
|
-
return advanceOnce(room, local, authorized, live);
|
|
3177
|
+
function standingOf(locator) {
|
|
3178
|
+
const held = replicas.get(locator);
|
|
3179
|
+
return { key: held?.record.key ?? null, confirmed: held?.confirmed ?? 0 };
|
|
3211
3180
|
}
|
|
3212
|
-
|
|
3181
|
+
function stands(locator, from) {
|
|
3182
|
+
const now = standingOf(locator);
|
|
3183
|
+
return now.key === from.key && now.confirmed === from.confirmed;
|
|
3184
|
+
}
|
|
3185
|
+
async function advance(room, local, authorized, running) {
|
|
3213
3186
|
const live = () => running() && !full;
|
|
3214
3187
|
if (!live())
|
|
3215
3188
|
return unconfirmed;
|
|
3216
3189
|
const held = replicas.get(room.locator);
|
|
3217
|
-
if (held !== undefined) {
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
emitRooms();
|
|
3229
|
-
}
|
|
3230
|
-
return null;
|
|
3231
|
-
}
|
|
3232
|
-
if (refreshed.kind === 'advanced') {
|
|
3233
|
-
const landing = { kind: 'refreshed', base: refreshed.base };
|
|
3234
|
-
if (!(await publish(local, room, refreshed.record, landing, live))) {
|
|
3235
|
-
const now = replicas.get(room.locator)?.record.roomVersion;
|
|
3236
|
-
const reached = now !== undefined &&
|
|
3237
|
-
options.adapter.reached(room.locator, now, refreshed.record.roomVersion);
|
|
3238
|
-
return reached ? null : advance(room, local, authorized, live);
|
|
3239
|
-
}
|
|
3240
|
-
await retainModel(local, room, modelOf(room.locator));
|
|
3241
|
-
return null;
|
|
3242
|
-
}
|
|
3243
|
-
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')
|
|
3244
3201
|
return refreshed;
|
|
3245
|
-
if (
|
|
3246
|
-
owed.add(room.locator);
|
|
3247
|
-
liveQueries.recover();
|
|
3248
|
-
return unconfirmed;
|
|
3249
|
-
}
|
|
3250
|
-
if (reloadsDue.get(room.locator) === generation)
|
|
3202
|
+
if (reloadsDue.has(room.locator))
|
|
3251
3203
|
return unconfirmed;
|
|
3204
|
+
replaced([room.locator]);
|
|
3252
3205
|
}
|
|
3206
|
+
const from = standingOf(room.locator);
|
|
3253
3207
|
const bootstrapped = await pullBootstrap(authorized, room, local, live);
|
|
3254
|
-
if (bootstrapped.kind
|
|
3255
|
-
|
|
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)) {
|
|
3256
3212
|
return live() ? null : unconfirmed;
|
|
3257
3213
|
}
|
|
3258
|
-
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;
|
|
3259
3247
|
}
|
|
3260
3248
|
async function withhold(withheld, room, local, progress) {
|
|
3261
|
-
if (withheld.kind === 'unconfirmed')
|
|
3249
|
+
if (withheld.kind === 'unconfirmed' || isFenced(room.locator))
|
|
3262
3250
|
return;
|
|
3263
|
-
|
|
3264
|
-
stale.delete(room.locator);
|
|
3265
|
-
else
|
|
3266
|
-
stale.add(room.locator);
|
|
3251
|
+
stale.add(room.locator);
|
|
3267
3252
|
if (withheld.kind === 'transport-uncertainty' || withheld.kind === 'retryable-refusal') {
|
|
3268
3253
|
const reset = withheld.kind === 'retryable-refusal' ? withheld.reset : undefined;
|
|
3269
3254
|
repairable.set(room.locator, reset);
|
|
@@ -3285,33 +3270,11 @@ export function composeRuntime(options) {
|
|
|
3285
3270
|
list(room, false);
|
|
3286
3271
|
emitRooms();
|
|
3287
3272
|
}
|
|
3288
|
-
else if (withheld.kind === 'refused') {
|
|
3289
|
-
refusedSource(room, withheld);
|
|
3290
|
-
}
|
|
3291
3273
|
else if (withheld.kind === 'revoked-authorization') {
|
|
3292
3274
|
await refuse('revoked-authorization');
|
|
3293
3275
|
}
|
|
3294
3276
|
}
|
|
3295
|
-
function refusedSource(room, { refusal }) {
|
|
3296
|
-
sourceRefusals.set(room.locator, {
|
|
3297
|
-
code: refusal.rule,
|
|
3298
|
-
retryable: false,
|
|
3299
|
-
fields: {
|
|
3300
|
-
file: refusal.file,
|
|
3301
|
-
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
3302
|
-
...(refusal.referrers === undefined ? {} : { referrers: refusal.referrers }),
|
|
3303
|
-
},
|
|
3304
|
-
});
|
|
3305
|
-
declarationRefusals.delete(room.locator);
|
|
3306
|
-
unreadable.add(room.locator);
|
|
3307
|
-
list(room, false);
|
|
3308
|
-
emitRooms();
|
|
3309
|
-
moveTo({ ...status, ...attention() });
|
|
3310
|
-
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
3311
|
-
}
|
|
3312
3277
|
function leaving(batch, result) {
|
|
3313
|
-
if (result.kind === 'expired')
|
|
3314
|
-
return { batch, returned: returnedBy('expired') };
|
|
3315
3278
|
return {
|
|
3316
3279
|
batch,
|
|
3317
3280
|
returned: {
|
|
@@ -3330,8 +3293,8 @@ export function composeRuntime(options) {
|
|
|
3330
3293
|
function settling(acceptance) {
|
|
3331
3294
|
return acceptance !== null && 'apply' in acceptance;
|
|
3332
3295
|
}
|
|
3333
|
-
function
|
|
3334
|
-
return settling(acceptance) ?
|
|
3296
|
+
function failureOf(acceptance) {
|
|
3297
|
+
return settling(acceptance) ? null : acceptance;
|
|
3335
3298
|
}
|
|
3336
3299
|
async function acceptance(room, local, submitted, accepted) {
|
|
3337
3300
|
if (!continuing())
|
|
@@ -3344,44 +3307,17 @@ export function composeRuntime(options) {
|
|
|
3344
3307
|
logAccepted(room.locator, submitted, accepted);
|
|
3345
3308
|
let record = current.record;
|
|
3346
3309
|
let changes;
|
|
3347
|
-
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)) {
|
|
3348
3312
|
const paths = [...returnedPaths(submitted, accepted)];
|
|
3349
3313
|
const confirmedLayer = await stepped(slicing(await local.entries(record.key, 'confirmed', paths), paths));
|
|
3350
|
-
const back = await stepped(returning(confirmedLayer, submitted, accepted, accepted.roomVersion));
|
|
3314
|
+
const back = await stepped(returning(confirmedLayer, submitted, accepted, accepted.roomVersion, composed(record)));
|
|
3351
3315
|
record = {
|
|
3352
3316
|
...record,
|
|
3353
3317
|
roomVersion: options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
3354
3318
|
? accepted.roomVersion
|
|
3355
3319
|
: held,
|
|
3356
3320
|
};
|
|
3357
|
-
const overrides = new Map([
|
|
3358
|
-
...back.set.map((entry) => [entry.path, entry]),
|
|
3359
|
-
...back.removed.map((path) => [path, null]),
|
|
3360
|
-
]);
|
|
3361
|
-
const refused = await validatedRoom(local, record, 'confirmed', { record: current.record, changed: [...overrides.keys()] }, overrides);
|
|
3362
|
-
if (refused !== null) {
|
|
3363
|
-
const key = pendingKey(room.locator, submitted.id);
|
|
3364
|
-
return {
|
|
3365
|
-
locator: room.locator,
|
|
3366
|
-
left: [],
|
|
3367
|
-
commit: {
|
|
3368
|
-
rooms: [],
|
|
3369
|
-
writes: [
|
|
3370
|
-
{
|
|
3371
|
-
locator: room.locator,
|
|
3372
|
-
accepted: [{ id: submitted.id, roomVersion: accepted.roomVersion }],
|
|
3373
|
-
},
|
|
3374
|
-
],
|
|
3375
|
-
},
|
|
3376
|
-
refused,
|
|
3377
|
-
apply() {
|
|
3378
|
-
const state = pendings.get(key);
|
|
3379
|
-
if (state !== undefined && !fences.has(room.locator))
|
|
3380
|
-
pendings.set(key, { ...state, accepted: accepted.roomVersion });
|
|
3381
|
-
return changeSet(room.locator, { queue: true });
|
|
3382
|
-
},
|
|
3383
|
-
};
|
|
3384
|
-
}
|
|
3385
3321
|
changes = back;
|
|
3386
3322
|
}
|
|
3387
3323
|
const settled = await settlement(local, room.locator, current, record, current.queue.filter((batch) => batch.id !== submitted.id), { room: true, ...(changes === undefined ? {} : { changes }) });
|
|
@@ -3389,10 +3325,9 @@ export function composeRuntime(options) {
|
|
|
3389
3325
|
...settled,
|
|
3390
3326
|
apply(answer) {
|
|
3391
3327
|
const set = settled.apply(answer);
|
|
3328
|
+
showing(room.locator, accepted.roomVersion);
|
|
3392
3329
|
pendings.delete(pendingKey(room.locator, submitted.id));
|
|
3393
3330
|
unreadable.delete(room.locator);
|
|
3394
|
-
sourceRefusals.delete(room.locator);
|
|
3395
|
-
declarationRefusals.delete(room.locator);
|
|
3396
3331
|
list(room, true);
|
|
3397
3332
|
return set;
|
|
3398
3333
|
},
|
|
@@ -3410,13 +3345,9 @@ export function composeRuntime(options) {
|
|
|
3410
3345
|
const failed = await exclusive(async () => {
|
|
3411
3346
|
const adopting = await acceptance(room, local, submitted, accepted);
|
|
3412
3347
|
await adoptedTogether(local, [adopting]);
|
|
3413
|
-
return
|
|
3348
|
+
return failureOf(adopting);
|
|
3414
3349
|
});
|
|
3415
|
-
|
|
3416
|
-
return true;
|
|
3417
|
-
if (failed.kind === 'refused' && continuing())
|
|
3418
|
-
refusedSource(room, failed);
|
|
3419
|
-
return false;
|
|
3350
|
+
return failed === null;
|
|
3420
3351
|
}
|
|
3421
3352
|
async function adoptedMove(room, partner, local, submitted, accepted) {
|
|
3422
3353
|
const other = discovered.get(partner);
|
|
@@ -3430,18 +3361,10 @@ export function composeRuntime(options) {
|
|
|
3430
3361
|
...accepted.destination,
|
|
3431
3362
|
});
|
|
3432
3363
|
await adoptedTogether(local, [left, arrived]);
|
|
3433
|
-
|
|
3434
|
-
if (other !== undefined && refusedArrival?.kind === 'refused' && continuing()) {
|
|
3435
|
-
refusedSource(other, refusedArrival);
|
|
3436
|
-
}
|
|
3437
|
-
return refusalOf(left);
|
|
3364
|
+
return failureOf(left);
|
|
3438
3365
|
});
|
|
3439
3366
|
submitLater(partner);
|
|
3440
|
-
|
|
3441
|
-
return true;
|
|
3442
|
-
if (failed.kind === 'refused' && continuing())
|
|
3443
|
-
refusedSource(room, failed);
|
|
3444
|
-
return false;
|
|
3367
|
+
return failed === null;
|
|
3445
3368
|
}
|
|
3446
3369
|
function queueIn(locator) {
|
|
3447
3370
|
return replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
@@ -3552,20 +3475,13 @@ export function composeRuntime(options) {
|
|
|
3552
3475
|
return;
|
|
3553
3476
|
moveTo({ ...status, ...attention() });
|
|
3554
3477
|
}
|
|
3555
|
-
async function discardHeld(head, room, local, signal) {
|
|
3556
|
-
const partner = partnerOf(head, room.locator);
|
|
3557
|
-
const other = partner === null ? undefined : discovered.get(partner);
|
|
3558
|
-
await discardPending(other === undefined ? [room] : [room, other], local, signal);
|
|
3559
|
-
}
|
|
3560
3478
|
async function submitRoom(room, local, authorized, progress, signal) {
|
|
3561
3479
|
while (continuing() && authorization !== undefined && !signal.aborted) {
|
|
3562
3480
|
const replica = replicas.get(room.locator);
|
|
3563
|
-
const head = replica?.queue
|
|
3481
|
+
const head = replica?.queue.find((batch) => !landedAhead.has(pendingKey(room.locator, batch.id)));
|
|
3564
3482
|
if (replica === undefined || head === undefined)
|
|
3565
3483
|
return 'done';
|
|
3566
3484
|
const state = pendings.get(pendingKey(room.locator, head.id));
|
|
3567
|
-
if (state?.accepted !== undefined)
|
|
3568
|
-
return 'done';
|
|
3569
3485
|
const partner = partnerOf(head, room.locator);
|
|
3570
3486
|
if (partner !== null && !leavingIn(head, room.locator)) {
|
|
3571
3487
|
submitLater(partner);
|
|
@@ -3589,10 +3505,6 @@ export function composeRuntime(options) {
|
|
|
3589
3505
|
return 'done';
|
|
3590
3506
|
}
|
|
3591
3507
|
const lost = earlier || submission.state === 'submitted';
|
|
3592
|
-
if (lost && pastHorizon(head.id)) {
|
|
3593
|
-
await discardHeld(head, room, local, signal);
|
|
3594
|
-
return 'done';
|
|
3595
|
-
}
|
|
3596
3508
|
let result;
|
|
3597
3509
|
journal.record(`submission ${room.locator} ${head.id}`);
|
|
3598
3510
|
await exclusive(() => mark(local, room.locator, head, { state: 'submitted' }, lost));
|
|
@@ -3670,32 +3582,12 @@ export function composeRuntime(options) {
|
|
|
3670
3582
|
const other = partner === null ? undefined : discovered.get(partner);
|
|
3671
3583
|
if (result.kind === 'conflicted' ||
|
|
3672
3584
|
result.kind === 'rejected' ||
|
|
3673
|
-
result.kind === 'expired' ||
|
|
3674
3585
|
result.kind === 'retryable-refusal') {
|
|
3675
|
-
if (result.kind === 'expired' && lost && result.restored !== true) {
|
|
3676
|
-
journal.record(`outcome ${room.locator} ${head.id} expired`);
|
|
3677
|
-
await discardHeld(head, room, local, signal);
|
|
3678
|
-
return 'done';
|
|
3679
|
-
}
|
|
3680
|
-
if (result.kind === 'expired') {
|
|
3681
|
-
await exclusive(() => mark(local, room.locator, head, { state: 'waiting' }, partner !== null && lost));
|
|
3682
|
-
const there = other === undefined ? null : await advance(other, local, authorized, continuing);
|
|
3683
|
-
if (other !== undefined && there !== null) {
|
|
3684
|
-
await withhold(there, other, local, progress);
|
|
3685
|
-
return 'done';
|
|
3686
|
-
}
|
|
3687
|
-
const withheld = await advance(room, local, authorized, continuing);
|
|
3688
|
-
if (withheld !== null) {
|
|
3689
|
-
await withhold(withheld, room, local, progress);
|
|
3690
|
-
return 'done';
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
3586
|
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` +
|
|
3694
3587
|
(result.kind === 'conflicted' && result.roomVersion !== undefined
|
|
3695
3588
|
? ` ${result.roomVersion}`
|
|
3696
3589
|
: ''));
|
|
3697
|
-
|
|
3698
|
-
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
3590
|
+
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
3699
3591
|
}
|
|
3700
3592
|
if (result.kind === 'retryable-refusal') {
|
|
3701
3593
|
const reset = result.reset;
|
|
@@ -3703,11 +3595,11 @@ export function composeRuntime(options) {
|
|
|
3703
3595
|
await exclusive(() => mark(local, room.locator, head, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
3704
3596
|
return reset === undefined ? 'again' : { at: Date.parse(reset) };
|
|
3705
3597
|
}
|
|
3706
|
-
if (result.kind === 'conflicted' || result.kind === 'rejected'
|
|
3598
|
+
if (result.kind === 'conflicted' || result.kind === 'rejected') {
|
|
3707
3599
|
const left = leaving(head, result);
|
|
3708
3600
|
const refreshed = async () => ({
|
|
3709
|
-
there: other === undefined ? null : await
|
|
3710
|
-
here: await
|
|
3601
|
+
there: other === undefined ? null : await advanced(other, local, authorized),
|
|
3602
|
+
here: await advanced(room, local, authorized),
|
|
3711
3603
|
});
|
|
3712
3604
|
const judged = result.kind === 'conflicted' && other !== undefined ? await refreshed() : null;
|
|
3713
3605
|
if (judged !== null && (judged.there !== null || judged.here !== null)) {
|
|
@@ -3727,16 +3619,19 @@ export function composeRuntime(options) {
|
|
|
3727
3619
|
return;
|
|
3728
3620
|
}
|
|
3729
3621
|
const confirmedLayer = await sliceAt(current.record, 'confirmed', [head]);
|
|
3730
|
-
const held =
|
|
3731
|
-
(await landedWhole(room.locator, head, confirmedLayer))
|
|
3732
|
-
|
|
3733
|
-
|
|
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
|
+
}
|
|
3734
3631
|
publishChangeSet(await settle(local, room.locator, current, current.record, current.queue.filter((batch) => batch.id !== head.id), { leaving: [held ? { ...left, returned: null } : left] }));
|
|
3735
3632
|
await unpair(local);
|
|
3736
3633
|
moveTo({ ...status, ...attention() });
|
|
3737
3634
|
});
|
|
3738
|
-
if (result.kind === 'expired')
|
|
3739
|
-
continue;
|
|
3740
3635
|
const { there, here } = judged ?? (await refreshed());
|
|
3741
3636
|
if (other !== undefined && there !== null)
|
|
3742
3637
|
await withhold(there, other, local, progress);
|
|
@@ -3756,93 +3651,55 @@ export function composeRuntime(options) {
|
|
|
3756
3651
|
}
|
|
3757
3652
|
return 'done';
|
|
3758
3653
|
}
|
|
3759
|
-
function pastHorizon(identity) {
|
|
3760
|
-
return (outcomeRetention !== null && identityTime(identity) + outcomeRetention * 1000 <= Date.now());
|
|
3761
|
-
}
|
|
3762
|
-
async function discardPending(rooms, local, signal) {
|
|
3763
|
-
const discarded = await exclusive(async () => {
|
|
3764
|
-
const held = rooms
|
|
3765
|
-
.filter(({ locator }) => !fences.has(locator))
|
|
3766
|
-
.map((room) => ({
|
|
3767
|
-
room,
|
|
3768
|
-
before: replicas.get(room.locator),
|
|
3769
|
-
queue: queueIn(room.locator),
|
|
3770
|
-
}));
|
|
3771
|
-
if (held.length === 0)
|
|
3772
|
-
return [];
|
|
3773
|
-
const answer = await local.commit({
|
|
3774
|
-
rooms: held.flatMap(({ room, before }) => before === undefined
|
|
3775
|
-
? []
|
|
3776
|
-
: [
|
|
3777
|
-
{
|
|
3778
|
-
locator: room.locator,
|
|
3779
|
-
key: before.record.key,
|
|
3780
|
-
roots: before.record.typedSelection?.roots ?? [],
|
|
3781
|
-
overlay: { set: [], cleared: [...before.overlaid] },
|
|
3782
|
-
},
|
|
3783
|
-
]),
|
|
3784
|
-
discards: held.map(({ room }) => room.locator),
|
|
3785
|
-
});
|
|
3786
|
-
for (const { room, before, queue } of held) {
|
|
3787
|
-
const { locator } = room;
|
|
3788
|
-
lastFenced.set(locator, ++fencings);
|
|
3789
|
-
for (const batch of queue)
|
|
3790
|
-
pendings.delete(pendingKey(locator, batch.id));
|
|
3791
|
-
queuesWithoutReplica.delete(locator);
|
|
3792
|
-
if (before !== undefined) {
|
|
3793
|
-
replicas.set(locator, {
|
|
3794
|
-
...before,
|
|
3795
|
-
queue: [],
|
|
3796
|
-
overlaid: new Set(),
|
|
3797
|
-
count: answer.counts[locator] ?? before.count,
|
|
3798
|
-
});
|
|
3799
|
-
}
|
|
3800
|
-
journal.record(`room discarded ${locator}`);
|
|
3801
|
-
}
|
|
3802
|
-
publishChangeSet(mergeChangeSets(held.map(({ room }) => changeSet(room.locator, {
|
|
3803
|
-
documents: answer.changed[room.locator] ?? [],
|
|
3804
|
-
queue: true,
|
|
3805
|
-
}))));
|
|
3806
|
-
moveTo({ ...status, ...attention() });
|
|
3807
|
-
return held.map(({ room }) => room);
|
|
3808
|
-
});
|
|
3809
|
-
for (const room of discarded) {
|
|
3810
|
-
if (signal.aborted)
|
|
3811
|
-
return;
|
|
3812
|
-
const progress = { complete: true, reachable: true };
|
|
3813
|
-
await roomTask(room.locator, async (live) => {
|
|
3814
|
-
await Promise.race([
|
|
3815
|
-
reload(room, local, progress, () => live() && !signal.aborted),
|
|
3816
|
-
abandonedOn(signal),
|
|
3817
|
-
]);
|
|
3818
|
-
});
|
|
3819
|
-
if (signal.aborted)
|
|
3820
|
-
return;
|
|
3821
|
-
await rest(progress);
|
|
3822
|
-
}
|
|
3823
|
-
}
|
|
3824
3654
|
function awaitingAnswer(locator) {
|
|
3655
|
+
answering.get(locator)?.release();
|
|
3825
3656
|
let release = () => { };
|
|
3826
|
-
const
|
|
3657
|
+
const answered = new Promise((resolve) => {
|
|
3827
3658
|
release = resolve;
|
|
3828
3659
|
});
|
|
3829
|
-
|
|
3660
|
+
const awaited = { answered, release };
|
|
3661
|
+
answering.set(locator, awaited);
|
|
3830
3662
|
return () => {
|
|
3831
|
-
if (answering.get(locator)
|
|
3663
|
+
if (answering.get(locator) === awaited)
|
|
3832
3664
|
answering.delete(locator);
|
|
3833
3665
|
release();
|
|
3834
3666
|
};
|
|
3835
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
|
+
}
|
|
3836
3699
|
async function retainModel(local, room, next) {
|
|
3837
3700
|
const key = room.locator;
|
|
3838
3701
|
const previous = modelOf(room.locator);
|
|
3839
|
-
|
|
3840
|
-
if (JSON.stringify(previous) === JSON.stringify(next) &&
|
|
3841
|
-
(heldBefore === undefined ||
|
|
3842
|
-
(JSON.stringify(heldBefore.operations) === JSON.stringify(room.operations) &&
|
|
3843
|
-
JSON.stringify(heldBefore.replica) === JSON.stringify(room.replica) &&
|
|
3844
|
-
JSON.stringify(heldBefore.typedSelection) ===
|
|
3845
|
-
JSON.stringify(room.typedSelection ?? null))))
|
|
3702
|
+
if (JSON.stringify(previous) === JSON.stringify(next) && declaredAsHeld(room))
|
|
3846
3703
|
return;
|
|
3847
3704
|
if (next.status === 'unavailable') {
|
|
3848
3705
|
invalidModels.add(key);
|
|
@@ -3854,26 +3711,15 @@ export function composeRuntime(options) {
|
|
|
3854
3711
|
const held = replicas.get(room.locator);
|
|
3855
3712
|
let changed = null;
|
|
3856
3713
|
if (held !== undefined) {
|
|
3857
|
-
const
|
|
3714
|
+
const record = {
|
|
3858
3715
|
...held.record,
|
|
3716
|
+
name: room.name,
|
|
3717
|
+
handle: room.handle,
|
|
3859
3718
|
operations: room.operations,
|
|
3860
3719
|
replica: room.replica,
|
|
3861
3720
|
typedSelection: room.typedSelection ?? null,
|
|
3862
3721
|
model: next,
|
|
3863
3722
|
};
|
|
3864
|
-
let read = 'staged';
|
|
3865
|
-
if (!sameMembership(declared, held.record)) {
|
|
3866
|
-
const verdict = await validatedCollection(walkOf(local, held.record.key, 'confirmed'), lookupOf(local, held.record.key, 'confirmed'), composed(declared));
|
|
3867
|
-
read =
|
|
3868
|
-
verdict.verdict !== 'refuse'
|
|
3869
|
-
? 'staged'
|
|
3870
|
-
: verdict.rule === 'model-unavailable'
|
|
3871
|
-
? 'deferred'
|
|
3872
|
-
: { kind: 'refused', refusal: verdict };
|
|
3873
|
-
}
|
|
3874
|
-
const record = read === 'staged'
|
|
3875
|
-
? declared
|
|
3876
|
-
: { ...declared, typedSelection: held.record.typedSelection };
|
|
3877
3723
|
const confirmedLayer = await sliceAt(record, 'confirmed', held.queue, held.overlaid);
|
|
3878
3724
|
const replayedLayer = await replayed(record, confirmedLayer, held.queue);
|
|
3879
3725
|
const diff = await overlayDiff(record, held, replayedLayer.visible, replayedLayer.touched);
|
|
@@ -3884,7 +3730,7 @@ export function composeRuntime(options) {
|
|
|
3884
3730
|
key: record.key,
|
|
3885
3731
|
record: storedRecord(record),
|
|
3886
3732
|
roots: record.typedSelection?.roots ?? [],
|
|
3887
|
-
|
|
3733
|
+
composed: composedOf(composed(record)),
|
|
3888
3734
|
overlay: diff.overlay,
|
|
3889
3735
|
},
|
|
3890
3736
|
],
|
|
@@ -3895,29 +3741,22 @@ export function composeRuntime(options) {
|
|
|
3895
3741
|
documents: answer.changed[room.locator] ?? [],
|
|
3896
3742
|
room: true,
|
|
3897
3743
|
});
|
|
3898
|
-
if (read === 'staged' || read === 'deferred') {
|
|
3899
|
-
if (declarationRefusals.delete(room.locator)) {
|
|
3900
|
-
sourceRefusals.delete(room.locator);
|
|
3901
|
-
unreadable.delete(room.locator);
|
|
3902
|
-
}
|
|
3903
|
-
}
|
|
3904
|
-
else {
|
|
3905
|
-
refusedSource(room, read);
|
|
3906
|
-
declarationRefusals.add(room.locator);
|
|
3907
|
-
}
|
|
3908
|
-
}
|
|
3909
|
-
models.set(key, next);
|
|
3910
|
-
if (next.status === 'current') {
|
|
3911
|
-
liveModels.set(key, JSON.stringify(next.snapshot));
|
|
3912
|
-
invalidModels.delete(key);
|
|
3913
3744
|
}
|
|
3914
|
-
|
|
3745
|
+
heldModel(room, next);
|
|
3915
3746
|
if (changed === null)
|
|
3916
3747
|
emitRooms();
|
|
3917
3748
|
else
|
|
3918
3749
|
publishChangeSet(changed);
|
|
3919
3750
|
});
|
|
3920
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
|
+
}
|
|
3921
3760
|
async function scoped(result, room, local) {
|
|
3922
3761
|
if (result.scope === 'content' && !result.retryable) {
|
|
3923
3762
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
@@ -3946,6 +3785,17 @@ export function composeRuntime(options) {
|
|
|
3946
3785
|
emitRooms();
|
|
3947
3786
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
3948
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
|
+
}
|
|
3949
3799
|
function heldRooms(listed) {
|
|
3950
3800
|
return listed.flatMap((room) => {
|
|
3951
3801
|
const record = replicas.get(room.locator)?.record;
|
|
@@ -3963,14 +3813,19 @@ export function composeRuntime(options) {
|
|
|
3963
3813
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
3964
3814
|
});
|
|
3965
3815
|
if (attempt === 'opened') {
|
|
3816
|
+
naming += 1;
|
|
3966
3817
|
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
3967
3818
|
handed = true;
|
|
3968
3819
|
}
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
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');
|
|
3974
3829
|
if (failed !== undefined)
|
|
3975
3830
|
throw failed.reason;
|
|
3976
3831
|
}
|
|
@@ -3989,12 +3844,16 @@ export function composeRuntime(options) {
|
|
|
3989
3844
|
}
|
|
3990
3845
|
if (prior !== undefined)
|
|
3991
3846
|
await retainModel(local, room, modelOf(room.locator));
|
|
3847
|
+
if (prior !== undefined)
|
|
3848
|
+
await keepNaming(local, room);
|
|
3992
3849
|
if (discoveryPaused || authorization === undefined)
|
|
3993
3850
|
return false;
|
|
3994
3851
|
return room.replica.admission === 'admitted';
|
|
3995
3852
|
}
|
|
3996
3853
|
async function caughtUp(room, local, progress, live) {
|
|
3997
|
-
if (
|
|
3854
|
+
if (options.adapter.roomStream !== undefined || authorization === undefined)
|
|
3855
|
+
return;
|
|
3856
|
+
if (!live() || !behind(room))
|
|
3998
3857
|
return;
|
|
3999
3858
|
const withheld = await advance(room, local, authorization, live);
|
|
4000
3859
|
if (withheld !== null)
|
|
@@ -4007,6 +3866,8 @@ export function composeRuntime(options) {
|
|
|
4007
3866
|
async function loadRoom(room, local, progress, live) {
|
|
4008
3867
|
if (!live() || authorization === undefined)
|
|
4009
3868
|
return;
|
|
3869
|
+
if (replicas.has(room.locator) && !(await prepareRoom(room, local, progress)))
|
|
3870
|
+
return;
|
|
4010
3871
|
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
4011
3872
|
await reload(room, local, progress, live);
|
|
4012
3873
|
return;
|
|
@@ -4024,20 +3885,53 @@ export function composeRuntime(options) {
|
|
|
4024
3885
|
reloadsDue.delete(room.locator);
|
|
4025
3886
|
if (!live() || authorization === undefined)
|
|
4026
3887
|
return;
|
|
3888
|
+
const from = standingOf(room.locator);
|
|
4027
3889
|
const bootstrapped = await pullBootstrap(authorization, room, local, live);
|
|
4028
3890
|
if (bootstrapped.kind !== 'staged') {
|
|
4029
3891
|
if (live())
|
|
4030
3892
|
await withhold(bootstrapped, room, local, progress);
|
|
4031
3893
|
return;
|
|
4032
3894
|
}
|
|
4033
|
-
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);
|
|
4034
3896
|
await caughtUp(room, local, progress, live);
|
|
4035
3897
|
}
|
|
4036
3898
|
finally {
|
|
4037
3899
|
caughtUpTo(room.locator);
|
|
4038
3900
|
}
|
|
4039
3901
|
}
|
|
4040
|
-
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', 'erase'].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) {
|
|
4041
3935
|
if (closed || authorization === undefined) {
|
|
4042
3936
|
notifyReady();
|
|
4043
3937
|
return;
|
|
@@ -4045,13 +3939,9 @@ export function composeRuntime(options) {
|
|
|
4045
3939
|
moveTo(transferring(status));
|
|
4046
3940
|
discoveries += 1;
|
|
4047
3941
|
const started = discoveries;
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
}
|
|
4052
|
-
catch {
|
|
4053
|
-
result = { kind: 'transport-uncertainty' };
|
|
4054
|
-
}
|
|
3942
|
+
const result = found ?? (await asked(authorization));
|
|
3943
|
+
if (result.kind === 'discovery')
|
|
3944
|
+
roomsNamed = true;
|
|
4055
3945
|
if (!live()) {
|
|
4056
3946
|
progress.complete = false;
|
|
4057
3947
|
notifyReady();
|
|
@@ -4059,9 +3949,7 @@ export function composeRuntime(options) {
|
|
|
4059
3949
|
}
|
|
4060
3950
|
journal.record(`connection discover ${result.kind}`);
|
|
4061
3951
|
if (result.kind === 'transport-uncertainty') {
|
|
4062
|
-
|
|
4063
|
-
await retainModel(local, room, cached(modelOf(room.locator)));
|
|
4064
|
-
}
|
|
3952
|
+
await cachedModels(local);
|
|
4065
3953
|
progress.reachable = false;
|
|
4066
3954
|
progress.complete = false;
|
|
4067
3955
|
notifyReady();
|
|
@@ -4082,26 +3970,12 @@ export function composeRuntime(options) {
|
|
|
4082
3970
|
notifyReady();
|
|
4083
3971
|
return;
|
|
4084
3972
|
}
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
room.replica.selection.length === 0 ||
|
|
4092
|
-
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
4093
|
-
!['locked', 'open'].includes(room.replica.offline)) {
|
|
4094
|
-
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
4095
|
-
await refuse('missing-required-guarantee');
|
|
4096
|
-
notifyReady();
|
|
4097
|
-
return;
|
|
4098
|
-
}
|
|
4099
|
-
if (!declaredSelection(room)) {
|
|
4100
|
-
journal.record(`invalid typed selection declaration ${room.locator}`);
|
|
4101
|
-
await refuse('missing-required-guarantee');
|
|
4102
|
-
notifyReady();
|
|
4103
|
-
return;
|
|
4104
|
-
}
|
|
3973
|
+
const invalid = malformed(result.rooms);
|
|
3974
|
+
if (invalid !== null) {
|
|
3975
|
+
journal.record(invalid);
|
|
3976
|
+
await refuse('missing-required-guarantee');
|
|
3977
|
+
notifyReady();
|
|
3978
|
+
return;
|
|
4105
3979
|
}
|
|
4106
3980
|
if (refusal !== null) {
|
|
4107
3981
|
refusal = null;
|
|
@@ -4109,7 +3983,6 @@ export function composeRuntime(options) {
|
|
|
4109
3983
|
}
|
|
4110
3984
|
const previousCapabilities = capabilities;
|
|
4111
3985
|
capabilities = result.capabilities;
|
|
4112
|
-
outcomeRetention = result.retention.outcome;
|
|
4113
3986
|
notifyReady();
|
|
4114
3987
|
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
4115
3988
|
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)) {
|
|
@@ -4117,13 +3990,8 @@ export function composeRuntime(options) {
|
|
|
4117
3990
|
await local.fact('providerLimits', limits);
|
|
4118
3991
|
await local.fact('capabilities', capabilities);
|
|
4119
3992
|
}
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
await local.fact('member', signedIn);
|
|
4123
|
-
member = signedIn;
|
|
4124
|
-
emitMember();
|
|
4125
|
-
}
|
|
4126
|
-
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);
|
|
4127
3995
|
}
|
|
4128
3996
|
async function ensureAuthorization() {
|
|
4129
3997
|
if (authorization !== undefined)
|
|
@@ -4138,23 +4006,45 @@ export function composeRuntime(options) {
|
|
|
4138
4006
|
}
|
|
4139
4007
|
}
|
|
4140
4008
|
async function admittedRead(work) {
|
|
4141
|
-
|
|
4142
|
-
|
|
4009
|
+
while (roomReads.free === 0) {
|
|
4010
|
+
await new Promise((admit) => roomReads.waiting.push(admit));
|
|
4143
4011
|
}
|
|
4144
|
-
|
|
4145
|
-
while (reads.free === 0)
|
|
4146
|
-
await new Promise((admit) => reads.waiting.push(admit));
|
|
4147
|
-
reads.free -= 1;
|
|
4012
|
+
roomReads.free -= 1;
|
|
4148
4013
|
try {
|
|
4149
4014
|
await work();
|
|
4150
4015
|
}
|
|
4151
4016
|
finally {
|
|
4152
|
-
|
|
4153
|
-
|
|
4017
|
+
roomReads.free += 1;
|
|
4018
|
+
roomReads.waiting.shift()?.();
|
|
4154
4019
|
}
|
|
4155
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
|
+
}
|
|
4156
4041
|
function roomTask(locator, work) {
|
|
4157
|
-
|
|
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));
|
|
4158
4048
|
}
|
|
4159
4049
|
function task(lane, work) {
|
|
4160
4050
|
if (stopping)
|
|
@@ -4170,23 +4060,19 @@ export function composeRuntime(options) {
|
|
|
4170
4060
|
void run.finally(() => working.delete(run));
|
|
4171
4061
|
return run;
|
|
4172
4062
|
}
|
|
4173
|
-
function sync(attempt) {
|
|
4063
|
+
function sync(attempt, found) {
|
|
4174
4064
|
return task(OPENING_LANE, async (live) => {
|
|
4175
4065
|
if (!live()) {
|
|
4176
|
-
|
|
4066
|
+
readyFromStore();
|
|
4177
4067
|
return;
|
|
4178
4068
|
}
|
|
4179
4069
|
const progress = { complete: true, reachable: true };
|
|
4180
|
-
|
|
4181
|
-
if (attempt === 'opened')
|
|
4182
|
-
opened = generation;
|
|
4183
|
-
underway = attempt === 'opened' ? { generation, progress } : null;
|
|
4070
|
+
underway = attempt === 'opened' ? progress : null;
|
|
4184
4071
|
if (attempt === 'opened')
|
|
4185
4072
|
streamed = progress;
|
|
4186
4073
|
complete = false;
|
|
4187
4074
|
recorded = false;
|
|
4188
4075
|
moveTo(transferring(status));
|
|
4189
|
-
handed = false;
|
|
4190
4076
|
try {
|
|
4191
4077
|
const local = await openLocal();
|
|
4192
4078
|
if (!live())
|
|
@@ -4194,7 +4080,7 @@ export function composeRuntime(options) {
|
|
|
4194
4080
|
await ensureAuthorization();
|
|
4195
4081
|
if (!live())
|
|
4196
4082
|
return;
|
|
4197
|
-
await discoverRooms(local, progress, attempt, live);
|
|
4083
|
+
await discoverRooms(local, progress, attempt, live, found);
|
|
4198
4084
|
}
|
|
4199
4085
|
catch {
|
|
4200
4086
|
progress.complete = false;
|
|
@@ -4202,7 +4088,6 @@ export function composeRuntime(options) {
|
|
|
4202
4088
|
progress.reachable = false;
|
|
4203
4089
|
}
|
|
4204
4090
|
finally {
|
|
4205
|
-
gate.release();
|
|
4206
4091
|
notifyReady();
|
|
4207
4092
|
}
|
|
4208
4093
|
if (!live())
|
|
@@ -4214,35 +4099,59 @@ export function composeRuntime(options) {
|
|
|
4214
4099
|
if (handed || closed || progress.reachable)
|
|
4215
4100
|
return;
|
|
4216
4101
|
underway = null;
|
|
4217
|
-
opened = null;
|
|
4218
|
-
journal.record('connection reopen');
|
|
4219
4102
|
await rest(progress, live);
|
|
4220
|
-
options.adapter.roomStream?.reopen();
|
|
4221
4103
|
});
|
|
4222
4104
|
}
|
|
4223
|
-
function
|
|
4105
|
+
function resynchronize() {
|
|
4224
4106
|
return task(OPENING_LANE, async (live) => {
|
|
4225
|
-
if (!live() || !
|
|
4107
|
+
if (!live() || !announced)
|
|
4226
4108
|
return;
|
|
4227
4109
|
const progress = { complete: true, reachable: true };
|
|
4228
|
-
const gate = discover();
|
|
4229
4110
|
moveTo(transferring(status));
|
|
4230
4111
|
try {
|
|
4231
4112
|
const local = await openLocal();
|
|
4232
4113
|
if (!live())
|
|
4233
4114
|
return;
|
|
4234
|
-
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();
|
|
4235
4138
|
if (!live())
|
|
4236
4139
|
return;
|
|
4237
|
-
|
|
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);
|
|
4238
4151
|
}
|
|
4239
4152
|
catch {
|
|
4240
4153
|
progress.complete = false;
|
|
4241
4154
|
}
|
|
4242
|
-
finally {
|
|
4243
|
-
gate.release();
|
|
4244
|
-
notifyReady();
|
|
4245
|
-
}
|
|
4246
4155
|
if (!live())
|
|
4247
4156
|
return;
|
|
4248
4157
|
if (!progress.complete)
|
|
@@ -4250,14 +4159,17 @@ export function composeRuntime(options) {
|
|
|
4250
4159
|
await rest(progress, live);
|
|
4251
4160
|
});
|
|
4252
4161
|
}
|
|
4253
|
-
function closedSocket(revoked, lost) {
|
|
4254
|
-
|
|
4162
|
+
function closedSocket(revoked, lost, authorityChanged) {
|
|
4163
|
+
notifyReady();
|
|
4164
|
+
const opened = announced;
|
|
4255
4165
|
socketOpen = false;
|
|
4166
|
+
announced = false;
|
|
4167
|
+
missedChange ||= authorityChanged;
|
|
4168
|
+
handed = false;
|
|
4169
|
+
naming = 0;
|
|
4256
4170
|
connectionLost();
|
|
4257
|
-
if (lost)
|
|
4258
|
-
reads.lost();
|
|
4171
|
+
if (lost)
|
|
4259
4172
|
conversations.lost();
|
|
4260
|
-
}
|
|
4261
4173
|
if (revoked) {
|
|
4262
4174
|
void refuse('revoked-authorization');
|
|
4263
4175
|
return;
|
|
@@ -4268,18 +4180,32 @@ export function composeRuntime(options) {
|
|
|
4268
4180
|
moveTo({
|
|
4269
4181
|
...status,
|
|
4270
4182
|
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
4271
|
-
providerUnreachable: status.providerUnreachable || !opened,
|
|
4183
|
+
providerUnreachable: status.providerUnreachable || (!opened && lost),
|
|
4272
4184
|
});
|
|
4185
|
+
void closing(opened);
|
|
4273
4186
|
}
|
|
4274
|
-
function
|
|
4275
|
-
|
|
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);
|
|
4276
4204
|
const cycle = progress ?? { complete: true, reachable: true };
|
|
4277
|
-
|
|
4278
|
-
opened = null;
|
|
4205
|
+
replaced(expired);
|
|
4279
4206
|
const reloading = progress === null ? [] : owedReloads(expired);
|
|
4280
4207
|
const replacing = reloadNow((room) => expired.includes(room.locator) || reloading.includes(room.locator), cycle);
|
|
4281
4208
|
return task(OPENING_LANE, async (live) => {
|
|
4282
|
-
underway = null;
|
|
4283
4209
|
if (!live())
|
|
4284
4210
|
return;
|
|
4285
4211
|
if (progress === null) {
|
|
@@ -4287,24 +4213,14 @@ export function composeRuntime(options) {
|
|
|
4287
4213
|
await rest(cycle, live);
|
|
4288
4214
|
return;
|
|
4289
4215
|
}
|
|
4290
|
-
await roomsCarried([...expired, ...reloading]);
|
|
4291
|
-
if (!live())
|
|
4292
|
-
return;
|
|
4293
4216
|
try {
|
|
4294
4217
|
const local = await openLocal();
|
|
4295
|
-
|
|
4218
|
+
await replacing;
|
|
4296
4219
|
if (progress.reachable) {
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
!reloading.includes(room.locator)) {
|
|
4301
|
-
await confirmModels(room, local);
|
|
4302
|
-
}
|
|
4303
|
-
}
|
|
4220
|
+
await settleModels([...discovered.values()].filter((room) => replicas.has(room.locator) &&
|
|
4221
|
+
!expired.includes(room.locator) &&
|
|
4222
|
+
!reloading.includes(room.locator)), 'current', local);
|
|
4304
4223
|
}
|
|
4305
|
-
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
4306
|
-
if (failed !== undefined)
|
|
4307
|
-
throw failed.reason;
|
|
4308
4224
|
}
|
|
4309
4225
|
catch {
|
|
4310
4226
|
progress.complete = false;
|
|
@@ -4316,11 +4232,8 @@ export function composeRuntime(options) {
|
|
|
4316
4232
|
}
|
|
4317
4233
|
function owedReloads(expired) {
|
|
4318
4234
|
const reloading = [...discovered.values()]
|
|
4319
|
-
.filter((room) => replicas.has(room.locator) &&
|
|
4320
|
-
!expired.includes(room.locator) &&
|
|
4321
|
-
(owed.has(room.locator) || revoked(room.locator)))
|
|
4235
|
+
.filter((room) => replicas.has(room.locator) && !expired.includes(room.locator) && revoked(room.locator))
|
|
4322
4236
|
.map((room) => room.locator);
|
|
4323
|
-
owed.clear();
|
|
4324
4237
|
for (const locator of reloading)
|
|
4325
4238
|
catching.add(locator);
|
|
4326
4239
|
liveQueries.recover();
|
|
@@ -4329,22 +4242,44 @@ export function composeRuntime(options) {
|
|
|
4329
4242
|
async function reloadNow(replaced, progress) {
|
|
4330
4243
|
const reloaded = [...discovered.values()].filter((room) => replicas.has(room.locator) && replaced(room));
|
|
4331
4244
|
for (const room of reloaded)
|
|
4332
|
-
reloadsDue.
|
|
4245
|
+
reloadsDue.add(room.locator);
|
|
4333
4246
|
try {
|
|
4334
4247
|
const local = await openLocal();
|
|
4335
|
-
|
|
4248
|
+
for (const room of reloaded) {
|
|
4249
|
+
void roomTask(room.locator, (live) => reload(room, local, progress, live)).then(() => rest(progress));
|
|
4250
|
+
}
|
|
4336
4251
|
}
|
|
4337
4252
|
catch {
|
|
4338
4253
|
for (const room of reloaded)
|
|
4339
4254
|
reloadsDue.delete(room.locator);
|
|
4340
4255
|
progress.reachable = false;
|
|
4341
|
-
return [];
|
|
4342
4256
|
}
|
|
4343
4257
|
}
|
|
4344
|
-
async function
|
|
4345
|
-
const
|
|
4346
|
-
|
|
4347
|
-
|
|
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
|
+
}
|
|
4348
4283
|
}
|
|
4349
4284
|
function submitLater(locator) {
|
|
4350
4285
|
if (localSaves)
|
|
@@ -4388,14 +4323,10 @@ export function composeRuntime(options) {
|
|
|
4388
4323
|
try {
|
|
4389
4324
|
const local = await openLocal();
|
|
4390
4325
|
await roomTask(locator, async (live) => {
|
|
4391
|
-
|
|
4392
|
-
if (!current() || authorization === undefined)
|
|
4326
|
+
if (!live() || authorization === undefined)
|
|
4393
4327
|
return;
|
|
4394
|
-
const withheld = await
|
|
4395
|
-
|
|
4396
|
-
abandonedOn(signal),
|
|
4397
|
-
]);
|
|
4398
|
-
if (withheld !== null && current())
|
|
4328
|
+
const withheld = await advance(room, local, authorization, live);
|
|
4329
|
+
if (withheld !== null && live())
|
|
4399
4330
|
await withhold(withheld, room, local, progress);
|
|
4400
4331
|
});
|
|
4401
4332
|
}
|
|
@@ -4450,14 +4381,11 @@ export function composeRuntime(options) {
|
|
|
4450
4381
|
progress.complete = false;
|
|
4451
4382
|
return;
|
|
4452
4383
|
}
|
|
4453
|
-
reads.applied();
|
|
4454
4384
|
submitAll();
|
|
4455
4385
|
for (const locator of stale)
|
|
4456
4386
|
if (repairable.has(locator))
|
|
4457
4387
|
repairLater(locator, {});
|
|
4458
4388
|
await conversations.reconcile();
|
|
4459
|
-
if (status.providerUnreachable)
|
|
4460
|
-
conversations.reconnected();
|
|
4461
4389
|
const reading = { complete: true, reachable: true };
|
|
4462
4390
|
await conversations.catchUp(reading);
|
|
4463
4391
|
if (progress.reachable && reading.reachable)
|
|
@@ -4483,7 +4411,7 @@ export function composeRuntime(options) {
|
|
|
4483
4411
|
if (!live())
|
|
4484
4412
|
return;
|
|
4485
4413
|
let lastCompleted = status.lastCompleted;
|
|
4486
|
-
let synced = complete && stale.size === 0 && !pending();
|
|
4414
|
+
let synced = complete && roomWork === 0 && naming === 0 && stale.size === 0 && !pending();
|
|
4487
4415
|
if (synced && !recorded) {
|
|
4488
4416
|
const completedAt = new Date().toISOString();
|
|
4489
4417
|
try {
|
|
@@ -4498,74 +4426,69 @@ export function composeRuntime(options) {
|
|
|
4498
4426
|
}
|
|
4499
4427
|
}
|
|
4500
4428
|
moveTo({
|
|
4501
|
-
state: pending()
|
|
4429
|
+
state: pending()
|
|
4430
|
+
? 'pending'
|
|
4431
|
+
: synced && complete && roomWork === 0 && naming === 0
|
|
4432
|
+
? 'synced'
|
|
4433
|
+
: 'syncing',
|
|
4502
4434
|
lastCompleted,
|
|
4503
|
-
providerUnreachable: !progress.reachable,
|
|
4435
|
+
providerUnreachable: progress === null ? status.providerUnreachable : !progress.reachable,
|
|
4504
4436
|
...attention(),
|
|
4505
4437
|
});
|
|
4506
4438
|
}
|
|
4507
|
-
function
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
}
|
|
4515
|
-
function applyRoom(locator, roomVersion) {
|
|
4516
|
-
announced.set(locator, roomVersion);
|
|
4517
|
-
return task(roomLane(locator), async (live) => {
|
|
4518
|
-
if (discovering?.generation === generation)
|
|
4519
|
-
await discovering.done;
|
|
4520
|
-
const saving = answering.get(locator);
|
|
4521
|
-
if (saving?.generation === generation)
|
|
4522
|
-
await saving.answered;
|
|
4523
|
-
if (!live() || authorization === undefined)
|
|
4524
|
-
return;
|
|
4525
|
-
const room = discovered.get(locator);
|
|
4526
|
-
const held = replicas.get(locator);
|
|
4527
|
-
if (room === undefined || room.replica.admission !== 'admitted')
|
|
4528
|
-
return;
|
|
4529
|
-
if (held !== undefined &&
|
|
4530
|
-
(held.record.replica.selection !== room.replica.selection ||
|
|
4531
|
-
options.adapter.reached(locator, held.record.roomVersion, roomVersion))) {
|
|
4532
|
-
return;
|
|
4533
|
-
}
|
|
4534
|
-
const during = underway?.generation === generation ? underway.progress : null;
|
|
4535
|
-
const progress = during ?? { complete: true, reachable: true };
|
|
4536
|
-
if (during === null)
|
|
4537
|
-
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 };
|
|
4538
4446
|
try {
|
|
4539
4447
|
const local = await openLocal();
|
|
4540
|
-
|
|
4541
|
-
if (!live()
|
|
4542
|
-
return;
|
|
4543
|
-
const
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
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
|
+
}
|
|
4547
4483
|
}
|
|
4548
4484
|
catch {
|
|
4485
|
+
if (!stale.has(locator))
|
|
4486
|
+
name(locator);
|
|
4549
4487
|
stale.add(locator);
|
|
4488
|
+
return progress;
|
|
4550
4489
|
}
|
|
4551
|
-
|
|
4552
|
-
if (during === null)
|
|
4553
|
-
await rest(progress, live);
|
|
4554
|
-
}
|
|
4555
|
-
});
|
|
4490
|
+
}).then((progress) => rest(progress), () => undefined);
|
|
4556
4491
|
}
|
|
4557
|
-
function roomsCarried(replacing) {
|
|
4558
|
-
const replaced = new Set(replacing.map(roomLane));
|
|
4559
|
-
return Promise.all([...lanes]
|
|
4560
|
-
.filter(([lane, held]) => lane !== OPENING_LANE && !replaced.has(lane) && held.generation === generation)
|
|
4561
|
-
.map(([, held]) => held.chain));
|
|
4562
|
-
}
|
|
4563
|
-
const receipts = {
|
|
4564
|
-
heads: async () => (await openLocal()).receivedHeads(),
|
|
4565
|
-
parts: async (key, count) => (await openLocal()).receivedParts(key, count),
|
|
4566
|
-
keep: async (key, head, part) => (await openLocal()).keepReceived(key, head, part),
|
|
4567
|
-
drop: async (keys) => (await openLocal()).dropReceived(keys),
|
|
4568
|
-
};
|
|
4569
4492
|
function observeStream() {
|
|
4570
4493
|
const stream = options.adapter.roomStream;
|
|
4571
4494
|
if (stream === undefined || withdrawStream !== undefined)
|
|
@@ -4575,29 +4498,33 @@ export function composeRuntime(options) {
|
|
|
4575
4498
|
return;
|
|
4576
4499
|
if (event.kind === 'opened') {
|
|
4577
4500
|
socketOpen = true;
|
|
4501
|
+
announced = false;
|
|
4502
|
+
handed = false;
|
|
4503
|
+
naming = 0;
|
|
4578
4504
|
connectionLive();
|
|
4579
4505
|
generation += 1;
|
|
4580
|
-
reads.opened();
|
|
4581
4506
|
conversations.reopened();
|
|
4582
4507
|
recovery.resume();
|
|
4583
|
-
|
|
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);
|
|
4584
4518
|
}
|
|
4585
4519
|
else if (event.kind === 'closed')
|
|
4586
|
-
closedSocket(event.revoked, event.lost);
|
|
4587
|
-
else if (event.kind === '
|
|
4588
|
-
void
|
|
4520
|
+
closedSocket(event.revoked, event.lost, event.authorityChanged);
|
|
4521
|
+
else if (event.kind === 'caught-up')
|
|
4522
|
+
void caughtUpRooms(event.expired);
|
|
4589
4523
|
else if (event.kind === 'authority-changed')
|
|
4590
|
-
void
|
|
4524
|
+
void changeAuthority(event);
|
|
4591
4525
|
else
|
|
4592
|
-
void applyRoom(event
|
|
4593
|
-
}
|
|
4594
|
-
}
|
|
4595
|
-
async function heldRevisions(locator, paths) {
|
|
4596
|
-
const held = replicas.get(locator);
|
|
4597
|
-
if (held === undefined || paths.length === 0)
|
|
4598
|
-
return new Map();
|
|
4599
|
-
const entries = await (await openLocal()).entries(held.record.key, 'confirmed', paths);
|
|
4600
|
-
return new Map(entries.flatMap(({ path, revision }) => revision === null ? [] : [[path, revision]]));
|
|
4526
|
+
void applyRoom(event);
|
|
4527
|
+
});
|
|
4601
4528
|
}
|
|
4602
4529
|
function writesHeld() {
|
|
4603
4530
|
return serviceMode === 'write-halted' || haltedRefusal;
|
|
@@ -4667,10 +4594,9 @@ export function composeRuntime(options) {
|
|
|
4667
4594
|
if (closed)
|
|
4668
4595
|
return;
|
|
4669
4596
|
closed = true;
|
|
4597
|
+
notifyDiscovered();
|
|
4670
4598
|
if (probing !== null)
|
|
4671
4599
|
clearInterval(probing);
|
|
4672
|
-
for (const resolve of shownWaiters.splice(0))
|
|
4673
|
-
resolve();
|
|
4674
4600
|
connectionLive();
|
|
4675
4601
|
lockListeners.clear();
|
|
4676
4602
|
readinessWatches.clear();
|
|
@@ -4682,11 +4608,13 @@ export function composeRuntime(options) {
|
|
|
4682
4608
|
conversations.close();
|
|
4683
4609
|
void recovery.retire();
|
|
4684
4610
|
roomListeners.clear();
|
|
4611
|
+
unlistedRooms.clear();
|
|
4685
4612
|
refusalListeners.clear();
|
|
4686
4613
|
storageLossListeners.clear();
|
|
4687
4614
|
statusListeners.clear();
|
|
4688
4615
|
serviceModeListeners.clear();
|
|
4689
4616
|
memberListeners.clear();
|
|
4617
|
+
authorityChangeListeners.clear();
|
|
4690
4618
|
liveQueries.close();
|
|
4691
4619
|
queueSubscriptions.clear();
|
|
4692
4620
|
roomDraftListeners.clear();
|
|
@@ -4694,6 +4622,7 @@ export function composeRuntime(options) {
|
|
|
4694
4622
|
}
|
|
4695
4623
|
async function stop() {
|
|
4696
4624
|
stopping = true;
|
|
4625
|
+
inFlight.abort();
|
|
4697
4626
|
await recovery.retire();
|
|
4698
4627
|
await Promise.all(working);
|
|
4699
4628
|
await mutations;
|
|
@@ -4707,20 +4636,20 @@ export function composeRuntime(options) {
|
|
|
4707
4636
|
observeStream();
|
|
4708
4637
|
void openLocal().then(() => {
|
|
4709
4638
|
if (capabilities.length > 0)
|
|
4710
|
-
|
|
4639
|
+
readyFromStore();
|
|
4711
4640
|
}, () => {
|
|
4712
|
-
|
|
4641
|
+
readyFromStore();
|
|
4713
4642
|
});
|
|
4714
4643
|
if (options.adapter.roomStream === undefined)
|
|
4715
|
-
void sync('unstreamed');
|
|
4644
|
+
void sync('unstreamed', null);
|
|
4716
4645
|
function planned(input, reach) {
|
|
4717
|
-
const plan =
|
|
4646
|
+
const plan = planWithin(input, roomFacts(reach), planReach(reach));
|
|
4718
4647
|
const named = plan.paths !== null &&
|
|
4719
4648
|
plan.rooms !== null &&
|
|
4720
4649
|
plan.paths.length * plan.rooms.length <= MAXIMUM_WINDOW;
|
|
4721
4650
|
return plan.limit === null && named ? plan : { ...plan, limit: capped(plan.limit) };
|
|
4722
4651
|
}
|
|
4723
|
-
function watchPlan(plan, deliver) {
|
|
4652
|
+
function watchPlan(plan, deliver, fail) {
|
|
4724
4653
|
let seeded = false;
|
|
4725
4654
|
let pending = true;
|
|
4726
4655
|
const shown = () => {
|
|
@@ -4746,7 +4675,7 @@ export function composeRuntime(options) {
|
|
|
4746
4675
|
}
|
|
4747
4676
|
seeded = false;
|
|
4748
4677
|
deliver(message);
|
|
4749
|
-
});
|
|
4678
|
+
}, fail);
|
|
4750
4679
|
return () => {
|
|
4751
4680
|
watchers.delete(shown);
|
|
4752
4681
|
if (watchers.size === 0)
|
|
@@ -4754,61 +4683,59 @@ export function composeRuntime(options) {
|
|
|
4754
4683
|
stop();
|
|
4755
4684
|
};
|
|
4756
4685
|
}
|
|
4757
|
-
async function searched(input, reach
|
|
4686
|
+
async function searched(input, reach) {
|
|
4758
4687
|
const asked = searchPlanOf(input);
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
rooms: answered.coverage.rooms,
|
|
4803
|
-
conversations: named,
|
|
4804
|
-
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)),
|
|
4805
4731
|
};
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4732
|
+
}
|
|
4733
|
+
catch {
|
|
4734
|
+
journal.record('outcome search transport-uncertainty');
|
|
4735
|
+
return UNCERTAIN;
|
|
4809
4736
|
}
|
|
4810
4737
|
}
|
|
4811
|
-
const watch = (topic, input, deliver) => {
|
|
4738
|
+
const watch = (topic, input, deliver, fail) => {
|
|
4812
4739
|
if (topic === 'room-drafts') {
|
|
4813
4740
|
const listener = { notify: deliver, ready: false };
|
|
4814
4741
|
roomDraftListeners.add(listener);
|
|
@@ -4857,12 +4784,13 @@ export function composeRuntime(options) {
|
|
|
4857
4784
|
};
|
|
4858
4785
|
}
|
|
4859
4786
|
if (topic === 'readiness') {
|
|
4860
|
-
return watchReadiness({ target: '', code: 'stored'
|
|
4787
|
+
return watchReadiness({ target: '', code: 'stored' }, null, deliver);
|
|
4861
4788
|
}
|
|
4862
4789
|
if (topic === 'rooms') {
|
|
4863
|
-
|
|
4864
|
-
|
|
4790
|
+
unlistedRooms.add(deliver);
|
|
4791
|
+
emitKnownRooms();
|
|
4865
4792
|
return () => {
|
|
4793
|
+
unlistedRooms.delete(deliver);
|
|
4866
4794
|
roomListeners.delete(deliver);
|
|
4867
4795
|
};
|
|
4868
4796
|
}
|
|
@@ -4901,6 +4829,13 @@ export function composeRuntime(options) {
|
|
|
4901
4829
|
memberListeners.delete(deliver);
|
|
4902
4830
|
};
|
|
4903
4831
|
}
|
|
4832
|
+
if (topic === 'authority-changes') {
|
|
4833
|
+
authorityChangeListeners.add(deliver);
|
|
4834
|
+
deliver(authorityChanges);
|
|
4835
|
+
return () => {
|
|
4836
|
+
authorityChangeListeners.delete(deliver);
|
|
4837
|
+
};
|
|
4838
|
+
}
|
|
4904
4839
|
if (topic === 'queue') {
|
|
4905
4840
|
const queue = { locator: input[0], notify: deliver };
|
|
4906
4841
|
queueSubscriptions.add(queue);
|
|
@@ -4909,17 +4844,27 @@ export function composeRuntime(options) {
|
|
|
4909
4844
|
queueSubscriptions.delete(queue);
|
|
4910
4845
|
};
|
|
4911
4846
|
}
|
|
4912
|
-
return watchPlan(planned(input[0], null), deliver);
|
|
4847
|
+
return watchPlan(planned(input[0], null), deliver, fail);
|
|
4913
4848
|
};
|
|
4914
4849
|
const md = {
|
|
4915
4850
|
loaded() {
|
|
4916
4851
|
return openLocal().then(() => undefined);
|
|
4917
4852
|
},
|
|
4918
4853
|
rooms() {
|
|
4919
|
-
|
|
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
|
+
});
|
|
4920
4865
|
},
|
|
4921
4866
|
query: ((input) => rowsQuery(planned(input, null))),
|
|
4922
|
-
search: (input) => searched(input, null
|
|
4867
|
+
search: (input) => searched(input, null),
|
|
4923
4868
|
types(locator) {
|
|
4924
4869
|
return {
|
|
4925
4870
|
collection: modelTypes(modelOf(locator)),
|
|
@@ -5022,6 +4967,8 @@ export function composeRuntime(options) {
|
|
|
5022
4967
|
return journal.export();
|
|
5023
4968
|
},
|
|
5024
4969
|
cleanUp,
|
|
4970
|
+
unsent: () => database.ask({ kind: 'authored.unsent' }),
|
|
4971
|
+
reset: () => options.shell.reset(),
|
|
5025
4972
|
persistence() {
|
|
5026
4973
|
return persistence === 'granted';
|
|
5027
4974
|
},
|
|
@@ -5044,7 +4991,7 @@ export function composeRuntime(options) {
|
|
|
5044
4991
|
authorization = undefined;
|
|
5045
4992
|
discoveryPaused = false;
|
|
5046
4993
|
if (options.adapter.roomStream === undefined)
|
|
5047
|
-
await sync('unstreamed');
|
|
4994
|
+
await sync('unstreamed', null);
|
|
5048
4995
|
else
|
|
5049
4996
|
await task(OPENING_LANE, ensureAuthorization);
|
|
5050
4997
|
},
|
|
@@ -5061,12 +5008,12 @@ export function composeRuntime(options) {
|
|
|
5061
5008
|
lockedRooms,
|
|
5062
5009
|
watchLocks,
|
|
5063
5010
|
located,
|
|
5011
|
+
room: (locator) => listed().find((one) => one.locator === locator),
|
|
5064
5012
|
};
|
|
5065
5013
|
return {
|
|
5066
5014
|
md,
|
|
5067
5015
|
seed,
|
|
5068
5016
|
stop,
|
|
5069
|
-
lose: loseRooms,
|
|
5070
5017
|
watch,
|
|
5071
5018
|
persisted(answer) {
|
|
5072
5019
|
if (answer === persistence)
|
|
@@ -5075,7 +5022,6 @@ export function composeRuntime(options) {
|
|
|
5075
5022
|
journal.record(`persistence ${answer}`);
|
|
5076
5023
|
queueReadiness();
|
|
5077
5024
|
},
|
|
5078
|
-
shown: screen,
|
|
5079
5025
|
within: (reach, port) => {
|
|
5080
5026
|
if (full)
|
|
5081
5027
|
void probed();
|