@agentshouse/mdruntime 0.1.0-c56daf494ecb4 → 0.1.0-c59a5a6bd2f31
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 +19 -16
- package/dist/acceptance.d.ts +26 -0
- package/dist/acceptance.d.ts.map +1 -0
- package/dist/acceptance.js +384 -0
- package/dist/adapter.d.ts +152 -57
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +7 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +40 -41
- package/dist/binary.d.ts +0 -5
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +1 -1
- package/dist/bootstrap.d.ts +31 -11
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +323 -93
- package/dist/chain.d.ts +12 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +9 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +11 -63
- package/dist/change.d.ts +32 -1
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +10 -1
- package/dist/chat-types.d.ts +3 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +2 -0
- package/dist/client.d.ts +38 -16
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +101 -12
- package/dist/collection.d.ts +11 -7
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +38 -79
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +717 -0
- package/dist/connection.d.ts +44 -39
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +2307 -1712
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/{chat/context.js → context.js} +1 -4
- package/dist/conversation-row.d.ts +33 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +47 -0
- package/dist/conversation.d.ts +50 -20
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +897 -303
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/durability.d.ts +10 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +7 -4
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +33 -28
- package/dist/figures.d.ts +48 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +197 -0
- package/dist/file.d.ts +2 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +7 -122
- package/dist/host.d.ts +11 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +127 -17
- package/dist/index.d.ts +27 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/install.d.ts +86 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +537 -0
- package/dist/job.d.ts +33 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +54 -0
- package/dist/live.d.ts +23 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +165 -0
- package/dist/log.d.ts +1 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/media.d.ts +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +218 -0
- package/dist/memory.d.ts +19 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +253 -94
- package/dist/messages.d.ts +52 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +168 -0
- package/dist/model.d.ts +11 -11
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +7 -10
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +22 -4
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +120 -14
- package/dist/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/outcome.d.ts +50 -7
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +43 -0
- package/dist/overlay.d.ts +4 -4
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +111 -182
- package/dist/{chat/parts.d.ts → parts.d.ts} +11 -9
- package/dist/parts.d.ts.map +1 -0
- package/dist/{chat/parts.js → parts.js} +45 -46
- 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 +50 -183
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -464
- package/dist/{chat/questions.d.ts → questions.d.ts} +3 -2
- package/dist/questions.d.ts.map +1 -0
- package/dist/{chat/questions.js → questions.js} +8 -1
- package/dist/reach.d.ts +19 -7
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +289 -89
- package/dist/react.d.ts +11 -6
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +7 -17
- package/dist/{chat/read.d.ts → read.d.ts} +2 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/{chat/read.js → read.js} +4 -0
- package/dist/readiness.d.ts +50 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +27 -0
- package/dist/reads.d.ts +35 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +128 -0
- package/dist/recovery.d.ts +26 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +232 -0
- package/dist/refresh.d.ts +17 -14
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +143 -51
- package/dist/replica.d.ts +55 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +332 -154
- package/dist/room-draft.d.ts +37 -6
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +161 -74
- package/dist/room-drafts.d.ts +29 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +216 -0
- package/dist/rows.d.ts +30 -37
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +59 -126
- package/dist/save.d.ts +53 -9
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +542 -172
- package/dist/saves.d.ts +96 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +999 -0
- package/dist/search.d.ts +15 -55
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -121
- package/dist/seed.d.ts +30 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +141 -0
- package/dist/shared.d.ts +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +421 -0
- package/dist/socket/admission.d.ts +7 -0
- package/dist/socket/admission.d.ts.map +1 -0
- package/dist/socket/admission.js +39 -0
- package/dist/socket/answers.d.ts +6 -0
- package/dist/socket/answers.d.ts.map +1 -0
- package/dist/socket/answers.js +20 -0
- package/dist/socket/reads.d.ts +10 -0
- package/dist/socket/reads.d.ts.map +1 -0
- package/dist/socket/reads.js +31 -0
- package/dist/socket.d.ts +43 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +96 -0
- package/dist/source.d.ts +61 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +81 -0
- package/dist/status.d.ts +5 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store/fences.d.ts +16 -0
- package/dist/store/fences.d.ts.map +1 -0
- package/dist/store/fences.js +26 -0
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +34 -0
- package/dist/store/records.d.ts +212 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +408 -0
- package/dist/store/room-held.d.ts +8 -0
- package/dist/store/room-held.d.ts.map +1 -0
- package/dist/store/room-held.js +17 -0
- package/dist/store/watchers.d.ts +15 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +36 -0
- package/dist/store/writes.d.ts +24 -0
- package/dist/store/writes.d.ts.map +1 -0
- package/dist/store/writes.js +125 -0
- package/dist/store.d.ts +77 -120
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +112 -529
- package/dist/stored.d.ts +156 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +637 -0
- package/dist/tables.d.ts +407 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +724 -0
- package/dist/tallies.d.ts +18 -0
- package/dist/tallies.d.ts.map +1 -0
- package/dist/tallies.js +129 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +75 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +104 -0
- package/dist/windows.d.ts +11 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +16 -0
- package/dist/wire.d.ts +179 -32
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +61 -31
- package/package.json +18 -19
- package/AGENTS.md +0 -302
- package/dist/chat/acts.d.ts +0 -34
- package/dist/chat/acts.d.ts.map +0 -1
- package/dist/chat/acts.js +0 -36
- package/dist/chat/context.d.ts +0 -3
- package/dist/chat/context.d.ts.map +0 -1
- package/dist/chat/input.d.ts +0 -9
- package/dist/chat/input.d.ts.map +0 -1
- package/dist/chat/input.js +0 -99
- package/dist/chat/kit.d.ts +0 -60
- package/dist/chat/kit.d.ts.map +0 -1
- package/dist/chat/kit.js +0 -123
- package/dist/chat/lasting.d.ts +0 -6
- package/dist/chat/lasting.d.ts.map +0 -1
- package/dist/chat/lasting.js +0 -19
- package/dist/chat/list.d.ts +0 -26
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -62
- package/dist/chat/markdown.d.ts +0 -20
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -188
- package/dist/chat/message.d.ts +0 -6
- package/dist/chat/message.d.ts.map +0 -1
- package/dist/chat/message.js +0 -192
- package/dist/chat/messages.d.ts +0 -21
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -151
- package/dist/chat/name.d.ts +0 -6
- package/dist/chat/name.d.ts.map +0 -1
- package/dist/chat/name.js +0 -37
- package/dist/chat/opened.d.ts +0 -11
- package/dist/chat/opened.d.ts.map +0 -1
- package/dist/chat/opened.js +0 -17
- package/dist/chat/overlay.d.ts +0 -31
- package/dist/chat/overlay.d.ts.map +0 -1
- package/dist/chat/overlay.js +0 -97
- package/dist/chat/parts.d.ts.map +0 -1
- package/dist/chat/process.d.ts +0 -20
- package/dist/chat/process.d.ts.map +0 -1
- package/dist/chat/process.js +0 -32
- package/dist/chat/provider.d.ts +0 -63
- package/dist/chat/provider.d.ts.map +0 -1
- package/dist/chat/provider.js +0 -645
- package/dist/chat/question.d.ts +0 -9
- package/dist/chat/question.d.ts.map +0 -1
- package/dist/chat/question.js +0 -80
- package/dist/chat/questions.d.ts.map +0 -1
- package/dist/chat/read.d.ts.map +0 -1
- package/dist/chat/ready.d.ts +0 -33
- package/dist/chat/ready.d.ts.map +0 -1
- package/dist/chat/ready.js +0 -91
- package/dist/chat/style.d.ts +0 -4
- package/dist/chat/style.d.ts.map +0 -1
- package/dist/chat/style.js +0 -302
- package/dist/chat.d.ts +0 -14
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -8
- package/dist/read-side.d.ts +0 -18
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -440
package/dist/connection.js
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { identityOf, roomTarget, target, } from '@agentshouse/mdmodel';
|
|
2
|
+
import { builtInModel, roomTypesOf } from './attached-model.js';
|
|
3
|
+
import { modelTypes, NO_MODEL } from './model.js';
|
|
3
4
|
import { observing } from './observation.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { planWithin, } from './query.js';
|
|
6
|
+
import { createFigures, ROOM_TOTALS } from './figures.js';
|
|
7
|
+
import { createLiveQueries } from './live.js';
|
|
8
|
+
import { keepEntrySeed, seedApplies, seededResult, watchSeeded, } from './seed.js';
|
|
9
|
+
import { documentKey, roomOfKey } from './rows.js';
|
|
10
|
+
import { searchPlanOf } from './search.js';
|
|
11
|
+
import { capped, MAXIMUM_WINDOW } from './windows.js';
|
|
12
|
+
import { behind, catching, caughtUpTo, forgetInstall, installationOf, publish, pullBootstrap, pullRefresh, reachedBy, replaced, roomInFlight, standingAt, writtenAfter, } from './install.js';
|
|
9
13
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
10
14
|
import { createConversationStore, } from './conversation.js';
|
|
11
|
-
import {
|
|
12
|
-
import { validRoot } from './collection.js';
|
|
13
|
-
import { runtimeError } from './failure.js';
|
|
15
|
+
import { composedOf, validRoot } from './collection.js';
|
|
14
16
|
import { createLog } from './log.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
17
|
+
import { ABSENT_TARGET, calledResult, failure, historyAnswer, LOCKED_UNREADABLE, lockedRoom, OFFLINE, undeclared, UNCERTAIN, UNREADABLE, } from './outcome.js';
|
|
18
|
+
import { readAnswers } from './reads.js';
|
|
19
|
+
import { createRecovery } from './recovery.js';
|
|
20
|
+
import { storedRecord } from './replica.js';
|
|
21
|
+
import { same, STARTING, } from './status.js';
|
|
22
|
+
import { detectStorageLoss, nextGeneration, refusedForSpace, } from './durability.js';
|
|
23
|
+
import { readinessOf, } from './readiness.js';
|
|
24
|
+
import { yielded } from './turn.js';
|
|
25
|
+
import { beneath } from './paths.js';
|
|
26
|
+
import { createStore, } from './store.js';
|
|
27
|
+
import { emitPresentation, presentationSurface, publishRoomDrafts, roomDraftSurface, watchPresentation, watchRoomDrafts, } from './room-drafts.js';
|
|
28
|
+
import { createSocket } from './socket.js';
|
|
26
29
|
import { unconfirmed } from './transfer.js';
|
|
27
30
|
import { governs, holds, narrowed, planReach, } from './reach.js';
|
|
31
|
+
import { binaryFileSurface, overlayDiff, preparedAfter, replayed, saveParts, saveRoomDrafts, settle, sliceAt, } from './saves.js';
|
|
32
|
+
import { submitAll, submitLater, unpair } from './acceptance.js';
|
|
28
33
|
function within(root, path) {
|
|
29
34
|
return root === '' || beneath(root, path);
|
|
30
35
|
}
|
|
@@ -38,222 +43,448 @@ function declaredSelection(room) {
|
|
|
38
43
|
root !== '' &&
|
|
39
44
|
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
40
45
|
}
|
|
46
|
+
const OPENING_LANE = 'opening';
|
|
47
|
+
const CONVERSATION_LANE = 'conversations';
|
|
48
|
+
const ROOM_READS = 256;
|
|
49
|
+
const ROOM_ATTEMPTS = 16;
|
|
50
|
+
const ERASE_BATCH = 512;
|
|
51
|
+
const QUERY_BYTES = 1 << 20;
|
|
52
|
+
// A Room the Provider answers not ready is asked again after these waits, the last repeating, so a chunk build that catches up within seconds installs at once and a long one is asked every 15 seconds.
|
|
53
|
+
const NOT_READY_MS = [1_000, 2_000, 3_000, 5_000, 8_000, 11_000, 15_000];
|
|
54
|
+
// 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.
|
|
55
|
+
const LOCK_GRACE_MS = 120_000;
|
|
56
|
+
function operationOf(input) {
|
|
57
|
+
const operation = input?.operation_id;
|
|
58
|
+
return typeof operation === 'string' ? operation : null;
|
|
59
|
+
}
|
|
41
60
|
export function composeRuntime(options) {
|
|
42
61
|
const localSaves = options.adapter.localSaves === true;
|
|
62
|
+
const roomTypes = roomTypesOf(options.adapter.roomTypes, options.adapter.nameableRoomTypes);
|
|
43
63
|
let authorization = options.authorization;
|
|
44
|
-
let
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const roomDrafts = new Map();
|
|
50
|
-
const presentations = new Map();
|
|
51
|
-
let limits = [];
|
|
52
|
-
let capabilities = [];
|
|
53
|
-
const rooms = new Map();
|
|
54
|
-
const counted = new Map();
|
|
64
|
+
let discoveries = 0;
|
|
65
|
+
const usableWhenHeld = options.usableWhenHeld ?? [];
|
|
66
|
+
const build = options.build ?? '';
|
|
67
|
+
const declared = new Map();
|
|
68
|
+
const unheldModels = new Map();
|
|
55
69
|
const unreadable = new Set();
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const invalidModels = new Set();
|
|
70
|
+
const builtIn = builtInModel(roomTypes);
|
|
71
|
+
const typedPaths = options.adapter.typedPaths ?? [];
|
|
59
72
|
const discovered = new Map();
|
|
73
|
+
const journal = createLog();
|
|
74
|
+
const database = {
|
|
75
|
+
async ask(request) {
|
|
76
|
+
try {
|
|
77
|
+
return await options.database.ask(request);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (refusedForSpace(error))
|
|
81
|
+
filled();
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
const socket = createSocket(options.adapter);
|
|
87
|
+
const store = createStore({
|
|
88
|
+
database,
|
|
89
|
+
roomTypes,
|
|
90
|
+
builtIn,
|
|
91
|
+
typedPaths,
|
|
92
|
+
opening: open,
|
|
93
|
+
unpublished,
|
|
94
|
+
});
|
|
95
|
+
const { covering, durably, exclusive, composed, publication } = store;
|
|
96
|
+
const openLocal = store.open;
|
|
97
|
+
const isFenced = store.fences.has;
|
|
98
|
+
const storedRooms = store.rooms;
|
|
99
|
+
const recordAt = store.record;
|
|
100
|
+
const workOf = store.work;
|
|
101
|
+
const replicaOf = store.replica;
|
|
102
|
+
const draftsOf = store.drafts;
|
|
103
|
+
const draftOf = store.draft;
|
|
104
|
+
const reads = socket.reads;
|
|
60
105
|
let roomsListed = false;
|
|
61
106
|
const scopedRefusals = new Map();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
function modelOf(locator, attached = 'collection') {
|
|
68
|
-
return models.get(modelKey(locator, attached)) ?? NO_MODEL;
|
|
69
|
-
}
|
|
70
|
-
function savedModel(locator, attached) {
|
|
71
|
-
return invalidModels.has(modelKey(locator, attached))
|
|
72
|
-
? { status: 'unavailable', reason: 'revoked' }
|
|
73
|
-
: modelOf(locator, attached);
|
|
107
|
+
function modelIn(locator, stored) {
|
|
108
|
+
if (refusal?.code === 'revoked-authorization')
|
|
109
|
+
return { status: 'unavailable', reason: 'revoked' };
|
|
110
|
+
return stored?.record.model ?? unheldModels.get(locator) ?? NO_MODEL;
|
|
74
111
|
}
|
|
75
112
|
function compositionFor(record) {
|
|
76
|
-
return
|
|
113
|
+
return composed({
|
|
77
114
|
...record,
|
|
78
|
-
model:
|
|
79
|
-
selectionModel: savedModel(record.locator, 'typed-selection'),
|
|
115
|
+
model: modelIn(record.locator, { record, indexed: true, documents: 0 }),
|
|
80
116
|
});
|
|
81
117
|
}
|
|
82
118
|
let refusal = null;
|
|
83
119
|
let storageLoss = null;
|
|
84
|
-
let status =
|
|
120
|
+
let status = { state: 'syncing', providerUnreachable: false, resting: false };
|
|
85
121
|
let shown = STARTING;
|
|
86
122
|
let discoveryPaused = false;
|
|
87
|
-
let resetTimer;
|
|
88
123
|
let closed = false;
|
|
89
124
|
let stopping = false;
|
|
90
|
-
let withdrawStream;
|
|
91
|
-
const announced = new Map();
|
|
92
125
|
let underway = null;
|
|
93
126
|
let streamed = null;
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const
|
|
127
|
+
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
128
|
+
const repairing = new Set();
|
|
129
|
+
const calling = new Set();
|
|
130
|
+
const repairable = new Map();
|
|
131
|
+
const unready = new Map();
|
|
97
132
|
let complete = false;
|
|
98
133
|
let recorded = false;
|
|
99
134
|
const stale = new Set();
|
|
100
|
-
|
|
101
|
-
|
|
135
|
+
const reloadsDue = new Set();
|
|
136
|
+
const lanes = new Map();
|
|
137
|
+
const roomLanes = new Map();
|
|
138
|
+
let roomWork = 0;
|
|
139
|
+
const roomReads = { free: ROOM_READS, waiting: [] };
|
|
140
|
+
const working = new Set();
|
|
102
141
|
const roomListeners = new Set();
|
|
142
|
+
const unlistedRooms = new Set();
|
|
103
143
|
const refusalListeners = new Set();
|
|
104
144
|
const storageLossListeners = new Set();
|
|
105
145
|
const statusListeners = new Set();
|
|
106
146
|
const queueSubscriptions = new Set();
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
147
|
+
const liveQueries = createLiveQueries((plan, cover) => answeredPlan(plan, cover), () => covering());
|
|
148
|
+
const figures = createFigures({
|
|
149
|
+
cover: figuresCover,
|
|
150
|
+
rooms: async (rooms, since) => database.ask({
|
|
151
|
+
kind: 'replica.tallies',
|
|
152
|
+
totals: { ...(await planned(ROOM_TOTALS, null)), limit: null },
|
|
153
|
+
rooms,
|
|
154
|
+
since,
|
|
155
|
+
}),
|
|
156
|
+
conversations: (conversations, since) => database.ask({ kind: 'conversation.tallies', conversations, since }),
|
|
157
|
+
});
|
|
158
|
+
let currencyQueued = null;
|
|
159
|
+
const confirmed = new Set();
|
|
160
|
+
let lockGrace = null;
|
|
161
|
+
const lockListeners = new Set();
|
|
113
162
|
let serviceMode = 'open';
|
|
114
163
|
let haltedRefusal = false;
|
|
115
164
|
let openSignals = 0;
|
|
116
165
|
const serviceModeListeners = new Set();
|
|
117
|
-
let member = null;
|
|
118
166
|
const memberListeners = new Set();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
167
|
+
let authorityChanges = 0;
|
|
168
|
+
const authorityChangeListeners = new Set();
|
|
169
|
+
const unpreparedDemand = new Set();
|
|
170
|
+
const unpreparable = new Set();
|
|
171
|
+
let preparing = false;
|
|
172
|
+
let persistence = options.shell.persistence;
|
|
173
|
+
let heldWhenOpened = false;
|
|
174
|
+
let full = false;
|
|
175
|
+
let probing = null;
|
|
176
|
+
let testing = null;
|
|
177
|
+
const readinessWatches = new Set();
|
|
178
|
+
let readinessQueued = false;
|
|
122
179
|
const conversations = createConversationStore({
|
|
123
|
-
agents: declaredAgents,
|
|
124
|
-
documents: (locator) => rowsQuery(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
125
180
|
call: (name, input) => invokeAction(name, input),
|
|
181
|
+
history: (request) => readConversationHistory(request),
|
|
126
182
|
durably,
|
|
183
|
+
local: () => openLocal(),
|
|
184
|
+
again: () => {
|
|
185
|
+
void ready.then(scheduleCurrency);
|
|
186
|
+
},
|
|
187
|
+
settled: () => {
|
|
188
|
+
show();
|
|
189
|
+
},
|
|
127
190
|
stream: () => options.adapter.conversations,
|
|
128
191
|
publish: publishChangeSet,
|
|
192
|
+
online: providerReached,
|
|
193
|
+
full: () => full,
|
|
129
194
|
closed: () => closed,
|
|
195
|
+
unanswered: resendLater,
|
|
130
196
|
});
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
197
|
+
const lastAnswers = readAnswers({
|
|
198
|
+
database,
|
|
199
|
+
build,
|
|
200
|
+
unkept: options.adapter.unkeptReads ?? [],
|
|
201
|
+
recovery: recovery.scope(),
|
|
202
|
+
call: (name, input) => md.call(name, input),
|
|
203
|
+
online: providerReached,
|
|
204
|
+
closed: () => closed,
|
|
205
|
+
full: () => full,
|
|
206
|
+
filled,
|
|
136
207
|
});
|
|
137
|
-
|
|
138
|
-
let notifyReady = () => { };
|
|
208
|
+
let readyFromStore = () => { };
|
|
139
209
|
const ready = new Promise((resolve) => {
|
|
140
|
-
|
|
210
|
+
readyFromStore = resolve;
|
|
141
211
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
room.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
212
|
+
let notifyDiscovered = () => { };
|
|
213
|
+
const firstDiscovery = new Promise((resolve) => {
|
|
214
|
+
notifyDiscovered = resolve;
|
|
215
|
+
});
|
|
216
|
+
let roomsNamed = false;
|
|
217
|
+
const seeding = {
|
|
218
|
+
store,
|
|
219
|
+
reached: reads.reached,
|
|
220
|
+
listed: () => roomsListed,
|
|
221
|
+
declares: (room) => declared.has(room),
|
|
222
|
+
};
|
|
223
|
+
const drafting = {
|
|
224
|
+
store,
|
|
225
|
+
closed: () => closed,
|
|
226
|
+
publishChangeSet,
|
|
227
|
+
statusShown: () => moveTo(status),
|
|
228
|
+
};
|
|
229
|
+
const saver = {
|
|
230
|
+
store,
|
|
231
|
+
socket,
|
|
232
|
+
journal,
|
|
233
|
+
localSaves,
|
|
234
|
+
ready,
|
|
235
|
+
firstDiscovery,
|
|
236
|
+
full: () => full,
|
|
237
|
+
closed: () => closed,
|
|
238
|
+
refused: () => refusal !== null,
|
|
239
|
+
roomsNamed: () => roomsNamed,
|
|
240
|
+
authorized: () => authorization,
|
|
241
|
+
ensureAuthorization,
|
|
242
|
+
connectionNow,
|
|
243
|
+
scoped,
|
|
244
|
+
refuse,
|
|
245
|
+
compositionFor,
|
|
246
|
+
prepare,
|
|
247
|
+
publishChangeSet,
|
|
248
|
+
pendingShown,
|
|
249
|
+
submitLater: (locator) => submitLater(accepting, locator),
|
|
250
|
+
};
|
|
251
|
+
const accepting = {
|
|
252
|
+
...saver,
|
|
253
|
+
recovery,
|
|
254
|
+
stopping: () => stopping,
|
|
255
|
+
paused: () => discoveryPaused,
|
|
256
|
+
continuing,
|
|
257
|
+
discovered: (locator) => discovered.get(locator),
|
|
258
|
+
readable(room) {
|
|
259
|
+
unreadable.delete(room.locator);
|
|
260
|
+
list(room, true);
|
|
261
|
+
},
|
|
262
|
+
unreadable: roomUnreadable,
|
|
263
|
+
advanced,
|
|
264
|
+
withhold,
|
|
265
|
+
writesHeld,
|
|
266
|
+
openSignals: () => openSignals,
|
|
267
|
+
haltedSince,
|
|
268
|
+
rest,
|
|
269
|
+
statusShown: () => moveTo(status),
|
|
270
|
+
};
|
|
271
|
+
const installer = {
|
|
272
|
+
store,
|
|
273
|
+
socket,
|
|
274
|
+
journal,
|
|
275
|
+
roomTypes,
|
|
276
|
+
builtIn,
|
|
277
|
+
typedPaths,
|
|
278
|
+
unheldModel: (locator) => unheldModels.get(locator),
|
|
279
|
+
recover: recoverReads,
|
|
280
|
+
queueReadiness,
|
|
281
|
+
sweep,
|
|
282
|
+
settle: (local, locator, work, record, queue, settling) => settle(saver, local, locator, work, record, queue, settling),
|
|
283
|
+
readied,
|
|
284
|
+
modelKept: (locator) => {
|
|
285
|
+
unheldModels.delete(locator);
|
|
286
|
+
},
|
|
287
|
+
name,
|
|
288
|
+
usable(room) {
|
|
289
|
+
stale.delete(room.locator);
|
|
290
|
+
unpreparable.delete(room.locator);
|
|
291
|
+
if (unreadable.delete(room.locator))
|
|
292
|
+
scopedRefusals.delete(room.locator);
|
|
293
|
+
list(room, true);
|
|
294
|
+
},
|
|
295
|
+
publishChangeSet,
|
|
296
|
+
unpair: (local) => unpair(accepting, local),
|
|
297
|
+
statusShown: () => moveTo(status),
|
|
298
|
+
submitLater: (locator) => submitLater(accepting, locator),
|
|
299
|
+
};
|
|
300
|
+
function notifyReady() {
|
|
301
|
+
notifyDiscovered();
|
|
302
|
+
readyFromStore();
|
|
303
|
+
}
|
|
304
|
+
async function attended() {
|
|
305
|
+
const held = await (await openLocal()).status();
|
|
306
|
+
const attentionRequired = held.returned.flatMap((record) => record.returned === null || isFenced(record.locator)
|
|
307
|
+
? []
|
|
308
|
+
: [{ room: record.locator, path: record.path, returned: structuredClone(record.returned) }]);
|
|
309
|
+
attentionRequired.sort((left, right) => {
|
|
310
|
+
if (left.room !== right.room)
|
|
311
|
+
return left.room < right.room ? -1 : 1;
|
|
170
312
|
if (left.path !== right.path)
|
|
171
313
|
return left.path < right.path ? -1 : 1;
|
|
172
314
|
return 0;
|
|
173
315
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
for (const listener of roomDraftListeners) {
|
|
181
|
-
if (waitingOnly && listener.ready)
|
|
182
|
-
continue;
|
|
183
|
-
listener.ready = true;
|
|
184
|
-
listener.notify(value);
|
|
316
|
+
const changed = new Map();
|
|
317
|
+
if (!localSaves) {
|
|
318
|
+
for (const change of held.unsent) {
|
|
319
|
+
if (!isFenced(change.room))
|
|
320
|
+
changed.set(`${change.room}\u0000${change.path}`, change);
|
|
321
|
+
}
|
|
185
322
|
}
|
|
323
|
+
const unsent = [...changed]
|
|
324
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
325
|
+
.map(([, change]) => change);
|
|
326
|
+
return {
|
|
327
|
+
queued: !localSaves && held.queued.some((locator) => !isFenced(locator)),
|
|
328
|
+
lastCompleted: held.lastCompleted,
|
|
329
|
+
attentionRequired,
|
|
330
|
+
unsent,
|
|
331
|
+
reset: held.resets.reduce((earliest, { locator, reset }) => isFenced(locator) || (earliest !== null && earliest <= reset) ? earliest : reset, null),
|
|
332
|
+
};
|
|
186
333
|
}
|
|
187
|
-
function
|
|
188
|
-
const
|
|
189
|
-
return
|
|
334
|
+
async function queuedWork() {
|
|
335
|
+
const held = await (await openLocal()).queued();
|
|
336
|
+
return !localSaves && held.some((locator) => !isFenced(locator));
|
|
190
337
|
}
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
338
|
+
function declarationOf(locator, stored) {
|
|
339
|
+
const held = declared.get(locator);
|
|
340
|
+
const room = stored?.record ?? held?.room ?? discovered.get(locator);
|
|
341
|
+
return room === undefined ? undefined : { room, available: held?.available ?? true };
|
|
342
|
+
}
|
|
343
|
+
function listedLocators(held) {
|
|
344
|
+
return [
|
|
345
|
+
...declared.keys(),
|
|
346
|
+
...[...held.keys()].filter((locator) => !declared.has(locator)),
|
|
347
|
+
].filter((locator) => !isFenced(locator));
|
|
348
|
+
}
|
|
349
|
+
function selectionModel() {
|
|
350
|
+
return refusal?.code === 'revoked-authorization'
|
|
351
|
+
? { status: 'unavailable', reason: 'revoked' }
|
|
352
|
+
: builtIn;
|
|
353
|
+
}
|
|
354
|
+
function listedRoom(locator, stored) {
|
|
355
|
+
const declaration = declarationOf(locator, stored);
|
|
356
|
+
if (declaration === undefined)
|
|
357
|
+
return undefined;
|
|
358
|
+
const { room, available } = declaration;
|
|
359
|
+
const selection = room.typedSelection ?? null;
|
|
360
|
+
return {
|
|
361
|
+
name: room.name,
|
|
362
|
+
locator,
|
|
363
|
+
handle: room.handle,
|
|
364
|
+
operations: room.operations,
|
|
365
|
+
replica: room.replica,
|
|
366
|
+
model: modelTypes(modelIn(locator, stored)),
|
|
367
|
+
typedSelection: selection === null ? null : { roots: selection.roots, model: modelTypes(selectionModel()) },
|
|
368
|
+
providerOwnedPaths: room.providerOwnedPaths,
|
|
369
|
+
roomVersion: stored?.record.roomVersion ?? null,
|
|
370
|
+
synchronizationAvailable: available,
|
|
371
|
+
refusal: scopedRefusals.get(locator) ?? null,
|
|
372
|
+
documents: stored?.documents ?? 0,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function listingOf(held) {
|
|
376
|
+
const listing = new Map();
|
|
377
|
+
for (const locator of listedLocators(held)) {
|
|
378
|
+
const room = listedRoom(locator, held.get(locator));
|
|
379
|
+
if (room !== undefined)
|
|
380
|
+
listing.set(locator, room);
|
|
381
|
+
}
|
|
382
|
+
return listing;
|
|
383
|
+
}
|
|
384
|
+
async function patchedListing(changed) {
|
|
385
|
+
if (roomsShown === null)
|
|
386
|
+
return null;
|
|
387
|
+
const held = await storedRooms([...changed]);
|
|
388
|
+
const listing = new Map(roomsShown);
|
|
389
|
+
for (const locator of changed) {
|
|
390
|
+
const room = isFenced(locator) ? undefined : listedRoom(locator, held.get(locator));
|
|
391
|
+
if (room === undefined || !listing.has(locator))
|
|
392
|
+
return null;
|
|
393
|
+
listing.set(locator, room);
|
|
197
394
|
}
|
|
395
|
+
return listing;
|
|
198
396
|
}
|
|
199
|
-
function
|
|
200
|
-
return (
|
|
201
|
-
(queuesWithoutReplica.size > 0 ||
|
|
202
|
-
[...replicas.values()].some((replica) => replica.queue.length > 0)));
|
|
397
|
+
async function listingNow() {
|
|
398
|
+
return listingOf(await storedRooms());
|
|
203
399
|
}
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
room
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
400
|
+
function lockedIn(listing, locator) {
|
|
401
|
+
return listing.get(locator)?.replica.offline === 'locked' && !confirmed.has(locator);
|
|
402
|
+
}
|
|
403
|
+
function shownRooms(listing) {
|
|
404
|
+
return structuredClone([...listing.values()].map(({ documents, ...room }) => lockedIn(listing, room.locator)
|
|
405
|
+
? {
|
|
406
|
+
...room,
|
|
407
|
+
model: modelTypes(NO_MODEL),
|
|
408
|
+
typedSelection: null,
|
|
409
|
+
documents: 0,
|
|
410
|
+
locked: true,
|
|
411
|
+
}
|
|
412
|
+
: { ...room, documents, locked: false }));
|
|
413
|
+
}
|
|
414
|
+
async function offlineModes() {
|
|
415
|
+
const modes = new Map();
|
|
416
|
+
for (const [locator, { room }] of declared) {
|
|
417
|
+
const declaration = room ?? discovered.get(locator);
|
|
418
|
+
if (declaration !== undefined)
|
|
419
|
+
modes.set(locator, declaration.replica.offline);
|
|
214
420
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
421
|
+
for (const [locator, { offline }] of (await covering()).rooms)
|
|
422
|
+
modes.set(locator, offline);
|
|
423
|
+
return modes;
|
|
424
|
+
}
|
|
425
|
+
async function locked(locator) {
|
|
426
|
+
if (isFenced(locator) || confirmed.has(locator))
|
|
427
|
+
return false;
|
|
428
|
+
const held = declared.get(locator)?.room ?? discovered.get(locator);
|
|
429
|
+
const mode = (await (await openLocal()).offline(locator)) ?? held?.replica.offline;
|
|
430
|
+
return mode === 'locked';
|
|
431
|
+
}
|
|
432
|
+
async function lockedRooms() {
|
|
433
|
+
return [...(await offlineModes())]
|
|
434
|
+
.filter(([locator, mode]) => mode === 'locked' && !confirmed.has(locator) && !isFenced(locator))
|
|
435
|
+
.map(([locator]) => locator)
|
|
436
|
+
.sort();
|
|
437
|
+
}
|
|
438
|
+
async function relocked(before) {
|
|
439
|
+
const after = await lockedRooms();
|
|
440
|
+
if (JSON.stringify(after) === JSON.stringify(before))
|
|
441
|
+
return;
|
|
442
|
+
journal.record(`locked ${after.length}`);
|
|
443
|
+
emitRooms();
|
|
444
|
+
for (const notify of [...lockListeners])
|
|
445
|
+
notify();
|
|
446
|
+
}
|
|
447
|
+
function confirmLocks(listed) {
|
|
448
|
+
if (!socket.isOpen())
|
|
449
|
+
return;
|
|
450
|
+
for (const room of listed) {
|
|
451
|
+
if (room.replica.admission === 'admitted')
|
|
452
|
+
confirmed.add(room.locator);
|
|
228
453
|
}
|
|
229
|
-
return { attentionRequired, reset };
|
|
230
454
|
}
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
function
|
|
250
|
-
|
|
455
|
+
function connectionLost() {
|
|
456
|
+
if (lockGrace !== null || closed)
|
|
457
|
+
return;
|
|
458
|
+
lockGrace = setTimeout(() => {
|
|
459
|
+
lockGrace = null;
|
|
460
|
+
if (socket.isOpen() || closed)
|
|
461
|
+
return;
|
|
462
|
+
void lockedRooms().then((before) => {
|
|
463
|
+
confirmed.clear();
|
|
464
|
+
return relocked(before);
|
|
465
|
+
}, () => { });
|
|
466
|
+
}, LOCK_GRACE_MS);
|
|
467
|
+
}
|
|
468
|
+
function connectionLive() {
|
|
469
|
+
if (lockGrace !== null)
|
|
470
|
+
clearTimeout(lockGrace);
|
|
471
|
+
lockGrace = null;
|
|
472
|
+
}
|
|
473
|
+
function watchLocks(notify) {
|
|
474
|
+
lockListeners.add(notify);
|
|
475
|
+
return () => {
|
|
476
|
+
lockListeners.delete(notify);
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
async function roomFacts(reach = null) {
|
|
480
|
+
const held = await storedRooms();
|
|
481
|
+
const rooms = [...listingOf(held).values()]
|
|
251
482
|
.filter((room) => holds(reach, room.locator))
|
|
252
483
|
.map((room) => {
|
|
253
484
|
const governed = governs(reach, room);
|
|
254
485
|
const attached = [
|
|
255
|
-
...(governed.collection ? [
|
|
256
|
-
...(governed.selection ? [
|
|
486
|
+
...(governed.collection ? [modelIn(room.locator, held.get(room.locator))] : []),
|
|
487
|
+
...(governed.selection ? [builtIn] : []),
|
|
257
488
|
];
|
|
258
489
|
return {
|
|
259
490
|
locator: room.locator,
|
|
@@ -261,186 +492,658 @@ export function composeRuntime(options) {
|
|
|
261
492
|
types: attached.flatMap((model) => model.status === 'unavailable' ? [] : Object.values(model.types)),
|
|
262
493
|
};
|
|
263
494
|
});
|
|
264
|
-
return { listed: roomsListed, rooms
|
|
495
|
+
return { listed: roomsListed, rooms };
|
|
496
|
+
}
|
|
497
|
+
function emitRooms(changed) {
|
|
498
|
+
recoverReads();
|
|
499
|
+
queueReadiness();
|
|
500
|
+
return changed === undefined ? publishRooms('all') : publishRooms('changed', changed);
|
|
265
501
|
}
|
|
266
|
-
function
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
502
|
+
function emitKnownRooms() {
|
|
503
|
+
conversations.learned();
|
|
504
|
+
if (unlistedRooms.size === 0)
|
|
505
|
+
return;
|
|
506
|
+
void publishRooms('known');
|
|
507
|
+
}
|
|
508
|
+
let roomsOwed = null;
|
|
509
|
+
let roomsShown = null;
|
|
510
|
+
function publishRooms(mode, touched = []) {
|
|
511
|
+
if (closed)
|
|
512
|
+
return Promise.resolve();
|
|
513
|
+
if (roomsOwed !== null) {
|
|
514
|
+
roomsOwed.all ||= mode === 'all';
|
|
515
|
+
for (const locator of touched) {
|
|
516
|
+
(mode === 'changed' ? roomsOwed.changed : roomsOwed.counted).add(locator);
|
|
517
|
+
}
|
|
518
|
+
return roomsOwed.shown;
|
|
519
|
+
}
|
|
520
|
+
const owing = {
|
|
521
|
+
all: mode === 'all',
|
|
522
|
+
changed: new Set(mode === 'changed' ? touched : []),
|
|
523
|
+
counted: new Set(mode === 'changed' ? [] : touched),
|
|
524
|
+
shown: Promise.resolve(),
|
|
525
|
+
};
|
|
526
|
+
roomsOwed = owing;
|
|
527
|
+
owing.shown = publication(async () => {
|
|
528
|
+
const owed = owing;
|
|
529
|
+
if (roomsOwed === owing)
|
|
530
|
+
roomsOwed = null;
|
|
531
|
+
if (closed || (roomListeners.size === 0 && unlistedRooms.size === 0))
|
|
532
|
+
return;
|
|
533
|
+
const patched = owed.all || owed.changed.size === 0 || unlistedRooms.size > 0
|
|
534
|
+
? null
|
|
535
|
+
: await patchedListing(new Set([...owed.changed, ...owed.counted]));
|
|
536
|
+
if (patched === null && owed.changed.size > 0)
|
|
537
|
+
owed.all = true;
|
|
538
|
+
if (patched === null && !owed.all && owed.counted.size > 0) {
|
|
539
|
+
const held = await covering().catch(() => null);
|
|
540
|
+
if (held === null)
|
|
541
|
+
return;
|
|
542
|
+
owed.all = [...owed.counted].some((locator) => held.rooms.has(locator) &&
|
|
543
|
+
roomsShown?.get(locator)?.documents !== held.rooms.get(locator).documents);
|
|
544
|
+
if (owed.all) {
|
|
545
|
+
recoverReads();
|
|
546
|
+
queueReadiness();
|
|
547
|
+
}
|
|
548
|
+
else if (unlistedRooms.size === 0)
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const listing = patched ?? (await listingNow().catch(() => null));
|
|
552
|
+
if (listing === null || closed)
|
|
553
|
+
return;
|
|
554
|
+
const value = shownRooms(listing);
|
|
555
|
+
const joining = unlistedRooms.size > 0 &&
|
|
556
|
+
(roomsListed || listing.size > 0 || refusal !== null || status.providerUnreachable);
|
|
557
|
+
if (!owed.all && patched === null && !joining)
|
|
558
|
+
return;
|
|
559
|
+
const moved = owed.all ||
|
|
560
|
+
patched !== null ||
|
|
561
|
+
(roomsShown !== null && JSON.stringify(shownRooms(roomsShown)) !== JSON.stringify(value));
|
|
562
|
+
if (moved)
|
|
563
|
+
for (const listener of roomListeners)
|
|
564
|
+
listener(value);
|
|
565
|
+
if (joining) {
|
|
566
|
+
for (const listener of [...unlistedRooms]) {
|
|
567
|
+
unlistedRooms.delete(listener);
|
|
568
|
+
roomListeners.add(listener);
|
|
569
|
+
listener(value);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
roomsShown = roomListeners.size === 0 ? null : listing;
|
|
573
|
+
});
|
|
574
|
+
return owing.shown;
|
|
270
575
|
}
|
|
271
576
|
function emitRefusal() {
|
|
577
|
+
queueReadiness();
|
|
272
578
|
for (const listener of refusalListeners)
|
|
273
579
|
listener(refusal);
|
|
580
|
+
emitKnownRooms();
|
|
274
581
|
}
|
|
275
582
|
function emitStorageLoss() {
|
|
583
|
+
queueReadiness();
|
|
276
584
|
for (const listener of storageLossListeners)
|
|
277
585
|
listener(storageLoss);
|
|
278
586
|
}
|
|
279
|
-
function emitMember() {
|
|
587
|
+
function emitMember(member) {
|
|
280
588
|
for (const listener of memberListeners)
|
|
281
589
|
listener(member);
|
|
282
590
|
}
|
|
283
591
|
function moveTo(next) {
|
|
284
592
|
if (closed)
|
|
285
|
-
return;
|
|
593
|
+
return Promise.resolve();
|
|
286
594
|
status = next;
|
|
287
|
-
show();
|
|
595
|
+
return show();
|
|
596
|
+
}
|
|
597
|
+
function transferring() {
|
|
598
|
+
return { ...status, state: 'syncing', resting: false };
|
|
288
599
|
}
|
|
600
|
+
let statusOwed = null;
|
|
601
|
+
let statusShown = Promise.resolve();
|
|
289
602
|
function show() {
|
|
290
603
|
if (closed)
|
|
291
|
-
return;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
604
|
+
return Promise.resolve();
|
|
605
|
+
queueReadiness();
|
|
606
|
+
emitKnownRooms();
|
|
607
|
+
const moved = { moving: status, current: conversations.current() };
|
|
608
|
+
if (statusOwed !== null) {
|
|
609
|
+
statusOwed.push(moved);
|
|
610
|
+
return statusShown;
|
|
611
|
+
}
|
|
612
|
+
const owed = [moved];
|
|
613
|
+
statusOwed = owed;
|
|
614
|
+
statusShown = publication(async () => {
|
|
615
|
+
if (statusOwed === owed)
|
|
616
|
+
statusOwed = null;
|
|
617
|
+
const held = await attended();
|
|
618
|
+
if (closed)
|
|
619
|
+
return;
|
|
620
|
+
for (const { moving, current } of owed) {
|
|
621
|
+
const state = moving.resting && held.queued ? 'pending' : moving.state;
|
|
622
|
+
const next = {
|
|
623
|
+
state: state === 'synced' && !current ? 'syncing' : state,
|
|
624
|
+
lastCompleted: held.lastCompleted,
|
|
625
|
+
providerUnreachable: moving.providerUnreachable,
|
|
626
|
+
attentionRequired: held.attentionRequired,
|
|
627
|
+
unsent: held.unsent,
|
|
628
|
+
reset: held.reset,
|
|
629
|
+
};
|
|
630
|
+
if (same(shown, next))
|
|
631
|
+
continue;
|
|
632
|
+
shown = next;
|
|
633
|
+
for (const listener of statusListeners)
|
|
634
|
+
listener(shown);
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
return statusShown;
|
|
300
638
|
}
|
|
301
|
-
function entryOf(batch) {
|
|
639
|
+
function entryOf(states, batch) {
|
|
302
640
|
if (localSaves)
|
|
303
641
|
return Object.freeze({ batch, state: 'local' });
|
|
304
|
-
const state =
|
|
305
|
-
if (state?.accepted !== undefined) {
|
|
306
|
-
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
307
|
-
}
|
|
642
|
+
const state = states.get(batch.id);
|
|
308
643
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
309
644
|
}
|
|
310
|
-
function entriesOf(locator) {
|
|
311
|
-
|
|
645
|
+
async function entriesOf(locator) {
|
|
646
|
+
if (isFenced(locator))
|
|
647
|
+
return [];
|
|
648
|
+
const { queue, states } = await workOf(locator);
|
|
649
|
+
return queue.map((batch) => entryOf(states, batch));
|
|
312
650
|
}
|
|
313
651
|
function emitQueue(locator) {
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
652
|
+
const listening = () => [...queueSubscriptions].filter((subscription) => subscription.locator === locator);
|
|
653
|
+
if (closed || listening().length === 0)
|
|
654
|
+
return Promise.resolve();
|
|
655
|
+
return publication(async () => {
|
|
656
|
+
if (closed || listening().length === 0)
|
|
657
|
+
return;
|
|
658
|
+
const entries = await entriesOf(locator);
|
|
659
|
+
if (closed)
|
|
660
|
+
return;
|
|
661
|
+
for (const subscription of listening())
|
|
317
662
|
subscription.notify(entries);
|
|
318
|
-
}
|
|
663
|
+
});
|
|
319
664
|
}
|
|
320
665
|
function applyChangeSet(set) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
666
|
+
liveQueries.touched(set);
|
|
667
|
+
figures.touched(set);
|
|
668
|
+
}
|
|
669
|
+
function recoverReads() {
|
|
670
|
+
liveQueries.recover();
|
|
671
|
+
figures.recover();
|
|
672
|
+
}
|
|
673
|
+
function unpublished(error) {
|
|
674
|
+
journal.record(`publication failed ${error instanceof Error ? error.name : 'unknown'}`);
|
|
675
|
+
}
|
|
676
|
+
async function prepareReplica(locator) {
|
|
677
|
+
for (;;) {
|
|
678
|
+
const held = (await storedRooms([locator])).get(locator);
|
|
679
|
+
if (held === undefined || closed || held.indexed)
|
|
680
|
+
return;
|
|
681
|
+
const key = held.record.key;
|
|
682
|
+
const done = await exclusive(async () => {
|
|
683
|
+
const local = await openLocal();
|
|
684
|
+
return store.rederive(local, key, composed(held.record));
|
|
685
|
+
});
|
|
686
|
+
if (!done.done)
|
|
687
|
+
continue;
|
|
688
|
+
if ((await storedRooms([locator])).get(locator)?.record.key !== key)
|
|
689
|
+
continue;
|
|
690
|
+
publishChangeSet(changeSet(locator, { documents: null }));
|
|
691
|
+
recoverReads();
|
|
692
|
+
queueReadiness();
|
|
329
693
|
return;
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async function unprepared() {
|
|
697
|
+
const held = (await covering()).rooms;
|
|
698
|
+
return [...unpreparedDemand, ...held.keys()].find((locator) => !(held.get(locator)?.indexed ?? false) && !unpreparable.has(locator));
|
|
699
|
+
}
|
|
700
|
+
function unpreparedBy(locator, error) {
|
|
701
|
+
unpreparable.add(locator);
|
|
702
|
+
journal.record(`preparation failed ${locator} ${error instanceof Error ? error.name : 'unknown'}`);
|
|
703
|
+
queueReadiness();
|
|
704
|
+
}
|
|
705
|
+
function prepare(locators = []) {
|
|
706
|
+
for (const locator of locators)
|
|
707
|
+
unpreparedDemand.add(locator);
|
|
708
|
+
if (preparing)
|
|
709
|
+
return;
|
|
710
|
+
preparing = true;
|
|
711
|
+
void (async () => {
|
|
712
|
+
try {
|
|
713
|
+
for (let waiting = await unprepared(); waiting !== undefined; waiting = await unprepared()) {
|
|
714
|
+
await yielded();
|
|
715
|
+
if (closed)
|
|
716
|
+
return;
|
|
717
|
+
unpreparedDemand.delete(waiting);
|
|
718
|
+
await prepareReplica(waiting).catch((error) => unpreparedBy(waiting, error));
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
journal.record('preparation failed');
|
|
723
|
+
}
|
|
724
|
+
finally {
|
|
725
|
+
preparing = false;
|
|
726
|
+
}
|
|
727
|
+
})();
|
|
728
|
+
}
|
|
729
|
+
function roomsOfReach(reach) {
|
|
730
|
+
return new Set([...reach.rooms, ...reach.documents.map(roomOfKey)]);
|
|
731
|
+
}
|
|
732
|
+
function everyRoom(held) {
|
|
733
|
+
return [...new Set([...declared.keys(), ...held.rooms.keys(), ...held.queues.keys()])];
|
|
734
|
+
}
|
|
735
|
+
function reachedRooms(plan, held) {
|
|
736
|
+
const reached = plan.reach === null ? null : roomsOfReach(plan.reach);
|
|
737
|
+
const named = plan.rooms ?? (reached === null ? everyRoom(held) : [...reached]);
|
|
738
|
+
return [...new Set(named)]
|
|
739
|
+
.filter((locator) => (reached === null || reached.has(locator)) && !plan.locked.includes(locator))
|
|
740
|
+
.sort();
|
|
741
|
+
}
|
|
742
|
+
function awaited(locator) {
|
|
743
|
+
return (!roomsListed ||
|
|
744
|
+
(declared.has(locator) && discovered.get(locator)?.replica.admission === 'admitted'));
|
|
745
|
+
}
|
|
746
|
+
function covered(plan, held) {
|
|
747
|
+
return coveredRooms(reachedRooms(plan, held), plan.rooms === null && plan.reach === null, held);
|
|
748
|
+
}
|
|
749
|
+
async function figuresCover() {
|
|
750
|
+
const held = await covering();
|
|
751
|
+
const locked = await lockedRooms();
|
|
752
|
+
const cover = coveredRooms(everyRoom(held)
|
|
753
|
+
.filter((locator) => !locked.includes(locator))
|
|
754
|
+
.sort(), true, held);
|
|
755
|
+
awaitPreparation(cover);
|
|
756
|
+
return 'pending' in cover ? cover : { rooms: cover.rooms };
|
|
757
|
+
}
|
|
758
|
+
function coveredRooms(locators, whole, held) {
|
|
759
|
+
if (locators.length === 0 && whole && !roomsListed)
|
|
760
|
+
return { pending: [] };
|
|
761
|
+
const pending = [];
|
|
762
|
+
const coverage = [];
|
|
763
|
+
const overlay = [];
|
|
764
|
+
const read = [];
|
|
765
|
+
for (const locator of locators) {
|
|
766
|
+
const replica = held.rooms.get(locator);
|
|
767
|
+
if (replica === undefined) {
|
|
768
|
+
if (awaited(locator))
|
|
769
|
+
pending.push({ room: locator, awaits: 'acquisition' });
|
|
335
770
|
continue;
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
771
|
+
}
|
|
772
|
+
if (!replica.indexed) {
|
|
773
|
+
pending.push({ room: locator, awaits: 'preparation' });
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
coverage.push({
|
|
777
|
+
room: locator,
|
|
778
|
+
generation: replica.transfer,
|
|
779
|
+
current: !roomInFlight(store, locator),
|
|
780
|
+
});
|
|
781
|
+
read.push({ locator, key: replica.key });
|
|
782
|
+
for (const id of held.queues.get(locator) ?? [])
|
|
783
|
+
overlay.push(id);
|
|
340
784
|
}
|
|
341
|
-
if (
|
|
785
|
+
if (pending.length > 0)
|
|
786
|
+
return { pending };
|
|
787
|
+
return { coverage: { rooms: coverage, indexes: [], overlay }, rooms: read };
|
|
788
|
+
}
|
|
789
|
+
function awaitPreparation(cover) {
|
|
790
|
+
if (!('pending' in cover))
|
|
342
791
|
return;
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
function
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (
|
|
351
|
-
return
|
|
352
|
-
|
|
792
|
+
const waiting = cover.pending.filter(({ awaits }) => awaits === 'preparation');
|
|
793
|
+
if (waiting.length > 0)
|
|
794
|
+
prepare(waiting.map(({ room }) => room));
|
|
795
|
+
}
|
|
796
|
+
function requiredRooms(given, held) {
|
|
797
|
+
const reach = planReach(given);
|
|
798
|
+
const listed = new Set(listedLocators(held));
|
|
799
|
+
if (reach === null)
|
|
800
|
+
return { listed: roomsListed, rooms: [...listed].sort() };
|
|
801
|
+
const named = [...roomsOfReach(reach)].sort();
|
|
802
|
+
if (roomsListed)
|
|
803
|
+
return { listed: true, rooms: named.filter((room) => listed.has(room)) };
|
|
804
|
+
return { listed: named.every((room) => held.has(room)), rooms: named };
|
|
805
|
+
}
|
|
806
|
+
function roomState(locator, held) {
|
|
807
|
+
const listedRoom = discovered.get(locator);
|
|
808
|
+
if (roomsListed &&
|
|
809
|
+
(listedRoom === undefined ||
|
|
810
|
+
listedRoom.replica.admission !== 'admitted' ||
|
|
811
|
+
unreadable.has(locator)))
|
|
812
|
+
return 'unadmitted';
|
|
813
|
+
const stored = held.get(locator);
|
|
814
|
+
if (stored === undefined)
|
|
815
|
+
return 'acquiring';
|
|
816
|
+
if (!stored.indexed)
|
|
817
|
+
return 'preparing';
|
|
818
|
+
return 'complete';
|
|
819
|
+
}
|
|
820
|
+
function conversationCoverage(reach) {
|
|
821
|
+
const declaredConversations = reach === null || reach.conversations;
|
|
822
|
+
if (!declaredConversations || options.adapter.conversations === undefined)
|
|
823
|
+
return 'undeclared';
|
|
824
|
+
if (!conversations.known() || !conversations.current())
|
|
825
|
+
return 'acquiring';
|
|
826
|
+
return 'complete';
|
|
827
|
+
}
|
|
828
|
+
function connectionOf(lastCompleted) {
|
|
829
|
+
if (socket.isOpen())
|
|
830
|
+
return 'connected';
|
|
831
|
+
if (status.providerUnreachable)
|
|
832
|
+
return 'unreachable';
|
|
833
|
+
return options.adapter.roomStream === undefined && lastCompleted !== null
|
|
834
|
+
? 'connected'
|
|
835
|
+
: 'connecting';
|
|
836
|
+
}
|
|
837
|
+
async function connectionNow() {
|
|
838
|
+
if (socket.isOpen() || status.providerUnreachable || options.adapter.roomStream !== undefined) {
|
|
839
|
+
return connectionOf(null);
|
|
840
|
+
}
|
|
841
|
+
return connectionOf((await (await openLocal()).facts()).lastCompleted);
|
|
842
|
+
}
|
|
843
|
+
async function readinessHeld() {
|
|
844
|
+
const local = await openLocal();
|
|
845
|
+
const [held, facts] = await Promise.all([covering(), local.facts()]);
|
|
846
|
+
return { held: held.rooms, facts };
|
|
847
|
+
}
|
|
848
|
+
function readinessFor(port, reach, { held, facts }) {
|
|
849
|
+
const required = requiredRooms(reach, held);
|
|
850
|
+
const coverage = required.rooms.map((room) => ({ room, state: roomState(room, held) }));
|
|
851
|
+
const preparing = coverage
|
|
852
|
+
.filter((room) => room.state === 'preparing' && !unpreparable.has(room.room))
|
|
853
|
+
.map(({ room }) => room);
|
|
854
|
+
if (preparing.length > 0)
|
|
855
|
+
prepare(preparing);
|
|
856
|
+
return readinessOf({
|
|
857
|
+
code: port.code,
|
|
858
|
+
coverage: {
|
|
859
|
+
listed: required.listed,
|
|
860
|
+
rooms: coverage,
|
|
861
|
+
conversations: conversationCoverage(reach),
|
|
862
|
+
},
|
|
863
|
+
persistence,
|
|
864
|
+
connection: connectionOf(facts.lastCompleted),
|
|
865
|
+
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
866
|
+
space: full ? 'full' : null,
|
|
867
|
+
installation: installationOf(store, coverage, held),
|
|
868
|
+
lost: storageLoss !== null,
|
|
869
|
+
established: facts.targets[port.target]?.established === true ||
|
|
870
|
+
(heldWhenOpened && usableWhenHeld.includes(port.target)),
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
const establishing = new Set();
|
|
874
|
+
function establish(name) {
|
|
875
|
+
if (establishing.has(name))
|
|
876
|
+
return;
|
|
877
|
+
establishing.add(name);
|
|
878
|
+
void durably(async (local) => {
|
|
879
|
+
const { targets } = await local.facts();
|
|
880
|
+
await local.fact('targets', { ...targets, [name]: { established: true } });
|
|
881
|
+
})
|
|
882
|
+
.then(() => {
|
|
883
|
+
queueReadiness();
|
|
884
|
+
void probeEachMinute();
|
|
885
|
+
}, () => {
|
|
886
|
+
journal.record(`target record failed ${name}`);
|
|
887
|
+
})
|
|
888
|
+
.finally(() => establishing.delete(name));
|
|
889
|
+
}
|
|
890
|
+
function deliverReadiness(watch, held) {
|
|
891
|
+
if (!readinessWatches.has(watch))
|
|
892
|
+
return;
|
|
893
|
+
const readiness = readinessFor(watch.port, watch.reach, held);
|
|
894
|
+
if (readiness.ready && held.facts.targets[watch.port.target]?.established !== true) {
|
|
895
|
+
establish(watch.port.target);
|
|
896
|
+
}
|
|
897
|
+
const shownReadiness = JSON.stringify(readiness);
|
|
898
|
+
if (shownReadiness === watch.last)
|
|
899
|
+
return;
|
|
900
|
+
watch.last = shownReadiness;
|
|
901
|
+
watch.deliver(structuredClone(readiness));
|
|
902
|
+
}
|
|
903
|
+
let readinessShown = Promise.resolve();
|
|
904
|
+
const readinessOwed = new Set();
|
|
905
|
+
function deliverAll(watches) {
|
|
906
|
+
const queued = readinessOwed.size > 0;
|
|
907
|
+
for (const watch of watches)
|
|
908
|
+
readinessOwed.add(watch);
|
|
909
|
+
if (queued)
|
|
910
|
+
return;
|
|
911
|
+
readinessShown = readinessShown
|
|
912
|
+
.then(async () => {
|
|
913
|
+
const owed = [...readinessOwed];
|
|
914
|
+
readinessOwed.clear();
|
|
915
|
+
const held = await readinessHeld();
|
|
916
|
+
if (closed)
|
|
917
|
+
return;
|
|
918
|
+
for (const watch of owed)
|
|
919
|
+
deliverReadiness(watch, held);
|
|
920
|
+
})
|
|
921
|
+
.catch(unpublished);
|
|
353
922
|
}
|
|
354
|
-
function
|
|
355
|
-
|
|
356
|
-
|
|
923
|
+
function queueReadiness() {
|
|
924
|
+
if (readinessQueued || closed || readinessWatches.size === 0)
|
|
925
|
+
return;
|
|
926
|
+
readinessQueued = true;
|
|
927
|
+
queueMicrotask(() => {
|
|
928
|
+
readinessQueued = false;
|
|
929
|
+
if (closed)
|
|
930
|
+
return;
|
|
931
|
+
deliverAll([...readinessWatches]);
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
function watchReadiness(port, reach, deliver) {
|
|
935
|
+
const watch = { port, reach, deliver, last: null };
|
|
936
|
+
readinessWatches.add(watch);
|
|
937
|
+
void openLocal().then(() => deliverAll([watch]), () => deliverAll([watch]));
|
|
938
|
+
return () => {
|
|
939
|
+
readinessWatches.delete(watch);
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
function providerReached() {
|
|
943
|
+
return (socket.isOpen() || !status.providerUnreachable) && refusal === null;
|
|
944
|
+
}
|
|
945
|
+
function filled() {
|
|
946
|
+
if (full || closed)
|
|
947
|
+
return;
|
|
948
|
+
full = true;
|
|
949
|
+
journal.record('space full');
|
|
950
|
+
queueReadiness();
|
|
951
|
+
void probeEachMinute();
|
|
952
|
+
}
|
|
953
|
+
async function installedTarget() {
|
|
954
|
+
if (heldWhenOpened && usableWhenHeld.length > 0)
|
|
955
|
+
return true;
|
|
956
|
+
const { targets } = await (await openLocal()).facts();
|
|
957
|
+
return Object.values(targets).some((target) => target.established);
|
|
958
|
+
}
|
|
959
|
+
async function probeEachMinute() {
|
|
960
|
+
if (!full || probing !== null || closed)
|
|
961
|
+
return;
|
|
962
|
+
if (!(await installedTarget().catch(() => false)))
|
|
963
|
+
return;
|
|
964
|
+
if (!full || probing !== null || closed)
|
|
965
|
+
return;
|
|
966
|
+
// A device whose storage filled after its install tries a write each minute, so space freed elsewhere ends its read-only mode without a Retry.
|
|
967
|
+
probing = setInterval(() => {
|
|
968
|
+
void probed();
|
|
969
|
+
}, 60_000);
|
|
970
|
+
}
|
|
971
|
+
function probed() {
|
|
972
|
+
testing ??= database.ask({ kind: 'space.probe' }).then(() => {
|
|
973
|
+
testing = null;
|
|
974
|
+
spaceFreed();
|
|
975
|
+
return true;
|
|
976
|
+
}, () => {
|
|
977
|
+
testing = null;
|
|
978
|
+
return false;
|
|
979
|
+
});
|
|
980
|
+
return testing;
|
|
981
|
+
}
|
|
982
|
+
function spaceFreed() {
|
|
983
|
+
if (!full)
|
|
984
|
+
return;
|
|
985
|
+
full = false;
|
|
986
|
+
if (probing !== null)
|
|
987
|
+
clearInterval(probing);
|
|
988
|
+
probing = null;
|
|
989
|
+
unpreparable.clear();
|
|
990
|
+
journal.record('space freed');
|
|
991
|
+
queueReadiness();
|
|
992
|
+
if (closed || authorization === undefined)
|
|
993
|
+
return;
|
|
994
|
+
if (options.adapter.roomStream === undefined)
|
|
995
|
+
void sync('unstreamed', null);
|
|
996
|
+
else
|
|
997
|
+
void resynchronize();
|
|
998
|
+
}
|
|
999
|
+
function sameCover(left, right) {
|
|
1000
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
1001
|
+
}
|
|
1002
|
+
async function coveredRows(plan, cover) {
|
|
1003
|
+
for (;;) {
|
|
1004
|
+
const before = await cover();
|
|
1005
|
+
if ('pending' in before)
|
|
1006
|
+
return { pending: before };
|
|
1007
|
+
const local = await openLocal();
|
|
1008
|
+
const keys = before.rooms.map(({ key }) => key);
|
|
1009
|
+
const changes = local.changes(keys);
|
|
1010
|
+
const current = () => local.changes(keys) === changes;
|
|
1011
|
+
const rows = await windowRows(local, plan, before.rooms, current);
|
|
1012
|
+
const after = await cover();
|
|
1013
|
+
if (rows !== null && sameCover(before, after))
|
|
1014
|
+
return { rows, cover: before, current };
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
async function windowRows(local, plan, rooms, unchanged) {
|
|
1018
|
+
const first = await local.query(plan, rooms, QUERY_BYTES);
|
|
1019
|
+
if (first.more === undefined || !('documents' in first)) {
|
|
1020
|
+
const { more: _more, ...rows } = first;
|
|
1021
|
+
return rows;
|
|
1022
|
+
}
|
|
1023
|
+
const documents = [...first.documents];
|
|
1024
|
+
for (let more = first.more; more !== undefined;) {
|
|
1025
|
+
if (!unchanged())
|
|
1026
|
+
return null;
|
|
1027
|
+
const page = await local.query({
|
|
1028
|
+
...plan,
|
|
1029
|
+
after: { text: more, values: JSON.parse(more) },
|
|
1030
|
+
limit: plan.limit === null ? null : plan.limit - documents.length,
|
|
1031
|
+
total: false,
|
|
1032
|
+
}, rooms, QUERY_BYTES);
|
|
1033
|
+
if (!('documents' in page))
|
|
1034
|
+
return null;
|
|
1035
|
+
documents.push(...page.documents);
|
|
1036
|
+
more = page.more;
|
|
1037
|
+
if (more === undefined) {
|
|
1038
|
+
return {
|
|
1039
|
+
documents,
|
|
1040
|
+
...(page.next === undefined ? {} : { next: page.next }),
|
|
1041
|
+
...(first.total === undefined ? {} : { total: first.total }),
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
function keyedOf(rows, coverage) {
|
|
1048
|
+
if ('groups' in rows) {
|
|
1049
|
+
return {
|
|
1050
|
+
result: { ...rows, coverage },
|
|
1051
|
+
keys: rows.groups.map((group) => JSON.stringify(group.key)),
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
return {
|
|
1055
|
+
result: { ...rows, coverage },
|
|
1056
|
+
keys: rows.documents.map((document) => documentKey(document.room, document.path)),
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
async function answeredPlan(plan, cover) {
|
|
1060
|
+
const answered = await coveredRows(plan, cover);
|
|
1061
|
+
if ('pending' in answered) {
|
|
1062
|
+
const pending = answered.pending;
|
|
1063
|
+
return { result: pending, keys: [], current: () => true };
|
|
1064
|
+
}
|
|
1065
|
+
const held = answered.cover;
|
|
1066
|
+
return { ...keyedOf(answered.rows, held.coverage), current: answered.current };
|
|
1067
|
+
}
|
|
1068
|
+
async function coverNow(plan) {
|
|
1069
|
+
const cover = covered(plan, await covering());
|
|
1070
|
+
awaitPreparation(cover);
|
|
1071
|
+
return cover;
|
|
1072
|
+
}
|
|
1073
|
+
async function rowsQuery(plan) {
|
|
1074
|
+
const held = await covering();
|
|
1075
|
+
const before = covered(plan, held);
|
|
1076
|
+
awaitPreparation(before);
|
|
1077
|
+
const seeded = seedApplies(seeding, plan, held);
|
|
1078
|
+
if (seeded !== null)
|
|
1079
|
+
return seededResult(seeded).result;
|
|
1080
|
+
if ('pending' in before)
|
|
1081
|
+
return before;
|
|
1082
|
+
const answered = await coveredRows(plan, () => coverNow(plan));
|
|
1083
|
+
if ('pending' in answered)
|
|
1084
|
+
return answered.pending;
|
|
1085
|
+
const shownCover = answered.cover;
|
|
1086
|
+
return { ...answered.rows, coverage: shownCover.coverage };
|
|
357
1087
|
}
|
|
358
1088
|
function publishChangeSet(set) {
|
|
359
1089
|
applyChangeSet(set);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
1090
|
+
for (const { locator, path } of set.documents) {
|
|
1091
|
+
if (path !== null)
|
|
1092
|
+
store.attached.drop(locator, path);
|
|
1093
|
+
}
|
|
1094
|
+
const shownNow = [];
|
|
1095
|
+
if (set.queues.length > 0 || set.drafts.length > 0)
|
|
1096
|
+
shownNow.push(show());
|
|
1097
|
+
if (set.rooms.length > 0)
|
|
1098
|
+
shownNow.push(emitRooms(set.rooms));
|
|
1099
|
+
else if (set.documents.length > 0) {
|
|
1100
|
+
shownNow.push(publishRooms('counted', [...new Set(set.documents.map(({ locator }) => locator))]));
|
|
1101
|
+
}
|
|
363
1102
|
for (const locator of set.queues)
|
|
364
|
-
emitQueue(locator);
|
|
365
|
-
if (set.drafts.length > 0
|
|
366
|
-
|
|
367
|
-
|
|
1103
|
+
shownNow.push(emitQueue(locator));
|
|
1104
|
+
if (set.drafts.length > 0)
|
|
1105
|
+
shownNow.push(publishRoomDrafts(drafting, false));
|
|
1106
|
+
else if (set.documents.length > 0) {
|
|
1107
|
+
shownNow.push(publishRoomDrafts(drafting, false, set.documents));
|
|
368
1108
|
}
|
|
369
1109
|
for (const app of set.presentations)
|
|
370
|
-
emitPresentation(app);
|
|
1110
|
+
shownNow.push(emitPresentation(drafting, app));
|
|
371
1111
|
conversations.changed(set);
|
|
372
|
-
|
|
373
|
-
function stopTimer() {
|
|
374
|
-
if (resetTimer === undefined)
|
|
375
|
-
return;
|
|
376
|
-
clearTimeout(resetTimer);
|
|
377
|
-
resetTimer = undefined;
|
|
378
|
-
}
|
|
379
|
-
function armTimer() {
|
|
380
|
-
stopTimer();
|
|
381
|
-
if (closed || stopping)
|
|
382
|
-
return;
|
|
383
|
-
let earliest = null;
|
|
384
|
-
for (const { submission } of pendings.values()) {
|
|
385
|
-
if (submission.state !== 'reset')
|
|
386
|
-
continue;
|
|
387
|
-
const at = Date.parse(submission.reset);
|
|
388
|
-
if (earliest === null || at < earliest)
|
|
389
|
-
earliest = at;
|
|
390
|
-
}
|
|
391
|
-
if (earliest === null)
|
|
392
|
-
return;
|
|
393
|
-
// A Save House held back goes again only once the reset its answer named has passed.
|
|
394
|
-
resetTimer = setTimeout(() => {
|
|
395
|
-
resetTimer = undefined;
|
|
396
|
-
submitAll();
|
|
397
|
-
}, Math.max(0, earliest - Date.now()));
|
|
1112
|
+
return Promise.all(shownNow).then(() => undefined);
|
|
398
1113
|
}
|
|
399
1114
|
async function refuse(reason, provider = null) {
|
|
400
|
-
stopTimer();
|
|
401
1115
|
discoveryPaused = true;
|
|
1116
|
+
socket.abortReads();
|
|
402
1117
|
try {
|
|
403
1118
|
if (reason === 'revoked-authorization') {
|
|
404
1119
|
await exclusive(async () => {
|
|
405
1120
|
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
1121
|
+
let held = new Map();
|
|
406
1122
|
try {
|
|
407
1123
|
const local = await openLocal();
|
|
408
|
-
await
|
|
1124
|
+
held = await storedRooms();
|
|
1125
|
+
await local.commit({
|
|
1126
|
+
rooms: [...held.values()].map(({ record: kept }) => {
|
|
1127
|
+
const record = { ...kept, model: revoked };
|
|
1128
|
+
return {
|
|
1129
|
+
locator: record.locator,
|
|
1130
|
+
key: record.key,
|
|
1131
|
+
record: storedRecord(record),
|
|
1132
|
+
composed: composedOf(composed(record)),
|
|
1133
|
+
};
|
|
1134
|
+
}),
|
|
1135
|
+
});
|
|
409
1136
|
}
|
|
410
1137
|
catch {
|
|
411
1138
|
journal.record('model revocation persistence failed');
|
|
412
1139
|
}
|
|
413
|
-
const
|
|
414
|
-
for (const locator of
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
for (const key of keys) {
|
|
419
|
-
invalidModels.add(key);
|
|
420
|
-
models.set(key, revoked);
|
|
421
|
-
liveModels.delete(key);
|
|
1140
|
+
const listedNow = listedLocators(held);
|
|
1141
|
+
for (const locator of listedNow) {
|
|
1142
|
+
if (!held.has(locator))
|
|
1143
|
+
unheldModels.set(locator, revoked);
|
|
422
1144
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const before = replicas.get(locator) ?? null;
|
|
426
|
-
if (before !== null) {
|
|
427
|
-
const record = {
|
|
428
|
-
...before.confirmed.record,
|
|
429
|
-
model: revoked,
|
|
430
|
-
selectionModel: revoked,
|
|
431
|
-
};
|
|
432
|
-
replicas.set(locator, replicaOf(confirmedOf(record, before.confirmed), before.queue, before));
|
|
433
|
-
}
|
|
434
|
-
rooms.set(locator, {
|
|
435
|
-
...room,
|
|
436
|
-
model: modelTypes(revoked),
|
|
437
|
-
typedSelection: room.typedSelection === null
|
|
438
|
-
? null
|
|
439
|
-
: { ...room.typedSelection, model: modelTypes(revoked) },
|
|
440
|
-
});
|
|
441
|
-
changed.push(changeSet(locator, before, replicas.get(locator) ?? null, { room: true }));
|
|
442
|
-
}
|
|
443
|
-
publishChangeSet(mergeChangeSets(changed));
|
|
1145
|
+
publishChangeSet(mergeChangeSets(listedNow.map((locator) => changeSet(locator, { documents: null, room: true }))));
|
|
1146
|
+
prepare();
|
|
444
1147
|
});
|
|
445
1148
|
}
|
|
446
1149
|
}
|
|
@@ -456,196 +1159,162 @@ export function composeRuntime(options) {
|
|
|
456
1159
|
}
|
|
457
1160
|
}
|
|
458
1161
|
function list(room, synchronizationAvailable) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
:
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
},
|
|
473
|
-
providerOwnedPaths: room.providerOwnedPaths,
|
|
474
|
-
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
475
|
-
synchronizationAvailable,
|
|
476
|
-
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
1162
|
+
if (isFenced(room.locator))
|
|
1163
|
+
return;
|
|
1164
|
+
declared.set(room.locator, {
|
|
1165
|
+
room: {
|
|
1166
|
+
name: room.name,
|
|
1167
|
+
locator: room.locator,
|
|
1168
|
+
handle: room.handle,
|
|
1169
|
+
operations: room.operations,
|
|
1170
|
+
replica: room.replica,
|
|
1171
|
+
typedSelection: room.typedSelection ?? null,
|
|
1172
|
+
providerOwnedPaths: room.providerOwnedPaths,
|
|
1173
|
+
},
|
|
1174
|
+
available: synchronizationAvailable,
|
|
477
1175
|
});
|
|
478
1176
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
1177
|
+
function listHeld(locator) {
|
|
1178
|
+
if (isFenced(locator))
|
|
1179
|
+
return;
|
|
1180
|
+
declared.set(locator, { room: null, available: false });
|
|
1181
|
+
}
|
|
1182
|
+
async function applyDiscovery(found, started, left) {
|
|
1183
|
+
const lockedBefore = await lockedRooms();
|
|
1184
|
+
const local = await openLocal();
|
|
1185
|
+
const stillFenced = new Set(await local.fences());
|
|
1186
|
+
const relisting = found
|
|
1187
|
+
.map(({ locator }) => locator)
|
|
1188
|
+
.filter((locator) => {
|
|
1189
|
+
const fence = store.fences.of(locator);
|
|
1190
|
+
return stillFenced.has(locator) && (fence === undefined || fence.at < started);
|
|
1191
|
+
});
|
|
1192
|
+
const relisted = relisting.length === 0 ? new Set() : await durably((held) => held.unfence(relisting));
|
|
1193
|
+
for (const { locator } of found) {
|
|
1194
|
+
const fence = store.fences.of(locator);
|
|
1195
|
+
if (fence === undefined || fence.at >= started)
|
|
1196
|
+
continue;
|
|
1197
|
+
if (relisted.has(locator) || (!stillFenced.has(locator) && !fence.written)) {
|
|
1198
|
+
store.fences.lift(locator);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
const kept = await local.held();
|
|
1202
|
+
await loseRooms(found
|
|
1203
|
+
.filter((room) => room.replica.admission !== 'admitted' &&
|
|
1204
|
+
(kept.held.has(room.locator) || store.installs.of(room.locator).transfer !== null))
|
|
1205
|
+
.map((room) => room.locator));
|
|
1206
|
+
const listed = found.filter(({ locator }) => !isFenced(locator) && (!stillFenced.has(locator) || relisted.has(locator)));
|
|
1207
|
+
const listedLocatorsNow = new Set(listed.map((room) => room.locator));
|
|
1208
|
+
for (const locator of listedLocatorsNow)
|
|
482
1209
|
scopedRefusals.delete(locator);
|
|
483
|
-
|
|
1210
|
+
if (left === undefined)
|
|
1211
|
+
discovered.clear();
|
|
484
1212
|
for (const room of listed)
|
|
485
1213
|
discovered.set(room.locator, room);
|
|
486
|
-
|
|
1214
|
+
const named = new Set(found.map((room) => room.locator));
|
|
1215
|
+
await loseRooms(left ??
|
|
1216
|
+
[...new Set([...declared.keys(), ...kept.held, ...store.installs.transferring()])].filter((locator) => !named.has(locator)));
|
|
1217
|
+
const held = await storedRooms(listed.map((room) => room.locator));
|
|
487
1218
|
for (const room of listed) {
|
|
488
|
-
const
|
|
489
|
-
if (
|
|
490
|
-
|
|
1219
|
+
const record = held.get(room.locator)?.record;
|
|
1220
|
+
if (record !== undefined && record.replica.selection !== room.replica.selection) {
|
|
1221
|
+
listHeld(room.locator);
|
|
491
1222
|
continue;
|
|
492
1223
|
}
|
|
493
1224
|
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
494
1225
|
}
|
|
1226
|
+
confirmLocks(listed);
|
|
495
1227
|
roomsListed = true;
|
|
496
1228
|
emitRooms();
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
1229
|
+
await relocked(lockedBefore);
|
|
1230
|
+
return listed;
|
|
1231
|
+
}
|
|
1232
|
+
function forget(locator) {
|
|
1233
|
+
confirmed.delete(locator);
|
|
1234
|
+
forgetInstall(store, locator);
|
|
1235
|
+
declared.delete(locator);
|
|
1236
|
+
discovered.delete(locator);
|
|
1237
|
+
unreadable.delete(locator);
|
|
1238
|
+
stale.delete(locator);
|
|
1239
|
+
repairable.delete(locator);
|
|
1240
|
+
unpreparable.delete(locator);
|
|
1241
|
+
scopedRefusals.delete(locator);
|
|
1242
|
+
unheldModels.delete(locator);
|
|
1243
|
+
store.forget(locator);
|
|
1244
|
+
}
|
|
1245
|
+
async function eraseFenced(locators) {
|
|
1246
|
+
for (const locator of locators) {
|
|
1247
|
+
while (await durably((local) => local.erase(locator, ERASE_BATCH)))
|
|
1248
|
+
await yielded();
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
let sweeping = false;
|
|
1252
|
+
function sweep() {
|
|
1253
|
+
if (sweeping || closed)
|
|
510
1254
|
return;
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
queue: true,
|
|
517
|
-
drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
|
|
518
|
-
}));
|
|
519
|
-
for (const locator of unlisted) {
|
|
520
|
-
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
521
|
-
pendings.delete(batch.id);
|
|
522
|
-
replicas.delete(locator);
|
|
523
|
-
queuesWithoutReplica.delete(locator);
|
|
524
|
-
rooms.delete(locator);
|
|
525
|
-
roomDrafts.delete(locator);
|
|
526
|
-
transfers.delete(locator);
|
|
527
|
-
unreadable.delete(locator);
|
|
528
|
-
stale.delete(locator);
|
|
529
|
-
scopedRefusals.delete(locator);
|
|
530
|
-
sourceRefusals.delete(locator);
|
|
531
|
-
declarationRefusals.delete(locator);
|
|
532
|
-
for (const attached of ['collection', 'typed-selection']) {
|
|
533
|
-
const key = modelKey(locator, attached);
|
|
534
|
-
models.delete(key);
|
|
535
|
-
liveModels.delete(key);
|
|
536
|
-
invalidModels.delete(key);
|
|
1255
|
+
sweeping = true;
|
|
1256
|
+
void (async () => {
|
|
1257
|
+
try {
|
|
1258
|
+
while (!closed && (await durably((local) => local.erase(null, ERASE_BATCH)))) {
|
|
1259
|
+
await yielded();
|
|
537
1260
|
}
|
|
538
|
-
journal.record(`room unlisted ${locator}`);
|
|
539
1261
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
const prior = models.get(key);
|
|
546
|
-
if (prior !== undefined &&
|
|
547
|
-
prior.status !== 'unavailable' &&
|
|
548
|
-
(persisted.status === 'unavailable' || !sameModel(prior.snapshot, persisted.snapshot))) {
|
|
549
|
-
invalidModels.add(key);
|
|
550
|
-
}
|
|
551
|
-
const model = persisted.status === 'unavailable' ||
|
|
552
|
-
liveModels.get(key) === JSON.stringify(persisted.snapshot)
|
|
553
|
-
? persisted
|
|
554
|
-
: cached(persisted);
|
|
555
|
-
models.set(key, model);
|
|
556
|
-
if (model.status === 'unavailable') {
|
|
557
|
-
liveModels.delete(key);
|
|
558
|
-
invalidModels.delete(key);
|
|
559
|
-
}
|
|
560
|
-
return model;
|
|
561
|
-
}
|
|
562
|
-
async function hydrate(opened) {
|
|
563
|
-
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known, staged,] = await Promise.all([
|
|
564
|
-
opened.all(),
|
|
565
|
-
opened.queues(),
|
|
566
|
-
opened.roomDrafts(),
|
|
567
|
-
opened.presentation(),
|
|
568
|
-
opened.conversations(),
|
|
569
|
-
opened.chatDrafts(),
|
|
570
|
-
opened.lastCompleted(),
|
|
571
|
-
opened.providerLimits(),
|
|
572
|
-
opened.capabilities(),
|
|
573
|
-
opened.member(),
|
|
574
|
-
opened.transfers(),
|
|
575
|
-
]);
|
|
576
|
-
transfers.clear();
|
|
577
|
-
for (const record of staged)
|
|
578
|
-
transfers.set(record.locator, record);
|
|
579
|
-
limits = declared;
|
|
580
|
-
capabilities = named;
|
|
581
|
-
member = known;
|
|
582
|
-
roomDrafts.clear();
|
|
583
|
-
for (const record of kept)
|
|
584
|
-
rememberRoomDraft(record);
|
|
585
|
-
presentations.clear();
|
|
586
|
-
for (const record of presented)
|
|
587
|
-
presentations.set(record.app, record.value);
|
|
588
|
-
conversations.hydrate(held, chatDrafts);
|
|
589
|
-
const queuedBatches = new Map(queues.map((queue) => [queue.locator, queue.pending.map(({ batch }) => batch)]));
|
|
590
|
-
pendings.clear();
|
|
591
|
-
for (const queue of queues) {
|
|
592
|
-
for (const { batch, state } of queue.pending)
|
|
593
|
-
pendings.set(batch.id, state);
|
|
594
|
-
}
|
|
595
|
-
const previousReplicas = new Map(replicas);
|
|
596
|
-
replicas.clear();
|
|
597
|
-
queuesWithoutReplica.clear();
|
|
598
|
-
const replicated = new Set(records.map((record) => record.locator));
|
|
599
|
-
for (const [locator, queue] of queuedBatches) {
|
|
600
|
-
if (!replicated.has(locator))
|
|
601
|
-
queuesWithoutReplica.set(locator, queue);
|
|
602
|
-
}
|
|
603
|
-
for (const record of records) {
|
|
604
|
-
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
605
|
-
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
606
|
-
const prior = previousReplicas.get(record.locator);
|
|
607
|
-
const confirmed = confirmedOf({ ...record, model, selectionModel }, prior?.confirmed);
|
|
608
|
-
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
609
|
-
const existing = rooms.get(record.locator);
|
|
610
|
-
if (existing === undefined)
|
|
611
|
-
list(record, false);
|
|
612
|
-
else {
|
|
613
|
-
rooms.set(record.locator, {
|
|
614
|
-
...existing,
|
|
615
|
-
roomVersion: record.roomVersion,
|
|
616
|
-
operations: record.operations,
|
|
617
|
-
replica: record.replica,
|
|
618
|
-
model: modelTypes(model),
|
|
619
|
-
typedSelection: record.typedSelection === null
|
|
620
|
-
? null
|
|
621
|
-
: {
|
|
622
|
-
roots: record.typedSelection.roots,
|
|
623
|
-
model: modelTypes(selectionModel),
|
|
624
|
-
},
|
|
625
|
-
});
|
|
1262
|
+
catch {
|
|
1263
|
+
journal.record('erasure failed');
|
|
1264
|
+
}
|
|
1265
|
+
finally {
|
|
1266
|
+
sweeping = false;
|
|
626
1267
|
}
|
|
1268
|
+
})();
|
|
1269
|
+
}
|
|
1270
|
+
function fenceLive(locators) {
|
|
1271
|
+
for (const locator of new Set(locators)) {
|
|
1272
|
+
if (!store.fences.raise(locator, discoveries))
|
|
1273
|
+
continue;
|
|
1274
|
+
forget(locator);
|
|
1275
|
+
journal.record(`room lost ${locator}`);
|
|
627
1276
|
}
|
|
628
|
-
return {
|
|
629
|
-
lastCompleted,
|
|
630
|
-
locators: new Set([...queuedBatches.keys(), ...replicas.keys()]),
|
|
631
|
-
read: mergeChangeSets([...new Set([...previousReplicas.keys(), ...replicas.keys()])].map((locator) => changeSet(locator, previousReplicas.get(locator) ?? null, replicas.get(locator) ?? null))),
|
|
632
|
-
};
|
|
633
1277
|
}
|
|
634
|
-
async function
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1278
|
+
async function loseRooms(locators) {
|
|
1279
|
+
fenceLive(locators);
|
|
1280
|
+
const fresh = [...new Set(locators)].filter((locator) => {
|
|
1281
|
+
const fence = store.fences.of(locator);
|
|
1282
|
+
if (fence === undefined || fence.written)
|
|
1283
|
+
return false;
|
|
1284
|
+
store.fences.mark(locator, true);
|
|
1285
|
+
return true;
|
|
1286
|
+
});
|
|
1287
|
+
if (fresh.length === 0)
|
|
1288
|
+
return;
|
|
1289
|
+
let held = [];
|
|
1290
|
+
try {
|
|
1291
|
+
await durably(async (local) => {
|
|
1292
|
+
held = await local.roomDrafts();
|
|
1293
|
+
await local.fence(fresh);
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
catch (error) {
|
|
1297
|
+
for (const locator of fresh)
|
|
1298
|
+
store.fences.mark(locator, false);
|
|
1299
|
+
throw error;
|
|
1300
|
+
}
|
|
1301
|
+
finally {
|
|
1302
|
+
publishChangeSet(mergeChangeSets(fresh.map((locator) => changeSet(locator, {
|
|
1303
|
+
documents: null,
|
|
1304
|
+
room: true,
|
|
1305
|
+
queue: true,
|
|
1306
|
+
drafts: held.filter((draft) => draft.locator === locator).map(({ path }) => path),
|
|
1307
|
+
}))));
|
|
1308
|
+
recoverReads();
|
|
1309
|
+
moveTo(status);
|
|
1310
|
+
}
|
|
1311
|
+
await durably((local) => unpair(accepting, local));
|
|
1312
|
+
await eraseFenced(fresh);
|
|
645
1313
|
}
|
|
646
|
-
async function open() {
|
|
647
|
-
|
|
648
|
-
|
|
1314
|
+
async function open(opened) {
|
|
1315
|
+
if (closed)
|
|
1316
|
+
throw new Error('runtime closed');
|
|
1317
|
+
const interrupted = await opened.fences();
|
|
649
1318
|
const kept = options.shell.generation;
|
|
650
1319
|
const storedGeneration = await opened.generation();
|
|
651
1320
|
const loss = detectStorageLoss(storedGeneration, kept);
|
|
@@ -658,330 +1327,42 @@ export function composeRuntime(options) {
|
|
|
658
1327
|
storageLoss = loss;
|
|
659
1328
|
emitStorageLoss();
|
|
660
1329
|
}
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
return opened;
|
|
664
|
-
}
|
|
665
|
-
function durably(work) {
|
|
666
|
-
return exclusive(async () => {
|
|
667
|
-
const local = await openLocal();
|
|
668
|
-
return work(local);
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
function openLocal() {
|
|
672
|
-
opening ??= open().catch((error) => {
|
|
673
|
-
opening = undefined;
|
|
674
|
-
throw error;
|
|
675
|
-
});
|
|
676
|
-
return opening;
|
|
677
|
-
}
|
|
678
|
-
function exclusive(work) {
|
|
679
|
-
const next = mutations.then(work, work);
|
|
680
|
-
mutations = next.then(() => undefined, () => undefined);
|
|
681
|
-
return next;
|
|
682
|
-
}
|
|
683
|
-
function withdrawable(batch) {
|
|
684
|
-
const state = pendings.get(batch.id);
|
|
685
|
-
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
686
|
-
}
|
|
687
|
-
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
688
|
-
const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
689
|
-
const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
|
|
690
|
-
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
691
|
-
const unqueued = withdrawn.map((batch) => ({
|
|
692
|
-
batch,
|
|
693
|
-
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
694
|
-
}));
|
|
695
|
-
const returning = [
|
|
696
|
-
...[...unqueued].reverse(),
|
|
697
|
-
...(settling.leaving === undefined ? [] : [settling.leaving]),
|
|
698
|
-
];
|
|
699
|
-
const written = new Map();
|
|
700
|
-
for (const { batch, returned } of returning) {
|
|
701
|
-
if (returned === null)
|
|
702
|
-
continue;
|
|
703
|
-
for (const saved of pendings.get(batch.id)?.drafts ?? []) {
|
|
704
|
-
const existing = written.get(saved.path) ?? roomDrafts.get(locator)?.get(saved.path);
|
|
705
|
-
written.set(saved.path, {
|
|
706
|
-
locator,
|
|
707
|
-
path: saved.path,
|
|
708
|
-
revision: roomDraftRevision(),
|
|
709
|
-
parts: returnedInto(existing?.parts, saved.parts),
|
|
710
|
-
returned,
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
|
|
715
|
-
for (const batch of previousQueue) {
|
|
716
|
-
if (!kept.includes(batch))
|
|
717
|
-
pendings.delete(batch.id);
|
|
718
|
-
}
|
|
719
|
-
for (const record of written.values())
|
|
720
|
-
rememberRoomDraft(record);
|
|
1330
|
+
const held = await opened.opened();
|
|
1331
|
+
heldWhenOpened = usableWhenHeld.length > 0 && (await opened.covering()).rooms.size > 0;
|
|
721
1332
|
try {
|
|
722
|
-
await
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
});
|
|
1333
|
+
await opened.demote();
|
|
1334
|
+
await database.ask({ kind: 'read.builds', build });
|
|
1335
|
+
for (const key of held.orphans)
|
|
1336
|
+
await opened.release(key);
|
|
727
1337
|
}
|
|
728
1338
|
catch (error) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
throw error;
|
|
742
|
-
}
|
|
743
|
-
queuesWithoutReplica.delete(locator);
|
|
744
|
-
for (const { batch, returned } of unqueued) {
|
|
745
|
-
const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
|
|
746
|
-
journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
|
|
747
|
-
}
|
|
748
|
-
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
749
|
-
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
750
|
-
room: settling.room,
|
|
751
|
-
queue: kept.length !== previousQueue.length,
|
|
752
|
-
drafts: [...written.keys()],
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
function adoptModels(locator, record) {
|
|
756
|
-
for (const [attached, model] of [
|
|
757
|
-
['collection', record.model],
|
|
758
|
-
['typed-selection', record.selectionModel],
|
|
759
|
-
]) {
|
|
760
|
-
const key = modelKey(locator, attached);
|
|
761
|
-
models.set(key, model);
|
|
762
|
-
invalidModels.delete(key);
|
|
763
|
-
if (model.status === 'current')
|
|
764
|
-
liveModels.set(key, JSON.stringify(model.snapshot));
|
|
765
|
-
else if (model.status === 'unavailable')
|
|
766
|
-
liveModels.delete(key);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
function remodel(locator, answers, held) {
|
|
770
|
-
const taken = (answer, previous, attached) => {
|
|
771
|
-
if (answer === undefined)
|
|
772
|
-
return previous;
|
|
773
|
-
const { state, refused } = attachedModel(answer, previous);
|
|
774
|
-
if (refused !== null) {
|
|
775
|
-
const where = refused.location === undefined ? '' : ` ${refused.location}`;
|
|
776
|
-
journal.record(`model invalid ${locator} ${attached} ${refused.identity} ${refused.rule}${where}`);
|
|
777
|
-
}
|
|
778
|
-
models.set(modelKey(locator, attached), state);
|
|
779
|
-
return state;
|
|
780
|
-
};
|
|
781
|
-
return {
|
|
782
|
-
collection: taken(answers?.collection, held.collection, 'collection'),
|
|
783
|
-
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
function publish(local, room, confirmed, landing = {}) {
|
|
787
|
-
const { base, stored } = landing;
|
|
788
|
-
return exclusive(async () => {
|
|
789
|
-
if (!continuing())
|
|
790
|
-
return true;
|
|
791
|
-
const prior = replicas.get(room.locator);
|
|
792
|
-
if (base !== undefined &&
|
|
793
|
-
(prior?.confirmed.record.roomVersion !== base.record.roomVersion ||
|
|
794
|
-
prior.confirmed.record.revisions !== base.record.revisions)) {
|
|
795
|
-
return false;
|
|
796
|
-
}
|
|
797
|
-
adoptModels(room.locator, confirmed.record);
|
|
798
|
-
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
799
|
-
const roomVersion = confirmed.record.roomVersion;
|
|
800
|
-
const accepted = queue.filter((batch) => {
|
|
801
|
-
const accepted = pendings.get(batch.id)?.accepted;
|
|
802
|
-
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
803
|
-
});
|
|
804
|
-
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true, ...(stored === undefined ? {} : { stored }) });
|
|
805
|
-
if (stored !== undefined)
|
|
806
|
-
transfers.delete(room.locator);
|
|
807
|
-
for (const batch of accepted)
|
|
808
|
-
pendings.delete(batch.id);
|
|
809
|
-
stale.delete(room.locator);
|
|
810
|
-
if (unreadable.delete(room.locator))
|
|
811
|
-
scopedRefusals.delete(room.locator);
|
|
812
|
-
sourceRefusals.delete(room.locator);
|
|
813
|
-
declarationRefusals.delete(room.locator);
|
|
814
|
-
list(room, true);
|
|
815
|
-
publishChangeSet(set);
|
|
816
|
-
moveTo({ ...status, ...attention() });
|
|
817
|
-
return true;
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
821
|
-
const local = await openLocal();
|
|
822
|
-
const replica = replicas.get(locator);
|
|
823
|
-
const room = rooms.get(locator);
|
|
824
|
-
if (replica === undefined || room === undefined) {
|
|
825
|
-
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
826
|
-
}
|
|
827
|
-
const saved = [];
|
|
828
|
-
for (const { path, revision } of named) {
|
|
829
|
-
const record = roomDrafts.get(locator)?.get(path);
|
|
830
|
-
if (record === undefined || record.revision !== revision) {
|
|
831
|
-
return { outcome: 'refused', code: 'stale', retryable: false, file: path };
|
|
832
|
-
}
|
|
833
|
-
if (!saved.some((draft) => draft.path === path))
|
|
834
|
-
saved.push({ path, parts: record.parts });
|
|
835
|
-
}
|
|
836
|
-
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
837
|
-
if (changed.length > 0)
|
|
838
|
-
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
839
|
-
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
840
|
-
const changes = parts.flatMap(({ changes }) => changes);
|
|
841
|
-
const requeue = requeued(replica.queue, changes, replica.visible, withdrawable, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
842
|
-
if (requeue.kind === 'refused')
|
|
843
|
-
return { outcome: 'refused', ...requeue.refusal };
|
|
844
|
-
const foreign = [...requeue.withdrawn.keys()].some((origin) => {
|
|
845
|
-
const writer = replica.queue.find((batch) => batch.id === origin)?.writtenBy;
|
|
846
|
-
return writer?.app !== writtenBy?.app || writer?.release !== writtenBy?.release;
|
|
847
|
-
});
|
|
848
|
-
if (foreign) {
|
|
849
|
-
return {
|
|
850
|
-
outcome: 'refused',
|
|
851
|
-
code: 'unknown-revision',
|
|
852
|
-
retryable: false,
|
|
853
|
-
file: [...requeue.queued].at(-1),
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
const withdrawing = requeue.withdrawn.size > 0;
|
|
857
|
-
const input = {
|
|
858
|
-
local: localSaves,
|
|
859
|
-
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
860
|
-
composition: compositionFor(replica.confirmed.record),
|
|
861
|
-
operations: room.operations,
|
|
862
|
-
providerOwnedPaths: room.providerOwnedPaths,
|
|
863
|
-
providerLimits: limits,
|
|
864
|
-
handle: room.handle,
|
|
865
|
-
visible: replica.visible,
|
|
866
|
-
changes,
|
|
867
|
-
bases: parts.flatMap(({ bases }) => bases),
|
|
868
|
-
writtenBy,
|
|
869
|
-
};
|
|
870
|
-
const checked = withdrawing ? buildBatch({ ...input, queued: requeue.queued }) : null;
|
|
871
|
-
if (checked?.kind === 'refused')
|
|
872
|
-
return { outcome: 'refused', ...checked.refusal };
|
|
873
|
-
const owners = [...requeue.owners].flatMap((origin) => {
|
|
874
|
-
const batch = requeue.withdrawn.get(origin);
|
|
875
|
-
return batch === undefined || batch === null ? [] : [{ origin, batch }];
|
|
876
|
-
});
|
|
877
|
-
if (owners.length > 1) {
|
|
878
|
-
return {
|
|
879
|
-
outcome: 'refused',
|
|
880
|
-
code: 'unknown-revision',
|
|
881
|
-
retryable: false,
|
|
882
|
-
file: [...requeue.queued].at(-1),
|
|
883
|
-
};
|
|
884
|
-
}
|
|
885
|
-
const built = !withdrawing
|
|
886
|
-
? buildBatch(input)
|
|
887
|
-
: requeue.changes.length === 0
|
|
888
|
-
? null
|
|
889
|
-
: buildBatch({
|
|
890
|
-
...input,
|
|
891
|
-
visible: replicaOf(replica.confirmed, requeue.queue, replica).visible,
|
|
892
|
-
changes: requeue.changes,
|
|
893
|
-
});
|
|
894
|
-
if (built?.kind === 'refused')
|
|
895
|
-
return { outcome: 'refused', ...built.refusal };
|
|
896
|
-
const [owner] = owners;
|
|
897
|
-
const join = built === null || owner === undefined
|
|
898
|
-
? null
|
|
899
|
-
: joined(requeue.queue, owner.batch, built.batch);
|
|
900
|
-
if (join?.kind === 'refused')
|
|
901
|
-
return { outcome: 'refused', ...join.refusal };
|
|
902
|
-
const withdrawn = new Map(requeue.withdrawn);
|
|
903
|
-
if (join !== null && owner !== undefined)
|
|
904
|
-
withdrawn.set(owner.origin, join.batch);
|
|
905
|
-
const drafted = new Map();
|
|
906
|
-
for (const [origin, replaced] of withdrawn) {
|
|
907
|
-
if (replaced === null)
|
|
908
|
-
continue;
|
|
909
|
-
const carried = withdrawnDrafts(pendings.get(origin)?.drafts ?? [], changes);
|
|
910
|
-
drafted.set(replaced.id, replaced === join?.batch
|
|
911
|
-
? [
|
|
912
|
-
...carried.map((held) => joinedDraft(held, saved)),
|
|
913
|
-
...saved.filter((draft) => !carried.some((held) => held.path === draft.path)),
|
|
914
|
-
]
|
|
915
|
-
: carried);
|
|
916
|
-
}
|
|
917
|
-
if (built !== null && join === null)
|
|
918
|
-
drafted.set(built.batch.id, saved);
|
|
919
|
-
const batches = join !== null ? join.queue : built === null ? requeue.queue : [...requeue.queue, built.batch];
|
|
920
|
-
for (const replaced of withdrawn.values()) {
|
|
921
|
-
if (replaced === null || !batches.includes(replaced))
|
|
922
|
-
continue;
|
|
923
|
-
const limited = limitedIn(batches, replaced, limits, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
924
|
-
if (limited !== null)
|
|
925
|
-
return { outcome: 'refused', ...limited };
|
|
926
|
-
}
|
|
927
|
-
const consumed = saved.map(({ path }) => path);
|
|
928
|
-
try {
|
|
929
|
-
await local.keepAuthored({
|
|
930
|
-
locator,
|
|
931
|
-
queue: batches,
|
|
932
|
-
saved: drafted,
|
|
933
|
-
consumedRoomDrafts: consumed,
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
catch {
|
|
937
|
-
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
938
|
-
}
|
|
939
|
-
for (const origin of withdrawn.keys())
|
|
940
|
-
pendings.delete(origin);
|
|
941
|
-
for (const [id, drafts] of drafted) {
|
|
942
|
-
pendings.set(id, { drafts, submission: { state: 'waiting' }, lost: false });
|
|
943
|
-
}
|
|
944
|
-
if (!localSaves)
|
|
945
|
-
recorded = false;
|
|
946
|
-
for (const path of consumed)
|
|
947
|
-
forgetRoomDraft(locator, path);
|
|
948
|
-
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
949
|
-
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
950
|
-
queue: true,
|
|
951
|
-
drafts: consumed,
|
|
952
|
-
}));
|
|
953
|
-
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
954
|
-
submitLater(locator);
|
|
955
|
-
const identifier = join?.batch.id ??
|
|
956
|
-
built?.batch.id ??
|
|
957
|
-
[...requeue.withdrawn].map(([origin, replaced]) => replaced?.id ?? origin).at(-1);
|
|
958
|
-
return { outcome: 'saved', identifier };
|
|
959
|
-
}
|
|
960
|
-
async function mark(local, locator, identifier, submission, lost) {
|
|
961
|
-
const state = pendings.get(identifier);
|
|
962
|
-
if (!continuing() ||
|
|
963
|
-
state === undefined ||
|
|
964
|
-
(JSON.stringify(state.submission) === JSON.stringify(submission) && state.lost === lost)) {
|
|
965
|
-
return;
|
|
1339
|
+
if (!refusedForSpace(error))
|
|
1340
|
+
throw error;
|
|
1341
|
+
}
|
|
1342
|
+
if (held.erasing || held.orphans.length > 0)
|
|
1343
|
+
queueMicrotask(sweep);
|
|
1344
|
+
await conversations.opened(opened);
|
|
1345
|
+
if (interrupted.length > 0) {
|
|
1346
|
+
queueMicrotask(() => {
|
|
1347
|
+
void eraseFenced(interrupted).catch(() => {
|
|
1348
|
+
journal.record('erasure failed');
|
|
1349
|
+
});
|
|
1350
|
+
});
|
|
966
1351
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
async function keepUpload(local, locator, identifier, upload) {
|
|
975
|
-
if (!continuing())
|
|
976
|
-
throw new Error('connection refused');
|
|
977
|
-
await local.keepUpload(locator, identifier, upload);
|
|
978
|
-
pendings.set(identifier, { ...pendings.get(identifier), upload });
|
|
979
|
-
if (!continuing())
|
|
980
|
-
throw new Error('connection refused');
|
|
1352
|
+
moveTo(status);
|
|
1353
|
+
prepare();
|
|
1354
|
+
emitRooms();
|
|
1355
|
+
void exclusive(() => unpair(accepting, opened)).catch(() => {
|
|
1356
|
+
journal.record('unpair failed');
|
|
1357
|
+
});
|
|
1358
|
+
return opened;
|
|
981
1359
|
}
|
|
982
1360
|
async function readHistory(locator, path, query = {}) {
|
|
983
1361
|
if (closed || refusal !== null)
|
|
984
1362
|
return UNCERTAIN;
|
|
1363
|
+
await firstDiscovery;
|
|
1364
|
+
if (!roomsNamed)
|
|
1365
|
+
return UNCERTAIN;
|
|
985
1366
|
try {
|
|
986
1367
|
await ensureAuthorization();
|
|
987
1368
|
}
|
|
@@ -990,6 +1371,7 @@ export function composeRuntime(options) {
|
|
|
990
1371
|
}
|
|
991
1372
|
if (closed || authorization === undefined || refusal !== null)
|
|
992
1373
|
return UNCERTAIN;
|
|
1374
|
+
const since = store.fences.count();
|
|
993
1375
|
const transfer = ['transfer', 'history', locator, path];
|
|
994
1376
|
if (query.revision !== undefined)
|
|
995
1377
|
transfer.push(query.revision);
|
|
@@ -1004,8 +1386,10 @@ export function composeRuntime(options) {
|
|
|
1004
1386
|
...(query.pageToken === undefined ? {} : { pageToken: query.pageToken }),
|
|
1005
1387
|
});
|
|
1006
1388
|
journal.record(`outcome history ${result.kind}`);
|
|
1389
|
+
if (store.fences.since(locator, since))
|
|
1390
|
+
return failure({ kind: 'room-not-readable', locator });
|
|
1007
1391
|
if (result.kind === 'scoped-refusal') {
|
|
1008
|
-
const room =
|
|
1392
|
+
const room = await recordAt(result.locator);
|
|
1009
1393
|
if (room !== undefined)
|
|
1010
1394
|
await scoped(result, room, await openLocal());
|
|
1011
1395
|
}
|
|
@@ -1018,99 +1402,48 @@ export function composeRuntime(options) {
|
|
|
1018
1402
|
return UNCERTAIN;
|
|
1019
1403
|
}
|
|
1020
1404
|
}
|
|
1021
|
-
function
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
if (
|
|
1039
|
-
return
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
return readBinary({ kind: 'path', locator, path, revision });
|
|
1064
|
-
const visible = replicas.get(locator)?.visible;
|
|
1065
|
-
const held = visible?.binaries[path];
|
|
1066
|
-
if (visible === undefined || held === undefined)
|
|
1067
|
-
return absentBinary(path);
|
|
1068
|
-
if (held.state === 'pending') {
|
|
1069
|
-
const blob = queuedBlob(locator, path, held.localId);
|
|
1070
|
-
return blob === null ? absentBinary(path) : { kind: 'binary', blob };
|
|
1071
|
-
}
|
|
1072
|
-
const record = binaryRecord(path, held, visible.revisions[path]);
|
|
1073
|
-
if (record.state !== 'accepted')
|
|
1074
|
-
return absentBinary(path);
|
|
1075
|
-
const replica = replicas.get(locator);
|
|
1076
|
-
const queued = (replica?.queue ?? []).flatMap((batch) => batch.changes);
|
|
1077
|
-
const holds = (at) => {
|
|
1078
|
-
const kept = replica?.confirmed.record.binaries[at];
|
|
1079
|
-
return (kept?.state === 'accepted' &&
|
|
1080
|
-
kept.fileRef === record.fileRef &&
|
|
1081
|
-
replica?.confirmed.record.revisions[at] === record.revision);
|
|
1082
|
-
};
|
|
1083
|
-
let confirmed = path;
|
|
1084
|
-
for (let index = queued.length - 1; index >= 0 && !holds(confirmed); index -= 1) {
|
|
1085
|
-
const change = queued[index];
|
|
1086
|
-
if (change.kind === 'move' && change.to === confirmed)
|
|
1087
|
-
confirmed = change.path;
|
|
1088
|
-
}
|
|
1089
|
-
return readBinary({
|
|
1090
|
-
kind: 'path',
|
|
1091
|
-
locator,
|
|
1092
|
-
path: confirmed,
|
|
1093
|
-
revision: record.revision,
|
|
1094
|
-
fileRef: record.fileRef,
|
|
1095
|
-
});
|
|
1096
|
-
}
|
|
1097
|
-
async function readBinaryReference(reference) {
|
|
1098
|
-
await ready;
|
|
1099
|
-
return readBinary({
|
|
1100
|
-
kind: 'reference',
|
|
1101
|
-
fileRef: reference.fileRef,
|
|
1102
|
-
referrer: reference.referrer,
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
function declaredAgents() {
|
|
1106
|
-
for (const capability of capabilities) {
|
|
1107
|
-
if (capability.conversationAgents !== undefined)
|
|
1108
|
-
return capability.conversationAgents;
|
|
1109
|
-
}
|
|
1110
|
-
return undefined;
|
|
1111
|
-
}
|
|
1112
|
-
function declaredAction(name) {
|
|
1113
|
-
for (const capability of capabilities) {
|
|
1405
|
+
async function located(room, path, destination) {
|
|
1406
|
+
const crossing = roomTarget(destination);
|
|
1407
|
+
const listing = await listingNow();
|
|
1408
|
+
if (crossing === null) {
|
|
1409
|
+
const named = target(path, destination);
|
|
1410
|
+
return named === null || !listing.has(room) ? null : { room, path: named };
|
|
1411
|
+
}
|
|
1412
|
+
const held = [...listing.values()].find((listed) => listed.handle === crossing.handle);
|
|
1413
|
+
return held === undefined ? null : { room: held.locator, path: crossing.path };
|
|
1414
|
+
}
|
|
1415
|
+
async function resolveReference(room, path, destination) {
|
|
1416
|
+
if (roomTarget(destination) === null && target(path, destination) === null) {
|
|
1417
|
+
return ABSENT_TARGET;
|
|
1418
|
+
}
|
|
1419
|
+
const at = await located(room, path, destination);
|
|
1420
|
+
if (at === null)
|
|
1421
|
+
return UNREADABLE;
|
|
1422
|
+
if (await locked(at.room))
|
|
1423
|
+
return LOCKED_UNREADABLE;
|
|
1424
|
+
const held = await recordAt(at.room);
|
|
1425
|
+
if (held !== undefined &&
|
|
1426
|
+
(await (await openLocal()).entries(held.key, 'visible', [at.path])).length > 0) {
|
|
1427
|
+
return { kind: 'document', ...at };
|
|
1428
|
+
}
|
|
1429
|
+
const history = await readHistory(at.room, at.path);
|
|
1430
|
+
if (history.kind === 'no-history' || history.kind === 'revision')
|
|
1431
|
+
return ABSENT_TARGET;
|
|
1432
|
+
if (history.kind !== 'history')
|
|
1433
|
+
return history;
|
|
1434
|
+
const [last] = history.revisions;
|
|
1435
|
+
if (last === undefined)
|
|
1436
|
+
return ABSENT_TARGET;
|
|
1437
|
+
return history.deleted === true
|
|
1438
|
+
? { kind: 'deleted', ...at, revision: last }
|
|
1439
|
+
: { kind: 'document', ...at };
|
|
1440
|
+
}
|
|
1441
|
+
async function cleanUp() {
|
|
1442
|
+
const freed = await database.ask({ kind: 'opened.clean' });
|
|
1443
|
+
return freed + ((await options.shell.cleanUp?.()) ?? 0);
|
|
1444
|
+
}
|
|
1445
|
+
async function declaredAction(name) {
|
|
1446
|
+
for (const capability of (await (await openLocal()).facts()).capabilities) {
|
|
1114
1447
|
for (const action of capability.actions ?? []) {
|
|
1115
1448
|
if (action.name === name)
|
|
1116
1449
|
return action;
|
|
@@ -1118,11 +1451,18 @@ export function composeRuntime(options) {
|
|
|
1118
1451
|
}
|
|
1119
1452
|
return undefined;
|
|
1120
1453
|
}
|
|
1121
|
-
async function
|
|
1454
|
+
async function calledRooms(name, input) {
|
|
1455
|
+
const member = (await declaredAction(name))?.room;
|
|
1456
|
+
if (member === undefined || input === null || typeof input !== 'object')
|
|
1457
|
+
return [];
|
|
1458
|
+
const named = input[member];
|
|
1459
|
+
return (Array.isArray(named) ? named : [named]).filter((room) => typeof room === 'string');
|
|
1460
|
+
}
|
|
1461
|
+
async function invokeAction(name, input, dispatching) {
|
|
1122
1462
|
await ready;
|
|
1123
1463
|
if (closed || refusal !== null)
|
|
1124
1464
|
return UNCERTAIN;
|
|
1125
|
-
if (declaredAction(name) === undefined)
|
|
1465
|
+
if ((await declaredAction(name)) === undefined)
|
|
1126
1466
|
return undeclared(name);
|
|
1127
1467
|
try {
|
|
1128
1468
|
await ensureAuthorization();
|
|
@@ -1132,11 +1472,12 @@ export function composeRuntime(options) {
|
|
|
1132
1472
|
}
|
|
1133
1473
|
if (closed || authorization === undefined || refusal !== null)
|
|
1134
1474
|
return UNCERTAIN;
|
|
1475
|
+
await dispatching?.();
|
|
1135
1476
|
journal.record(`transfer action ${name}`);
|
|
1136
1477
|
try {
|
|
1137
1478
|
const result = await options.adapter.action(authorization, { name, input });
|
|
1138
1479
|
if (result.kind === 'scoped-refusal') {
|
|
1139
|
-
const room =
|
|
1480
|
+
const room = await recordAt(result.locator);
|
|
1140
1481
|
if (room !== undefined)
|
|
1141
1482
|
await scoped(result, room, await openLocal());
|
|
1142
1483
|
}
|
|
@@ -1152,113 +1493,158 @@ export function composeRuntime(options) {
|
|
|
1152
1493
|
return UNCERTAIN;
|
|
1153
1494
|
}
|
|
1154
1495
|
}
|
|
1155
|
-
function
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion));
|
|
1162
|
-
}
|
|
1163
|
-
function writtenAfter(locator, roomVersion, path) {
|
|
1164
|
-
const written = replicas.get(locator)?.confirmed.record.written[path];
|
|
1165
|
-
return (written !== undefined &&
|
|
1166
|
-
options.adapter.reached(locator, written, roomVersion) &&
|
|
1167
|
-
!options.adapter.reached(locator, roomVersion, written));
|
|
1168
|
-
}
|
|
1169
|
-
async function pullRefresh(authorized, confirmed) {
|
|
1170
|
-
if (!continuing())
|
|
1171
|
-
return unconfirmed;
|
|
1496
|
+
async function readConversationHistory(request) {
|
|
1497
|
+
await ready;
|
|
1498
|
+
const stream = options.adapter.conversations;
|
|
1499
|
+
if (closed || refusal !== null || stream?.history === undefined)
|
|
1500
|
+
return UNCERTAIN;
|
|
1501
|
+
journal.record('transfer conversation history');
|
|
1172
1502
|
try {
|
|
1173
|
-
const result = await
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1503
|
+
const result = await stream.history(request);
|
|
1504
|
+
if (result.kind === 'revoked-authorization')
|
|
1505
|
+
await refuse('revoked-authorization');
|
|
1506
|
+
journal.record(`outcome conversation history ${result.kind}`);
|
|
1507
|
+
return calledResult(result);
|
|
1177
1508
|
}
|
|
1178
|
-
catch
|
|
1179
|
-
journal.record(
|
|
1180
|
-
|
|
1509
|
+
catch {
|
|
1510
|
+
journal.record('outcome conversation history transport-uncertainty');
|
|
1511
|
+
return UNCERTAIN;
|
|
1181
1512
|
}
|
|
1182
1513
|
}
|
|
1183
|
-
function
|
|
1184
|
-
return
|
|
1185
|
-
held: (locator) => transfers.get(locator) ?? null,
|
|
1186
|
-
staged: (record) => local.staged(record),
|
|
1187
|
-
stage: (record, page) => exclusive(async () => {
|
|
1188
|
-
if (!continuing())
|
|
1189
|
-
return false;
|
|
1190
|
-
await local.stage(record, page);
|
|
1191
|
-
transfers.set(record.locator, record);
|
|
1192
|
-
return true;
|
|
1193
|
-
}),
|
|
1194
|
-
abandon: (locator) => exclusive(async () => {
|
|
1195
|
-
if (!continuing())
|
|
1196
|
-
return;
|
|
1197
|
-
await local.abandon(locator);
|
|
1198
|
-
transfers.delete(locator);
|
|
1199
|
-
}),
|
|
1200
|
-
};
|
|
1514
|
+
function unsettled(result) {
|
|
1515
|
+
return result.kind !== 'action' && result.retryable;
|
|
1201
1516
|
}
|
|
1202
|
-
async function
|
|
1203
|
-
|
|
1204
|
-
|
|
1517
|
+
async function recoveredAction(name, input) {
|
|
1518
|
+
const operation = operationOf(input);
|
|
1519
|
+
if (operation === null)
|
|
1520
|
+
return invokeAction(name, input);
|
|
1521
|
+
const kept = async () => (await openLocal()).keepCall({ operation, name, input });
|
|
1522
|
+
calling.add(operation);
|
|
1205
1523
|
try {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1524
|
+
let result = await invokeAction(name, input, kept);
|
|
1525
|
+
if (unsettled(result)) {
|
|
1526
|
+
await recovery.attempt(`action ${operation}`, async () => {
|
|
1527
|
+
if (closed || refusal !== null)
|
|
1528
|
+
return 'done';
|
|
1529
|
+
if (!socket.isOpen())
|
|
1530
|
+
return 'again';
|
|
1531
|
+
result = await invokeAction(name, input, kept);
|
|
1532
|
+
return unsettled(result) ? 'again' : 'done';
|
|
1533
|
+
}, { again: true });
|
|
1534
|
+
}
|
|
1535
|
+
if (!unsettled(result))
|
|
1536
|
+
await (await openLocal()).dropCall(operation);
|
|
1212
1537
|
return result;
|
|
1213
1538
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
throw error;
|
|
1539
|
+
finally {
|
|
1540
|
+
calling.delete(operation);
|
|
1217
1541
|
}
|
|
1218
1542
|
}
|
|
1219
|
-
async function
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
return null;
|
|
1543
|
+
async function resendCalls(signal) {
|
|
1544
|
+
if (!socket.isOpen() || closed || stopping)
|
|
1545
|
+
return 'done';
|
|
1546
|
+
const local = await openLocal();
|
|
1547
|
+
for (const held of await local.calls()) {
|
|
1548
|
+
if (calling.has(held.operation))
|
|
1549
|
+
continue;
|
|
1550
|
+
if (closed || signal.aborted)
|
|
1551
|
+
return 'done';
|
|
1552
|
+
calling.add(held.operation);
|
|
1553
|
+
try {
|
|
1554
|
+
if (unsettled(await invokeAction(held.name, held.input)))
|
|
1555
|
+
return 'again';
|
|
1556
|
+
await local.dropCall(held.operation);
|
|
1235
1557
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
1239
|
-
const reached = now !== undefined &&
|
|
1240
|
-
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
1241
|
-
return reached ? null : advance(room, local, authorized);
|
|
1242
|
-
}
|
|
1243
|
-
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
1244
|
-
return null;
|
|
1558
|
+
finally {
|
|
1559
|
+
calling.delete(held.operation);
|
|
1245
1560
|
}
|
|
1246
|
-
if (refreshed.kind !== 'beyond-horizon')
|
|
1247
|
-
return refreshed;
|
|
1248
1561
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1562
|
+
return 'done';
|
|
1563
|
+
}
|
|
1564
|
+
function continuing() {
|
|
1565
|
+
return !closed && !discoveryPaused;
|
|
1566
|
+
}
|
|
1567
|
+
async function advance(room, local, authorized, running, unheld) {
|
|
1568
|
+
const live = () => running() && !full;
|
|
1569
|
+
if (!live())
|
|
1251
1570
|
return unconfirmed;
|
|
1571
|
+
const held = unheld === undefined ? await recordAt(room.locator) : undefined;
|
|
1572
|
+
if (options.adapter.roomStream !== undefined &&
|
|
1573
|
+
(held !== undefined || !store.installs.of(room.locator).beyondHorizon)) {
|
|
1574
|
+
stale.delete(room.locator);
|
|
1575
|
+
await name(room.locator);
|
|
1576
|
+
return null;
|
|
1577
|
+
}
|
|
1578
|
+
const refresh = reads.refresh;
|
|
1579
|
+
if (held !== undefined && refresh !== undefined) {
|
|
1580
|
+
const refreshed = await store.ahead((hold) => refreshedRoom(room, local, authorized, held, live, hold, refresh));
|
|
1581
|
+
if (refreshed === 'behind')
|
|
1582
|
+
return advance(room, local, authorized, live);
|
|
1583
|
+
if (refreshed !== 'beyond-horizon')
|
|
1584
|
+
return refreshed;
|
|
1585
|
+
if (reloadsDue.has(room.locator))
|
|
1586
|
+
return unconfirmed;
|
|
1587
|
+
replaced(store, [room.locator]);
|
|
1588
|
+
}
|
|
1589
|
+
const prior = held === undefined ? undefined : await recordAt(room.locator);
|
|
1590
|
+
const from = standingAt(store, room.locator, prior);
|
|
1591
|
+
const bootstrapped = await pullBootstrap(installer, authorized, room, local, live, prior, reads, unheld);
|
|
1252
1592
|
if (bootstrapped.kind !== 'staged')
|
|
1253
|
-
return bootstrapped;
|
|
1254
|
-
|
|
1255
|
-
|
|
1593
|
+
return live() ? bootstrapped : unconfirmed;
|
|
1594
|
+
const landing = { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from };
|
|
1595
|
+
if (await publish(installer, local, room, bootstrapped.record, landing, live)) {
|
|
1596
|
+
return live() ? null : unconfirmed;
|
|
1597
|
+
}
|
|
1598
|
+
return (await reachedBy(installer, room.locator, bootstrapped.record.roomVersion))
|
|
1599
|
+
? null
|
|
1600
|
+
: advance(room, local, authorized, live);
|
|
1256
1601
|
}
|
|
1257
|
-
async function
|
|
1258
|
-
|
|
1602
|
+
async function refreshedRoom(room, local, authorized, held, live, hold, refresh) {
|
|
1603
|
+
const refreshed = await pullRefresh(installer, authorized, held, live, hold, refresh);
|
|
1604
|
+
if (!live())
|
|
1605
|
+
return unconfirmed;
|
|
1606
|
+
if (refreshed.kind === 'still-current') {
|
|
1259
1607
|
stale.delete(room.locator);
|
|
1608
|
+
if (unreadable.delete(room.locator)) {
|
|
1609
|
+
scopedRefusals.delete(room.locator);
|
|
1610
|
+
list(room, true);
|
|
1611
|
+
emitRooms();
|
|
1612
|
+
}
|
|
1613
|
+
return null;
|
|
1614
|
+
}
|
|
1615
|
+
if (refreshed.kind === 'advanced') {
|
|
1616
|
+
const landing = {
|
|
1617
|
+
kind: 'refreshed',
|
|
1618
|
+
from: refreshed.from,
|
|
1619
|
+
remodelled: refreshed.remodelled,
|
|
1620
|
+
};
|
|
1621
|
+
if (!(await publish(installer, local, room, refreshed.record, landing, live))) {
|
|
1622
|
+
const now = (await recordAt(room.locator))?.roomVersion;
|
|
1623
|
+
const reached = now !== undefined &&
|
|
1624
|
+
options.adapter.reached(room.locator, now, refreshed.record.roomVersion);
|
|
1625
|
+
return reached ? null : 'behind';
|
|
1626
|
+
}
|
|
1627
|
+
await retainModel(local, room, null);
|
|
1628
|
+
return null;
|
|
1629
|
+
}
|
|
1630
|
+
return refreshed.kind === 'beyond-horizon' ? 'beyond-horizon' : refreshed;
|
|
1631
|
+
}
|
|
1632
|
+
async function withhold(withheld, room, local, progress) {
|
|
1633
|
+
if (withheld.kind === 'unconfirmed' || isFenced(room.locator))
|
|
1634
|
+
return;
|
|
1635
|
+
if (withheld.kind === 'not-ready') {
|
|
1636
|
+
progress.complete = false;
|
|
1637
|
+
askAgain(room.locator);
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
stale.add(room.locator);
|
|
1641
|
+
if (withheld.kind === 'transport-uncertainty' || withheld.kind === 'retryable-refusal') {
|
|
1642
|
+
const reset = withheld.kind === 'retryable-refusal' ? withheld.reset : undefined;
|
|
1643
|
+
repairable.set(room.locator, reset);
|
|
1644
|
+
repairLater(room.locator, reset === undefined ? { again: true } : { at: Date.parse(reset) });
|
|
1645
|
+
}
|
|
1260
1646
|
else
|
|
1261
|
-
|
|
1647
|
+
repairable.delete(room.locator);
|
|
1262
1648
|
if (withheld.kind === 'scoped-refusal') {
|
|
1263
1649
|
await scoped(withheld, room, local);
|
|
1264
1650
|
}
|
|
@@ -1269,308 +1655,141 @@ export function composeRuntime(options) {
|
|
|
1269
1655
|
journal.record(`refusal ${withheld.kind} ${withheld.code}`);
|
|
1270
1656
|
}
|
|
1271
1657
|
else if (withheld.kind === 'room-not-readable') {
|
|
1272
|
-
|
|
1273
|
-
list(room, false);
|
|
1274
|
-
emitRooms();
|
|
1275
|
-
}
|
|
1276
|
-
else if (withheld.kind === 'refused') {
|
|
1277
|
-
refusedSource(room, withheld);
|
|
1658
|
+
roomUnreadable(room);
|
|
1278
1659
|
}
|
|
1279
1660
|
else if (withheld.kind === 'revoked-authorization') {
|
|
1280
1661
|
await refuse('revoked-authorization');
|
|
1281
1662
|
}
|
|
1282
1663
|
}
|
|
1283
|
-
function
|
|
1284
|
-
sourceRefusals.set(room.locator, {
|
|
1285
|
-
code: refusal.rule,
|
|
1286
|
-
retryable: false,
|
|
1287
|
-
fields: {
|
|
1288
|
-
file: refusal.file,
|
|
1289
|
-
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
1290
|
-
...(refusal.referrers === undefined ? {} : { referrers: refusal.referrers }),
|
|
1291
|
-
},
|
|
1292
|
-
});
|
|
1293
|
-
declarationRefusals.delete(room.locator);
|
|
1664
|
+
function roomUnreadable(room) {
|
|
1294
1665
|
unreadable.add(room.locator);
|
|
1295
1666
|
list(room, false);
|
|
1296
1667
|
emitRooms();
|
|
1297
|
-
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
1298
|
-
}
|
|
1299
|
-
function leaving(batch, result) {
|
|
1300
|
-
if (result.kind === 'expired')
|
|
1301
|
-
return { batch, returned: returnedBy('expired') };
|
|
1302
|
-
return {
|
|
1303
|
-
batch,
|
|
1304
|
-
returned: {
|
|
1305
|
-
kind: result.kind,
|
|
1306
|
-
code: result.code,
|
|
1307
|
-
fields: result.fields,
|
|
1308
|
-
retryable: result.retryable,
|
|
1309
|
-
},
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
function logAccepted(locator, submitted, accepted) {
|
|
1313
|
-
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
1314
|
-
(accepted.author === undefined ? '' : ` author ${accepted.author}`) +
|
|
1315
|
-
(accepted.time === undefined ? '' : ` time ${accepted.time}`));
|
|
1316
|
-
}
|
|
1317
|
-
async function adoptAccepted(room, local, submitted, accepted) {
|
|
1318
|
-
if (!continuing())
|
|
1319
|
-
return unconfirmed;
|
|
1320
|
-
const current = replicas.get(room.locator);
|
|
1321
|
-
if (current === undefined || !current.queue.some((batch) => batch.id === submitted.id)) {
|
|
1322
|
-
return null;
|
|
1323
|
-
}
|
|
1324
|
-
const held = current.confirmed.record.roomVersion;
|
|
1325
|
-
logAccepted(room.locator, submitted, accepted);
|
|
1326
|
-
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1327
|
-
? { kind: 'staged', confirmed: current.confirmed }
|
|
1328
|
-
: applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1329
|
-
? accepted.roomVersion
|
|
1330
|
-
: held, accepted.roomVersion);
|
|
1331
|
-
if (applied.kind !== 'staged') {
|
|
1332
|
-
const state = pendings.get(submitted.id);
|
|
1333
|
-
await local.accept(room.locator, submitted.id, accepted.roomVersion);
|
|
1334
|
-
if (state !== undefined)
|
|
1335
|
-
pendings.set(submitted.id, { ...state, accepted: accepted.roomVersion });
|
|
1336
|
-
emitQueue(room.locator);
|
|
1337
|
-
return applied;
|
|
1338
|
-
}
|
|
1339
|
-
const set = await settle(local, room.locator, current, applied.confirmed, current.queue.filter((batch) => batch.id !== submitted.id), { room: true });
|
|
1340
|
-
pendings.delete(submitted.id);
|
|
1341
|
-
unreadable.delete(room.locator);
|
|
1342
|
-
sourceRefusals.delete(room.locator);
|
|
1343
|
-
declarationRefusals.delete(room.locator);
|
|
1344
|
-
list(room, true);
|
|
1345
|
-
publishChangeSet(set);
|
|
1346
|
-
moveTo({ ...status, ...attention() });
|
|
1347
|
-
return null;
|
|
1348
|
-
}
|
|
1349
|
-
async function adopted(room, local, submitted, accepted) {
|
|
1350
|
-
const failed = await exclusive(() => adoptAccepted(room, local, submitted, accepted));
|
|
1351
|
-
if (failed === null)
|
|
1352
|
-
return true;
|
|
1353
|
-
if (failed.kind === 'refused' && continuing())
|
|
1354
|
-
refusedSource(room, failed);
|
|
1355
|
-
return false;
|
|
1356
1668
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
};
|
|
1370
|
-
if (submission.state === 'reset' && Date.now() < Date.parse(submission.reset)) {
|
|
1371
|
-
progress.complete = false;
|
|
1372
|
-
return;
|
|
1373
|
-
}
|
|
1374
|
-
if (writesHeld()) {
|
|
1375
|
-
progress.complete = false;
|
|
1376
|
-
journal.record(`submission ${room.locator} ${head.id} held`);
|
|
1377
|
-
return;
|
|
1378
|
-
}
|
|
1379
|
-
const lost = earlier || submission.state === 'submitted';
|
|
1380
|
-
let result;
|
|
1381
|
-
journal.record(`submission ${room.locator} ${head.id}`);
|
|
1382
|
-
await exclusive(() => mark(local, room.locator, head.id, { state: 'submitted' }, lost));
|
|
1383
|
-
if (discoveryPaused)
|
|
1384
|
-
return;
|
|
1385
|
-
if (writesHeld()) {
|
|
1386
|
-
await exclusive(() => mark(local, room.locator, head.id, submission, earlier));
|
|
1387
|
-
progress.complete = false;
|
|
1388
|
-
journal.record(`submission ${room.locator} ${head.id} held`);
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
|
-
const opensBefore = openSignals;
|
|
1392
|
-
const answered = awaitingAnswer(room.locator);
|
|
1393
|
-
try {
|
|
1394
|
-
result = await options.adapter.mutate(authorized, {
|
|
1395
|
-
locator: room.locator,
|
|
1396
|
-
changeBatch: head,
|
|
1397
|
-
...(state?.upload === undefined ? {} : { upload: state.upload }),
|
|
1398
|
-
}, (upload) => exclusive(() => keepUpload(local, room.locator, head.id, upload)));
|
|
1399
|
-
}
|
|
1400
|
-
catch {
|
|
1401
|
-
answered();
|
|
1402
|
-
progress.complete = false;
|
|
1403
|
-
if (continuing())
|
|
1404
|
-
progress.reachable = false;
|
|
1669
|
+
function askAgain(locator) {
|
|
1670
|
+
const held = unready.get(locator) ?? { tries: 0, timer: null };
|
|
1671
|
+
unready.set(locator, held);
|
|
1672
|
+
if (held.timer !== null)
|
|
1673
|
+
return;
|
|
1674
|
+
const wait = NOT_READY_MS[Math.min(held.tries, NOT_READY_MS.length - 1)];
|
|
1675
|
+
held.tries += 1;
|
|
1676
|
+
held.timer = setTimeout(() => {
|
|
1677
|
+
held.timer = null;
|
|
1678
|
+
const room = discovered.get(locator);
|
|
1679
|
+
const unstreamed = options.adapter.roomStream === undefined;
|
|
1680
|
+
if (closed || stopping || (!unstreamed && !socket.isOpen()) || room === undefined)
|
|
1405
1681
|
return;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1682
|
+
const progress = { complete: true, reachable: true };
|
|
1683
|
+
void openLocal()
|
|
1684
|
+
.then((local) => roomTask(locator, (live) => loadRoom(room, local, progress, live)))
|
|
1685
|
+
.then(() => rest(progress), () => undefined);
|
|
1686
|
+
}, wait);
|
|
1687
|
+
}
|
|
1688
|
+
function readied(locator) {
|
|
1689
|
+
const held = unready.get(locator);
|
|
1690
|
+
if (held === undefined)
|
|
1691
|
+
return;
|
|
1692
|
+
if (held.timer !== null)
|
|
1693
|
+
clearTimeout(held.timer);
|
|
1694
|
+
unready.delete(locator);
|
|
1695
|
+
}
|
|
1696
|
+
function unreadied() {
|
|
1697
|
+
for (const locator of [...unready.keys()])
|
|
1698
|
+
readied(locator);
|
|
1699
|
+
}
|
|
1700
|
+
function declaredAsHeld(held, room) {
|
|
1701
|
+
return (held === undefined ||
|
|
1702
|
+
(JSON.stringify(held.operations) === JSON.stringify(room.operations) &&
|
|
1703
|
+
JSON.stringify(held.replica) === JSON.stringify(room.replica) &&
|
|
1704
|
+
JSON.stringify(held.providerOwnedPaths) === JSON.stringify(room.providerOwnedPaths) &&
|
|
1705
|
+
JSON.stringify(held.typedSelection) === JSON.stringify(room.typedSelection ?? null)));
|
|
1706
|
+
}
|
|
1707
|
+
async function keepNaming(local, room) {
|
|
1708
|
+
const held = await recordAt(room.locator);
|
|
1709
|
+
if (held === undefined || (held.name === room.name && held.handle === room.handle))
|
|
1710
|
+
return;
|
|
1711
|
+
await exclusive(async () => {
|
|
1409
1712
|
if (!continuing())
|
|
1410
1713
|
return;
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
progress.reachable = false;
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
|
-
if (result.kind === 'room-not-readable') {
|
|
1417
|
-
progress.complete = false;
|
|
1418
|
-
unreadable.add(room.locator);
|
|
1419
|
-
list(room, false);
|
|
1420
|
-
emitRooms();
|
|
1421
|
-
return;
|
|
1422
|
-
}
|
|
1423
|
-
if (result.kind === 'revoked-authorization') {
|
|
1424
|
-
await refuse('revoked-authorization');
|
|
1425
|
-
return;
|
|
1426
|
-
}
|
|
1427
|
-
if (result.kind === 'scoped-refusal') {
|
|
1428
|
-
await scoped(result, room, local);
|
|
1429
|
-
progress.complete = false;
|
|
1430
|
-
return;
|
|
1431
|
-
}
|
|
1432
|
-
if (result.kind === 'write-halted') {
|
|
1433
|
-
journal.record(`outcome ${room.locator} ${head.id} write-halted`);
|
|
1434
|
-
await exclusive(() => mark(local, room.locator, head.id, { state: 'waiting' }, lost));
|
|
1435
|
-
if (serviceMode === 'open' && openSignals !== opensBefore)
|
|
1436
|
-
continue;
|
|
1437
|
-
haltedRefusal = true;
|
|
1438
|
-
progress.complete = false;
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1441
|
-
if (result.kind === 'conflicted' ||
|
|
1442
|
-
result.kind === 'rejected' ||
|
|
1443
|
-
result.kind === 'expired' ||
|
|
1444
|
-
result.kind === 'retryable-refusal') {
|
|
1445
|
-
if (result.kind === 'expired') {
|
|
1446
|
-
const withheld = await advance(room, local, authorized);
|
|
1447
|
-
if (withheld !== null)
|
|
1448
|
-
return withhold(withheld, room, local, progress);
|
|
1449
|
-
}
|
|
1450
|
-
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` +
|
|
1451
|
-
(result.kind === 'conflicted' && result.roomVersion !== undefined
|
|
1452
|
-
? ` ${result.roomVersion}`
|
|
1453
|
-
: ''));
|
|
1454
|
-
if (result.kind !== 'expired')
|
|
1455
|
-
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
1456
|
-
}
|
|
1457
|
-
if (result.kind === 'retryable-refusal') {
|
|
1458
|
-
const reset = result.reset;
|
|
1459
|
-
progress.complete = false;
|
|
1460
|
-
await exclusive(() => mark(local, room.locator, head.id, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
1461
|
-
armTimer();
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1464
|
-
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
1465
|
-
const left = leaving(head, result);
|
|
1466
|
-
await exclusive(async () => {
|
|
1467
|
-
if (!continuing())
|
|
1468
|
-
return;
|
|
1469
|
-
const current = replicas.get(room.locator);
|
|
1470
|
-
if (current === undefined || !current.queue.some((batch) => batch.id === head.id)) {
|
|
1471
|
-
return;
|
|
1472
|
-
}
|
|
1473
|
-
const set = await settle(local, room.locator, current, current.confirmed, current.queue.filter((batch) => batch.id !== head.id), {
|
|
1474
|
-
leaving: (result.kind === 'expired' && landed(head, current.confirmed)) ||
|
|
1475
|
-
(result.kind === 'conflicted' && landed(head, current.confirmed, result.affected))
|
|
1476
|
-
? { ...left, returned: null }
|
|
1477
|
-
: left,
|
|
1478
|
-
});
|
|
1479
|
-
publishChangeSet(set);
|
|
1480
|
-
moveTo({ ...status, ...attention() });
|
|
1481
|
-
});
|
|
1482
|
-
if (result.kind === 'expired')
|
|
1483
|
-
continue;
|
|
1484
|
-
const withheld = await advance(room, local, authorized);
|
|
1485
|
-
if (withheld !== null)
|
|
1486
|
-
return withhold(withheld, room, local, progress);
|
|
1487
|
-
continue;
|
|
1488
|
-
}
|
|
1489
|
-
const kept = await adopted(room, local, head, result).finally(answered);
|
|
1490
|
-
if (!kept) {
|
|
1491
|
-
progress.complete = false;
|
|
1714
|
+
const current = await recordAt(room.locator);
|
|
1715
|
+
if (current === undefined)
|
|
1492
1716
|
return;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1717
|
+
const record = { ...current, name: room.name, handle: room.handle };
|
|
1718
|
+
await local.commit({
|
|
1719
|
+
rooms: [
|
|
1720
|
+
{
|
|
1721
|
+
locator: room.locator,
|
|
1722
|
+
key: record.key,
|
|
1723
|
+
record: storedRecord(record),
|
|
1724
|
+
composed: composedOf(composed(record)),
|
|
1725
|
+
},
|
|
1726
|
+
],
|
|
1727
|
+
});
|
|
1728
|
+
emitRooms();
|
|
1500
1729
|
});
|
|
1501
|
-
answering.set(locator, awaited);
|
|
1502
|
-
return () => {
|
|
1503
|
-
if (answering.get(locator) === awaited)
|
|
1504
|
-
answering.delete(locator);
|
|
1505
|
-
release();
|
|
1506
|
-
};
|
|
1507
1730
|
}
|
|
1508
|
-
async function
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
JSON.stringify(heldBefore.typedSelection) ===
|
|
1517
|
-
JSON.stringify(room.typedSelection ?? null))))
|
|
1731
|
+
async function modelNow(locator) {
|
|
1732
|
+
return (await recordAt(locator))?.model ?? unheldModels.get(locator) ?? NO_MODEL;
|
|
1733
|
+
}
|
|
1734
|
+
async function retainModel(local, room, given) {
|
|
1735
|
+
const held = await recordAt(room.locator);
|
|
1736
|
+
const previous = held?.model ?? unheldModels.get(room.locator) ?? NO_MODEL;
|
|
1737
|
+
const next = given ?? previous;
|
|
1738
|
+
if (JSON.stringify(previous) === JSON.stringify(next) && declaredAsHeld(held, room))
|
|
1518
1739
|
return;
|
|
1519
|
-
if (next.status === 'unavailable') {
|
|
1520
|
-
invalidModels.add(key);
|
|
1521
|
-
liveModels.delete(key);
|
|
1522
|
-
}
|
|
1523
1740
|
await exclusive(async () => {
|
|
1524
1741
|
if (!continuing())
|
|
1525
1742
|
return;
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1743
|
+
const work = await workOf(room.locator);
|
|
1744
|
+
const current = work.replica;
|
|
1745
|
+
let changed = null;
|
|
1746
|
+
if (current !== undefined) {
|
|
1747
|
+
const record = {
|
|
1748
|
+
...current.record,
|
|
1749
|
+
name: room.name,
|
|
1750
|
+
handle: room.handle,
|
|
1530
1751
|
operations: room.operations,
|
|
1531
1752
|
replica: room.replica,
|
|
1753
|
+
providerOwnedPaths: room.providerOwnedPaths,
|
|
1532
1754
|
typedSelection: room.typedSelection ?? null,
|
|
1533
|
-
|
|
1755
|
+
model: next,
|
|
1534
1756
|
};
|
|
1535
|
-
const
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
}
|
|
1555
|
-
else if (read.kind === 'refused') {
|
|
1556
|
-
replicas.set(room.locator, { ...held, confirmed: { ...held.confirmed, record } });
|
|
1557
|
-
refusedSource(room, read);
|
|
1558
|
-
declarationRefusals.add(room.locator);
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
models.set(key, next);
|
|
1562
|
-
if (next.status === 'current') {
|
|
1563
|
-
liveModels.set(key, JSON.stringify(next.snapshot));
|
|
1564
|
-
invalidModels.delete(key);
|
|
1757
|
+
const confirmedLayer = await sliceAt(store, record, 'confirmed', current.queue, current.overlaid);
|
|
1758
|
+
const replayedLayer = await replayed(store, record, confirmedLayer, current.queue);
|
|
1759
|
+
const diff = await overlayDiff(record, current, replayedLayer.visible, replayedLayer.touched);
|
|
1760
|
+
const answer = await local.commit({
|
|
1761
|
+
rooms: [
|
|
1762
|
+
{
|
|
1763
|
+
locator: room.locator,
|
|
1764
|
+
key: record.key,
|
|
1765
|
+
record: storedRecord(record),
|
|
1766
|
+
roots: record.typedSelection?.roots ?? [],
|
|
1767
|
+
composed: composedOf(composed(record)),
|
|
1768
|
+
overlay: diff.overlay,
|
|
1769
|
+
},
|
|
1770
|
+
],
|
|
1771
|
+
});
|
|
1772
|
+
preparedAfter(saver, room.locator, answer);
|
|
1773
|
+
changed = changeSet(room.locator, {
|
|
1774
|
+
documents: answer.changed[room.locator] ?? [],
|
|
1775
|
+
room: true,
|
|
1776
|
+
});
|
|
1565
1777
|
}
|
|
1778
|
+
else
|
|
1779
|
+
unheldModels.set(room.locator, next);
|
|
1566
1780
|
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
1567
|
-
if (
|
|
1781
|
+
if (changed === null)
|
|
1568
1782
|
emitRooms();
|
|
1569
1783
|
else
|
|
1570
|
-
publishChangeSet(
|
|
1784
|
+
publishChangeSet(changed);
|
|
1571
1785
|
});
|
|
1572
1786
|
}
|
|
1573
1787
|
async function scoped(result, room, local) {
|
|
1788
|
+
if (result.scope === 'content' && !result.retryable) {
|
|
1789
|
+
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
1790
|
+
await loseRooms([room.locator]);
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1574
1793
|
scopedRefusals.set(room.locator, {
|
|
1575
1794
|
code: result.code,
|
|
1576
1795
|
fields: result.fields,
|
|
@@ -1578,22 +1797,14 @@ export function composeRuntime(options) {
|
|
|
1578
1797
|
});
|
|
1579
1798
|
let scopedAway = room;
|
|
1580
1799
|
if (result.scope === 'model')
|
|
1581
|
-
await retainModel(local, room,
|
|
1800
|
+
await retainModel(local, room, {
|
|
1582
1801
|
status: 'unavailable',
|
|
1583
1802
|
reason: 'not-readable',
|
|
1584
1803
|
});
|
|
1585
1804
|
else if (result.scope !== 'operation') {
|
|
1586
1805
|
unreadable.add(room.locator);
|
|
1587
|
-
scopedAway = {
|
|
1588
|
-
|
|
1589
|
-
replica: { ...room.replica, admission: 'unavailable' },
|
|
1590
|
-
operations: {
|
|
1591
|
-
...room.operations,
|
|
1592
|
-
...(result.scope === 'content' ? { read: [] } : {}),
|
|
1593
|
-
...(result.scope === 'sync' ? { sync: [] } : {}),
|
|
1594
|
-
},
|
|
1595
|
-
};
|
|
1596
|
-
await retainModel(local, scopedAway, 'collection', modelOf(room.locator));
|
|
1806
|
+
scopedAway = { ...room, replica: { ...room.replica, admission: 'unavailable' } };
|
|
1807
|
+
await retainModel(local, scopedAway, null);
|
|
1597
1808
|
}
|
|
1598
1809
|
if (!continuing())
|
|
1599
1810
|
return;
|
|
@@ -1601,131 +1812,182 @@ export function composeRuntime(options) {
|
|
|
1601
1812
|
emitRooms();
|
|
1602
1813
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
1603
1814
|
}
|
|
1604
|
-
function
|
|
1815
|
+
async function name(locator) {
|
|
1816
|
+
const room = discovered.get(locator);
|
|
1817
|
+
if (!socket.streamed || room === undefined || !socket.isOpen() || !socket.isHanded())
|
|
1818
|
+
return;
|
|
1819
|
+
const [held] = await heldRooms([room]);
|
|
1820
|
+
if (held === undefined || !socket.isOpen() || !socket.isHanded())
|
|
1821
|
+
return;
|
|
1822
|
+
socket.name(held);
|
|
1823
|
+
}
|
|
1824
|
+
async function heldRooms(listed) {
|
|
1825
|
+
const stored = await storedRooms(listed.map((room) => room.locator));
|
|
1605
1826
|
return listed.flatMap((room) => {
|
|
1606
|
-
const record =
|
|
1607
|
-
if (record === undefined
|
|
1827
|
+
const record = stored.get(room.locator)?.record;
|
|
1828
|
+
if (record === undefined) {
|
|
1829
|
+
return room.replica.admission === 'admitted'
|
|
1830
|
+
? [{ locator: room.locator, roomVersion: null }]
|
|
1831
|
+
: [];
|
|
1832
|
+
}
|
|
1833
|
+
if (record.replica.selection !== room.replica.selection)
|
|
1608
1834
|
return [];
|
|
1609
|
-
return [
|
|
1610
|
-
{
|
|
1611
|
-
locator: room.locator,
|
|
1612
|
-
roomVersion: record.roomVersion,
|
|
1613
|
-
documents: record.documents,
|
|
1614
|
-
revisions: record.revisions,
|
|
1615
|
-
},
|
|
1616
|
-
];
|
|
1835
|
+
return [{ locator: room.locator, roomVersion: record.roomVersion }];
|
|
1617
1836
|
});
|
|
1618
1837
|
}
|
|
1619
|
-
async function synchronize(listed, local, progress, attempt) {
|
|
1620
|
-
const
|
|
1838
|
+
async function synchronize(listed, local, progress, attempt, live) {
|
|
1839
|
+
const heldNow = await storedRooms(listed.map(({ locator }) => locator));
|
|
1840
|
+
const prepared = await Promise.allSettled(listed.map((room) => prepareRoom(room, local, progress, heldNow.get(room.locator)?.record)));
|
|
1841
|
+
if (!live())
|
|
1842
|
+
return;
|
|
1621
1843
|
const ready = listed.filter((_, index) => {
|
|
1622
1844
|
const outcome = prepared[index];
|
|
1623
1845
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
1624
1846
|
});
|
|
1847
|
+
const held = await heldRooms(ready);
|
|
1848
|
+
if (!live())
|
|
1849
|
+
return;
|
|
1625
1850
|
if (attempt === 'opened') {
|
|
1626
|
-
|
|
1627
|
-
|
|
1851
|
+
socket.hand(held);
|
|
1852
|
+
}
|
|
1853
|
+
const kept = attempt === 'unstreamed'
|
|
1854
|
+
? new Set()
|
|
1855
|
+
: new Set((await storedRooms(ready.map(({ locator }) => locator))).keys());
|
|
1856
|
+
for (const room of ready) {
|
|
1857
|
+
if (attempt === 'opened' || kept.has(room.locator)) {
|
|
1858
|
+
if (attempt === 'authorized')
|
|
1859
|
+
await name(room.locator);
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
void roomTask(room.locator, (live) => loadRoom(room, local, progress, live)).then(() => rest(progress));
|
|
1628
1863
|
}
|
|
1629
|
-
const
|
|
1630
|
-
.filter((room) => attempt === 'unstreamed' || !replicas.has(room.locator))
|
|
1631
|
-
.map((room) => loadRoom(room, local, progress)));
|
|
1632
|
-
const failed = [...prepared, ...loaded].find((room) => room.status === 'rejected');
|
|
1864
|
+
const failed = prepared.find((room) => room.status === 'rejected');
|
|
1633
1865
|
if (failed !== undefined)
|
|
1634
1866
|
throw failed.reason;
|
|
1635
1867
|
}
|
|
1636
|
-
async function prepareRoom(room, local, progress) {
|
|
1868
|
+
async function prepareRoom(room, local, progress, prior) {
|
|
1637
1869
|
if (closed || authorization === undefined) {
|
|
1638
1870
|
progress.complete = false;
|
|
1639
1871
|
return false;
|
|
1640
1872
|
}
|
|
1641
|
-
const prior = replicas.get(room.locator)?.confirmed.record;
|
|
1642
1873
|
if (prior !== undefined && prior.replica.selection !== room.replica.selection) {
|
|
1643
|
-
|
|
1874
|
+
listHeld(room.locator);
|
|
1644
1875
|
emitRooms();
|
|
1645
1876
|
journal.record(`selection changed ${room.locator}`);
|
|
1646
1877
|
progress.complete = false;
|
|
1647
1878
|
return false;
|
|
1648
1879
|
}
|
|
1649
|
-
if (prior !== undefined)
|
|
1650
|
-
await retainModel(local, room,
|
|
1880
|
+
if (prior !== undefined && !declaredAsHeld(prior, room))
|
|
1881
|
+
await retainModel(local, room, null);
|
|
1882
|
+
if (prior !== undefined && (prior.name !== room.name || prior.handle !== room.handle)) {
|
|
1883
|
+
await keepNaming(local, room);
|
|
1884
|
+
}
|
|
1651
1885
|
if (discoveryPaused || authorization === undefined)
|
|
1652
1886
|
return false;
|
|
1653
|
-
|
|
1654
|
-
if (replicas.has(room.locator))
|
|
1655
|
-
progress.complete = false;
|
|
1656
|
-
return false;
|
|
1657
|
-
}
|
|
1658
|
-
return true;
|
|
1887
|
+
return room.replica.admission === 'admitted';
|
|
1659
1888
|
}
|
|
1660
|
-
async function caughtUp(room, local, progress) {
|
|
1661
|
-
|
|
1662
|
-
if (held === undefined || authorization === undefined)
|
|
1889
|
+
async function caughtUp(room, local, progress, live) {
|
|
1890
|
+
if (options.adapter.roomStream !== undefined || authorization === undefined)
|
|
1663
1891
|
return;
|
|
1664
|
-
|
|
1665
|
-
if (!behind)
|
|
1892
|
+
if (!live() || !(await behind(installer, room)))
|
|
1666
1893
|
return;
|
|
1667
|
-
const withheld = await advance(room, local, authorization);
|
|
1894
|
+
const withheld = await advance(room, local, authorization, live);
|
|
1668
1895
|
if (withheld !== null)
|
|
1669
1896
|
await withhold(withheld, room, local, progress);
|
|
1670
1897
|
}
|
|
1671
|
-
function revoked(
|
|
1672
|
-
return
|
|
1673
|
-
const model = modelOf(locator, attached);
|
|
1674
|
-
return model.status === 'unavailable' && model.reason === 'revoked';
|
|
1675
|
-
});
|
|
1898
|
+
function revoked(model) {
|
|
1899
|
+
return model?.status === 'unavailable' && model.reason === 'revoked';
|
|
1676
1900
|
}
|
|
1677
|
-
async function loadRoom(room, local, progress) {
|
|
1678
|
-
if (
|
|
1679
|
-
return;
|
|
1680
|
-
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
1681
|
-
await reload(room, local, progress);
|
|
1901
|
+
async function loadRoom(room, local, progress, live, unheld) {
|
|
1902
|
+
if (!live() || authorization === undefined)
|
|
1682
1903
|
return;
|
|
1904
|
+
const prior = unheld === undefined ? await recordAt(room.locator) : undefined;
|
|
1905
|
+
if (prior !== undefined) {
|
|
1906
|
+
if (!(await prepareRoom(room, local, progress, prior)))
|
|
1907
|
+
return;
|
|
1908
|
+
if (revoked(prior.model)) {
|
|
1909
|
+
await reload(room, local, progress, live);
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1683
1912
|
}
|
|
1684
|
-
const withheld = await advance(room, local, authorization);
|
|
1913
|
+
const withheld = await advance(room, local, authorization, live, unheld);
|
|
1685
1914
|
if (withheld !== null) {
|
|
1686
1915
|
await withhold(withheld, room, local, progress);
|
|
1687
1916
|
return;
|
|
1688
1917
|
}
|
|
1689
|
-
await caughtUp(room, local, progress);
|
|
1918
|
+
await caughtUp(room, local, progress, live);
|
|
1919
|
+
}
|
|
1920
|
+
async function reload(room, local, progress, live) {
|
|
1921
|
+
try {
|
|
1922
|
+
if (live())
|
|
1923
|
+
reloadsDue.delete(room.locator);
|
|
1924
|
+
if (!live() || authorization === undefined)
|
|
1925
|
+
return;
|
|
1926
|
+
const prior = await recordAt(room.locator);
|
|
1927
|
+
const from = standingAt(store, room.locator, prior);
|
|
1928
|
+
const bootstrapped = await pullBootstrap(installer, authorization, room, local, live, prior);
|
|
1929
|
+
if (bootstrapped.kind !== 'staged') {
|
|
1930
|
+
if (live())
|
|
1931
|
+
await withhold(bootstrapped, room, local, progress);
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
await publish(installer, local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from }, live);
|
|
1935
|
+
await caughtUp(room, local, progress, live);
|
|
1936
|
+
}
|
|
1937
|
+
finally {
|
|
1938
|
+
caughtUpTo(installer, room.locator);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
async function asked(authorized) {
|
|
1942
|
+
try {
|
|
1943
|
+
return (await options.adapter.discover?.(authorized)) ?? { kind: 'transport-uncertainty' };
|
|
1944
|
+
}
|
|
1945
|
+
catch {
|
|
1946
|
+
return { kind: 'transport-uncertainty' };
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
function malformed(listed) {
|
|
1950
|
+
for (const room of listed) {
|
|
1951
|
+
if (room.operations === undefined ||
|
|
1952
|
+
!['write', 'erase'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
1953
|
+
room.operations[operation].every((scope) => typeof scope === 'string')) ||
|
|
1954
|
+
room.replica === undefined ||
|
|
1955
|
+
typeof room.replica.selection !== 'string' ||
|
|
1956
|
+
room.replica.selection.length === 0 ||
|
|
1957
|
+
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
1958
|
+
!['locked', 'open'].includes(room.replica.offline)) {
|
|
1959
|
+
return `invalid model or replica declaration ${room.locator}`;
|
|
1960
|
+
}
|
|
1961
|
+
if (!declaredSelection(room))
|
|
1962
|
+
return `invalid typed selection declaration ${room.locator}`;
|
|
1963
|
+
}
|
|
1964
|
+
return null;
|
|
1690
1965
|
}
|
|
1691
|
-
async function
|
|
1692
|
-
if (
|
|
1693
|
-
return;
|
|
1694
|
-
const bootstrapped = await pullBootstrap(authorization, room, local);
|
|
1695
|
-
if (!continuing())
|
|
1966
|
+
async function signIn(local, signedIn) {
|
|
1967
|
+
if (JSON.stringify(signedIn) === JSON.stringify((await local.facts()).member))
|
|
1696
1968
|
return;
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
return;
|
|
1700
|
-
}
|
|
1701
|
-
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
1702
|
-
await caughtUp(room, local, progress);
|
|
1969
|
+
await local.fact('member', signedIn);
|
|
1970
|
+
emitMember(signedIn);
|
|
1703
1971
|
}
|
|
1704
|
-
async function discoverRooms(local, progress, attempt) {
|
|
1972
|
+
async function discoverRooms(local, progress, attempt, live, found) {
|
|
1705
1973
|
if (closed || authorization === undefined) {
|
|
1706
1974
|
notifyReady();
|
|
1707
1975
|
return;
|
|
1708
1976
|
}
|
|
1709
|
-
moveTo(transferring(
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
if (closed) {
|
|
1977
|
+
moveTo(transferring());
|
|
1978
|
+
discoveries += 1;
|
|
1979
|
+
const started = discoveries;
|
|
1980
|
+
const result = found ?? (await asked(authorization));
|
|
1981
|
+
if (result.kind === 'discovery')
|
|
1982
|
+
roomsNamed = true;
|
|
1983
|
+
if (!live()) {
|
|
1984
|
+
progress.complete = false;
|
|
1718
1985
|
notifyReady();
|
|
1719
1986
|
return;
|
|
1720
1987
|
}
|
|
1721
1988
|
journal.record(`connection discover ${result.kind}`);
|
|
1722
1989
|
if (result.kind === 'transport-uncertainty') {
|
|
1723
|
-
|
|
1724
|
-
await retainModel(local, room, 'collection', cached(modelOf(room.locator)));
|
|
1725
|
-
if (room.typedSelection !== null) {
|
|
1726
|
-
await retainModel(local, room, 'typed-selection', cached(modelOf(room.locator, 'typed-selection')));
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1990
|
+
await cachedModels(local);
|
|
1729
1991
|
progress.reachable = false;
|
|
1730
1992
|
progress.complete = false;
|
|
1731
1993
|
notifyReady();
|
|
@@ -1746,46 +2008,26 @@ export function composeRuntime(options) {
|
|
|
1746
2008
|
notifyReady();
|
|
1747
2009
|
return;
|
|
1748
2010
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
room.replica.selection.length === 0 ||
|
|
1756
|
-
!['admitted', 'unavailable'].includes(room.replica.admission)) {
|
|
1757
|
-
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
1758
|
-
await refuse('missing-required-guarantee');
|
|
1759
|
-
notifyReady();
|
|
1760
|
-
return;
|
|
1761
|
-
}
|
|
1762
|
-
if (!declaredSelection(room)) {
|
|
1763
|
-
journal.record(`invalid typed selection declaration ${room.locator}`);
|
|
1764
|
-
await refuse('missing-required-guarantee');
|
|
1765
|
-
notifyReady();
|
|
1766
|
-
return;
|
|
1767
|
-
}
|
|
2011
|
+
const invalid = malformed(result.rooms);
|
|
2012
|
+
if (invalid !== null) {
|
|
2013
|
+
journal.record(invalid);
|
|
2014
|
+
await refuse('missing-required-guarantee');
|
|
2015
|
+
notifyReady();
|
|
2016
|
+
return;
|
|
1768
2017
|
}
|
|
1769
2018
|
if (refusal !== null) {
|
|
1770
2019
|
refusal = null;
|
|
1771
2020
|
emitRefusal();
|
|
1772
2021
|
}
|
|
1773
|
-
const
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
limits = result.providerLimits;
|
|
1779
|
-
await local.declare(limits, capabilities);
|
|
1780
|
-
}
|
|
1781
|
-
const signedIn = result.member ?? null;
|
|
1782
|
-
if (JSON.stringify(signedIn) !== JSON.stringify(member)) {
|
|
1783
|
-
await local.keepMember(signedIn);
|
|
1784
|
-
member = signedIn;
|
|
1785
|
-
emitMember();
|
|
2022
|
+
const held = await local.facts();
|
|
2023
|
+
if (JSON.stringify(result.providerLimits) !== JSON.stringify(held.providerLimits) ||
|
|
2024
|
+
JSON.stringify(result.capabilities) !== JSON.stringify(held.capabilities)) {
|
|
2025
|
+
await local.fact('providerLimits', result.providerLimits);
|
|
2026
|
+
await local.fact('capabilities', result.capabilities);
|
|
1786
2027
|
}
|
|
1787
|
-
|
|
1788
|
-
await
|
|
2028
|
+
notifyReady();
|
|
2029
|
+
await signIn(local, result.member ?? null);
|
|
2030
|
+
await synchronize(await applyDiscovery(result.rooms, started), local, progress, attempt, live);
|
|
1789
2031
|
}
|
|
1790
2032
|
async function ensureAuthorization() {
|
|
1791
2033
|
if (authorization !== undefined)
|
|
@@ -1799,16 +2041,66 @@ export function composeRuntime(options) {
|
|
|
1799
2041
|
throw error;
|
|
1800
2042
|
}
|
|
1801
2043
|
}
|
|
1802
|
-
function
|
|
2044
|
+
async function admittedRead(work) {
|
|
2045
|
+
while (roomReads.free === 0) {
|
|
2046
|
+
await new Promise((admit) => roomReads.waiting.push(admit));
|
|
2047
|
+
}
|
|
2048
|
+
roomReads.free -= 1;
|
|
2049
|
+
try {
|
|
2050
|
+
await work();
|
|
2051
|
+
}
|
|
2052
|
+
finally {
|
|
2053
|
+
roomReads.free += 1;
|
|
2054
|
+
roomReads.waiting.shift()?.();
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
function inRoom(locator, work) {
|
|
2058
|
+
roomWork += 1;
|
|
2059
|
+
const ran = (roomLanes.get(locator) ?? Promise.resolve()).then(async () => {
|
|
2060
|
+
try {
|
|
2061
|
+
return await work(continuing);
|
|
2062
|
+
}
|
|
2063
|
+
finally {
|
|
2064
|
+
roomWork -= 1;
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
const chain = ran.then(() => undefined, () => undefined);
|
|
2068
|
+
roomLanes.set(locator, chain);
|
|
2069
|
+
working.add(chain);
|
|
2070
|
+
void chain.finally(() => {
|
|
2071
|
+
working.delete(chain);
|
|
2072
|
+
if (roomLanes.get(locator) === chain)
|
|
2073
|
+
roomLanes.delete(locator);
|
|
2074
|
+
});
|
|
2075
|
+
return ran;
|
|
2076
|
+
}
|
|
2077
|
+
function roomTask(locator, work) {
|
|
1803
2078
|
if (stopping)
|
|
1804
|
-
return
|
|
1805
|
-
|
|
1806
|
-
return chain;
|
|
2079
|
+
return Promise.resolve();
|
|
2080
|
+
return inRoom(locator, (live) => admittedRead(() => work(live))).catch(() => undefined);
|
|
1807
2081
|
}
|
|
1808
|
-
function
|
|
1809
|
-
return
|
|
1810
|
-
|
|
1811
|
-
|
|
2082
|
+
function advanced(room, local, authorized) {
|
|
2083
|
+
return inRoom(room.locator, (live) => advance(room, local, authorized, live));
|
|
2084
|
+
}
|
|
2085
|
+
function task(lane, work) {
|
|
2086
|
+
if (stopping)
|
|
2087
|
+
return Promise.resolve();
|
|
2088
|
+
const held = lanes.get(lane);
|
|
2089
|
+
const opened = socket.generation();
|
|
2090
|
+
const current = held?.generation === opened ? held : { generation: opened, chain: Promise.resolve() };
|
|
2091
|
+
const turn = current.generation;
|
|
2092
|
+
const live = () => turn === socket.generation() && continuing();
|
|
2093
|
+
const run = current.chain.then(() => work(live)).catch(() => undefined);
|
|
2094
|
+
current.chain = run;
|
|
2095
|
+
lanes.set(lane, current);
|
|
2096
|
+
working.add(run);
|
|
2097
|
+
void run.finally(() => working.delete(run));
|
|
2098
|
+
return run;
|
|
2099
|
+
}
|
|
2100
|
+
function sync(attempt, found) {
|
|
2101
|
+
return task(OPENING_LANE, async (live) => {
|
|
2102
|
+
if (!live()) {
|
|
2103
|
+
readyFromStore();
|
|
1812
2104
|
return;
|
|
1813
2105
|
}
|
|
1814
2106
|
const progress = { complete: true, reachable: true };
|
|
@@ -1817,40 +2109,100 @@ export function composeRuntime(options) {
|
|
|
1817
2109
|
streamed = progress;
|
|
1818
2110
|
complete = false;
|
|
1819
2111
|
recorded = false;
|
|
1820
|
-
moveTo(transferring(
|
|
1821
|
-
handed = false;
|
|
2112
|
+
moveTo(transferring());
|
|
1822
2113
|
try {
|
|
1823
2114
|
const local = await openLocal();
|
|
1824
|
-
if (
|
|
2115
|
+
if (!live())
|
|
1825
2116
|
return;
|
|
1826
2117
|
await ensureAuthorization();
|
|
1827
|
-
if (
|
|
2118
|
+
if (!live())
|
|
1828
2119
|
return;
|
|
1829
|
-
await discoverRooms(local, progress, attempt);
|
|
2120
|
+
await discoverRooms(local, progress, attempt, live, found);
|
|
1830
2121
|
}
|
|
1831
2122
|
catch {
|
|
1832
2123
|
progress.complete = false;
|
|
1833
|
-
if (attempt === 'opened' && !
|
|
2124
|
+
if (attempt === 'opened' && !socket.isHanded())
|
|
1834
2125
|
progress.reachable = false;
|
|
1835
2126
|
}
|
|
1836
2127
|
finally {
|
|
1837
2128
|
notifyReady();
|
|
1838
2129
|
}
|
|
2130
|
+
if (!live())
|
|
2131
|
+
return;
|
|
1839
2132
|
if (attempt !== 'opened') {
|
|
1840
|
-
await finishOpening(progress);
|
|
2133
|
+
await finishOpening(progress, live);
|
|
1841
2134
|
return;
|
|
1842
2135
|
}
|
|
1843
|
-
if (
|
|
2136
|
+
if (socket.isHanded() || closed || progress.reachable)
|
|
1844
2137
|
return;
|
|
1845
2138
|
underway = null;
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2139
|
+
await rest(progress, live);
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
function resynchronize() {
|
|
2143
|
+
return task(OPENING_LANE, async (live) => {
|
|
2144
|
+
if (!live() || !socket.isAnnounced())
|
|
2145
|
+
return;
|
|
2146
|
+
const progress = { complete: true, reachable: true };
|
|
2147
|
+
moveTo(transferring());
|
|
2148
|
+
try {
|
|
2149
|
+
const local = await openLocal();
|
|
2150
|
+
if (!live())
|
|
2151
|
+
return;
|
|
2152
|
+
await synchronize([...discovered.values()], local, progress, 'unstreamed', live);
|
|
2153
|
+
}
|
|
2154
|
+
catch {
|
|
2155
|
+
progress.complete = false;
|
|
2156
|
+
}
|
|
2157
|
+
if (live())
|
|
2158
|
+
await finishOpening(progress, live);
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
function countAuthorityChange() {
|
|
2162
|
+
authorityChanges += 1;
|
|
2163
|
+
for (const listener of authorityChangeListeners)
|
|
2164
|
+
listener(authorityChanges);
|
|
2165
|
+
lastAnswers.again();
|
|
2166
|
+
}
|
|
2167
|
+
function changeAuthority(change) {
|
|
2168
|
+
return task(OPENING_LANE, async (live) => {
|
|
2169
|
+
if (!live() || !socket.isAnnounced())
|
|
2170
|
+
return;
|
|
2171
|
+
countAuthorityChange();
|
|
2172
|
+
const progress = { complete: true, reachable: true };
|
|
2173
|
+
moveTo(transferring());
|
|
2174
|
+
try {
|
|
2175
|
+
const local = await openLocal();
|
|
2176
|
+
if (!live())
|
|
2177
|
+
return;
|
|
2178
|
+
const invalid = malformed(change.rooms);
|
|
2179
|
+
if (invalid !== null) {
|
|
2180
|
+
journal.record(invalid);
|
|
2181
|
+
await refuse('missing-required-guarantee');
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
if (change.member !== undefined)
|
|
2185
|
+
await signIn(local, change.member);
|
|
2186
|
+
discoveries += 1;
|
|
2187
|
+
const listed = await applyDiscovery(change.rooms, discoveries, change.left);
|
|
2188
|
+
await synchronize(listed, local, progress, 'authorized', live);
|
|
2189
|
+
}
|
|
2190
|
+
catch {
|
|
2191
|
+
progress.complete = false;
|
|
2192
|
+
}
|
|
2193
|
+
if (!live())
|
|
2194
|
+
return;
|
|
2195
|
+
if (!progress.complete)
|
|
2196
|
+
complete = false;
|
|
2197
|
+
await rest(progress, live);
|
|
1849
2198
|
});
|
|
1850
2199
|
}
|
|
1851
|
-
function closedSocket(revoked) {
|
|
1852
|
-
|
|
1853
|
-
|
|
2200
|
+
function closedSocket(revoked, lost, opened) {
|
|
2201
|
+
notifyReady();
|
|
2202
|
+
unreadied();
|
|
2203
|
+
connectionLost();
|
|
2204
|
+
if (lost)
|
|
2205
|
+
conversations.lost();
|
|
1854
2206
|
if (revoked) {
|
|
1855
2207
|
void refuse('revoked-authorization');
|
|
1856
2208
|
return;
|
|
@@ -1861,201 +2213,366 @@ export function composeRuntime(options) {
|
|
|
1861
2213
|
moveTo({
|
|
1862
2214
|
...status,
|
|
1863
2215
|
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
1864
|
-
providerUnreachable: status.providerUnreachable || !opened,
|
|
2216
|
+
providerUnreachable: status.providerUnreachable || (!opened && lost),
|
|
1865
2217
|
});
|
|
2218
|
+
void closing(opened);
|
|
1866
2219
|
}
|
|
1867
|
-
function
|
|
1868
|
-
return task(async () => {
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
2220
|
+
function closing(opened) {
|
|
2221
|
+
return task(OPENING_LANE, async (live) => {
|
|
2222
|
+
if (!live())
|
|
2223
|
+
return;
|
|
2224
|
+
const local = await openLocal();
|
|
2225
|
+
if (!opened && !socket.isOpen())
|
|
2226
|
+
await cachedModels(local);
|
|
2227
|
+
await rest(null, live);
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
async function cachedModels(local) {
|
|
2231
|
+
const held = await storedRooms();
|
|
2232
|
+
await settleModels(listedLocators(held).flatMap((locator) => {
|
|
2233
|
+
const room = discovered.get(locator) ?? declarationOf(locator, held.get(locator))?.room;
|
|
2234
|
+
return room === undefined ? [] : [room];
|
|
2235
|
+
}), 'cached', local);
|
|
2236
|
+
}
|
|
2237
|
+
function caughtUpRooms(expired) {
|
|
2238
|
+
const progress = underway;
|
|
2239
|
+
underway = null;
|
|
2240
|
+
socket.named();
|
|
2241
|
+
const cycle = progress ?? { complete: true, reachable: true };
|
|
2242
|
+
replaced(store, expired);
|
|
2243
|
+
const installing = store.installs.underway();
|
|
2244
|
+
const held = progress === null ? storedRooms(expired) : storedRooms();
|
|
2245
|
+
const reloading = progress === null
|
|
2246
|
+
? Promise.resolve([])
|
|
2247
|
+
: held.then((stored) => owedReloads(expired, stored));
|
|
2248
|
+
const replacing = Promise.all([held, reloading]).then(([stored, owed]) => reloadNow((room) => expired.includes(room.locator) || owed.includes(room.locator), cycle, stored, installing));
|
|
2249
|
+
return task(OPENING_LANE, async (live) => {
|
|
2250
|
+
if (!live())
|
|
1872
2251
|
return;
|
|
1873
2252
|
if (progress === null) {
|
|
1874
|
-
await
|
|
2253
|
+
await replacing;
|
|
2254
|
+
await rest(cycle, live);
|
|
1875
2255
|
return;
|
|
1876
2256
|
}
|
|
1877
2257
|
try {
|
|
1878
2258
|
const local = await openLocal();
|
|
1879
|
-
|
|
1880
|
-
const reloading = held.filter((room) => expired.includes(room.locator) || revoked(room.locator));
|
|
1881
|
-
const reloaded = await Promise.allSettled(reloading.map((room) => reload(room, local, progress)));
|
|
2259
|
+
await replacing;
|
|
1882
2260
|
if (progress.reachable) {
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2261
|
+
const stored = await held;
|
|
2262
|
+
const owed = await reloading;
|
|
2263
|
+
await settleModels([...discovered.values()].filter((room) => stored.has(room.locator) &&
|
|
2264
|
+
!expired.includes(room.locator) &&
|
|
2265
|
+
!owed.includes(room.locator)), 'current', local);
|
|
1886
2266
|
}
|
|
1887
|
-
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
1888
|
-
if (failed !== undefined)
|
|
1889
|
-
throw failed.reason;
|
|
1890
2267
|
}
|
|
1891
2268
|
catch {
|
|
1892
2269
|
progress.complete = false;
|
|
1893
2270
|
progress.reachable = false;
|
|
1894
2271
|
}
|
|
1895
|
-
|
|
2272
|
+
if (live())
|
|
2273
|
+
await finishOpening(progress, live);
|
|
1896
2274
|
});
|
|
1897
2275
|
}
|
|
1898
|
-
|
|
1899
|
-
const
|
|
2276
|
+
function owedReloads(expired, held) {
|
|
2277
|
+
const reloading = [...discovered.values()]
|
|
2278
|
+
.filter((room) => !expired.includes(room.locator) && revoked(held.get(room.locator)?.record.model))
|
|
2279
|
+
.map((room) => room.locator);
|
|
2280
|
+
for (const locator of reloading)
|
|
2281
|
+
catching(store, locator);
|
|
2282
|
+
recoverReads();
|
|
2283
|
+
return reloading;
|
|
2284
|
+
}
|
|
2285
|
+
async function reloadNow(replaced, progress, held, installing) {
|
|
2286
|
+
const reloaded = [...discovered.values()].filter((room) => held.has(room.locator) && !installing.has(room.locator) && replaced(room));
|
|
2287
|
+
const acquired = [...discovered.values()].filter((room) => (!held.has(room.locator) || installing.has(room.locator)) &&
|
|
2288
|
+
room.replica.admission === 'admitted' &&
|
|
2289
|
+
replaced(room));
|
|
2290
|
+
for (const room of reloaded)
|
|
2291
|
+
reloadsDue.add(room.locator);
|
|
1900
2292
|
try {
|
|
1901
2293
|
const local = await openLocal();
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
.
|
|
2294
|
+
const kept = acquired.length === 0 ? new Set() : (await local.held()).staged;
|
|
2295
|
+
for (const room of reloaded) {
|
|
2296
|
+
void roomTask(room.locator, (live) => reload(room, local, progress, live)).then(() => rest(progress));
|
|
2297
|
+
}
|
|
2298
|
+
for (const room of acquired) {
|
|
2299
|
+
const unheld = installing.has(room.locator)
|
|
2300
|
+
? undefined
|
|
2301
|
+
: { staged: kept.has(room.locator) };
|
|
2302
|
+
void roomTask(room.locator, (live) => loadRoom(room, local, progress, live, unheld)).then(() => rest(progress));
|
|
2303
|
+
}
|
|
1905
2304
|
}
|
|
1906
2305
|
catch {
|
|
2306
|
+
for (const room of reloaded)
|
|
2307
|
+
reloadsDue.delete(room.locator);
|
|
1907
2308
|
progress.reachable = false;
|
|
1908
2309
|
}
|
|
1909
|
-
await rest(progress);
|
|
1910
2310
|
}
|
|
1911
|
-
async function
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
if (
|
|
1915
|
-
|
|
2311
|
+
async function settleModels(held, status, local) {
|
|
2312
|
+
const owed = (model) => model.status !== 'unavailable' && model.status !== status;
|
|
2313
|
+
const declaredAnew = await exclusive(async () => {
|
|
2314
|
+
if (!continuing())
|
|
2315
|
+
return [];
|
|
2316
|
+
const stored = await storedRooms(held.map(({ locator }) => locator));
|
|
2317
|
+
const unchanged = held.filter((room) => {
|
|
2318
|
+
const record = stored.get(room.locator)?.record;
|
|
2319
|
+
return (owed(record?.model ?? unheldModels.get(room.locator) ?? NO_MODEL) &&
|
|
2320
|
+
declaredAsHeld(record, room));
|
|
2321
|
+
});
|
|
2322
|
+
const records = unchanged.flatMap((room) => {
|
|
2323
|
+
const record = stored.get(room.locator)?.record;
|
|
2324
|
+
return record === undefined
|
|
2325
|
+
? []
|
|
2326
|
+
: [{ ...record, model: { ...record.model, status } }];
|
|
2327
|
+
});
|
|
2328
|
+
if (records.length > 0) {
|
|
2329
|
+
try {
|
|
2330
|
+
await local.commit({
|
|
2331
|
+
rooms: records.map((record) => ({
|
|
2332
|
+
locator: record.locator,
|
|
2333
|
+
key: record.key,
|
|
2334
|
+
record: storedRecord(record),
|
|
2335
|
+
composed: composedOf(composed(record)),
|
|
2336
|
+
})),
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
catch {
|
|
2340
|
+
journal.record(`model ${status} persistence failed`);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
for (const room of unchanged) {
|
|
2344
|
+
const unheld = unheldModels.get(room.locator);
|
|
2345
|
+
if (!stored.has(room.locator) && unheld !== undefined) {
|
|
2346
|
+
unheldModels.set(room.locator, { ...unheld, status });
|
|
2347
|
+
}
|
|
2348
|
+
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
1916
2349
|
}
|
|
2350
|
+
if (unchanged.length > 0)
|
|
2351
|
+
emitRooms();
|
|
2352
|
+
return held.filter((room) => {
|
|
2353
|
+
const record = stored.get(room.locator)?.record;
|
|
2354
|
+
return (!unchanged.includes(room) &&
|
|
2355
|
+
owed(record?.model ?? unheldModels.get(room.locator) ?? NO_MODEL));
|
|
2356
|
+
});
|
|
2357
|
+
});
|
|
2358
|
+
for (const room of declaredAnew) {
|
|
2359
|
+
const model = await modelNow(room.locator);
|
|
2360
|
+
if (owed(model))
|
|
2361
|
+
await retainModel(local, room, { ...model, status });
|
|
1917
2362
|
}
|
|
1918
2363
|
}
|
|
1919
|
-
function
|
|
1920
|
-
if (
|
|
1921
|
-
return;
|
|
1922
|
-
const running = submitting.get(locator);
|
|
1923
|
-
if (running !== undefined) {
|
|
1924
|
-
running.again = true;
|
|
2364
|
+
function repairLater(locator, when) {
|
|
2365
|
+
if (!socket.isOpen() || repairing.has(locator))
|
|
1925
2366
|
return;
|
|
2367
|
+
void recovery.attempt(`repair ${locator}`, (signal) => repairPass(locator, signal), when);
|
|
2368
|
+
}
|
|
2369
|
+
async function repairPass(locator, signal) {
|
|
2370
|
+
const room = discovered.get(locator);
|
|
2371
|
+
if (!socket.isOpen() || closed || stopping || discoveryPaused || !stale.has(locator))
|
|
2372
|
+
return 'done';
|
|
2373
|
+
if (room === undefined || room.replica.admission !== 'admitted')
|
|
2374
|
+
return 'done';
|
|
2375
|
+
const progress = { complete: true, reachable: true };
|
|
2376
|
+
repairing.add(locator);
|
|
2377
|
+
try {
|
|
2378
|
+
const local = await openLocal();
|
|
2379
|
+
await roomTask(locator, async (live) => {
|
|
2380
|
+
if (!live() || authorization === undefined)
|
|
2381
|
+
return;
|
|
2382
|
+
const withheld = await advance(room, local, authorization, live);
|
|
2383
|
+
if (withheld !== null && live())
|
|
2384
|
+
await withhold(withheld, room, local, progress);
|
|
2385
|
+
});
|
|
1926
2386
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
submitting.delete(locator);
|
|
1952
|
-
}
|
|
1953
|
-
})();
|
|
2387
|
+
catch {
|
|
2388
|
+
stale.add(locator);
|
|
2389
|
+
progress.reachable = false;
|
|
2390
|
+
}
|
|
2391
|
+
finally {
|
|
2392
|
+
repairing.delete(locator);
|
|
2393
|
+
}
|
|
2394
|
+
if (signal.aborted)
|
|
2395
|
+
return 'done';
|
|
2396
|
+
await rest(progress);
|
|
2397
|
+
if (!stale.has(locator)) {
|
|
2398
|
+
if ((await (await openLocal()).order(locator)).length > 0)
|
|
2399
|
+
submitLater(accepting, locator);
|
|
2400
|
+
return 'done';
|
|
2401
|
+
}
|
|
2402
|
+
if (!repairable.has(locator))
|
|
2403
|
+
return 'done';
|
|
2404
|
+
const reset = repairable.get(locator);
|
|
2405
|
+
return reset === undefined ? 'again' : { at: Date.parse(reset) };
|
|
2406
|
+
}
|
|
2407
|
+
function resendLater() {
|
|
2408
|
+
if (!socket.isOpen())
|
|
2409
|
+
return;
|
|
2410
|
+
void recovery.attempt('resend', resendPass, { again: true });
|
|
1954
2411
|
}
|
|
1955
|
-
function
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2412
|
+
async function resendPass(signal) {
|
|
2413
|
+
if (!socket.isOpen() || closed || stopping || discoveryPaused)
|
|
2414
|
+
return 'done';
|
|
2415
|
+
const reading = { complete: true, reachable: true };
|
|
2416
|
+
try {
|
|
2417
|
+
await conversations.resend(reading);
|
|
2418
|
+
}
|
|
2419
|
+
catch {
|
|
2420
|
+
reading.reachable = false;
|
|
1959
2421
|
}
|
|
2422
|
+
if (signal.aborted)
|
|
2423
|
+
return 'done';
|
|
2424
|
+
return reading.reachable && reading.complete ? 'done' : 'again';
|
|
1960
2425
|
}
|
|
1961
|
-
async function finishOpening(progress) {
|
|
2426
|
+
async function finishOpening(progress, live) {
|
|
1962
2427
|
try {
|
|
1963
|
-
if (
|
|
2428
|
+
if (!live()) {
|
|
1964
2429
|
progress.complete = false;
|
|
1965
2430
|
return;
|
|
1966
2431
|
}
|
|
1967
|
-
submitAll();
|
|
2432
|
+
await submitAll(accepting);
|
|
2433
|
+
for (const locator of stale)
|
|
2434
|
+
if (repairable.has(locator))
|
|
2435
|
+
repairLater(locator, {});
|
|
1968
2436
|
await conversations.reconcile();
|
|
1969
|
-
if (status.providerUnreachable)
|
|
1970
|
-
conversations.reconnected();
|
|
1971
2437
|
const reading = { complete: true, reachable: true };
|
|
1972
2438
|
await conversations.catchUp(reading);
|
|
1973
|
-
if (
|
|
2439
|
+
if (progress.reachable && reading.reachable)
|
|
2440
|
+
await conversations.resend(reading);
|
|
2441
|
+
if (progress.reachable)
|
|
2442
|
+
void recovery.attempt('calls', resendCalls);
|
|
2443
|
+
if (!reading.reachable) {
|
|
1974
2444
|
progress.reachable = false;
|
|
2445
|
+
resendLater();
|
|
2446
|
+
}
|
|
1975
2447
|
}
|
|
1976
2448
|
catch {
|
|
1977
2449
|
progress.complete = false;
|
|
1978
2450
|
}
|
|
1979
2451
|
finally {
|
|
1980
|
-
|
|
1981
|
-
|
|
2452
|
+
if (live()) {
|
|
2453
|
+
complete = progress.complete;
|
|
2454
|
+
await rest(progress, live);
|
|
2455
|
+
}
|
|
1982
2456
|
}
|
|
1983
2457
|
}
|
|
1984
|
-
async function rest(progress) {
|
|
1985
|
-
if (
|
|
2458
|
+
async function rest(progress, live = () => !closed) {
|
|
2459
|
+
if (!live())
|
|
1986
2460
|
return;
|
|
1987
|
-
|
|
1988
|
-
let synced =
|
|
2461
|
+
const settled = () => complete && roomWork === 0 && !socket.naming() && stale.size === 0;
|
|
2462
|
+
let synced = settled();
|
|
2463
|
+
if (synced && !recorded) {
|
|
2464
|
+
const queued = await queuedWork().catch(() => true);
|
|
2465
|
+
if (!live())
|
|
2466
|
+
return;
|
|
2467
|
+
synced = !queued && settled();
|
|
2468
|
+
}
|
|
1989
2469
|
if (synced && !recorded) {
|
|
1990
2470
|
const completedAt = new Date().toISOString();
|
|
1991
2471
|
try {
|
|
1992
|
-
await (await openLocal()).
|
|
2472
|
+
await (await openLocal()).fact('lastCompleted', completedAt);
|
|
2473
|
+
if (!live())
|
|
2474
|
+
return;
|
|
1993
2475
|
recorded = true;
|
|
1994
|
-
lastCompleted = completedAt;
|
|
1995
2476
|
}
|
|
1996
2477
|
catch {
|
|
1997
2478
|
synced = false;
|
|
1998
2479
|
}
|
|
1999
2480
|
}
|
|
2000
2481
|
moveTo({
|
|
2001
|
-
state:
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
...attention(),
|
|
2482
|
+
state: synced && complete && roomWork === 0 && !socket.naming() ? 'synced' : 'syncing',
|
|
2483
|
+
providerUnreachable: progress === null ? status.providerUnreachable : !progress.reachable,
|
|
2484
|
+
resting: true,
|
|
2005
2485
|
});
|
|
2006
2486
|
}
|
|
2007
|
-
function applyRoom(
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
const
|
|
2014
|
-
const held = replicas.get(locator);
|
|
2015
|
-
if (room === undefined || room.replica.admission !== 'admitted')
|
|
2016
|
-
return;
|
|
2017
|
-
if (held !== undefined &&
|
|
2018
|
-
(held.confirmed.record.replica.selection !== room.replica.selection ||
|
|
2019
|
-
options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion))) {
|
|
2020
|
-
return;
|
|
2021
|
-
}
|
|
2022
|
-
const during = underway;
|
|
2023
|
-
const progress = during ?? { complete: true, reachable: true };
|
|
2024
|
-
if (during === null)
|
|
2025
|
-
moveTo(transferring(status));
|
|
2487
|
+
function applyRoom(change) {
|
|
2488
|
+
const { locator } = change;
|
|
2489
|
+
if (stopping)
|
|
2490
|
+
return Promise.resolve();
|
|
2491
|
+
return inRoom(locator, async (live) => {
|
|
2492
|
+
await socket.answered(locator);
|
|
2493
|
+
const progress = underway ?? { complete: true, reachable: true };
|
|
2026
2494
|
try {
|
|
2027
2495
|
const local = await openLocal();
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2496
|
+
for (;;) {
|
|
2497
|
+
if (!live())
|
|
2498
|
+
return null;
|
|
2499
|
+
const room = discovered.get(locator);
|
|
2500
|
+
if (room === undefined || room.replica.admission !== 'admitted')
|
|
2501
|
+
return null;
|
|
2502
|
+
const held = await recordAt(locator);
|
|
2503
|
+
if (held === undefined) {
|
|
2504
|
+
if (change.from !== null)
|
|
2505
|
+
return null;
|
|
2506
|
+
await installCarried(room, local, change, live, progress);
|
|
2507
|
+
return progress;
|
|
2508
|
+
}
|
|
2509
|
+
if (held.replica.selection !== room.replica.selection)
|
|
2510
|
+
return null;
|
|
2511
|
+
const at = held.roomVersion;
|
|
2512
|
+
if (options.adapter.reached(locator, at, change.roomVersion))
|
|
2513
|
+
return progress;
|
|
2514
|
+
if (change.from === null || !options.adapter.reached(locator, at, change.from)) {
|
|
2515
|
+
await name(locator);
|
|
2516
|
+
return progress;
|
|
2517
|
+
}
|
|
2518
|
+
if (underway === null)
|
|
2519
|
+
moveTo(transferring());
|
|
2520
|
+
const writing = () => live() && !full;
|
|
2521
|
+
const refreshed = await store.ahead((hold) => pullRefresh(installer, authorization ?? {}, held, writing, hold, () => change.read()));
|
|
2522
|
+
if (refreshed.kind !== 'advanced')
|
|
2523
|
+
return progress;
|
|
2524
|
+
const landing = {
|
|
2525
|
+
kind: 'refreshed',
|
|
2526
|
+
from: refreshed.from,
|
|
2527
|
+
remodelled: refreshed.remodelled,
|
|
2528
|
+
};
|
|
2529
|
+
if (await publish(installer, local, room, refreshed.record, landing, writing)) {
|
|
2530
|
+
stale.delete(locator);
|
|
2531
|
+
if (!declaredAsHeld(held, room))
|
|
2532
|
+
await retainModel(local, room, null);
|
|
2533
|
+
return progress;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2031
2536
|
}
|
|
2032
2537
|
catch {
|
|
2538
|
+
if (!stale.has(locator))
|
|
2539
|
+
void name(locator);
|
|
2033
2540
|
stale.add(locator);
|
|
2541
|
+
return progress;
|
|
2034
2542
|
}
|
|
2035
|
-
|
|
2036
|
-
if (during === null)
|
|
2037
|
-
await rest(progress);
|
|
2038
|
-
}
|
|
2039
|
-
});
|
|
2543
|
+
}).then((progress) => rest(progress), () => undefined);
|
|
2040
2544
|
}
|
|
2041
|
-
function
|
|
2042
|
-
|
|
2043
|
-
if (stream === undefined || withdrawStream !== undefined)
|
|
2545
|
+
async function installCarried(room, local, change, live, progress) {
|
|
2546
|
+
if (authorization === undefined)
|
|
2044
2547
|
return;
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2548
|
+
if (underway === null)
|
|
2549
|
+
moveTo(transferring());
|
|
2550
|
+
const prior = await recordAt(room.locator);
|
|
2551
|
+
const from = standingAt(store, room.locator, prior);
|
|
2552
|
+
const page = await change.read();
|
|
2553
|
+
const carried = {
|
|
2554
|
+
bootstrap: () => Promise.resolve({
|
|
2555
|
+
kind: 'bootstrap',
|
|
2556
|
+
roomVersion: page.roomVersion,
|
|
2557
|
+
...(page.models === undefined ? {} : { models: page.models }),
|
|
2558
|
+
documents: page.documents,
|
|
2559
|
+
facts: page.facts,
|
|
2560
|
+
revisions: page.revisions,
|
|
2561
|
+
...(page.fileIds === undefined ? {} : { fileIds: page.fileIds }),
|
|
2562
|
+
...(page.created === undefined ? {} : { created: page.created }),
|
|
2563
|
+
changes: page.changes,
|
|
2564
|
+
binaryFiles: page.binaryFiles,
|
|
2565
|
+
...(page.folders === undefined ? {} : { folders: page.folders }),
|
|
2566
|
+
}),
|
|
2567
|
+
reached: (locator, roomVersion, target) => options.adapter.reached(locator, roomVersion, target),
|
|
2568
|
+
};
|
|
2569
|
+
const bootstrapped = await pullBootstrap(installer, authorization, room, local, live, prior, carried);
|
|
2570
|
+
if (bootstrapped.kind !== 'staged') {
|
|
2571
|
+
if (live())
|
|
2572
|
+
await withhold(bootstrapped, room, local, progress);
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
await publish(installer, local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed, from }, live);
|
|
2059
2576
|
}
|
|
2060
2577
|
function writesHeld() {
|
|
2061
2578
|
return serviceMode === 'write-halted' || haltedRefusal;
|
|
@@ -2070,37 +2587,35 @@ export function composeRuntime(options) {
|
|
|
2070
2587
|
haltedRefusal = false;
|
|
2071
2588
|
if (mode !== serviceMode) {
|
|
2072
2589
|
serviceMode = mode;
|
|
2590
|
+
queueReadiness();
|
|
2073
2591
|
journal.record(`service mode ${mode}`);
|
|
2074
2592
|
for (const listener of serviceModeListeners)
|
|
2075
2593
|
listener(mode);
|
|
2076
2594
|
}
|
|
2077
2595
|
if (releasing)
|
|
2078
|
-
submitAll();
|
|
2596
|
+
submitAll(accepting);
|
|
2079
2597
|
}
|
|
2080
|
-
function
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
return;
|
|
2091
|
-
show();
|
|
2092
|
-
scheduleCurrency();
|
|
2093
|
-
});
|
|
2598
|
+
function haltedSince(opens) {
|
|
2599
|
+
if (serviceMode === 'open' && openSignals !== opens)
|
|
2600
|
+
return false;
|
|
2601
|
+
haltedRefusal = true;
|
|
2602
|
+
return true;
|
|
2603
|
+
}
|
|
2604
|
+
function pendingShown() {
|
|
2605
|
+
if (!localSaves)
|
|
2606
|
+
recorded = false;
|
|
2607
|
+
return moveTo(localSaves ? status : { ...status, state: 'pending', resting: false });
|
|
2094
2608
|
}
|
|
2095
2609
|
function scheduleCurrency() {
|
|
2096
|
-
if (currencyQueued || closed || stopping)
|
|
2610
|
+
if (currencyQueued === socket.generation() || closed || stopping)
|
|
2097
2611
|
return;
|
|
2098
|
-
currencyQueued =
|
|
2099
|
-
void task(runCurrency);
|
|
2612
|
+
currencyQueued = socket.generation();
|
|
2613
|
+
void task(CONVERSATION_LANE, runCurrency);
|
|
2100
2614
|
}
|
|
2101
|
-
async function runCurrency() {
|
|
2102
|
-
currencyQueued
|
|
2103
|
-
|
|
2615
|
+
async function runCurrency(live) {
|
|
2616
|
+
if (currencyQueued === socket.generation())
|
|
2617
|
+
currencyQueued = null;
|
|
2618
|
+
if (!live())
|
|
2104
2619
|
return;
|
|
2105
2620
|
await conversations.reconcile();
|
|
2106
2621
|
if (conversations.current()) {
|
|
@@ -2108,97 +2623,184 @@ export function composeRuntime(options) {
|
|
|
2108
2623
|
return;
|
|
2109
2624
|
}
|
|
2110
2625
|
const progress = { complete: false, reachable: !status.providerUnreachable };
|
|
2111
|
-
moveTo(transferring(
|
|
2626
|
+
moveTo(transferring());
|
|
2112
2627
|
try {
|
|
2113
2628
|
await conversations.catchUp(progress);
|
|
2114
2629
|
}
|
|
2115
2630
|
catch {
|
|
2116
2631
|
progress.reachable = false;
|
|
2117
2632
|
}
|
|
2118
|
-
await rest(progress);
|
|
2633
|
+
await rest(progress, live);
|
|
2119
2634
|
}
|
|
2120
2635
|
function close() {
|
|
2121
2636
|
if (closed)
|
|
2122
2637
|
return;
|
|
2123
2638
|
closed = true;
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2639
|
+
notifyDiscovered();
|
|
2640
|
+
unreadied();
|
|
2641
|
+
if (probing !== null)
|
|
2642
|
+
clearInterval(probing);
|
|
2643
|
+
connectionLive();
|
|
2644
|
+
lockListeners.clear();
|
|
2645
|
+
readinessWatches.clear();
|
|
2646
|
+
socket.withdraw();
|
|
2128
2647
|
withdrawServiceMode?.();
|
|
2129
2648
|
conversations.close();
|
|
2130
|
-
|
|
2131
|
-
store?.close();
|
|
2132
|
-
void opening?.then((opened) => {
|
|
2133
|
-
opened.close();
|
|
2134
|
-
}, () => { });
|
|
2649
|
+
void recovery.retire();
|
|
2135
2650
|
roomListeners.clear();
|
|
2651
|
+
unlistedRooms.clear();
|
|
2136
2652
|
refusalListeners.clear();
|
|
2137
2653
|
storageLossListeners.clear();
|
|
2138
2654
|
statusListeners.clear();
|
|
2139
2655
|
serviceModeListeners.clear();
|
|
2140
2656
|
memberListeners.clear();
|
|
2141
|
-
|
|
2657
|
+
authorityChangeListeners.clear();
|
|
2658
|
+
liveQueries.close();
|
|
2659
|
+
figures.close();
|
|
2142
2660
|
queueSubscriptions.clear();
|
|
2143
|
-
|
|
2144
|
-
presentationListeners.clear();
|
|
2661
|
+
store.close();
|
|
2145
2662
|
}
|
|
2146
2663
|
async function stop() {
|
|
2147
2664
|
stopping = true;
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
await
|
|
2151
|
-
await
|
|
2665
|
+
unreadied();
|
|
2666
|
+
socket.stopReads();
|
|
2667
|
+
await recovery.retire();
|
|
2668
|
+
await Promise.all(working);
|
|
2669
|
+
await store.written();
|
|
2152
2670
|
close();
|
|
2153
2671
|
}
|
|
2154
2672
|
const withdrawServiceMode = options.adapter.serviceMode?.observe(signalled);
|
|
2155
|
-
observeConversations()
|
|
2673
|
+
socket.observeConversations((event) => {
|
|
2674
|
+
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted')
|
|
2675
|
+
queueReadiness();
|
|
2676
|
+
conversations.receive(event);
|
|
2677
|
+
if (event.kind !== 'gap' || conversations.current())
|
|
2678
|
+
return;
|
|
2679
|
+
show();
|
|
2680
|
+
scheduleCurrency();
|
|
2681
|
+
});
|
|
2156
2682
|
options.signal?.addEventListener('abort', close, { once: true });
|
|
2157
2683
|
if (options.signal?.aborted)
|
|
2158
2684
|
close();
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2685
|
+
socket.observe(() => !closed && !stopping, (event) => {
|
|
2686
|
+
if (event.kind === 'opened') {
|
|
2687
|
+
connectionLive();
|
|
2688
|
+
conversations.reopened();
|
|
2689
|
+
lastAnswers.reopened();
|
|
2690
|
+
recovery.resume();
|
|
2691
|
+
}
|
|
2692
|
+
else if (event.kind === 'discovered') {
|
|
2693
|
+
if (event.missed)
|
|
2694
|
+
countAuthorityChange();
|
|
2695
|
+
void sync('opened', event.discovery);
|
|
2696
|
+
}
|
|
2697
|
+
else if (event.kind === 'closed')
|
|
2698
|
+
closedSocket(event.revoked, event.lost, event.opened);
|
|
2699
|
+
else if (event.kind === 'caught-up')
|
|
2700
|
+
void caughtUpRooms(event.expired);
|
|
2701
|
+
else if (event.kind === 'authority-changed') {
|
|
2702
|
+
if (socket.isAnnounced() && socket.isOpen())
|
|
2703
|
+
fenceLive(event.left);
|
|
2704
|
+
void changeAuthority(event);
|
|
2705
|
+
}
|
|
2706
|
+
else
|
|
2707
|
+
void applyRoom(event);
|
|
2708
|
+
});
|
|
2709
|
+
void openLocal()
|
|
2710
|
+
.then(async (local) => {
|
|
2711
|
+
if ((await local.facts()).capabilities.length > 0)
|
|
2712
|
+
readyFromStore();
|
|
2713
|
+
})
|
|
2714
|
+
.catch(() => {
|
|
2715
|
+
readyFromStore();
|
|
2165
2716
|
});
|
|
2166
2717
|
if (options.adapter.roomStream === undefined)
|
|
2167
|
-
void sync('unstreamed');
|
|
2168
|
-
function planned(input, reach) {
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
return
|
|
2718
|
+
void sync('unstreamed', null);
|
|
2719
|
+
async function planned(input, reach) {
|
|
2720
|
+
const plan = planWithin(input, await roomFacts(reach), planReach(reach));
|
|
2721
|
+
const named = plan.paths !== null &&
|
|
2722
|
+
plan.rooms !== null &&
|
|
2723
|
+
plan.paths.length * plan.rooms.length <= MAXIMUM_WINDOW;
|
|
2724
|
+
return plan.limit === null && named ? plan : { ...plan, limit: capped(plan.limit) };
|
|
2725
|
+
}
|
|
2726
|
+
function watchPlan(plan, deliver, fail) {
|
|
2727
|
+
return watchSeeded(seeding, plan, deliver, (behind, answered) => liveQueries.watch(plan, identityOf(plan), (held) => {
|
|
2728
|
+
const cover = covered(plan, held);
|
|
2729
|
+
awaitPreparation(cover);
|
|
2730
|
+
return behind(cover, held);
|
|
2731
|
+
}, answered, fail));
|
|
2732
|
+
}
|
|
2733
|
+
function watchPlanned(planning, deliver, fail) {
|
|
2734
|
+
let stop = null;
|
|
2735
|
+
let stopped = false;
|
|
2736
|
+
planning.then((plan) => {
|
|
2737
|
+
if (!stopped)
|
|
2738
|
+
stop = watchPlan(plan, deliver, fail);
|
|
2739
|
+
}, (error) => {
|
|
2740
|
+
if (!stopped)
|
|
2741
|
+
fail(error);
|
|
2742
|
+
});
|
|
2743
|
+
return () => {
|
|
2744
|
+
stopped = true;
|
|
2745
|
+
stop?.();
|
|
2746
|
+
};
|
|
2174
2747
|
}
|
|
2175
2748
|
async function searched(input, reach) {
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
return
|
|
2199
|
-
|
|
2749
|
+
const asked = searchPlanOf(input);
|
|
2750
|
+
await ready;
|
|
2751
|
+
const search = options.adapter.search;
|
|
2752
|
+
if (search === undefined) {
|
|
2753
|
+
return {
|
|
2754
|
+
kind: 'undeclared',
|
|
2755
|
+
code: 'action-undeclared',
|
|
2756
|
+
fields: { name: 'search' },
|
|
2757
|
+
retryable: false,
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
if (closed || refusal !== null || (await connectionNow()) !== 'connected')
|
|
2761
|
+
return OFFLINE;
|
|
2762
|
+
const reached = reach === null ? null : new Set(reach.rooms);
|
|
2763
|
+
const named = asked.rooms === null
|
|
2764
|
+
? reached === null
|
|
2765
|
+
? null
|
|
2766
|
+
: [...reached]
|
|
2767
|
+
: asked.rooms.filter((room) => reached === null || reached.has(room));
|
|
2768
|
+
const held = named === null ? null : await storedRooms(named);
|
|
2769
|
+
const rooms = named === null ? null : named.filter((room) => held.has(room)).sort();
|
|
2770
|
+
if (rooms !== null && rooms.length === 0)
|
|
2771
|
+
return { kind: 'found', documents: [] };
|
|
2772
|
+
try {
|
|
2773
|
+
await ensureAuthorization();
|
|
2774
|
+
}
|
|
2775
|
+
catch {
|
|
2776
|
+
return UNCERTAIN;
|
|
2777
|
+
}
|
|
2778
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
2779
|
+
return UNCERTAIN;
|
|
2780
|
+
journal.record('transfer search');
|
|
2781
|
+
try {
|
|
2782
|
+
const result = await search(authorization, { text: asked.text, rooms });
|
|
2783
|
+
if (result.kind === 'revoked-authorization')
|
|
2784
|
+
await refuse('revoked-authorization');
|
|
2785
|
+
journal.record(`outcome search ${result.kind}`);
|
|
2786
|
+
if (result.kind === 'rejected')
|
|
2787
|
+
return result;
|
|
2788
|
+
if (result.kind !== 'found')
|
|
2789
|
+
return failure(result);
|
|
2790
|
+
const kept = rooms === null ? null : new Set(rooms);
|
|
2791
|
+
return {
|
|
2792
|
+
kind: 'found',
|
|
2793
|
+
documents: result.documents.filter((hit) => kept === null || kept.has(hit.room)),
|
|
2200
2794
|
};
|
|
2201
2795
|
}
|
|
2796
|
+
catch {
|
|
2797
|
+
journal.record('outcome search transport-uncertainty');
|
|
2798
|
+
return UNCERTAIN;
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
const watch = (topic, input, deliver, fail) => {
|
|
2802
|
+
if (topic === 'room-drafts')
|
|
2803
|
+
return watchRoomDrafts(drafting, deliver);
|
|
2202
2804
|
if (topic === 'conversation') {
|
|
2203
2805
|
return conversations.watch(input[0], input[1], deliver);
|
|
2204
2806
|
}
|
|
@@ -2206,30 +2808,23 @@ export function composeRuntime(options) {
|
|
|
2206
2808
|
return conversations.watchList(deliver);
|
|
2207
2809
|
if (topic === 'agents')
|
|
2208
2810
|
return conversations.watchAgents(deliver);
|
|
2209
|
-
if (topic === '
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
await openLocal();
|
|
2218
|
-
}).then(() => {
|
|
2219
|
-
if (closed || !presentationListeners.has(listener))
|
|
2220
|
-
return;
|
|
2221
|
-
listener.ready = true;
|
|
2222
|
-
listener.notify(presentationOf(listener.app));
|
|
2223
|
-
}, () => { });
|
|
2224
|
-
return () => {
|
|
2225
|
-
presentationListeners.delete(listener);
|
|
2226
|
-
};
|
|
2811
|
+
if (topic === 'figures')
|
|
2812
|
+
return figures.watch(deliver, fail);
|
|
2813
|
+
if (topic === 'read')
|
|
2814
|
+
return lastAnswers.watch(input[0], input[1], deliver);
|
|
2815
|
+
if (topic === 'presentation')
|
|
2816
|
+
return watchPresentation(drafting, input[0], deliver);
|
|
2817
|
+
if (topic === 'readiness') {
|
|
2818
|
+
return watchReadiness({ target: '', code: 'stored' }, null, deliver);
|
|
2227
2819
|
}
|
|
2228
2820
|
if (topic === 'rooms') {
|
|
2229
|
-
|
|
2230
|
-
|
|
2821
|
+
unlistedRooms.add(deliver);
|
|
2822
|
+
emitKnownRooms();
|
|
2231
2823
|
return () => {
|
|
2824
|
+
unlistedRooms.delete(deliver);
|
|
2232
2825
|
roomListeners.delete(deliver);
|
|
2826
|
+
if (roomListeners.size === 0)
|
|
2827
|
+
roomsShown = null;
|
|
2233
2828
|
};
|
|
2234
2829
|
}
|
|
2235
2830
|
if (topic === 'connection-refusal') {
|
|
@@ -2262,160 +2857,160 @@ export function composeRuntime(options) {
|
|
|
2262
2857
|
}
|
|
2263
2858
|
if (topic === 'member') {
|
|
2264
2859
|
memberListeners.add(deliver);
|
|
2265
|
-
|
|
2860
|
+
void openLocal()
|
|
2861
|
+
.then((local) => local.facts())
|
|
2862
|
+
.then(({ member }) => {
|
|
2863
|
+
if (memberListeners.has(deliver))
|
|
2864
|
+
deliver(member);
|
|
2865
|
+
}, () => { });
|
|
2266
2866
|
return () => {
|
|
2267
2867
|
memberListeners.delete(deliver);
|
|
2268
2868
|
};
|
|
2269
2869
|
}
|
|
2870
|
+
if (topic === 'authority-changes') {
|
|
2871
|
+
authorityChangeListeners.add(deliver);
|
|
2872
|
+
deliver(authorityChanges);
|
|
2873
|
+
return () => {
|
|
2874
|
+
authorityChangeListeners.delete(deliver);
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2270
2877
|
if (topic === 'queue') {
|
|
2271
2878
|
const queue = { locator: input[0], notify: deliver };
|
|
2272
2879
|
queueSubscriptions.add(queue);
|
|
2273
|
-
|
|
2880
|
+
void publication(async () => {
|
|
2881
|
+
if (!queueSubscriptions.has(queue))
|
|
2882
|
+
return;
|
|
2883
|
+
const entries = await entriesOf(queue.locator);
|
|
2884
|
+
if (queueSubscriptions.has(queue))
|
|
2885
|
+
queue.notify(entries);
|
|
2886
|
+
});
|
|
2274
2887
|
return () => {
|
|
2275
2888
|
queueSubscriptions.delete(queue);
|
|
2276
2889
|
};
|
|
2277
2890
|
}
|
|
2278
|
-
return
|
|
2891
|
+
return watchPlanned(planned(input[0], null), deliver, fail);
|
|
2279
2892
|
};
|
|
2280
2893
|
const md = {
|
|
2281
2894
|
loaded() {
|
|
2282
2895
|
return openLocal().then(() => undefined);
|
|
2283
2896
|
},
|
|
2284
2897
|
rooms() {
|
|
2285
|
-
|
|
2898
|
+
if (closed)
|
|
2899
|
+
return Promise.resolve([]);
|
|
2900
|
+
return new Promise((settle) => {
|
|
2901
|
+
const answer = (value) => {
|
|
2902
|
+
unlistedRooms.delete(answer);
|
|
2903
|
+
roomListeners.delete(answer);
|
|
2904
|
+
settle(value);
|
|
2905
|
+
};
|
|
2906
|
+
unlistedRooms.add(answer);
|
|
2907
|
+
emitKnownRooms();
|
|
2908
|
+
});
|
|
2286
2909
|
},
|
|
2287
|
-
query: ((input) =>
|
|
2910
|
+
query: ((input) => planned(input, null).then(rowsQuery)),
|
|
2288
2911
|
search: (input) => searched(input, null),
|
|
2289
|
-
types(locator) {
|
|
2912
|
+
async types(locator) {
|
|
2913
|
+
const held = (await storedRooms([locator])).get(locator);
|
|
2914
|
+
const room = isFenced(locator) ? undefined : declarationOf(locator, held)?.room;
|
|
2290
2915
|
return {
|
|
2291
|
-
collection: modelTypes(
|
|
2292
|
-
typedSelection:
|
|
2293
|
-
rooms.get(locator)?.typedSelection === null
|
|
2294
|
-
? null
|
|
2295
|
-
: modelTypes(modelOf(locator, 'typed-selection')),
|
|
2916
|
+
collection: modelTypes(isFenced(locator) ? NO_MODEL : modelIn(locator, held)),
|
|
2917
|
+
typedSelection: (room?.typedSelection ?? null) === null ? null : modelTypes(builtIn),
|
|
2296
2918
|
};
|
|
2297
2919
|
},
|
|
2298
|
-
roomDraft:
|
|
2299
|
-
list() {
|
|
2300
|
-
return exclusive(async () => {
|
|
2301
|
-
await openLocal();
|
|
2302
|
-
return snapshotRoomDrafts();
|
|
2303
|
-
}).then((listed) => {
|
|
2304
|
-
publishRoomDrafts(true);
|
|
2305
|
-
return listed;
|
|
2306
|
-
});
|
|
2307
|
-
},
|
|
2308
|
-
read(room, path) {
|
|
2309
|
-
const record = roomDrafts.get(room)?.get(path);
|
|
2310
|
-
return record === undefined ? null : roomDraftOf(record);
|
|
2311
|
-
},
|
|
2312
|
-
write(room, path, revision, input) {
|
|
2313
|
-
return exclusive(async () => {
|
|
2314
|
-
const parts = readParts(input);
|
|
2315
|
-
if (path === '' || normalize(path) !== path)
|
|
2316
|
-
throw runtimeError('room-draft-malformed');
|
|
2317
|
-
const local = await openLocal();
|
|
2318
|
-
const current = roomDrafts.get(room)?.get(path);
|
|
2319
|
-
if ((current?.revision ?? null) !== revision) {
|
|
2320
|
-
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2321
|
-
}
|
|
2322
|
-
const file = parts.file === undefined
|
|
2323
|
-
? undefined
|
|
2324
|
-
: await heldFile(parts.file.blob, binaryBase(replicas.get(room)?.visible, path));
|
|
2325
|
-
const record = {
|
|
2326
|
-
locator: room,
|
|
2327
|
-
path,
|
|
2328
|
-
revision: roomDraftRevision(),
|
|
2329
|
-
parts: held(current?.parts, parts, file),
|
|
2330
|
-
returned: current?.returned ?? null,
|
|
2331
|
-
};
|
|
2332
|
-
await local.keepRoomDraft(record);
|
|
2333
|
-
rememberRoomDraft(record);
|
|
2334
|
-
publishChangeSet(changeSet(room, null, null, { drafts: [path] }));
|
|
2335
|
-
return { outcome: 'written', roomDraft: roomDraftOf(record) };
|
|
2336
|
-
});
|
|
2337
|
-
},
|
|
2338
|
-
discard(room, path, revision) {
|
|
2339
|
-
return exclusive(async () => {
|
|
2340
|
-
const local = await openLocal();
|
|
2341
|
-
const current = roomDrafts.get(room)?.get(path);
|
|
2342
|
-
if (current === undefined || current.revision !== revision) {
|
|
2343
|
-
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2344
|
-
}
|
|
2345
|
-
await local.discardRoomDraft(room, path);
|
|
2346
|
-
forgetRoomDraft(room, path);
|
|
2347
|
-
publishChangeSet(changeSet(room, null, null, { drafts: [path] }));
|
|
2348
|
-
moveTo({ ...status, ...attention() });
|
|
2349
|
-
return { outcome: 'written', roomDraft: null };
|
|
2350
|
-
});
|
|
2351
|
-
},
|
|
2352
|
-
},
|
|
2920
|
+
roomDraft: roomDraftSurface(drafting),
|
|
2353
2921
|
conversation: conversations.surface,
|
|
2354
2922
|
chatDraft: conversations.chatDraft,
|
|
2355
|
-
presentation:
|
|
2356
|
-
read(app) {
|
|
2357
|
-
return exclusive(async () => {
|
|
2358
|
-
await openLocal();
|
|
2359
|
-
return presentationOf(app);
|
|
2360
|
-
});
|
|
2361
|
-
},
|
|
2362
|
-
write(app, value) {
|
|
2363
|
-
return exclusive(async () => {
|
|
2364
|
-
const local = await openLocal();
|
|
2365
|
-
const stored = structuredClone(value);
|
|
2366
|
-
await local.present({ app, value: stored });
|
|
2367
|
-
presentations.set(app, stored);
|
|
2368
|
-
publishChangeSet(changeSet('', null, null, { presentation: app }));
|
|
2369
|
-
});
|
|
2370
|
-
},
|
|
2371
|
-
},
|
|
2923
|
+
presentation: presentationSurface(drafting),
|
|
2372
2924
|
save(room, named, writtenBy) {
|
|
2373
|
-
return exclusive(() => saveRoomDrafts(room, named, writtenBy));
|
|
2925
|
+
return exclusive(() => saveRoomDrafts(saver, room, named, writtenBy));
|
|
2926
|
+
},
|
|
2927
|
+
saveParts(room, parts, writtenBy) {
|
|
2928
|
+
return exclusive(() => saveParts(saver, room, parts, writtenBy));
|
|
2374
2929
|
},
|
|
2375
2930
|
history(locator, path, query) {
|
|
2376
2931
|
return readHistory(locator, path, query);
|
|
2377
2932
|
},
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
readReference: readBinaryReference,
|
|
2933
|
+
resolve(room, path, destination) {
|
|
2934
|
+
return resolveReference(room, path, destination);
|
|
2381
2935
|
},
|
|
2382
|
-
|
|
2383
|
-
|
|
2936
|
+
binaryFile: binaryFileSurface(saver),
|
|
2937
|
+
async call(name, input) {
|
|
2938
|
+
await ready;
|
|
2939
|
+
const lockedNow = await lockedRooms();
|
|
2940
|
+
const room = (await calledRooms(name, input)).find((one) => lockedNow.includes(one));
|
|
2941
|
+
return room === undefined ? recoveredAction(name, input) : lockedRoom(room);
|
|
2384
2942
|
},
|
|
2385
|
-
reached(locator, roomVersion, path) {
|
|
2943
|
+
async reached(locator, roomVersion, path) {
|
|
2386
2944
|
return path === undefined
|
|
2387
|
-
? reachedBy(locator, roomVersion)
|
|
2388
|
-
: writtenAfter(locator, roomVersion, path);
|
|
2945
|
+
? reachedBy(installer, locator, roomVersion)
|
|
2946
|
+
: writtenAfter(installer, locator, roomVersion, path);
|
|
2389
2947
|
},
|
|
2390
2948
|
log() {
|
|
2391
2949
|
return journal.export();
|
|
2392
2950
|
},
|
|
2951
|
+
cleanUp,
|
|
2952
|
+
unsent: () => database.ask({ kind: 'authored.unsent' }),
|
|
2953
|
+
reset: () => options.shell.reset(),
|
|
2954
|
+
async readAgain() {
|
|
2955
|
+
lastAnswers.again();
|
|
2956
|
+
},
|
|
2393
2957
|
persistence() {
|
|
2394
|
-
return
|
|
2958
|
+
return persistence === 'granted';
|
|
2959
|
+
},
|
|
2960
|
+
async retry() {
|
|
2961
|
+
journal.record('retry');
|
|
2962
|
+
unpreparable.clear();
|
|
2963
|
+
queueReadiness();
|
|
2964
|
+
if (full)
|
|
2965
|
+
await probed();
|
|
2395
2966
|
},
|
|
2396
2967
|
observe: observing(watch),
|
|
2397
2968
|
async reauthorize() {
|
|
2398
2969
|
if (refusal === null || !refusal.reauthorizationOffered)
|
|
2399
2970
|
return;
|
|
2400
2971
|
journal.record('connection reauthorize');
|
|
2401
|
-
await
|
|
2402
|
-
|
|
2972
|
+
await Promise.all([...lanes.values()]
|
|
2973
|
+
.filter((held) => held.generation === socket.generation())
|
|
2974
|
+
.map((held) => held.chain));
|
|
2975
|
+
await store.written();
|
|
2403
2976
|
authorization = undefined;
|
|
2404
2977
|
discoveryPaused = false;
|
|
2405
2978
|
if (options.adapter.roomStream === undefined)
|
|
2406
|
-
await sync('unstreamed');
|
|
2979
|
+
await sync('unstreamed', null);
|
|
2407
2980
|
else
|
|
2408
|
-
await task(ensureAuthorization);
|
|
2981
|
+
await task(OPENING_LANE, ensureAuthorization);
|
|
2409
2982
|
},
|
|
2410
2983
|
};
|
|
2411
2984
|
const reachable = {
|
|
2412
2985
|
md,
|
|
2413
2986
|
watch,
|
|
2414
|
-
referrerScope: (referrer) => options.adapter.referrerScope(referrer),
|
|
2415
2987
|
plan: planned,
|
|
2416
2988
|
query: (plan) => rowsQuery(plan),
|
|
2417
2989
|
watchPlan,
|
|
2418
2990
|
search: searched,
|
|
2991
|
+
watchReadiness,
|
|
2992
|
+
locked,
|
|
2993
|
+
lockedRooms,
|
|
2994
|
+
watchLocks,
|
|
2995
|
+
located,
|
|
2996
|
+
room: async (locator) => shownRooms(await listingNow()).find((one) => one.locator === locator),
|
|
2997
|
+
};
|
|
2998
|
+
return {
|
|
2999
|
+
md,
|
|
3000
|
+
seed: (entry) => keepEntrySeed(store, entry),
|
|
3001
|
+
stop,
|
|
3002
|
+
watch,
|
|
3003
|
+
persisted(answer) {
|
|
3004
|
+
if (answer === persistence)
|
|
3005
|
+
return;
|
|
3006
|
+
persistence = answer;
|
|
3007
|
+
journal.record(`persistence ${answer}`);
|
|
3008
|
+
queueReadiness();
|
|
3009
|
+
},
|
|
3010
|
+
within: (reach, port) => {
|
|
3011
|
+
if (full)
|
|
3012
|
+
void probed();
|
|
3013
|
+
return narrowed(reachable, reach, port);
|
|
3014
|
+
},
|
|
2419
3015
|
};
|
|
2420
|
-
return { md, stop, watch, within: (reach) => narrowed(reachable, reach) };
|
|
2421
3016
|
}
|