@agentshouse/mdruntime 0.1.0-c5d81e5d5cb58 → 0.1.0-c5fe3b2cddda7
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/dist/adapter.d.ts +15 -28
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +7 -14
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +82 -107
- package/dist/collection.d.ts +0 -3
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +1 -17
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +16 -8
- package/dist/connection.d.ts +0 -1
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +455 -688
- package/dist/conversation.d.ts +2 -3
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +56 -10
- package/dist/durability.d.ts +0 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +1 -0
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +1 -1
- package/dist/fulltext.d.ts.map +1 -1
- package/dist/fulltext.js +7 -5
- package/dist/host.d.ts +0 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +6 -4
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/media.d.ts +1 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +19 -9
- package/dist/memory.d.ts +2 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +50 -20
- package/dist/model.d.ts +0 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -9
- package/dist/outcome.d.ts +0 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/reach.js +1 -1
- package/dist/readiness.d.ts +3 -15
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +4 -10
- package/dist/recovery.d.ts +1 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +26 -0
- package/dist/refresh.d.ts +5 -4
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +4 -4
- package/dist/replica.d.ts +1 -2
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +0 -4
- package/dist/room-draft.d.ts +17 -2
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +40 -6
- package/dist/shared.d.ts +0 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +0 -10
- package/dist/status.d.ts +0 -4
- package/dist/status.d.ts.map +1 -1
- package/dist/store.d.ts +6 -29
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +7 -63
- package/dist/stored.d.ts +1 -25
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +51 -115
- package/dist/tables.d.ts +3 -40
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +3 -52
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/validation.d.ts +3 -4
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +35 -96
- package/package.json +2 -2
- package/dist/reads.d.ts +0 -11
- package/dist/reads.d.ts.map +0 -1
- package/dist/reads.js +0 -100
package/dist/connection.js
CHANGED
|
@@ -8,11 +8,11 @@ import { seededResult } from './seed.js';
|
|
|
8
8
|
import { documentKey, roomOfKey } from './rows.js';
|
|
9
9
|
import { searchPlanOf } from './search.js';
|
|
10
10
|
import { capped, MAXIMUM_WINDOW } from './windows.js';
|
|
11
|
-
import { bootstrapRoom
|
|
11
|
+
import { bootstrapRoom } from './bootstrap.js';
|
|
12
12
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
13
13
|
import { createConversationStore, } from './conversation.js';
|
|
14
14
|
import { leaving as leavingIn, partnerOf, queuedFile, } from './change.js';
|
|
15
|
-
import { available, collectionOf, composedOf,
|
|
15
|
+
import { available, collectionOf, composedOf, validRoot, } from './collection.js';
|
|
16
16
|
import { runtimeError } from './failure.js';
|
|
17
17
|
import { createLog } from './log.js';
|
|
18
18
|
import { binaryRecord } from './binary.js';
|
|
@@ -20,11 +20,10 @@ 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';
|
|
@@ -34,7 +33,7 @@ import { paced, ROW_WEIGHT, stepped, yielded } from './turn.js';
|
|
|
34
33
|
import { identityTime, roomDraftRevision } from './identity.js';
|
|
35
34
|
import { ancestors, beneath, normalize } from './paths.js';
|
|
36
35
|
import { openRuntimeStore, } from './store.js';
|
|
37
|
-
import {
|
|
36
|
+
import { validatedNeighbourhood } from './validation.js';
|
|
38
37
|
import { unconfirmed } from './transfer.js';
|
|
39
38
|
import { governs, holds, narrowed, planReach, } from './reach.js';
|
|
40
39
|
function within(root, path) {
|
|
@@ -59,36 +58,33 @@ const DERIVE_BYTES = 1 << 23;
|
|
|
59
58
|
const DERIVE_FLOOR = 1 << 18;
|
|
60
59
|
// An index build aims at this much database work, so a Save, a query or the next part's progress waits behind at most one short build.
|
|
61
60
|
const DERIVE_AIM_MS = 250;
|
|
62
|
-
// A build past its floor of text stops after a second and a half of work, so a Room whose text costs ten times the usual
|
|
61
|
+
// A build past its floor of text stops after a second and a half of work, its full-text writes included, so a Room whose text costs ten times the usual holds a Save, a query or the next part's progress behind no longer build.
|
|
63
62
|
const DERIVE_LONGEST_MS = 1_500;
|
|
64
|
-
const WALK_BYTES = 1 << 20;
|
|
65
63
|
const QUERY_BYTES = 1 << 20;
|
|
66
64
|
const STAGE_BYTES = 1 << 20;
|
|
67
65
|
const STAGE_FLOOR = 1 << 16;
|
|
68
66
|
// A batch aims at this much database work, so a Save or a query waits behind at most one short write.
|
|
69
67
|
const STAGE_AIM_MS = 50;
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
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.
|
|
73
72
|
const ARRIVAL_REPORT_MS = 250;
|
|
74
|
-
const CLEAN_CHANGES = 2_000;
|
|
75
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.
|
|
76
74
|
const LOCK_GRACE_MS = 120_000;
|
|
77
75
|
function operationOf(input) {
|
|
78
76
|
const operation = input?.operation_id;
|
|
79
77
|
return typeof operation === 'string' ? operation : null;
|
|
80
78
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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));
|
|
87
86
|
});
|
|
88
87
|
}
|
|
89
|
-
function roomLane(locator) {
|
|
90
|
-
return `room ${locator}`;
|
|
91
|
-
}
|
|
92
88
|
function pendingKey(locator, id) {
|
|
93
89
|
return `${locator}\u0000${id}`;
|
|
94
90
|
}
|
|
@@ -123,11 +119,19 @@ export function composeRuntime(options) {
|
|
|
123
119
|
const liveModels = new FencedMap(isFenced);
|
|
124
120
|
const invalidModels = new Set();
|
|
125
121
|
const discovered = new Map();
|
|
126
|
-
|
|
122
|
+
let inFlight = new AbortController();
|
|
123
|
+
const refreshed = options.adapter.refresh?.bind(options.adapter);
|
|
124
|
+
const reads = {
|
|
125
|
+
bootstrap: (authorized, request) => readUntil(options.adapter.bootstrap(authorized, request), inFlight.signal),
|
|
126
|
+
...(refreshed === undefined
|
|
127
|
+
? {}
|
|
128
|
+
: {
|
|
129
|
+
refresh: (authorized, request) => readUntil(refreshed(authorized, request), inFlight.signal),
|
|
130
|
+
}),
|
|
131
|
+
reached: (locator, roomVersion, target) => options.adapter.reached(locator, roomVersion, target),
|
|
132
|
+
};
|
|
127
133
|
let roomsListed = false;
|
|
128
134
|
const scopedRefusals = new Map();
|
|
129
|
-
const sourceRefusals = new Map();
|
|
130
|
-
const declarationRefusals = new Set();
|
|
131
135
|
function modelOf(locator) {
|
|
132
136
|
return models.get(locator) ?? NO_MODEL;
|
|
133
137
|
}
|
|
@@ -150,10 +154,10 @@ export function composeRuntime(options) {
|
|
|
150
154
|
let closed = false;
|
|
151
155
|
let stopping = false;
|
|
152
156
|
let withdrawStream;
|
|
153
|
-
const announced = new Map();
|
|
154
157
|
let underway = null;
|
|
155
158
|
let streamed = null;
|
|
156
159
|
let handed = false;
|
|
160
|
+
let naming = 0;
|
|
157
161
|
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
158
162
|
const repairing = new Set();
|
|
159
163
|
const calling = new Set();
|
|
@@ -165,11 +169,11 @@ export function composeRuntime(options) {
|
|
|
165
169
|
const stale = new Set();
|
|
166
170
|
let generation = 0;
|
|
167
171
|
let discovering = null;
|
|
168
|
-
|
|
169
|
-
const owed = new Set();
|
|
170
|
-
const reloadsDue = new Map();
|
|
172
|
+
const reloadsDue = new Set();
|
|
171
173
|
const lanes = new Map();
|
|
172
|
-
|
|
174
|
+
const roomLanes = new Map();
|
|
175
|
+
let roomWork = 0;
|
|
176
|
+
const roomReads = { free: ROOM_READS, waiting: [] };
|
|
173
177
|
const working = new Set();
|
|
174
178
|
let mutations = Promise.resolve();
|
|
175
179
|
let gathering = null;
|
|
@@ -218,17 +222,16 @@ export function composeRuntime(options) {
|
|
|
218
222
|
let readinessQueued = false;
|
|
219
223
|
const catching = new Set();
|
|
220
224
|
const transfers = new FencedMap(isFenced);
|
|
221
|
-
const installs = new
|
|
222
|
-
const keptParts = new Map();
|
|
225
|
+
const installs = new Set();
|
|
223
226
|
const arrivals = new Map();
|
|
227
|
+
const streaming = new Set();
|
|
224
228
|
const underived = new Map();
|
|
225
229
|
const unindexed = new Map();
|
|
230
|
+
const startedOver = new Map();
|
|
226
231
|
let arrivalReported = 0;
|
|
227
|
-
let
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
let queuedBytes = 0;
|
|
231
|
-
const partWaiters = [];
|
|
232
|
+
let streamsFree = STREAMS_HELD;
|
|
233
|
+
let queuedText = 0;
|
|
234
|
+
const installWaiters = [];
|
|
232
235
|
const stageBudget = {
|
|
233
236
|
bytes: STAGE_FLOOR * 4,
|
|
234
237
|
floor: STAGE_FLOOR,
|
|
@@ -241,7 +244,7 @@ export function composeRuntime(options) {
|
|
|
241
244
|
ceiling: DERIVE_BYTES,
|
|
242
245
|
aim: DERIVE_AIM_MS,
|
|
243
246
|
};
|
|
244
|
-
|
|
247
|
+
const installTurns = { busy: false, ahead: 0, finishing: [], starting: [] };
|
|
245
248
|
const conversations = createConversationStore({
|
|
246
249
|
agents: declaredAgents,
|
|
247
250
|
documents: (declaration, after) => rowsQuery(planOf({
|
|
@@ -369,7 +372,9 @@ export function composeRuntime(options) {
|
|
|
369
372
|
}
|
|
370
373
|
const file = parts.file === undefined
|
|
371
374
|
? undefined
|
|
372
|
-
: await heldFile(parts.file.blob,
|
|
375
|
+
: await heldFile(parts.file.blob, parts.file.base === undefined
|
|
376
|
+
? binaryBase(await visibleOf(room, [path]), path)
|
|
377
|
+
: parts.file.base);
|
|
373
378
|
const record = {
|
|
374
379
|
locator: room,
|
|
375
380
|
path,
|
|
@@ -408,7 +413,7 @@ export function composeRuntime(options) {
|
|
|
408
413
|
const changed = new Map();
|
|
409
414
|
for (const room of [...replicas.keys(), ...queuesWithoutReplica.keys()]) {
|
|
410
415
|
for (const { batch, state } of entriesOf(room)) {
|
|
411
|
-
if (state === '
|
|
416
|
+
if (state === 'local')
|
|
412
417
|
continue;
|
|
413
418
|
for (const { path } of batch.changes)
|
|
414
419
|
changed.set(`${room}\u0000${path}`, { room, path });
|
|
@@ -429,16 +434,9 @@ export function composeRuntime(options) {
|
|
|
429
434
|
returned: structuredClone(record.returned),
|
|
430
435
|
});
|
|
431
436
|
}
|
|
432
|
-
for (const [room, refusal] of sourceRefusals)
|
|
433
|
-
attentionRequired.push({ room, refusal: structuredClone(refusal) });
|
|
434
437
|
attentionRequired.sort((left, right) => {
|
|
435
438
|
if (left.room !== right.room)
|
|
436
439
|
return left.room < right.room ? -1 : 1;
|
|
437
|
-
if ('refusal' in left || 'refusal' in right) {
|
|
438
|
-
if ('refusal' in left && 'refusal' in right)
|
|
439
|
-
return 0;
|
|
440
|
-
return 'refusal' in left ? -1 : 1;
|
|
441
|
-
}
|
|
442
440
|
if (left.path !== right.path)
|
|
443
441
|
return left.path < right.path ? -1 : 1;
|
|
444
442
|
return 0;
|
|
@@ -593,9 +591,6 @@ export function composeRuntime(options) {
|
|
|
593
591
|
if (localSaves)
|
|
594
592
|
return Object.freeze({ batch, state: 'local' });
|
|
595
593
|
const state = pendings.get(pendingKey(locator, batch.id));
|
|
596
|
-
if (state?.accepted !== undefined) {
|
|
597
|
-
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
598
|
-
}
|
|
599
594
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
600
595
|
}
|
|
601
596
|
function entriesOf(locator) {
|
|
@@ -712,7 +707,7 @@ export function composeRuntime(options) {
|
|
|
712
707
|
coverage.push({
|
|
713
708
|
room: locator,
|
|
714
709
|
generation: replica.record.transfer,
|
|
715
|
-
current: !(catching.has(locator) ||
|
|
710
|
+
current: !(catching.has(locator) || transfers.has(locator)),
|
|
716
711
|
});
|
|
717
712
|
read.push({ locator, key: replica.record.key });
|
|
718
713
|
for (const batch of replica.queue)
|
|
@@ -739,8 +734,6 @@ export function composeRuntime(options) {
|
|
|
739
734
|
return { listed: named.every((room) => replicas.has(room)), rooms: named };
|
|
740
735
|
}
|
|
741
736
|
function roomState(locator) {
|
|
742
|
-
if (sourceRefusals.has(locator))
|
|
743
|
-
return 'refused';
|
|
744
737
|
const listedRoom = discovered.get(locator);
|
|
745
738
|
if (roomsListed &&
|
|
746
739
|
(listedRoom === undefined ||
|
|
@@ -770,80 +763,47 @@ export function composeRuntime(options) {
|
|
|
770
763
|
? 'connected'
|
|
771
764
|
: 'connecting';
|
|
772
765
|
}
|
|
773
|
-
function
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
rule: held.code,
|
|
785
|
-
location: typeof location === 'string' ? location : null,
|
|
786
|
-
},
|
|
787
|
-
];
|
|
788
|
-
});
|
|
766
|
+
function bytesOf(room, underway) {
|
|
767
|
+
const transfer = transfers.get(room);
|
|
768
|
+
if (transfer !== undefined) {
|
|
769
|
+
const owed = (unindexed.get(room) ?? []).reduce((sum, part) => sum + part.bytes, 0);
|
|
770
|
+
return {
|
|
771
|
+
done: transfer.done.bytes - Math.ceil(owed),
|
|
772
|
+
total: Math.max(arrivals.get(room) ?? 0, transfer.done.bytes),
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
const held = underway ? NOTHING_HELD : (replicas.get(room)?.record.inventory ?? NOTHING_HELD);
|
|
776
|
+
return { done: held.bytes, total: arrivals.get(room) ?? held.bytes };
|
|
789
777
|
}
|
|
790
778
|
function installationOf(coverage) {
|
|
791
779
|
const rooms = { done: 0, total: 0 };
|
|
792
|
-
const parts = { done: 0, total: 0 };
|
|
793
780
|
const bytes = { done: 0, total: 0 };
|
|
794
|
-
const received = { done: 0, total: 0 };
|
|
795
781
|
let installing = 0;
|
|
796
|
-
let
|
|
797
|
-
let listed = false;
|
|
798
|
-
let waiting = false;
|
|
782
|
+
let downloading = false;
|
|
799
783
|
for (const { room, state } of coverage) {
|
|
800
784
|
if (state === 'unadmitted')
|
|
801
785
|
continue;
|
|
802
786
|
rooms.total += 1;
|
|
803
787
|
if (state === 'complete')
|
|
804
788
|
rooms.done += 1;
|
|
805
|
-
const transfer = transfers.get(room);
|
|
806
|
-
const replica = replicas.get(room)?.record.inventory ?? null;
|
|
807
|
-
const inventory = transfer?.inventory ?? replica ?? installs.get(room) ?? null;
|
|
808
789
|
const underway = state === 'acquiring' || (installs.has(room) && state !== 'complete');
|
|
809
790
|
if (underway)
|
|
810
791
|
installing += 1;
|
|
811
|
-
if (underway &&
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
parts.total += inventory.parts;
|
|
818
|
-
bytes.total += inventory.bytes;
|
|
819
|
-
parts.done += done.parts;
|
|
820
|
-
bytes.done += done.bytes - Math.ceil(owed);
|
|
821
|
-
received.total += inventory.bytes;
|
|
822
|
-
received.done += done.bytes;
|
|
823
|
-
for (const held of [keptParts.get(room), arrivals.get(room)]) {
|
|
824
|
-
for (const arrived of held?.values() ?? [])
|
|
825
|
-
received.done += arrived;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
waiting ||= underway;
|
|
792
|
+
if (underway && (streaming.has(room) || !arrivals.has(room)))
|
|
793
|
+
downloading = true;
|
|
794
|
+
const now = bytesOf(room, underway);
|
|
795
|
+
const earlier = underway ? startedOver.get(room) : undefined;
|
|
796
|
+
bytes.done += Math.max(now.done, earlier?.done ?? 0);
|
|
797
|
+
bytes.total += Math.max(now.total, earlier?.total ?? 0);
|
|
829
798
|
}
|
|
830
799
|
const step = installing === 0
|
|
831
800
|
? null
|
|
832
|
-
:
|
|
833
|
-
? '
|
|
834
|
-
:
|
|
835
|
-
? '
|
|
836
|
-
:
|
|
837
|
-
|
|
838
|
-
: 'replaying';
|
|
839
|
-
return {
|
|
840
|
-
step,
|
|
841
|
-
paused: waiting && !onScreen ? 'hidden' : null,
|
|
842
|
-
rooms,
|
|
843
|
-
parts,
|
|
844
|
-
bytes,
|
|
845
|
-
received,
|
|
846
|
-
};
|
|
801
|
+
: downloading
|
|
802
|
+
? 'downloading'
|
|
803
|
+
: bytes.done < bytes.total
|
|
804
|
+
? 'writing'
|
|
805
|
+
: 'replaying';
|
|
806
|
+
return { step, rooms, bytes };
|
|
847
807
|
}
|
|
848
808
|
function readinessFor(port, reach) {
|
|
849
809
|
const required = requiredRooms(reach);
|
|
@@ -867,7 +827,6 @@ export function composeRuntime(options) {
|
|
|
867
827
|
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
868
828
|
space: full ? 'full' : port.space,
|
|
869
829
|
installation: installationOf(coverage),
|
|
870
|
-
refused: refusedIn(required.rooms),
|
|
871
830
|
lost: storageLoss !== null,
|
|
872
831
|
established: targets[port.target]?.established === true ||
|
|
873
832
|
(usableWhenHeld.includes(port.target) && required.rooms.some((room) => reopened.has(room))),
|
|
@@ -1104,6 +1063,8 @@ export function composeRuntime(options) {
|
|
|
1104
1063
|
}
|
|
1105
1064
|
async function refuse(reason, provider = null) {
|
|
1106
1065
|
discoveryPaused = true;
|
|
1066
|
+
inFlight.abort();
|
|
1067
|
+
inFlight = new AbortController();
|
|
1107
1068
|
try {
|
|
1108
1069
|
if (reason === 'revoked-authorization') {
|
|
1109
1070
|
await exclusive(async () => {
|
|
@@ -1188,7 +1149,7 @@ export function composeRuntime(options) {
|
|
|
1188
1149
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
1189
1150
|
roomVersion: replicas.get(room.locator)?.record.roomVersion ?? null,
|
|
1190
1151
|
synchronizationAvailable,
|
|
1191
|
-
refusal: scopedRefusals.get(room.locator) ??
|
|
1152
|
+
refusal: scopedRefusals.get(room.locator) ?? null,
|
|
1192
1153
|
});
|
|
1193
1154
|
}
|
|
1194
1155
|
async function applyDiscovery(found, started, covered) {
|
|
@@ -1274,16 +1235,14 @@ export function composeRuntime(options) {
|
|
|
1274
1235
|
roomDrafts.delete(locator);
|
|
1275
1236
|
attachedFiles.delete(locator);
|
|
1276
1237
|
untransferred(locator);
|
|
1238
|
+
startedOver.delete(locator);
|
|
1277
1239
|
catching.delete(locator);
|
|
1278
1240
|
discovered.delete(locator);
|
|
1279
|
-
announced.delete(locator);
|
|
1280
1241
|
unreadable.delete(locator);
|
|
1281
1242
|
stale.delete(locator);
|
|
1282
1243
|
repairable.delete(locator);
|
|
1283
1244
|
unpreparable.delete(locator);
|
|
1284
1245
|
scopedRefusals.delete(locator);
|
|
1285
|
-
sourceRefusals.delete(locator);
|
|
1286
|
-
declarationRefusals.delete(locator);
|
|
1287
1246
|
models.delete(locator);
|
|
1288
1247
|
liveModels.delete(locator);
|
|
1289
1248
|
invalidModels.delete(locator);
|
|
@@ -1372,16 +1331,9 @@ export function composeRuntime(options) {
|
|
|
1372
1331
|
const queuedBatches = new Map(queues.map((queue) => [queue.locator, queue.pending.map(({ batch }) => batch)]));
|
|
1373
1332
|
targets = held.targets;
|
|
1374
1333
|
transfers.clear();
|
|
1375
|
-
for (const [locator, record] of held.transfers) {
|
|
1376
|
-
transfers.set(locator, {
|
|
1377
|
-
...record,
|
|
1378
|
-
models: { collection: retyped(record.models.collection) },
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
1334
|
arrivals.clear();
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
keptParts.set(locator, new Map(parts));
|
|
1335
|
+
streaming.clear();
|
|
1336
|
+
startedOver.clear();
|
|
1385
1337
|
limits = held.providerLimits;
|
|
1386
1338
|
capabilities = held.capabilities;
|
|
1387
1339
|
member = held.member;
|
|
@@ -1391,7 +1343,7 @@ export function composeRuntime(options) {
|
|
|
1391
1343
|
presentations.clear();
|
|
1392
1344
|
for (const record of held.presentation)
|
|
1393
1345
|
presentations.set(record.app, record.value);
|
|
1394
|
-
conversations.hydrate(heldConversations, chatDrafts);
|
|
1346
|
+
conversations.hydrate(heldConversations, chatDrafts, held.conversationList);
|
|
1395
1347
|
pendings.clear();
|
|
1396
1348
|
for (const queue of queues) {
|
|
1397
1349
|
for (const { batch, state } of queue.pending) {
|
|
@@ -1434,8 +1386,14 @@ export function composeRuntime(options) {
|
|
|
1434
1386
|
});
|
|
1435
1387
|
}
|
|
1436
1388
|
});
|
|
1437
|
-
|
|
1438
|
-
|
|
1389
|
+
try {
|
|
1390
|
+
for (const key of held.orphans)
|
|
1391
|
+
await opened.release(key);
|
|
1392
|
+
}
|
|
1393
|
+
catch (error) {
|
|
1394
|
+
if (!refusedForSpace(error))
|
|
1395
|
+
throw error;
|
|
1396
|
+
}
|
|
1439
1397
|
if (held.erasing || held.orphans.length > 0)
|
|
1440
1398
|
queueMicrotask(sweep);
|
|
1441
1399
|
return {
|
|
@@ -1637,8 +1595,6 @@ export function composeRuntime(options) {
|
|
|
1637
1595
|
function unpairedIn(locator, batch) {
|
|
1638
1596
|
if (!queueIn(locator).some((held) => held.id === batch.id))
|
|
1639
1597
|
return false;
|
|
1640
|
-
if (pendings.get(pendingKey(locator, batch.id))?.accepted !== undefined)
|
|
1641
|
-
return false;
|
|
1642
1598
|
return !leavingIn(batch, locator) || withdrawable(locator)(batch);
|
|
1643
1599
|
}
|
|
1644
1600
|
async function together(local, settlements) {
|
|
@@ -1807,28 +1763,34 @@ export function composeRuntime(options) {
|
|
|
1807
1763
|
models.set(locator, state);
|
|
1808
1764
|
return { collection: state };
|
|
1809
1765
|
}
|
|
1810
|
-
function publish(local, room,
|
|
1766
|
+
function publish(local, room, reached, landing, live) {
|
|
1811
1767
|
const landed = exclusive(async () => {
|
|
1812
1768
|
if (!live()) {
|
|
1813
1769
|
caughtUpTo(room.locator);
|
|
1814
1770
|
return true;
|
|
1815
1771
|
}
|
|
1816
1772
|
const prior = replicas.get(room.locator);
|
|
1817
|
-
if (landing.
|
|
1818
|
-
(
|
|
1819
|
-
|
|
1773
|
+
if (!stands(room.locator, landing.from)) {
|
|
1774
|
+
if (landing.kind === 'refreshed') {
|
|
1775
|
+
await local.unrefresh(reached.key);
|
|
1776
|
+
return false;
|
|
1777
|
+
}
|
|
1778
|
+
caughtUpTo(room.locator);
|
|
1779
|
+
await abandoned(local, room.locator);
|
|
1820
1780
|
return false;
|
|
1821
1781
|
}
|
|
1782
|
+
const record = landing.kind === 'refreshed' && prior !== undefined
|
|
1783
|
+
? {
|
|
1784
|
+
...prior.record,
|
|
1785
|
+
roomVersion: reached.roomVersion,
|
|
1786
|
+
...(landing.remodelled ? { model: reached.model } : {}),
|
|
1787
|
+
}
|
|
1788
|
+
: reached;
|
|
1822
1789
|
adoptModels(room.locator, record);
|
|
1823
1790
|
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
1824
|
-
const roomVersion = record.roomVersion;
|
|
1825
|
-
const accepted = queue.filter((batch) => {
|
|
1826
|
-
const at = pendings.get(pendingKey(room.locator, batch.id))?.accepted;
|
|
1827
|
-
return at !== undefined && options.adapter.reached(room.locator, roomVersion, at);
|
|
1828
|
-
});
|
|
1829
1791
|
let set;
|
|
1830
1792
|
try {
|
|
1831
|
-
set = await settle(local, room.locator, prior, record, queue
|
|
1793
|
+
set = await settle(local, room.locator, prior, record, queue, landing.kind === 'refreshed'
|
|
1832
1794
|
? {
|
|
1833
1795
|
room: true,
|
|
1834
1796
|
view: 'refreshed',
|
|
@@ -1851,23 +1813,21 @@ export function composeRuntime(options) {
|
|
|
1851
1813
|
}
|
|
1852
1814
|
if (landing.kind === 'bootstrapped') {
|
|
1853
1815
|
untransferred(room.locator);
|
|
1816
|
+
startedOver.delete(room.locator);
|
|
1854
1817
|
catching.delete(room.locator);
|
|
1855
|
-
|
|
1818
|
+
if (prior !== undefined && prior.record.key !== record.key)
|
|
1819
|
+
sweep();
|
|
1820
|
+
name(room.locator);
|
|
1856
1821
|
}
|
|
1857
|
-
for (const batch of accepted)
|
|
1858
|
-
pendings.delete(pendingKey(room.locator, batch.id));
|
|
1859
1822
|
stale.delete(room.locator);
|
|
1860
1823
|
unpreparable.delete(room.locator);
|
|
1861
1824
|
if (unreadable.delete(room.locator))
|
|
1862
1825
|
scopedRefusals.delete(room.locator);
|
|
1863
|
-
sourceRefusals.delete(room.locator);
|
|
1864
|
-
declarationRefusals.delete(room.locator);
|
|
1865
1826
|
list(room, true);
|
|
1866
1827
|
publishChangeSet(set);
|
|
1867
1828
|
await unpair(local);
|
|
1868
1829
|
moveTo({ ...status, ...attention() });
|
|
1869
|
-
|
|
1870
|
-
if (accepted.length > 0 && (replicas.get(room.locator)?.queue.length ?? 0) > 0) {
|
|
1830
|
+
if (landing.kind === 'bootstrapped' && (replicas.get(room.locator)?.queue.length ?? 0) > 0) {
|
|
1871
1831
|
submitLater(room.locator);
|
|
1872
1832
|
}
|
|
1873
1833
|
return true;
|
|
@@ -1897,12 +1857,7 @@ export function composeRuntime(options) {
|
|
|
1897
1857
|
return committed(locator, target, saved, new Set(saved.map(({ path }) => path)), writtenBy);
|
|
1898
1858
|
}
|
|
1899
1859
|
async function saveParts(locator, named, writtenBy) {
|
|
1900
|
-
const read = named
|
|
1901
|
-
if (read.some(({ path }, at) => path === '' ||
|
|
1902
|
-
normalize(path) !== path ||
|
|
1903
|
-
read.findIndex((one) => one.path === path) !== at)) {
|
|
1904
|
-
throw runtimeError('room-draft-malformed');
|
|
1905
|
-
}
|
|
1860
|
+
const read = readSavedParts(named);
|
|
1906
1861
|
const target = await savedRoom(locator);
|
|
1907
1862
|
const saved = [];
|
|
1908
1863
|
const files = [];
|
|
@@ -1911,7 +1866,7 @@ export function composeRuntime(options) {
|
|
|
1911
1866
|
for (const { path, parts } of read) {
|
|
1912
1867
|
const file = parts.file === undefined
|
|
1913
1868
|
? undefined
|
|
1914
|
-
: await heldFile(parts.file.blob, binaryBase(visible, path));
|
|
1869
|
+
: await heldFile(parts.file.blob, parts.file.base === undefined ? binaryBase(visible, path) : parts.file.base);
|
|
1915
1870
|
if (parts.file !== undefined && file !== undefined) {
|
|
1916
1871
|
files.push({ key: file.localId, blob: parts.file.blob });
|
|
1917
1872
|
}
|
|
@@ -1976,18 +1931,6 @@ export function composeRuntime(options) {
|
|
|
1976
1931
|
return undefined;
|
|
1977
1932
|
return entry.binary !== null ? null : entry.content;
|
|
1978
1933
|
}
|
|
1979
|
-
function walkOf(local, key, view) {
|
|
1980
|
-
return async (after) => {
|
|
1981
|
-
const page = await local.walk(key, view, after, WALK_BYTES);
|
|
1982
|
-
return {
|
|
1983
|
-
entries: page.entries.map(({ path, content, binary }) => ({
|
|
1984
|
-
path,
|
|
1985
|
-
content: binary !== null ? null : content,
|
|
1986
|
-
})),
|
|
1987
|
-
next: page.next,
|
|
1988
|
-
};
|
|
1989
|
-
};
|
|
1990
|
-
}
|
|
1991
1934
|
function lookupOf(local, key, view) {
|
|
1992
1935
|
return async (paths) => {
|
|
1993
1936
|
if (paths.length === 0)
|
|
@@ -2004,37 +1947,6 @@ export function composeRuntime(options) {
|
|
|
2004
1947
|
const read = parseFrontmatter(entry.content ?? '');
|
|
2005
1948
|
return { type: read.ok ? (read.frontmatter.type ?? 'any') : undefined, binary: false };
|
|
2006
1949
|
}
|
|
2007
|
-
function overriddenWalk(walk, overrides) {
|
|
2008
|
-
const sorted = [...overrides.keys()].sort();
|
|
2009
|
-
const beyond = (path) => {
|
|
2010
|
-
if (path === null)
|
|
2011
|
-
return 0;
|
|
2012
|
-
let low = 0;
|
|
2013
|
-
let high = sorted.length;
|
|
2014
|
-
while (low < high) {
|
|
2015
|
-
const middle = (low + high) >>> 1;
|
|
2016
|
-
if (sorted[middle] <= path)
|
|
2017
|
-
low = middle + 1;
|
|
2018
|
-
else
|
|
2019
|
-
high = middle;
|
|
2020
|
-
}
|
|
2021
|
-
return low;
|
|
2022
|
-
};
|
|
2023
|
-
return async (after) => {
|
|
2024
|
-
const page = await walk(after);
|
|
2025
|
-
const end = page.next;
|
|
2026
|
-
const added = sorted
|
|
2027
|
-
.slice(beyond(after), end === null ? sorted.length : beyond(end))
|
|
2028
|
-
.flatMap((path) => {
|
|
2029
|
-
const entry = overrides.get(path);
|
|
2030
|
-
return entry === null || entry === undefined
|
|
2031
|
-
? []
|
|
2032
|
-
: [{ path, content: entry.binary !== null ? null : entry.content }];
|
|
2033
|
-
});
|
|
2034
|
-
const entries = [...page.entries.filter(({ path }) => !overrides.has(path)), ...added].sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
2035
|
-
return { entries, next: end };
|
|
2036
|
-
};
|
|
2037
|
-
}
|
|
2038
1950
|
function overriddenLookup(lookup, overrides) {
|
|
2039
1951
|
return async (paths) => {
|
|
2040
1952
|
const known = new Map(await lookup(paths.filter((path) => !overrides.has(path))));
|
|
@@ -2052,9 +1964,8 @@ export function composeRuntime(options) {
|
|
|
2052
1964
|
}
|
|
2053
1965
|
function validatorOf(record, confirmedLayer) {
|
|
2054
1966
|
return async (resulting, touched) => {
|
|
2055
|
-
const composition = compositionFor(record);
|
|
2056
1967
|
const local = await openLocal();
|
|
2057
|
-
const
|
|
1968
|
+
const changes = [];
|
|
2058
1969
|
const overrides = new Map();
|
|
2059
1970
|
const pace = paced();
|
|
2060
1971
|
for (const path of new Set([...confirmedLayer.held, ...touched])) {
|
|
@@ -2065,19 +1976,13 @@ export function composeRuntime(options) {
|
|
|
2065
1976
|
continue;
|
|
2066
1977
|
overrides.set(path, now);
|
|
2067
1978
|
if (textOf(was) !== textOf(now))
|
|
2068
|
-
|
|
1979
|
+
changes.push({ path, was: textOf(was), now: textOf(now) });
|
|
2069
1980
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
texts: textsOf(local, record.key, 'confirmed'),
|
|
2076
|
-
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
2077
|
-
}));
|
|
2078
|
-
if (clean)
|
|
2079
|
-
return { verdict: 'accept' };
|
|
2080
|
-
return validatedCollection(overriddenWalk(walkOf(local, record.key, 'confirmed'), overrides), lookup, composition);
|
|
1981
|
+
return validatedNeighbourhood(changes, compositionFor(record), {
|
|
1982
|
+
lookup: overriddenLookup(lookupOf(local, record.key, 'confirmed'), overrides),
|
|
1983
|
+
texts: textsOf(local, record.key, 'confirmed'),
|
|
1984
|
+
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
1985
|
+
});
|
|
2081
1986
|
};
|
|
2082
1987
|
}
|
|
2083
1988
|
function textsOf(local, key, view) {
|
|
@@ -2154,7 +2059,7 @@ export function composeRuntime(options) {
|
|
|
2154
2059
|
}
|
|
2155
2060
|
const moves = saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
|
|
2156
2061
|
const sourceSaving = await savingIn(replica, moves.map(({ path }) => ({ kind: 'remove', path })), saved.map(({ path }) => path), moves.map(({ path }) => path));
|
|
2157
|
-
const changed = saved.flatMap(({ path, parts }) => changedParts(path,
|
|
2062
|
+
const changed = saved.flatMap(({ path, parts }) => changedParts(path, parts, sourceSaving.visible));
|
|
2158
2063
|
if (changed.length > 0)
|
|
2159
2064
|
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
2160
2065
|
const target = replicas.get(destination);
|
|
@@ -2279,7 +2184,7 @@ export function composeRuntime(options) {
|
|
|
2279
2184
|
...saved.map(({ path }) => path),
|
|
2280
2185
|
...parts.flatMap(({ bases }) => bases.map(({ path }) => path)),
|
|
2281
2186
|
]);
|
|
2282
|
-
const changed = saved.flatMap(({ path, parts: held }) => changedParts(path,
|
|
2187
|
+
const changed = saved.flatMap(({ path, parts: held }) => changedParts(path, held, saving.visible));
|
|
2283
2188
|
if (changed.length > 0)
|
|
2284
2189
|
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
2285
2190
|
const requeue = await stepped(requeuing(replica.queue, changes, saving.visible, withdrawable(locator), saving.visibleOver));
|
|
@@ -2852,115 +2757,16 @@ export function composeRuntime(options) {
|
|
|
2852
2757
|
options.adapter.reached(locator, written, roomVersion) &&
|
|
2853
2758
|
!options.adapter.reached(locator, roomVersion, written));
|
|
2854
2759
|
}
|
|
2855
|
-
function overriddenTexts(texts, overrides) {
|
|
2856
|
-
return async (paths) => {
|
|
2857
|
-
const known = new Map(await texts(paths.filter((path) => !overrides.has(path))));
|
|
2858
|
-
for (const path of paths) {
|
|
2859
|
-
const entry = overrides.get(path);
|
|
2860
|
-
if (entry === undefined)
|
|
2861
|
-
continue;
|
|
2862
|
-
if (entry === null)
|
|
2863
|
-
known.delete(path);
|
|
2864
|
-
else
|
|
2865
|
-
known.set(path, entry.binary !== null ? null : entry.content);
|
|
2866
|
-
}
|
|
2867
|
-
return known;
|
|
2868
|
-
};
|
|
2869
|
-
}
|
|
2870
|
-
async function unreadableRoom(texts, walk, paths) {
|
|
2871
|
-
if (paths !== null) {
|
|
2872
|
-
const text = {};
|
|
2873
|
-
for (const [path, content] of await texts(paths))
|
|
2874
|
-
if (content !== null)
|
|
2875
|
-
text[path] = content;
|
|
2876
|
-
return stepped(unreadableIn(text, Object.keys(text)));
|
|
2877
|
-
}
|
|
2878
|
-
for (let after = null;;) {
|
|
2879
|
-
const page = await walk(after);
|
|
2880
|
-
const text = {};
|
|
2881
|
-
for (const { path, content } of page.entries)
|
|
2882
|
-
if (content !== null)
|
|
2883
|
-
text[path] = content;
|
|
2884
|
-
const refused = await stepped(unreadableIn(text, Object.keys(text)));
|
|
2885
|
-
if (refused !== null)
|
|
2886
|
-
return refused;
|
|
2887
|
-
if (page.next === null)
|
|
2888
|
-
return null;
|
|
2889
|
-
after = page.next;
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
async function validatedRoom(local, record, view, previous, overrides = null) {
|
|
2893
|
-
const composition = composed(record);
|
|
2894
|
-
const modeled = record.model.status !== 'unavailable' || record.typedSelection !== null;
|
|
2895
|
-
const lookup = overrides === null
|
|
2896
|
-
? lookupOf(local, record.key, view)
|
|
2897
|
-
: overriddenLookup(lookupOf(local, record.key, view), overrides);
|
|
2898
|
-
const texts = overrides === null
|
|
2899
|
-
? textsOf(local, record.key, view)
|
|
2900
|
-
: overriddenTexts(textsOf(local, record.key, view), overrides);
|
|
2901
|
-
const walk = overrides === null
|
|
2902
|
-
? walkOf(local, record.key, view)
|
|
2903
|
-
: overriddenWalk(walkOf(local, record.key, view), overrides);
|
|
2904
|
-
let changes = null;
|
|
2905
|
-
let reshaped = false;
|
|
2906
|
-
if (previous !== null && previous.changed.length <= CLEAN_CHANGES) {
|
|
2907
|
-
const [was, now] = await Promise.all([
|
|
2908
|
-
local.entries(record.key, 'confirmed', previous.changed),
|
|
2909
|
-
overrides === null
|
|
2910
|
-
? local.entries(record.key, view, previous.changed)
|
|
2911
|
-
: Promise.resolve([]),
|
|
2912
|
-
]);
|
|
2913
|
-
const before = new Map(was.map((entry) => [entry.path, entry]));
|
|
2914
|
-
const after = overrides === null
|
|
2915
|
-
? new Map(now.map((entry) => [entry.path, entry]))
|
|
2916
|
-
: overrides;
|
|
2917
|
-
changes = previous.changed.flatMap((path) => {
|
|
2918
|
-
const one = before.get(path) ?? null;
|
|
2919
|
-
const other = after.get(path) ?? null;
|
|
2920
|
-
reshaped ||= textDiffers(one, other);
|
|
2921
|
-
const held = textOf(one);
|
|
2922
|
-
const next = textOf(other);
|
|
2923
|
-
return held === next ? [] : [{ path, was: held, now: next }];
|
|
2924
|
-
});
|
|
2925
|
-
}
|
|
2926
|
-
const named = changes === null ? null : changes.map(({ path }) => path);
|
|
2927
|
-
const textChanged = changes === null || changes.length > 0 || reshaped;
|
|
2928
|
-
const modelsChanged = previous === null || !sameComposition(composed(previous.record), composition);
|
|
2929
|
-
if (modeled && (textChanged || modelsChanged)) {
|
|
2930
|
-
const clean = previous !== null &&
|
|
2931
|
-
changes !== null &&
|
|
2932
|
-
(await changedCleanly(changes, composed(previous.record), composition, {
|
|
2933
|
-
lookup,
|
|
2934
|
-
texts,
|
|
2935
|
-
referrers: referrersOf(local, record.key, 'confirmed'),
|
|
2936
|
-
}));
|
|
2937
|
-
if (clean)
|
|
2938
|
-
return null;
|
|
2939
|
-
const verdict = await validatedCollection(walk, lookup, composition);
|
|
2940
|
-
if (verdict.verdict === 'accept')
|
|
2941
|
-
return null;
|
|
2942
|
-
if (verdict.rule !== 'model-unavailable')
|
|
2943
|
-
return { kind: 'refused', refusal: verdict };
|
|
2944
|
-
return unreadableRoom(texts, walk, named);
|
|
2945
|
-
}
|
|
2946
|
-
if (textChanged)
|
|
2947
|
-
return unreadableRoom(texts, walk, named);
|
|
2948
|
-
return null;
|
|
2949
|
-
}
|
|
2950
2760
|
function refreshingOf(local, key, composition, liveness, replaying = null) {
|
|
2951
|
-
const write = replaying === null ? exclusive :
|
|
2761
|
+
const write = replaying === null ? exclusive : finishingWrite;
|
|
2952
2762
|
return {
|
|
2953
|
-
|
|
2954
|
-
if (replaying
|
|
2955
|
-
|
|
2956
|
-
installing(replaying);
|
|
2957
|
-
await screened();
|
|
2763
|
+
turn() {
|
|
2764
|
+
if (replaying !== null)
|
|
2765
|
+
installing(replaying);
|
|
2958
2766
|
},
|
|
2959
2767
|
prior: async (paths) => new Map((await local.entries(key, 'refreshed', paths)).map((entry) => [entry.path, entry])),
|
|
2960
2768
|
async stage(page) {
|
|
2961
2769
|
for (let written = 0;;) {
|
|
2962
|
-
if (replaying !== null)
|
|
2963
|
-
await screened();
|
|
2964
2770
|
const end = batchEnd(page.entries, written);
|
|
2965
2771
|
const last = end === page.entries.length;
|
|
2966
2772
|
const entries = page.entries.slice(written, end);
|
|
@@ -3005,25 +2811,74 @@ export function composeRuntime(options) {
|
|
|
3005
2811
|
return done;
|
|
3006
2812
|
}
|
|
3007
2813
|
function installWrite(work) {
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
2814
|
+
return installTurn(installTurns.starting, work);
|
|
2815
|
+
}
|
|
2816
|
+
function finishingWrite(work) {
|
|
2817
|
+
return installTurn(installTurns.finishing, work);
|
|
2818
|
+
}
|
|
2819
|
+
function installTurn(queue, work) {
|
|
2820
|
+
return new Promise((resolve, reject) => {
|
|
2821
|
+
queue.push(() => void exclusive(work)
|
|
2822
|
+
.then(resolve, reject)
|
|
2823
|
+
.finally(() => {
|
|
2824
|
+
installTurns.busy = false;
|
|
2825
|
+
nextInstallTurn();
|
|
2826
|
+
}));
|
|
2827
|
+
nextInstallTurn();
|
|
2828
|
+
});
|
|
3017
2829
|
}
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
2830
|
+
function nextInstallTurn() {
|
|
2831
|
+
if (installTurns.busy || installTurns.ahead > 0)
|
|
2832
|
+
return;
|
|
2833
|
+
const turn = installTurns.finishing.shift() ?? installTurns.starting.shift();
|
|
2834
|
+
if (turn === undefined)
|
|
2835
|
+
return;
|
|
2836
|
+
installTurns.busy = true;
|
|
2837
|
+
turn();
|
|
2838
|
+
}
|
|
2839
|
+
async function ahead(work) {
|
|
2840
|
+
let taken = false;
|
|
2841
|
+
const hold = {
|
|
2842
|
+
take() {
|
|
2843
|
+
if (taken)
|
|
2844
|
+
return;
|
|
2845
|
+
taken = true;
|
|
2846
|
+
installTurns.ahead += 1;
|
|
2847
|
+
},
|
|
2848
|
+
release() {
|
|
2849
|
+
if (!taken)
|
|
2850
|
+
return;
|
|
2851
|
+
taken = false;
|
|
2852
|
+
installTurns.ahead -= 1;
|
|
2853
|
+
resumeInstall();
|
|
2854
|
+
},
|
|
2855
|
+
};
|
|
2856
|
+
hold.take();
|
|
2857
|
+
try {
|
|
2858
|
+
return await work(hold);
|
|
3021
2859
|
}
|
|
3022
|
-
|
|
2860
|
+
finally {
|
|
2861
|
+
hold.release();
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
function resumeInstall() {
|
|
2865
|
+
if (installTurns.ahead > 0)
|
|
2866
|
+
return;
|
|
2867
|
+
void yielded().then(nextInstallTurn);
|
|
2868
|
+
}
|
|
2869
|
+
async function heldStream() {
|
|
2870
|
+
while (streamsFree === 0)
|
|
2871
|
+
await new Promise((admit) => installWaiters.push(admit));
|
|
2872
|
+
streamsFree -= 1;
|
|
3023
2873
|
return once(() => {
|
|
3024
|
-
|
|
2874
|
+
streamsFree += 1;
|
|
3025
2875
|
});
|
|
3026
2876
|
}
|
|
2877
|
+
async function unqueued() {
|
|
2878
|
+
while (queuedText >= QUEUED_TEXT) {
|
|
2879
|
+
await new Promise((admit) => installWaiters.push(admit));
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
3027
2882
|
function once(release) {
|
|
3028
2883
|
let released = false;
|
|
3029
2884
|
return () => {
|
|
@@ -3031,20 +2886,29 @@ export function composeRuntime(options) {
|
|
|
3031
2886
|
return;
|
|
3032
2887
|
released = true;
|
|
3033
2888
|
release();
|
|
3034
|
-
for (const admit of
|
|
2889
|
+
for (const admit of installWaiters.splice(0))
|
|
3035
2890
|
admit();
|
|
3036
2891
|
};
|
|
3037
2892
|
}
|
|
3038
|
-
function installing(locator
|
|
3039
|
-
installs.
|
|
2893
|
+
function installing(locator) {
|
|
2894
|
+
installs.add(locator);
|
|
3040
2895
|
queueReadiness();
|
|
3041
2896
|
}
|
|
3042
2897
|
function untransferred(locator) {
|
|
3043
2898
|
transfers.delete(locator);
|
|
3044
|
-
|
|
2899
|
+
arrivals.delete(locator);
|
|
2900
|
+
streaming.delete(locator);
|
|
3045
2901
|
underived.delete(locator);
|
|
3046
2902
|
unindexed.delete(locator);
|
|
3047
2903
|
}
|
|
2904
|
+
function setBack(locator) {
|
|
2905
|
+
const now = bytesOf(locator, true);
|
|
2906
|
+
const earlier = startedOver.get(locator);
|
|
2907
|
+
startedOver.set(locator, {
|
|
2908
|
+
done: Math.max(now.done, earlier?.done ?? 0),
|
|
2909
|
+
total: Math.max(now.total, earlier?.total ?? 0),
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
3048
2912
|
function owedIndexes(locator, bytes, text) {
|
|
3049
2913
|
if (bytes <= 0 || text <= 0)
|
|
3050
2914
|
return;
|
|
@@ -3074,41 +2938,34 @@ export function composeRuntime(options) {
|
|
|
3074
2938
|
if (installs.delete(locator))
|
|
3075
2939
|
queueReadiness();
|
|
3076
2940
|
}
|
|
3077
|
-
function
|
|
3078
|
-
if (value === onScreen)
|
|
3079
|
-
return;
|
|
3080
|
-
onScreen = value;
|
|
3081
|
-
journal.record(`installation ${value ? 'shown' : 'hidden'}`);
|
|
3082
|
-
queueReadiness();
|
|
3083
|
-
if (!value)
|
|
3084
|
-
return;
|
|
3085
|
-
for (const resolve of shownWaiters.splice(0))
|
|
3086
|
-
resolve();
|
|
3087
|
-
}
|
|
3088
|
-
async function pullRefresh(authorized, held, liveness) {
|
|
2941
|
+
async function pullRefresh(authorized, held, liveness, hold, refresh) {
|
|
3089
2942
|
if (!liveness())
|
|
3090
2943
|
return unconfirmed;
|
|
3091
|
-
const turn = generation;
|
|
3092
2944
|
const { record } = held;
|
|
3093
|
-
const
|
|
2945
|
+
const from = standingOf(record.locator);
|
|
3094
2946
|
const local = await openLocal();
|
|
3095
2947
|
try {
|
|
3096
2948
|
await exclusive(() => local.unrefresh(record.key));
|
|
3097
|
-
const result = await advancement(
|
|
2949
|
+
const result = await advancement(async (authorization, request) => {
|
|
2950
|
+
hold.release();
|
|
2951
|
+
try {
|
|
2952
|
+
return await refresh(authorization, request);
|
|
2953
|
+
}
|
|
2954
|
+
finally {
|
|
2955
|
+
hold.take();
|
|
2956
|
+
}
|
|
2957
|
+
}, authorized, record, (models, kept) => (liveness() ? remodel(record.locator, models, kept) : kept), liveness, refreshingOf(local, record.key, composed(record), liveness));
|
|
3098
2958
|
if (result.kind !== 'advanced') {
|
|
3099
2959
|
journal.record(`transfer refresh ${record.locator} ${result.kind}`);
|
|
3100
2960
|
return result;
|
|
3101
2961
|
}
|
|
3102
|
-
const refused = await validatedRoom(local, result.record, 'refreshed', {
|
|
3103
|
-
record,
|
|
3104
|
-
changed: [...result.changed],
|
|
3105
|
-
});
|
|
3106
|
-
if (refused !== null) {
|
|
3107
|
-
journal.record(`transfer refresh ${record.locator} refused`);
|
|
3108
|
-
return refused;
|
|
3109
|
-
}
|
|
3110
2962
|
journal.record(`transfer refresh ${record.locator} ${result.record.roomVersion}`);
|
|
3111
|
-
return {
|
|
2963
|
+
return {
|
|
2964
|
+
kind: 'advanced',
|
|
2965
|
+
record: result.record,
|
|
2966
|
+
from,
|
|
2967
|
+
remodelled: result.record.model !== record.model,
|
|
2968
|
+
};
|
|
3112
2969
|
}
|
|
3113
2970
|
catch (error) {
|
|
3114
2971
|
if (refusedForSpace(error))
|
|
@@ -3116,9 +2973,6 @@ export function composeRuntime(options) {
|
|
|
3116
2973
|
journal.record(`transfer refresh ${record.locator} transport-uncertainty`);
|
|
3117
2974
|
throw error;
|
|
3118
2975
|
}
|
|
3119
|
-
finally {
|
|
3120
|
-
reads.finished(record.locator, turn);
|
|
3121
|
-
}
|
|
3122
2976
|
}
|
|
3123
2977
|
const heldContents = new Map();
|
|
3124
2978
|
function heldContentOf(local, key) {
|
|
@@ -3136,24 +2990,20 @@ export function composeRuntime(options) {
|
|
|
3136
2990
|
heldContents.set(key, made);
|
|
3137
2991
|
return made;
|
|
3138
2992
|
}
|
|
3139
|
-
async function
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
arrivals.get(locator)?.delete(token);
|
|
3146
|
-
}
|
|
3147
|
-
queueReadiness();
|
|
2993
|
+
async function abandoned(local, locator) {
|
|
2994
|
+
await local.abandon(locator);
|
|
2995
|
+
setBack(locator);
|
|
2996
|
+
untransferred(locator);
|
|
2997
|
+
liveQueries.recover();
|
|
2998
|
+
sweep();
|
|
3148
2999
|
}
|
|
3149
3000
|
function staging(local, liveness) {
|
|
3150
3001
|
const derived = async (locator, key, composition) => {
|
|
3151
3002
|
underived.delete(locator);
|
|
3152
3003
|
for (;;) {
|
|
3153
|
-
await screened();
|
|
3154
3004
|
if (!liveness())
|
|
3155
3005
|
return false;
|
|
3156
|
-
const done = await
|
|
3006
|
+
const done = await finishingWrite(async () => liveness()
|
|
3157
3007
|
? timed(deriveBudget, () => local.rederive(key, composition, deriveBudget.bytes, DERIVE_LONGEST_MS, DERIVE_FLOOR))
|
|
3158
3008
|
: null);
|
|
3159
3009
|
if (done === null)
|
|
@@ -3167,28 +3017,23 @@ export function composeRuntime(options) {
|
|
|
3167
3017
|
held: (locator) => transfers.get(locator) ?? null,
|
|
3168
3018
|
async turn(locator, step) {
|
|
3169
3019
|
installing(locator);
|
|
3170
|
-
|
|
3171
|
-
await screened();
|
|
3172
|
-
const release = step === 'downloading' ? await heldPart() : () => { };
|
|
3173
|
-
if (onScreen || closed)
|
|
3174
|
-
return release;
|
|
3175
|
-
release();
|
|
3176
|
-
}
|
|
3020
|
+
return step === 'downloading' ? heldStream() : () => { };
|
|
3177
3021
|
},
|
|
3178
|
-
|
|
3179
|
-
|
|
3022
|
+
unqueued,
|
|
3023
|
+
queued(text) {
|
|
3024
|
+
queuedText += text;
|
|
3180
3025
|
return once(() => {
|
|
3181
|
-
|
|
3026
|
+
queuedText -= text;
|
|
3182
3027
|
});
|
|
3183
3028
|
},
|
|
3184
3029
|
arriving(locator) {
|
|
3185
3030
|
let ended = false;
|
|
3186
3031
|
return {
|
|
3187
|
-
at(
|
|
3188
|
-
if (ended
|
|
3032
|
+
at(bytes) {
|
|
3033
|
+
if (ended)
|
|
3189
3034
|
return;
|
|
3190
|
-
|
|
3191
|
-
|
|
3035
|
+
arrivals.set(locator, bytes);
|
|
3036
|
+
streaming.add(locator);
|
|
3192
3037
|
const now = performance.now();
|
|
3193
3038
|
if (now - arrivalReported < ARRIVAL_REPORT_MS)
|
|
3194
3039
|
return;
|
|
@@ -3196,30 +3041,24 @@ export function composeRuntime(options) {
|
|
|
3196
3041
|
queueReadiness();
|
|
3197
3042
|
},
|
|
3198
3043
|
end() {
|
|
3044
|
+
if (ended)
|
|
3045
|
+
return;
|
|
3199
3046
|
ended = true;
|
|
3200
|
-
if (
|
|
3047
|
+
if (streaming.delete(locator))
|
|
3201
3048
|
queueReadiness();
|
|
3202
3049
|
},
|
|
3203
3050
|
};
|
|
3204
3051
|
},
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
? { local: earlier.local, parts: [...earlier.parts, ...later.parts] }
|
|
3208
|
-
: null, keptTogether),
|
|
3209
|
-
announced(locator) {
|
|
3210
|
-
const later = announced.get(locator);
|
|
3211
|
-
return later === undefined ? [] : [later];
|
|
3212
|
-
},
|
|
3213
|
-
async stage(record, page, composition, written) {
|
|
3052
|
+
streamed: options.adapter.roomStream !== undefined,
|
|
3053
|
+
async stage(record, page, composition) {
|
|
3214
3054
|
const { locator } = record;
|
|
3215
|
-
installing(locator
|
|
3216
|
-
await screened();
|
|
3055
|
+
installing(locator);
|
|
3217
3056
|
if (!liveness())
|
|
3218
3057
|
return null;
|
|
3219
3058
|
const key = await installWrite(async () => {
|
|
3220
3059
|
if (!liveness())
|
|
3221
3060
|
return null;
|
|
3222
|
-
return local.stage(record, composition, page
|
|
3061
|
+
return local.stage(record, composition, page);
|
|
3223
3062
|
});
|
|
3224
3063
|
if (key === null || !liveness())
|
|
3225
3064
|
return null;
|
|
@@ -3227,10 +3066,6 @@ export function composeRuntime(options) {
|
|
|
3227
3066
|
const prior = transfers.get(locator)?.done.bytes ?? 0;
|
|
3228
3067
|
const held = { ...record, key };
|
|
3229
3068
|
transfers.set(locator, held);
|
|
3230
|
-
if (written !== null) {
|
|
3231
|
-
keptParts.get(locator)?.delete(written);
|
|
3232
|
-
arrivals.get(locator)?.delete(written);
|
|
3233
|
-
}
|
|
3234
3069
|
if (arrived)
|
|
3235
3070
|
liveQueries.recover();
|
|
3236
3071
|
installing(locator);
|
|
@@ -3249,26 +3084,21 @@ export function composeRuntime(options) {
|
|
|
3249
3084
|
return refreshingOf(local, key, composition, liveness, locator);
|
|
3250
3085
|
},
|
|
3251
3086
|
abandon: (locator) => exclusive(async () => {
|
|
3252
|
-
if (
|
|
3253
|
-
|
|
3254
|
-
await local.abandon(locator);
|
|
3255
|
-
untransferred(locator);
|
|
3256
|
-
liveQueries.recover();
|
|
3257
|
-
sweep();
|
|
3087
|
+
if (liveness())
|
|
3088
|
+
await abandoned(local, locator);
|
|
3258
3089
|
}),
|
|
3259
|
-
setAside:
|
|
3090
|
+
setAside: async (locator) => {
|
|
3260
3091
|
if (!liveness())
|
|
3261
3092
|
return;
|
|
3262
|
-
|
|
3093
|
+
setBack(locator);
|
|
3263
3094
|
untransferred(locator);
|
|
3264
3095
|
liveQueries.recover();
|
|
3265
|
-
}
|
|
3096
|
+
},
|
|
3266
3097
|
release: (key) => exclusive(async () => {
|
|
3267
3098
|
await local.release(key);
|
|
3268
3099
|
sweep();
|
|
3269
3100
|
}),
|
|
3270
3101
|
content: (key) => heldContentOf(local, key),
|
|
3271
|
-
validate: (record, view) => validatedRoom(local, record, view, null),
|
|
3272
3102
|
};
|
|
3273
3103
|
}
|
|
3274
3104
|
function reusable(room, local) {
|
|
@@ -3280,7 +3110,6 @@ export function composeRuntime(options) {
|
|
|
3280
3110
|
async function pullBootstrap(authorized, room, local, liveness) {
|
|
3281
3111
|
if (!liveness())
|
|
3282
3112
|
return unconfirmed;
|
|
3283
|
-
const turn = generation;
|
|
3284
3113
|
if (replicas.has(room.locator)) {
|
|
3285
3114
|
catching.add(room.locator);
|
|
3286
3115
|
liveQueries.recover();
|
|
@@ -3303,9 +3132,6 @@ export function composeRuntime(options) {
|
|
|
3303
3132
|
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
3304
3133
|
throw error;
|
|
3305
3134
|
}
|
|
3306
|
-
finally {
|
|
3307
|
-
reads.finished(room.locator, turn);
|
|
3308
|
-
}
|
|
3309
3135
|
}
|
|
3310
3136
|
function caughtUpTo(locator) {
|
|
3311
3137
|
if (catching.delete(locator))
|
|
@@ -3313,72 +3139,82 @@ export function composeRuntime(options) {
|
|
|
3313
3139
|
}
|
|
3314
3140
|
function behind(room) {
|
|
3315
3141
|
const held = replicas.get(room.locator)?.record.roomVersion;
|
|
3316
|
-
return
|
|
3317
|
-
[announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later)));
|
|
3142
|
+
return held !== undefined && !options.adapter.reached(room.locator, held, room.roomVersion);
|
|
3318
3143
|
}
|
|
3319
|
-
|
|
3320
|
-
const
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
return
|
|
3144
|
+
function standingOf(locator) {
|
|
3145
|
+
const held = replicas.get(locator);
|
|
3146
|
+
return { key: held?.record.key ?? null, confirmed: held?.confirmed ?? 0 };
|
|
3147
|
+
}
|
|
3148
|
+
function stands(locator, from) {
|
|
3149
|
+
const now = standingOf(locator);
|
|
3150
|
+
return now.key === from.key && now.confirmed === from.confirmed;
|
|
3326
3151
|
}
|
|
3327
|
-
async function
|
|
3152
|
+
async function advance(room, local, authorized, running) {
|
|
3328
3153
|
const live = () => running() && !full;
|
|
3329
3154
|
if (!live())
|
|
3330
3155
|
return unconfirmed;
|
|
3331
3156
|
const held = replicas.get(room.locator);
|
|
3332
|
-
if (held !== undefined) {
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
emitRooms();
|
|
3344
|
-
}
|
|
3345
|
-
return null;
|
|
3346
|
-
}
|
|
3347
|
-
if (refreshed.kind === 'advanced') {
|
|
3348
|
-
const landing = { kind: 'refreshed', base: refreshed.base };
|
|
3349
|
-
if (!(await publish(local, room, refreshed.record, landing, live))) {
|
|
3350
|
-
const now = replicas.get(room.locator)?.record.roomVersion;
|
|
3351
|
-
const reached = now !== undefined &&
|
|
3352
|
-
options.adapter.reached(room.locator, now, refreshed.record.roomVersion);
|
|
3353
|
-
return reached ? null : advance(room, local, authorized, live);
|
|
3354
|
-
}
|
|
3355
|
-
await retainModel(local, room, modelOf(room.locator));
|
|
3356
|
-
return null;
|
|
3357
|
-
}
|
|
3358
|
-
if (refreshed.kind !== 'beyond-horizon')
|
|
3157
|
+
if (held !== undefined && options.adapter.roomStream !== undefined) {
|
|
3158
|
+
stale.delete(room.locator);
|
|
3159
|
+
name(room.locator);
|
|
3160
|
+
return null;
|
|
3161
|
+
}
|
|
3162
|
+
const refresh = reads.refresh;
|
|
3163
|
+
if (held !== undefined && refresh !== undefined) {
|
|
3164
|
+
const refreshed = await ahead((hold) => refreshedRoom(room, local, authorized, held, live, hold, refresh));
|
|
3165
|
+
if (refreshed === 'behind')
|
|
3166
|
+
return advance(room, local, authorized, live);
|
|
3167
|
+
if (refreshed !== 'beyond-horizon')
|
|
3359
3168
|
return refreshed;
|
|
3360
|
-
if (
|
|
3361
|
-
owed.add(room.locator);
|
|
3362
|
-
liveQueries.recover();
|
|
3363
|
-
return unconfirmed;
|
|
3364
|
-
}
|
|
3365
|
-
if (reloadsDue.get(room.locator) === generation)
|
|
3169
|
+
if (reloadsDue.has(room.locator))
|
|
3366
3170
|
return unconfirmed;
|
|
3367
3171
|
}
|
|
3172
|
+
const from = standingOf(room.locator);
|
|
3368
3173
|
const bootstrapped = await pullBootstrap(authorized, room, local, live);
|
|
3369
|
-
if (bootstrapped.kind
|
|
3370
|
-
|
|
3174
|
+
if (bootstrapped.kind !== 'staged')
|
|
3175
|
+
return live() ? bootstrapped : unconfirmed;
|
|
3176
|
+
const landing = { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from };
|
|
3177
|
+
if (await publish(local, room, bootstrapped.record, landing, live)) {
|
|
3371
3178
|
return live() ? null : unconfirmed;
|
|
3372
3179
|
}
|
|
3373
|
-
return
|
|
3180
|
+
return reachedBy(room.locator, bootstrapped.record.roomVersion)
|
|
3181
|
+
? null
|
|
3182
|
+
: advance(room, local, authorized, live);
|
|
3183
|
+
}
|
|
3184
|
+
async function refreshedRoom(room, local, authorized, held, live, hold, refresh) {
|
|
3185
|
+
const refreshed = await pullRefresh(authorized, held, live, hold, refresh);
|
|
3186
|
+
if (!live())
|
|
3187
|
+
return unconfirmed;
|
|
3188
|
+
if (refreshed.kind === 'still-current') {
|
|
3189
|
+
stale.delete(room.locator);
|
|
3190
|
+
if (unreadable.delete(room.locator)) {
|
|
3191
|
+
scopedRefusals.delete(room.locator);
|
|
3192
|
+
list(room, true);
|
|
3193
|
+
emitRooms();
|
|
3194
|
+
}
|
|
3195
|
+
return null;
|
|
3196
|
+
}
|
|
3197
|
+
if (refreshed.kind === 'advanced') {
|
|
3198
|
+
const landing = {
|
|
3199
|
+
kind: 'refreshed',
|
|
3200
|
+
from: refreshed.from,
|
|
3201
|
+
remodelled: refreshed.remodelled,
|
|
3202
|
+
};
|
|
3203
|
+
if (!(await publish(local, room, refreshed.record, landing, live))) {
|
|
3204
|
+
const now = replicas.get(room.locator)?.record.roomVersion;
|
|
3205
|
+
const reached = now !== undefined &&
|
|
3206
|
+
options.adapter.reached(room.locator, now, refreshed.record.roomVersion);
|
|
3207
|
+
return reached ? null : 'behind';
|
|
3208
|
+
}
|
|
3209
|
+
await retainModel(local, room, modelOf(room.locator));
|
|
3210
|
+
return null;
|
|
3211
|
+
}
|
|
3212
|
+
return refreshed.kind === 'beyond-horizon' ? 'beyond-horizon' : refreshed;
|
|
3374
3213
|
}
|
|
3375
3214
|
async function withhold(withheld, room, local, progress) {
|
|
3376
3215
|
if (withheld.kind === 'unconfirmed')
|
|
3377
3216
|
return;
|
|
3378
|
-
|
|
3379
|
-
stale.delete(room.locator);
|
|
3380
|
-
else
|
|
3381
|
-
stale.add(room.locator);
|
|
3217
|
+
stale.add(room.locator);
|
|
3382
3218
|
if (withheld.kind === 'transport-uncertainty' || withheld.kind === 'retryable-refusal') {
|
|
3383
3219
|
const reset = withheld.kind === 'retryable-refusal' ? withheld.reset : undefined;
|
|
3384
3220
|
repairable.set(room.locator, reset);
|
|
@@ -3400,30 +3236,10 @@ export function composeRuntime(options) {
|
|
|
3400
3236
|
list(room, false);
|
|
3401
3237
|
emitRooms();
|
|
3402
3238
|
}
|
|
3403
|
-
else if (withheld.kind === 'refused') {
|
|
3404
|
-
refusedSource(room, withheld);
|
|
3405
|
-
}
|
|
3406
3239
|
else if (withheld.kind === 'revoked-authorization') {
|
|
3407
3240
|
await refuse('revoked-authorization');
|
|
3408
3241
|
}
|
|
3409
3242
|
}
|
|
3410
|
-
function refusedSource(room, { refusal }) {
|
|
3411
|
-
sourceRefusals.set(room.locator, {
|
|
3412
|
-
code: refusal.rule,
|
|
3413
|
-
retryable: false,
|
|
3414
|
-
fields: {
|
|
3415
|
-
file: refusal.file,
|
|
3416
|
-
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
3417
|
-
...(refusal.referrers === undefined ? {} : { referrers: refusal.referrers }),
|
|
3418
|
-
},
|
|
3419
|
-
});
|
|
3420
|
-
declarationRefusals.delete(room.locator);
|
|
3421
|
-
unreadable.add(room.locator);
|
|
3422
|
-
list(room, false);
|
|
3423
|
-
emitRooms();
|
|
3424
|
-
moveTo({ ...status, ...attention() });
|
|
3425
|
-
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
3426
|
-
}
|
|
3427
3243
|
function leaving(batch, result) {
|
|
3428
3244
|
if (result.kind === 'expired')
|
|
3429
3245
|
return { batch, returned: returnedBy('expired') };
|
|
@@ -3445,8 +3261,8 @@ export function composeRuntime(options) {
|
|
|
3445
3261
|
function settling(acceptance) {
|
|
3446
3262
|
return acceptance !== null && 'apply' in acceptance;
|
|
3447
3263
|
}
|
|
3448
|
-
function
|
|
3449
|
-
return settling(acceptance) ?
|
|
3264
|
+
function failureOf(acceptance) {
|
|
3265
|
+
return settling(acceptance) ? null : acceptance;
|
|
3450
3266
|
}
|
|
3451
3267
|
async function acceptance(room, local, submitted, accepted) {
|
|
3452
3268
|
if (!continuing())
|
|
@@ -3469,34 +3285,6 @@ export function composeRuntime(options) {
|
|
|
3469
3285
|
? accepted.roomVersion
|
|
3470
3286
|
: held,
|
|
3471
3287
|
};
|
|
3472
|
-
const overrides = new Map([
|
|
3473
|
-
...back.set.map((entry) => [entry.path, entry]),
|
|
3474
|
-
...back.removed.map((path) => [path, null]),
|
|
3475
|
-
]);
|
|
3476
|
-
const refused = await validatedRoom(local, record, 'confirmed', { record: current.record, changed: [...overrides.keys()] }, overrides);
|
|
3477
|
-
if (refused !== null) {
|
|
3478
|
-
const key = pendingKey(room.locator, submitted.id);
|
|
3479
|
-
return {
|
|
3480
|
-
locator: room.locator,
|
|
3481
|
-
left: [],
|
|
3482
|
-
commit: {
|
|
3483
|
-
rooms: [],
|
|
3484
|
-
writes: [
|
|
3485
|
-
{
|
|
3486
|
-
locator: room.locator,
|
|
3487
|
-
accepted: [{ id: submitted.id, roomVersion: accepted.roomVersion }],
|
|
3488
|
-
},
|
|
3489
|
-
],
|
|
3490
|
-
},
|
|
3491
|
-
refused,
|
|
3492
|
-
apply() {
|
|
3493
|
-
const state = pendings.get(key);
|
|
3494
|
-
if (state !== undefined && !fences.has(room.locator))
|
|
3495
|
-
pendings.set(key, { ...state, accepted: accepted.roomVersion });
|
|
3496
|
-
return changeSet(room.locator, { queue: true });
|
|
3497
|
-
},
|
|
3498
|
-
};
|
|
3499
|
-
}
|
|
3500
3288
|
changes = back;
|
|
3501
3289
|
}
|
|
3502
3290
|
const settled = await settlement(local, room.locator, current, record, current.queue.filter((batch) => batch.id !== submitted.id), { room: true, ...(changes === undefined ? {} : { changes }) });
|
|
@@ -3506,8 +3294,6 @@ export function composeRuntime(options) {
|
|
|
3506
3294
|
const set = settled.apply(answer);
|
|
3507
3295
|
pendings.delete(pendingKey(room.locator, submitted.id));
|
|
3508
3296
|
unreadable.delete(room.locator);
|
|
3509
|
-
sourceRefusals.delete(room.locator);
|
|
3510
|
-
declarationRefusals.delete(room.locator);
|
|
3511
3297
|
list(room, true);
|
|
3512
3298
|
return set;
|
|
3513
3299
|
},
|
|
@@ -3525,13 +3311,9 @@ export function composeRuntime(options) {
|
|
|
3525
3311
|
const failed = await exclusive(async () => {
|
|
3526
3312
|
const adopting = await acceptance(room, local, submitted, accepted);
|
|
3527
3313
|
await adoptedTogether(local, [adopting]);
|
|
3528
|
-
return
|
|
3314
|
+
return failureOf(adopting);
|
|
3529
3315
|
});
|
|
3530
|
-
|
|
3531
|
-
return true;
|
|
3532
|
-
if (failed.kind === 'refused' && continuing())
|
|
3533
|
-
refusedSource(room, failed);
|
|
3534
|
-
return false;
|
|
3316
|
+
return failed === null;
|
|
3535
3317
|
}
|
|
3536
3318
|
async function adoptedMove(room, partner, local, submitted, accepted) {
|
|
3537
3319
|
const other = discovered.get(partner);
|
|
@@ -3545,18 +3327,10 @@ export function composeRuntime(options) {
|
|
|
3545
3327
|
...accepted.destination,
|
|
3546
3328
|
});
|
|
3547
3329
|
await adoptedTogether(local, [left, arrived]);
|
|
3548
|
-
|
|
3549
|
-
if (other !== undefined && refusedArrival?.kind === 'refused' && continuing()) {
|
|
3550
|
-
refusedSource(other, refusedArrival);
|
|
3551
|
-
}
|
|
3552
|
-
return refusalOf(left);
|
|
3330
|
+
return failureOf(left);
|
|
3553
3331
|
});
|
|
3554
3332
|
submitLater(partner);
|
|
3555
|
-
|
|
3556
|
-
return true;
|
|
3557
|
-
if (failed.kind === 'refused' && continuing())
|
|
3558
|
-
refusedSource(room, failed);
|
|
3559
|
-
return false;
|
|
3333
|
+
return failed === null;
|
|
3560
3334
|
}
|
|
3561
3335
|
function queueIn(locator) {
|
|
3562
3336
|
return replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
@@ -3679,8 +3453,6 @@ export function composeRuntime(options) {
|
|
|
3679
3453
|
if (replica === undefined || head === undefined)
|
|
3680
3454
|
return 'done';
|
|
3681
3455
|
const state = pendings.get(pendingKey(room.locator, head.id));
|
|
3682
|
-
if (state?.accepted !== undefined)
|
|
3683
|
-
return 'done';
|
|
3684
3456
|
const partner = partnerOf(head, room.locator);
|
|
3685
3457
|
if (partner !== null && !leavingIn(head, room.locator)) {
|
|
3686
3458
|
submitLater(partner);
|
|
@@ -3794,12 +3566,12 @@ export function composeRuntime(options) {
|
|
|
3794
3566
|
}
|
|
3795
3567
|
if (result.kind === 'expired') {
|
|
3796
3568
|
await exclusive(() => mark(local, room.locator, head, { state: 'waiting' }, partner !== null && lost));
|
|
3797
|
-
const there = other === undefined ? null : await
|
|
3569
|
+
const there = other === undefined ? null : await advanced(other, local, authorized);
|
|
3798
3570
|
if (other !== undefined && there !== null) {
|
|
3799
3571
|
await withhold(there, other, local, progress);
|
|
3800
3572
|
return 'done';
|
|
3801
3573
|
}
|
|
3802
|
-
const withheld = await
|
|
3574
|
+
const withheld = await advanced(room, local, authorized);
|
|
3803
3575
|
if (withheld !== null) {
|
|
3804
3576
|
await withhold(withheld, room, local, progress);
|
|
3805
3577
|
return 'done';
|
|
@@ -3821,8 +3593,8 @@ export function composeRuntime(options) {
|
|
|
3821
3593
|
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
3822
3594
|
const left = leaving(head, result);
|
|
3823
3595
|
const refreshed = async () => ({
|
|
3824
|
-
there: other === undefined ? null : await
|
|
3825
|
-
here: await
|
|
3596
|
+
there: other === undefined ? null : await advanced(other, local, authorized),
|
|
3597
|
+
here: await advanced(room, local, authorized),
|
|
3826
3598
|
});
|
|
3827
3599
|
const judged = result.kind === 'conflicted' && other !== undefined ? await refreshed() : null;
|
|
3828
3600
|
if (judged !== null && (judged.there !== null || judged.here !== null)) {
|
|
@@ -3926,25 +3698,22 @@ export function composeRuntime(options) {
|
|
|
3926
3698
|
if (signal.aborted)
|
|
3927
3699
|
return;
|
|
3928
3700
|
const progress = { complete: true, reachable: true };
|
|
3929
|
-
await roomTask(room.locator,
|
|
3930
|
-
await Promise.race([
|
|
3931
|
-
reload(room, local, progress, () => live() && !signal.aborted),
|
|
3932
|
-
abandonedOn(signal),
|
|
3933
|
-
]);
|
|
3934
|
-
});
|
|
3701
|
+
await roomTask(room.locator, (live) => reload(room, local, progress, live));
|
|
3935
3702
|
if (signal.aborted)
|
|
3936
3703
|
return;
|
|
3937
3704
|
await rest(progress);
|
|
3938
3705
|
}
|
|
3939
3706
|
}
|
|
3940
3707
|
function awaitingAnswer(locator) {
|
|
3708
|
+
answering.get(locator)?.release();
|
|
3941
3709
|
let release = () => { };
|
|
3942
|
-
const
|
|
3710
|
+
const answered = new Promise((resolve) => {
|
|
3943
3711
|
release = resolve;
|
|
3944
3712
|
});
|
|
3945
|
-
|
|
3713
|
+
const awaited = { answered, release };
|
|
3714
|
+
answering.set(locator, awaited);
|
|
3946
3715
|
return () => {
|
|
3947
|
-
if (answering.get(locator)
|
|
3716
|
+
if (answering.get(locator) === awaited)
|
|
3948
3717
|
answering.delete(locator);
|
|
3949
3718
|
release();
|
|
3950
3719
|
};
|
|
@@ -3971,26 +3740,13 @@ export function composeRuntime(options) {
|
|
|
3971
3740
|
const held = replicas.get(room.locator);
|
|
3972
3741
|
let changed = null;
|
|
3973
3742
|
if (held !== undefined) {
|
|
3974
|
-
const
|
|
3743
|
+
const record = {
|
|
3975
3744
|
...held.record,
|
|
3976
3745
|
operations: room.operations,
|
|
3977
3746
|
replica: room.replica,
|
|
3978
3747
|
typedSelection: room.typedSelection ?? null,
|
|
3979
3748
|
model: next,
|
|
3980
3749
|
};
|
|
3981
|
-
let read = 'staged';
|
|
3982
|
-
if (!sameMembership(declared, held.record)) {
|
|
3983
|
-
const verdict = await validatedCollection(walkOf(local, held.record.key, 'confirmed'), lookupOf(local, held.record.key, 'confirmed'), composed(declared));
|
|
3984
|
-
read =
|
|
3985
|
-
verdict.verdict !== 'refuse'
|
|
3986
|
-
? 'staged'
|
|
3987
|
-
: verdict.rule === 'model-unavailable'
|
|
3988
|
-
? 'deferred'
|
|
3989
|
-
: { kind: 'refused', refusal: verdict };
|
|
3990
|
-
}
|
|
3991
|
-
const record = read === 'staged'
|
|
3992
|
-
? declared
|
|
3993
|
-
: { ...declared, typedSelection: held.record.typedSelection };
|
|
3994
3750
|
const confirmedLayer = await sliceAt(record, 'confirmed', held.queue, held.overlaid);
|
|
3995
3751
|
const replayedLayer = await replayed(record, confirmedLayer, held.queue);
|
|
3996
3752
|
const diff = await overlayDiff(record, held, replayedLayer.visible, replayedLayer.touched);
|
|
@@ -4012,16 +3768,6 @@ export function composeRuntime(options) {
|
|
|
4012
3768
|
documents: answer.changed[room.locator] ?? [],
|
|
4013
3769
|
room: true,
|
|
4014
3770
|
});
|
|
4015
|
-
if (read === 'staged' || read === 'deferred') {
|
|
4016
|
-
if (declarationRefusals.delete(room.locator)) {
|
|
4017
|
-
sourceRefusals.delete(room.locator);
|
|
4018
|
-
unreadable.delete(room.locator);
|
|
4019
|
-
}
|
|
4020
|
-
}
|
|
4021
|
-
else {
|
|
4022
|
-
refusedSource(room, read);
|
|
4023
|
-
declarationRefusals.add(room.locator);
|
|
4024
|
-
}
|
|
4025
3771
|
}
|
|
4026
3772
|
heldModel(room, next);
|
|
4027
3773
|
if (changed === null)
|
|
@@ -4066,6 +3812,17 @@ export function composeRuntime(options) {
|
|
|
4066
3812
|
emitRooms();
|
|
4067
3813
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
4068
3814
|
}
|
|
3815
|
+
function name(locator) {
|
|
3816
|
+
const stream = options.adapter.roomStream;
|
|
3817
|
+
const record = replicas.get(locator)?.record;
|
|
3818
|
+
const room = discovered.get(locator);
|
|
3819
|
+
if (stream === undefined || record === undefined || room === undefined)
|
|
3820
|
+
return;
|
|
3821
|
+
if (!socketOpen || !handed || record.replica.selection !== room.replica.selection)
|
|
3822
|
+
return;
|
|
3823
|
+
naming += 1;
|
|
3824
|
+
stream.hold([{ locator, roomVersion: record.roomVersion }]);
|
|
3825
|
+
}
|
|
4069
3826
|
function heldRooms(listed) {
|
|
4070
3827
|
return listed.flatMap((room) => {
|
|
4071
3828
|
const record = replicas.get(room.locator)?.record;
|
|
@@ -4083,14 +3840,20 @@ export function composeRuntime(options) {
|
|
|
4083
3840
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
4084
3841
|
});
|
|
4085
3842
|
if (attempt === 'opened') {
|
|
3843
|
+
naming += 1;
|
|
4086
3844
|
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
4087
3845
|
handed = true;
|
|
4088
3846
|
}
|
|
4089
3847
|
discovering?.release();
|
|
4090
|
-
const
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
3848
|
+
for (const room of ready) {
|
|
3849
|
+
if (attempt !== 'unstreamed' && replicas.has(room.locator)) {
|
|
3850
|
+
if (attempt === 'authorized')
|
|
3851
|
+
name(room.locator);
|
|
3852
|
+
continue;
|
|
3853
|
+
}
|
|
3854
|
+
void roomTask(room.locator, (live) => loadRoom(room, local, progress, live)).then(() => rest(progress));
|
|
3855
|
+
}
|
|
3856
|
+
const failed = prepared.find((room) => room.status === 'rejected');
|
|
4094
3857
|
if (failed !== undefined)
|
|
4095
3858
|
throw failed.reason;
|
|
4096
3859
|
}
|
|
@@ -4114,7 +3877,9 @@ export function composeRuntime(options) {
|
|
|
4114
3877
|
return room.replica.admission === 'admitted';
|
|
4115
3878
|
}
|
|
4116
3879
|
async function caughtUp(room, local, progress, live) {
|
|
4117
|
-
if (
|
|
3880
|
+
if (options.adapter.roomStream !== undefined || authorization === undefined)
|
|
3881
|
+
return;
|
|
3882
|
+
if (!live() || !behind(room))
|
|
4118
3883
|
return;
|
|
4119
3884
|
const withheld = await advance(room, local, authorization, live);
|
|
4120
3885
|
if (withheld !== null)
|
|
@@ -4127,6 +3892,8 @@ export function composeRuntime(options) {
|
|
|
4127
3892
|
async function loadRoom(room, local, progress, live) {
|
|
4128
3893
|
if (!live() || authorization === undefined)
|
|
4129
3894
|
return;
|
|
3895
|
+
if (replicas.has(room.locator) && !(await prepareRoom(room, local, progress)))
|
|
3896
|
+
return;
|
|
4130
3897
|
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
4131
3898
|
await reload(room, local, progress, live);
|
|
4132
3899
|
return;
|
|
@@ -4144,13 +3911,14 @@ export function composeRuntime(options) {
|
|
|
4144
3911
|
reloadsDue.delete(room.locator);
|
|
4145
3912
|
if (!live() || authorization === undefined)
|
|
4146
3913
|
return;
|
|
3914
|
+
const from = standingOf(room.locator);
|
|
4147
3915
|
const bootstrapped = await pullBootstrap(authorization, room, local, live);
|
|
4148
3916
|
if (bootstrapped.kind !== 'staged') {
|
|
4149
3917
|
if (live())
|
|
4150
3918
|
await withhold(bootstrapped, room, local, progress);
|
|
4151
3919
|
return;
|
|
4152
3920
|
}
|
|
4153
|
-
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed }, live);
|
|
3921
|
+
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from }, live);
|
|
4154
3922
|
await caughtUp(room, local, progress, live);
|
|
4155
3923
|
}
|
|
4156
3924
|
finally {
|
|
@@ -4259,23 +4027,45 @@ export function composeRuntime(options) {
|
|
|
4259
4027
|
}
|
|
4260
4028
|
}
|
|
4261
4029
|
async function admittedRead(work) {
|
|
4262
|
-
|
|
4263
|
-
|
|
4030
|
+
while (roomReads.free === 0) {
|
|
4031
|
+
await new Promise((admit) => roomReads.waiting.push(admit));
|
|
4264
4032
|
}
|
|
4265
|
-
|
|
4266
|
-
while (reads.free === 0)
|
|
4267
|
-
await new Promise((admit) => reads.waiting.push(admit));
|
|
4268
|
-
reads.free -= 1;
|
|
4033
|
+
roomReads.free -= 1;
|
|
4269
4034
|
try {
|
|
4270
4035
|
await work();
|
|
4271
4036
|
}
|
|
4272
4037
|
finally {
|
|
4273
|
-
|
|
4274
|
-
|
|
4038
|
+
roomReads.free += 1;
|
|
4039
|
+
roomReads.waiting.shift()?.();
|
|
4275
4040
|
}
|
|
4276
4041
|
}
|
|
4042
|
+
function inRoom(locator, work) {
|
|
4043
|
+
roomWork += 1;
|
|
4044
|
+
const ran = (roomLanes.get(locator) ?? Promise.resolve()).then(async () => {
|
|
4045
|
+
try {
|
|
4046
|
+
return await work(continuing);
|
|
4047
|
+
}
|
|
4048
|
+
finally {
|
|
4049
|
+
roomWork -= 1;
|
|
4050
|
+
}
|
|
4051
|
+
});
|
|
4052
|
+
const chain = ran.then(() => undefined, () => undefined);
|
|
4053
|
+
roomLanes.set(locator, chain);
|
|
4054
|
+
working.add(chain);
|
|
4055
|
+
void chain.finally(() => {
|
|
4056
|
+
working.delete(chain);
|
|
4057
|
+
if (roomLanes.get(locator) === chain)
|
|
4058
|
+
roomLanes.delete(locator);
|
|
4059
|
+
});
|
|
4060
|
+
return ran;
|
|
4061
|
+
}
|
|
4277
4062
|
function roomTask(locator, work) {
|
|
4278
|
-
|
|
4063
|
+
if (stopping)
|
|
4064
|
+
return Promise.resolve();
|
|
4065
|
+
return inRoom(locator, (live) => admittedRead(() => work(live))).catch(() => undefined);
|
|
4066
|
+
}
|
|
4067
|
+
function advanced(room, local, authorized) {
|
|
4068
|
+
return inRoom(room.locator, (live) => advance(room, local, authorized, live));
|
|
4279
4069
|
}
|
|
4280
4070
|
function task(lane, work) {
|
|
4281
4071
|
if (stopping)
|
|
@@ -4299,15 +4089,12 @@ export function composeRuntime(options) {
|
|
|
4299
4089
|
}
|
|
4300
4090
|
const progress = { complete: true, reachable: true };
|
|
4301
4091
|
const gate = discover();
|
|
4302
|
-
|
|
4303
|
-
opened = generation;
|
|
4304
|
-
underway = attempt === 'opened' ? { generation, progress } : null;
|
|
4092
|
+
underway = attempt === 'opened' ? progress : null;
|
|
4305
4093
|
if (attempt === 'opened')
|
|
4306
4094
|
streamed = progress;
|
|
4307
4095
|
complete = false;
|
|
4308
4096
|
recorded = false;
|
|
4309
4097
|
moveTo(transferring(status));
|
|
4310
|
-
handed = false;
|
|
4311
4098
|
try {
|
|
4312
4099
|
const local = await openLocal();
|
|
4313
4100
|
if (!live())
|
|
@@ -4335,7 +4122,6 @@ export function composeRuntime(options) {
|
|
|
4335
4122
|
if (handed || closed || progress.reachable)
|
|
4336
4123
|
return;
|
|
4337
4124
|
underway = null;
|
|
4338
|
-
opened = null;
|
|
4339
4125
|
journal.record('connection reopen');
|
|
4340
4126
|
await rest(progress, live);
|
|
4341
4127
|
options.adapter.roomStream?.reopen();
|
|
@@ -4375,11 +4161,11 @@ export function composeRuntime(options) {
|
|
|
4375
4161
|
notifyDiscovered();
|
|
4376
4162
|
const opened = socketOpen;
|
|
4377
4163
|
socketOpen = false;
|
|
4164
|
+
handed = false;
|
|
4165
|
+
naming = 0;
|
|
4378
4166
|
connectionLost();
|
|
4379
|
-
if (lost)
|
|
4380
|
-
reads.lost();
|
|
4167
|
+
if (lost)
|
|
4381
4168
|
conversations.lost();
|
|
4382
|
-
}
|
|
4383
4169
|
if (revoked) {
|
|
4384
4170
|
void refuse('revoked-authorization');
|
|
4385
4171
|
return;
|
|
@@ -4394,14 +4180,13 @@ export function composeRuntime(options) {
|
|
|
4394
4180
|
});
|
|
4395
4181
|
}
|
|
4396
4182
|
function applyOpening(expired) {
|
|
4397
|
-
const progress = underway
|
|
4183
|
+
const progress = underway;
|
|
4184
|
+
underway = null;
|
|
4185
|
+
naming = Math.max(0, naming - 1);
|
|
4398
4186
|
const cycle = progress ?? { complete: true, reachable: true };
|
|
4399
|
-
if (opened === generation)
|
|
4400
|
-
opened = null;
|
|
4401
4187
|
const reloading = progress === null ? [] : owedReloads(expired);
|
|
4402
4188
|
const replacing = reloadNow((room) => expired.includes(room.locator) || reloading.includes(room.locator), cycle);
|
|
4403
4189
|
return task(OPENING_LANE, async (live) => {
|
|
4404
|
-
underway = null;
|
|
4405
4190
|
if (!live())
|
|
4406
4191
|
return;
|
|
4407
4192
|
if (progress === null) {
|
|
@@ -4409,20 +4194,14 @@ export function composeRuntime(options) {
|
|
|
4409
4194
|
await rest(cycle, live);
|
|
4410
4195
|
return;
|
|
4411
4196
|
}
|
|
4412
|
-
await roomsCarried([...expired, ...reloading]);
|
|
4413
|
-
if (!live())
|
|
4414
|
-
return;
|
|
4415
4197
|
try {
|
|
4416
4198
|
const local = await openLocal();
|
|
4417
|
-
|
|
4199
|
+
await replacing;
|
|
4418
4200
|
if (progress.reachable) {
|
|
4419
4201
|
await settleModels([...discovered.values()].filter((room) => replicas.has(room.locator) &&
|
|
4420
4202
|
!expired.includes(room.locator) &&
|
|
4421
4203
|
!reloading.includes(room.locator)), 'current', local);
|
|
4422
4204
|
}
|
|
4423
|
-
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
4424
|
-
if (failed !== undefined)
|
|
4425
|
-
throw failed.reason;
|
|
4426
4205
|
}
|
|
4427
4206
|
catch {
|
|
4428
4207
|
progress.complete = false;
|
|
@@ -4434,11 +4213,8 @@ export function composeRuntime(options) {
|
|
|
4434
4213
|
}
|
|
4435
4214
|
function owedReloads(expired) {
|
|
4436
4215
|
const reloading = [...discovered.values()]
|
|
4437
|
-
.filter((room) => replicas.has(room.locator) &&
|
|
4438
|
-
!expired.includes(room.locator) &&
|
|
4439
|
-
(owed.has(room.locator) || revoked(room.locator)))
|
|
4216
|
+
.filter((room) => replicas.has(room.locator) && !expired.includes(room.locator) && revoked(room.locator))
|
|
4440
4217
|
.map((room) => room.locator);
|
|
4441
|
-
owed.clear();
|
|
4442
4218
|
for (const locator of reloading)
|
|
4443
4219
|
catching.add(locator);
|
|
4444
4220
|
liveQueries.recover();
|
|
@@ -4447,16 +4223,17 @@ export function composeRuntime(options) {
|
|
|
4447
4223
|
async function reloadNow(replaced, progress) {
|
|
4448
4224
|
const reloaded = [...discovered.values()].filter((room) => replicas.has(room.locator) && replaced(room));
|
|
4449
4225
|
for (const room of reloaded)
|
|
4450
|
-
reloadsDue.
|
|
4226
|
+
reloadsDue.add(room.locator);
|
|
4451
4227
|
try {
|
|
4452
4228
|
const local = await openLocal();
|
|
4453
|
-
|
|
4229
|
+
for (const room of reloaded) {
|
|
4230
|
+
void roomTask(room.locator, (live) => reload(room, local, progress, live)).then(() => rest(progress));
|
|
4231
|
+
}
|
|
4454
4232
|
}
|
|
4455
4233
|
catch {
|
|
4456
4234
|
for (const room of reloaded)
|
|
4457
4235
|
reloadsDue.delete(room.locator);
|
|
4458
4236
|
progress.reachable = false;
|
|
4459
|
-
return [];
|
|
4460
4237
|
}
|
|
4461
4238
|
}
|
|
4462
4239
|
async function settleModels(held, status, local) {
|
|
@@ -4467,7 +4244,7 @@ export function composeRuntime(options) {
|
|
|
4467
4244
|
const settled = await exclusive(async () => {
|
|
4468
4245
|
if (!continuing())
|
|
4469
4246
|
return [];
|
|
4470
|
-
const unchanged = held.filter((room) => owed(room) &&
|
|
4247
|
+
const unchanged = held.filter((room) => owed(room) && declaredAsHeld(room));
|
|
4471
4248
|
for (const room of unchanged) {
|
|
4472
4249
|
const next = { ...modelOf(room.locator), status };
|
|
4473
4250
|
const replica = replicas.get(room.locator);
|
|
@@ -4527,14 +4304,10 @@ export function composeRuntime(options) {
|
|
|
4527
4304
|
try {
|
|
4528
4305
|
const local = await openLocal();
|
|
4529
4306
|
await roomTask(locator, async (live) => {
|
|
4530
|
-
|
|
4531
|
-
if (!current() || authorization === undefined)
|
|
4307
|
+
if (!live() || authorization === undefined)
|
|
4532
4308
|
return;
|
|
4533
|
-
const withheld = await
|
|
4534
|
-
|
|
4535
|
-
abandonedOn(signal),
|
|
4536
|
-
]);
|
|
4537
|
-
if (withheld !== null && current())
|
|
4309
|
+
const withheld = await advance(room, local, authorization, live);
|
|
4310
|
+
if (withheld !== null && live())
|
|
4538
4311
|
await withhold(withheld, room, local, progress);
|
|
4539
4312
|
});
|
|
4540
4313
|
}
|
|
@@ -4589,14 +4362,11 @@ export function composeRuntime(options) {
|
|
|
4589
4362
|
progress.complete = false;
|
|
4590
4363
|
return;
|
|
4591
4364
|
}
|
|
4592
|
-
reads.applied();
|
|
4593
4365
|
submitAll();
|
|
4594
4366
|
for (const locator of stale)
|
|
4595
4367
|
if (repairable.has(locator))
|
|
4596
4368
|
repairLater(locator, {});
|
|
4597
4369
|
await conversations.reconcile();
|
|
4598
|
-
if (status.providerUnreachable)
|
|
4599
|
-
conversations.reconnected();
|
|
4600
4370
|
const reading = { complete: true, reachable: true };
|
|
4601
4371
|
await conversations.catchUp(reading);
|
|
4602
4372
|
if (progress.reachable && reading.reachable)
|
|
@@ -4622,7 +4392,7 @@ export function composeRuntime(options) {
|
|
|
4622
4392
|
if (!live())
|
|
4623
4393
|
return;
|
|
4624
4394
|
let lastCompleted = status.lastCompleted;
|
|
4625
|
-
let synced = complete && stale.size === 0 && !pending();
|
|
4395
|
+
let synced = complete && roomWork === 0 && naming === 0 && stale.size === 0 && !pending();
|
|
4626
4396
|
if (synced && !recorded) {
|
|
4627
4397
|
const completedAt = new Date().toISOString();
|
|
4628
4398
|
try {
|
|
@@ -4637,74 +4407,79 @@ export function composeRuntime(options) {
|
|
|
4637
4407
|
}
|
|
4638
4408
|
}
|
|
4639
4409
|
moveTo({
|
|
4640
|
-
state: pending()
|
|
4410
|
+
state: pending()
|
|
4411
|
+
? 'pending'
|
|
4412
|
+
: synced && complete && roomWork === 0 && naming === 0
|
|
4413
|
+
? 'synced'
|
|
4414
|
+
: 'syncing',
|
|
4641
4415
|
lastCompleted,
|
|
4642
|
-
providerUnreachable: !progress.reachable,
|
|
4416
|
+
providerUnreachable: progress === null ? status.providerUnreachable : !progress.reachable,
|
|
4643
4417
|
...attention(),
|
|
4644
4418
|
});
|
|
4645
4419
|
}
|
|
4646
4420
|
function discover() {
|
|
4421
|
+
discovering?.release();
|
|
4647
4422
|
let release = () => { };
|
|
4648
4423
|
const done = new Promise((resolve) => {
|
|
4649
4424
|
release = resolve;
|
|
4650
4425
|
});
|
|
4651
|
-
discovering = {
|
|
4426
|
+
discovering = { done, release };
|
|
4652
4427
|
return discovering;
|
|
4653
4428
|
}
|
|
4654
|
-
function applyRoom(
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
if (!live() || authorization === undefined)
|
|
4663
|
-
return;
|
|
4664
|
-
const room = discovered.get(locator);
|
|
4665
|
-
const held = replicas.get(locator);
|
|
4666
|
-
if (room === undefined || room.replica.admission !== 'admitted')
|
|
4667
|
-
return;
|
|
4668
|
-
if (held !== undefined &&
|
|
4669
|
-
(held.record.replica.selection !== room.replica.selection ||
|
|
4670
|
-
options.adapter.reached(locator, held.record.roomVersion, roomVersion))) {
|
|
4671
|
-
return;
|
|
4672
|
-
}
|
|
4673
|
-
const during = underway?.generation === generation ? underway.progress : null;
|
|
4674
|
-
const progress = during ?? { complete: true, reachable: true };
|
|
4675
|
-
if (during === null)
|
|
4676
|
-
moveTo(transferring(status));
|
|
4429
|
+
function applyRoom(change) {
|
|
4430
|
+
const { locator } = change;
|
|
4431
|
+
if (stopping)
|
|
4432
|
+
return Promise.resolve();
|
|
4433
|
+
return inRoom(locator, async (live) => {
|
|
4434
|
+
await discovering?.done;
|
|
4435
|
+
await answering.get(locator)?.answered;
|
|
4436
|
+
const progress = underway ?? { complete: true, reachable: true };
|
|
4677
4437
|
try {
|
|
4678
4438
|
const local = await openLocal();
|
|
4679
|
-
|
|
4680
|
-
if (!live()
|
|
4681
|
-
return;
|
|
4682
|
-
const
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4439
|
+
for (;;) {
|
|
4440
|
+
if (!live())
|
|
4441
|
+
return null;
|
|
4442
|
+
const room = discovered.get(locator);
|
|
4443
|
+
const held = replicas.get(locator);
|
|
4444
|
+
if (room === undefined ||
|
|
4445
|
+
held === undefined ||
|
|
4446
|
+
room.replica.admission !== 'admitted' ||
|
|
4447
|
+
held.record.replica.selection !== room.replica.selection) {
|
|
4448
|
+
return null;
|
|
4449
|
+
}
|
|
4450
|
+
const at = held.record.roomVersion;
|
|
4451
|
+
if (options.adapter.reached(locator, at, change.roomVersion))
|
|
4452
|
+
return progress;
|
|
4453
|
+
if (!options.adapter.reached(locator, at, change.from)) {
|
|
4454
|
+
name(locator);
|
|
4455
|
+
return progress;
|
|
4456
|
+
}
|
|
4457
|
+
if (underway === null)
|
|
4458
|
+
moveTo(transferring(status));
|
|
4459
|
+
const writing = () => live() && !full;
|
|
4460
|
+
const refreshed = await ahead((hold) => pullRefresh(authorization ?? {}, held, writing, hold, () => change.read()));
|
|
4461
|
+
if (refreshed.kind !== 'advanced')
|
|
4462
|
+
return progress;
|
|
4463
|
+
const landing = {
|
|
4464
|
+
kind: 'refreshed',
|
|
4465
|
+
from: refreshed.from,
|
|
4466
|
+
remodelled: refreshed.remodelled,
|
|
4467
|
+
};
|
|
4468
|
+
if (await publish(local, room, refreshed.record, landing, writing)) {
|
|
4469
|
+
stale.delete(locator);
|
|
4470
|
+
await retainModel(local, room, modelOf(locator));
|
|
4471
|
+
return progress;
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4686
4474
|
}
|
|
4687
4475
|
catch {
|
|
4476
|
+
if (!stale.has(locator))
|
|
4477
|
+
name(locator);
|
|
4688
4478
|
stale.add(locator);
|
|
4479
|
+
return progress;
|
|
4689
4480
|
}
|
|
4690
|
-
|
|
4691
|
-
if (during === null)
|
|
4692
|
-
await rest(progress, live);
|
|
4693
|
-
}
|
|
4694
|
-
});
|
|
4481
|
+
}).then((progress) => rest(progress), () => undefined);
|
|
4695
4482
|
}
|
|
4696
|
-
function roomsCarried(replacing) {
|
|
4697
|
-
const replaced = new Set(replacing.map(roomLane));
|
|
4698
|
-
return Promise.all([...lanes]
|
|
4699
|
-
.filter(([lane, held]) => lane !== OPENING_LANE && !replaced.has(lane) && held.generation === generation)
|
|
4700
|
-
.map(([, held]) => held.chain));
|
|
4701
|
-
}
|
|
4702
|
-
const receipts = {
|
|
4703
|
-
heads: async () => (await openLocal()).receivedHeads(),
|
|
4704
|
-
parts: async (key, count) => (await openLocal()).receivedParts(key, count),
|
|
4705
|
-
keep: async (key, head, part) => (await openLocal()).keepReceived(key, head, part),
|
|
4706
|
-
drop: async (keys) => (await openLocal()).dropReceived(keys),
|
|
4707
|
-
};
|
|
4708
4483
|
function observeStream() {
|
|
4709
4484
|
const stream = options.adapter.roomStream;
|
|
4710
4485
|
if (stream === undefined || withdrawStream !== undefined)
|
|
@@ -4714,9 +4489,10 @@ export function composeRuntime(options) {
|
|
|
4714
4489
|
return;
|
|
4715
4490
|
if (event.kind === 'opened') {
|
|
4716
4491
|
socketOpen = true;
|
|
4492
|
+
handed = false;
|
|
4493
|
+
naming = 0;
|
|
4717
4494
|
connectionLive();
|
|
4718
4495
|
generation += 1;
|
|
4719
|
-
reads.opened();
|
|
4720
4496
|
conversations.reopened();
|
|
4721
4497
|
recovery.resume();
|
|
4722
4498
|
void sync('opened');
|
|
@@ -4728,15 +4504,8 @@ export function composeRuntime(options) {
|
|
|
4728
4504
|
else if (event.kind === 'authority-changed')
|
|
4729
4505
|
void rediscover(event.scope);
|
|
4730
4506
|
else
|
|
4731
|
-
void applyRoom(event
|
|
4732
|
-
}
|
|
4733
|
-
}
|
|
4734
|
-
async function heldRevisions(locator, paths) {
|
|
4735
|
-
const held = replicas.get(locator);
|
|
4736
|
-
if (held === undefined || paths.length === 0)
|
|
4737
|
-
return new Map();
|
|
4738
|
-
const entries = await (await openLocal()).entries(held.record.key, 'confirmed', paths);
|
|
4739
|
-
return new Map(entries.flatMap(({ path, revision }) => revision === null ? [] : [[path, revision]]));
|
|
4507
|
+
void applyRoom(event);
|
|
4508
|
+
});
|
|
4740
4509
|
}
|
|
4741
4510
|
function writesHeld() {
|
|
4742
4511
|
return serviceMode === 'write-halted' || haltedRefusal;
|
|
@@ -4809,8 +4578,6 @@ export function composeRuntime(options) {
|
|
|
4809
4578
|
notifyDiscovered();
|
|
4810
4579
|
if (probing !== null)
|
|
4811
4580
|
clearInterval(probing);
|
|
4812
|
-
for (const resolve of shownWaiters.splice(0))
|
|
4813
|
-
resolve();
|
|
4814
4581
|
connectionLive();
|
|
4815
4582
|
lockListeners.clear();
|
|
4816
4583
|
readinessWatches.clear();
|
|
@@ -4835,6 +4602,7 @@ export function composeRuntime(options) {
|
|
|
4835
4602
|
}
|
|
4836
4603
|
async function stop() {
|
|
4837
4604
|
stopping = true;
|
|
4605
|
+
inFlight.abort();
|
|
4838
4606
|
await recovery.retire();
|
|
4839
4607
|
await Promise.all(working);
|
|
4840
4608
|
await mutations;
|
|
@@ -5228,7 +4996,6 @@ export function composeRuntime(options) {
|
|
|
5228
4996
|
journal.record(`persistence ${answer}`);
|
|
5229
4997
|
queueReadiness();
|
|
5230
4998
|
},
|
|
5231
|
-
shown: screen,
|
|
5232
4999
|
within: (reach, port) => {
|
|
5233
5000
|
if (full)
|
|
5234
5001
|
void probed();
|