@agentshouse/mdruntime 0.1.0-alpha.9 → 0.1.0-c3903a68dcbbf
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/AGENTS.md +210 -0
- package/README.md +44 -0
- package/dist/adapter.d.ts +155 -12
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +2 -3
- package/dist/attachment.d.ts +3 -0
- package/dist/attachment.d.ts.map +1 -0
- package/dist/attachment.js +8 -0
- package/dist/bootstrap.d.ts +3 -7
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +29 -27
- package/dist/change-set.d.ts +33 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/change-set.js +87 -0
- package/dist/change.d.ts +9 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +6 -5
- package/dist/client.d.ts +40 -28
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +121 -177
- package/dist/collection.d.ts +15 -16
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +54 -87
- package/dist/connection.d.ts +57 -35
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1475 -964
- package/dist/conversation.d.ts +41 -19
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +483 -327
- package/dist/durability.js +6 -6
- package/dist/file.d.ts +2 -2
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +48 -71
- package/dist/host.d.ts +7 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +100 -102
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +10 -9
- package/dist/index.d.ts +12 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/log.js +5 -6
- package/dist/memory.d.ts +6 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +206 -210
- package/dist/model.d.ts +10 -25
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +18 -21
- package/dist/observation.d.ts +25 -0
- package/dist/observation.d.ts.map +1 -0
- package/dist/observation.js +85 -0
- package/dist/outcome.d.ts +17 -20
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +1 -23
- package/dist/overlay.d.ts +9 -2
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +187 -43
- package/dist/paths.d.ts +0 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +12 -19
- package/dist/query.d.ts +210 -10
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +420 -15
- package/dist/read-side.d.ts +14 -0
- package/dist/read-side.d.ts.map +1 -0
- package/dist/read-side.js +371 -0
- package/dist/refresh.d.ts +4 -5
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +45 -30
- package/dist/replica.d.ts +25 -5
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +232 -38
- package/dist/room-draft.d.ts +124 -0
- package/dist/room-draft.d.ts.map +1 -0
- package/dist/room-draft.js +327 -0
- package/dist/rows.d.ts +41 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +100 -0
- package/dist/save.d.ts +8 -4
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +265 -350
- package/dist/search.d.ts +61 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +125 -0
- package/dist/status.d.ts +4 -6
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +9 -13
- package/dist/store.d.ts +68 -30
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +360 -134
- package/dist/transfer.d.ts +5 -0
- package/dist/transfer.d.ts.map +1 -1
- package/dist/transfer.js +1 -5
- package/dist/wire.d.ts +4 -3
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +50 -65
- package/package.json +6 -12
- package/dist/adapter.js.map +0 -1
- package/dist/bootstrap.js.map +0 -1
- package/dist/change.js.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/collection.js.map +0 -1
- package/dist/connection.js.map +0 -1
- package/dist/conversation.js.map +0 -1
- package/dist/durability.js.map +0 -1
- package/dist/file.js.map +0 -1
- package/dist/host.js.map +0 -1
- package/dist/identity.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/log.js.map +0 -1
- package/dist/memory.js.map +0 -1
- package/dist/model.js.map +0 -1
- package/dist/outcome.js.map +0 -1
- package/dist/overlay.js.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/query.js.map +0 -1
- package/dist/refresh.js.map +0 -1
- package/dist/replica.js.map +0 -1
- package/dist/save.js.map +0 -1
- package/dist/status.js.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/transfer.js.map +0 -1
- package/dist/wire.js.map +0 -1
package/dist/connection.js
CHANGED
|
@@ -1,47 +1,89 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { cached, modelTypes, NO_MODEL, sameModel } from './model.js';
|
|
3
|
-
import {
|
|
1
|
+
import { verify } from '@agentshouse/mdmodel';
|
|
2
|
+
import { cached, modelTypes, NO_MODEL, sameModel, } from './model.js';
|
|
3
|
+
import { opened } from './observation.js';
|
|
4
|
+
import { filterOf, identityOf, planOf, } from './query.js';
|
|
5
|
+
import { createReadSide } from './read-side.js';
|
|
6
|
+
import { createSearchIndex, searchPlanOf, } from './search.js';
|
|
7
|
+
import { ROOM_CONTRACT_VERSION, VERSION_1_CAPABILITIES, } from './adapter.js';
|
|
4
8
|
import { bootstrapRoom } from './bootstrap.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
9
|
+
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
10
|
+
import { createConversationStore, } from './conversation.js';
|
|
11
|
+
import { MD_MODEL, parseCollection, validRoot } from './collection.js';
|
|
7
12
|
import { createLog } from './log.js';
|
|
8
|
-
import { queuedOutcome, refusalOf, refused, withheldUntilReset } from './outcome.js';
|
|
9
13
|
import { refreshRoom } from './refresh.js';
|
|
10
14
|
import { buildBatch } from './save.js';
|
|
11
|
-
import { applyReturned, compositionOf, readConfirmed, replicaOf } from './replica.js';
|
|
12
|
-
import {
|
|
15
|
+
import { applyReturned, compositionOf, landed, readConfirmed, readReclassified, replicaOf, sameMembership, withdrawals, } from './replica.js';
|
|
16
|
+
import { changedParts, changesOf, held, readParts, returnedInto, underneath, } from './room-draft.js';
|
|
17
|
+
import { settled, same, saving, STARTING, transferring, } from './status.js';
|
|
13
18
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
19
|
+
import { roomDraftRevision } from './identity.js';
|
|
20
|
+
import { beneath, normalize } from './paths.js';
|
|
21
|
+
import { openRuntimeStore, } from './store.js';
|
|
16
22
|
import { unconfirmed } from './transfer.js';
|
|
23
|
+
// Discovery repeats each minute so a Room the stream stopped serving stays unrefreshed no longer.
|
|
17
24
|
const DEFAULT_DISCOVERY_INTERVAL_MS = 60_000;
|
|
25
|
+
const EMPTY_SCHEMA = { verdict: 'accept', model: { types: {} } };
|
|
18
26
|
const knownCapabilities = new Set(VERSION_1_CAPABILITIES);
|
|
19
27
|
function within(root, path) {
|
|
20
28
|
return root === '' || beneath(root, path);
|
|
21
29
|
}
|
|
22
30
|
function declaredIdentity(model) {
|
|
23
|
-
if (model === undefined)
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
if (model === undefined)
|
|
32
|
+
return false;
|
|
33
|
+
if (model === null)
|
|
34
|
+
return true;
|
|
35
|
+
return (typeof model.identity === 'string' &&
|
|
36
|
+
model.identity.length > 0 &&
|
|
37
|
+
typeof model.version === 'string' &&
|
|
38
|
+
model.version.length > 0);
|
|
26
39
|
}
|
|
27
40
|
function declaredSelection(room) {
|
|
28
41
|
const selection = room.typedSelection;
|
|
29
|
-
if (selection === undefined)
|
|
30
|
-
|
|
31
|
-
if (!
|
|
32
|
-
|
|
42
|
+
if (selection === undefined)
|
|
43
|
+
return true;
|
|
44
|
+
if (!Array.isArray(selection.roots) || selection.roots.length === 0)
|
|
45
|
+
return false;
|
|
46
|
+
if (!declaredIdentity(selection.attachedModel))
|
|
47
|
+
return false;
|
|
48
|
+
return selection.roots.every((root, index) => validRoot(root) &&
|
|
49
|
+
root !== '' &&
|
|
50
|
+
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
51
|
+
}
|
|
52
|
+
function declaredUntypedFolders(room) {
|
|
53
|
+
const roots = room.untypedFolders;
|
|
54
|
+
if (roots === undefined)
|
|
55
|
+
return true;
|
|
56
|
+
if (!Array.isArray(roots) || roots.length === 0)
|
|
57
|
+
return false;
|
|
58
|
+
const selected = room.typedSelection?.roots ?? [];
|
|
59
|
+
return roots.every((root, index) => validRoot(root) &&
|
|
60
|
+
root !== '' &&
|
|
61
|
+
roots.every((other, at) => at === index || !within(other, root)) &&
|
|
62
|
+
selected.every((other) => !within(other, root) && !within(root, other)));
|
|
33
63
|
}
|
|
34
64
|
export function createMdRuntime(options) {
|
|
65
|
+
return composeRuntime(options).md;
|
|
66
|
+
}
|
|
67
|
+
export function composeRuntime(options) {
|
|
35
68
|
const period = options.discoveryIntervalMs ?? DEFAULT_DISCOVERY_INTERVAL_MS;
|
|
36
69
|
let authorization = options.authorization;
|
|
37
70
|
let opening;
|
|
38
71
|
let store;
|
|
39
72
|
const replicas = new Map();
|
|
40
|
-
const
|
|
73
|
+
const pendings = new Map();
|
|
74
|
+
const roomDrafts = new Map();
|
|
41
75
|
const presentations = new Map();
|
|
42
76
|
const outcomes = new Map();
|
|
77
|
+
const outcomeRooms = new Map();
|
|
78
|
+
const wanted = new Map();
|
|
43
79
|
let limits = [];
|
|
80
|
+
let slots = [];
|
|
44
81
|
let capabilities = [];
|
|
82
|
+
function rewriting() {
|
|
83
|
+
return capabilities.some((capability) => capability.name === 'move-reference-rewrites')
|
|
84
|
+
? 'provider'
|
|
85
|
+
: 'runtime';
|
|
86
|
+
}
|
|
45
87
|
const rooms = new Map();
|
|
46
88
|
const unreadable = new Set();
|
|
47
89
|
const models = new Map();
|
|
@@ -49,7 +91,10 @@ export function createMdRuntime(options) {
|
|
|
49
91
|
const invalidModels = new Set();
|
|
50
92
|
const modelEpoch = new Map();
|
|
51
93
|
const discovered = new Map();
|
|
94
|
+
let roomsListed = false;
|
|
52
95
|
const scopedRefusals = new Map();
|
|
96
|
+
const sourceRefusals = new Map();
|
|
97
|
+
const declarationRefusals = new Set();
|
|
53
98
|
function modelKey(locator, attached) {
|
|
54
99
|
return `${attached} ${locator}`;
|
|
55
100
|
}
|
|
@@ -57,25 +102,25 @@ export function createMdRuntime(options) {
|
|
|
57
102
|
return models.get(modelKey(locator, attached)) ?? NO_MODEL;
|
|
58
103
|
}
|
|
59
104
|
function savedModel(locator, attached) {
|
|
60
|
-
return invalidModels.has(modelKey(locator, attached))
|
|
61
|
-
status: 'unavailable',
|
|
62
|
-
|
|
63
|
-
} : modelOf(locator, attached);
|
|
105
|
+
return invalidModels.has(modelKey(locator, attached))
|
|
106
|
+
? { status: 'unavailable', reason: 'revoked' }
|
|
107
|
+
: modelOf(locator, attached);
|
|
64
108
|
}
|
|
65
109
|
function compositionFor(record) {
|
|
66
110
|
return compositionOf({
|
|
67
111
|
...record,
|
|
68
112
|
model: savedModel(record.locator, 'collection'),
|
|
69
|
-
selectionModel: savedModel(record.locator, 'typed-selection')
|
|
113
|
+
selectionModel: savedModel(record.locator, 'typed-selection'),
|
|
70
114
|
});
|
|
71
115
|
}
|
|
72
|
-
const mismatchedRoots = new Set();
|
|
73
116
|
let refusal = null;
|
|
74
117
|
let storageLoss = null;
|
|
75
118
|
let status = STARTING;
|
|
119
|
+
let shown = STARTING;
|
|
76
120
|
let discoveryPaused = false;
|
|
77
121
|
let timer;
|
|
78
122
|
let closed = false;
|
|
123
|
+
let stopping = false;
|
|
79
124
|
let withdrawNotifications;
|
|
80
125
|
let handed;
|
|
81
126
|
const notices = new Map();
|
|
@@ -87,296 +132,332 @@ export function createMdRuntime(options) {
|
|
|
87
132
|
const refusalListeners = new Set();
|
|
88
133
|
const storageLossListeners = new Set();
|
|
89
134
|
const statusListeners = new Set();
|
|
90
|
-
const querySubscriptions = new Set();
|
|
91
135
|
const queueSubscriptions = new Set();
|
|
92
|
-
const
|
|
136
|
+
const roomDraftListeners = new Set();
|
|
93
137
|
const presentationListeners = new Set();
|
|
138
|
+
const readSide = createReadSide();
|
|
94
139
|
let withdrawConversations;
|
|
140
|
+
let currencyQueued = false;
|
|
141
|
+
let serviceMode = 'open';
|
|
142
|
+
let haltedRefusal = false;
|
|
143
|
+
let openSignals = 0;
|
|
144
|
+
const serviceModeListeners = new Set();
|
|
95
145
|
const conversations = createConversationStore({
|
|
96
146
|
cards: declaredCards,
|
|
97
|
-
documents: ()=>
|
|
98
|
-
call: (name, input)=>invokeAction(name, input),
|
|
147
|
+
documents: (locator) => readSide.query(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
148
|
+
call: (name, input) => invokeAction(name, input),
|
|
99
149
|
durably,
|
|
100
|
-
|
|
101
|
-
|
|
150
|
+
stream: () => options.adapter.conversations,
|
|
151
|
+
publish: publishChangeSet,
|
|
152
|
+
closed: () => closed,
|
|
153
|
+
});
|
|
154
|
+
const searchIndex = createSearchIndex({
|
|
155
|
+
documents: () => readSide.documents(),
|
|
156
|
+
document: (room, path) => readSide.document(room, path),
|
|
157
|
+
messages: () => conversations.messages(),
|
|
158
|
+
message: (conversation, identity) => conversations.message(conversation, identity),
|
|
102
159
|
});
|
|
103
160
|
const journal = createLog();
|
|
104
|
-
let notifyReady = ()=>{};
|
|
105
|
-
const ready = new Promise((resolve)=>{
|
|
161
|
+
let notifyReady = () => { };
|
|
162
|
+
const ready = new Promise((resolve) => {
|
|
106
163
|
notifyReady = resolve;
|
|
107
164
|
});
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const room = drafts.get(locator);
|
|
118
|
-
if (room === undefined) return;
|
|
165
|
+
function rememberRoomDraft(record) {
|
|
166
|
+
const room = roomDrafts.get(record.locator) ?? new Map();
|
|
167
|
+
room.set(record.path, record);
|
|
168
|
+
roomDrafts.set(record.locator, room);
|
|
169
|
+
}
|
|
170
|
+
function forgetRoomDraft(locator, path) {
|
|
171
|
+
const room = roomDrafts.get(locator);
|
|
172
|
+
if (room === undefined)
|
|
173
|
+
return;
|
|
119
174
|
room.delete(path);
|
|
120
|
-
if (room.size === 0)
|
|
175
|
+
if (room.size === 0)
|
|
176
|
+
roomDrafts.delete(locator);
|
|
177
|
+
}
|
|
178
|
+
function roomDraftOf(record) {
|
|
179
|
+
return structuredClone({
|
|
180
|
+
room: record.locator,
|
|
181
|
+
path: record.path,
|
|
182
|
+
revision: record.revision,
|
|
183
|
+
parts: record.parts,
|
|
184
|
+
changedUnderneath: underneath(record.parts, record.path, replicas.get(record.locator)?.visible),
|
|
185
|
+
returned: record.returned,
|
|
186
|
+
});
|
|
121
187
|
}
|
|
122
|
-
function
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
content: structuredClone(content)
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
entries.sort((left, right)=>{
|
|
134
|
-
if (left.locator !== right.locator) return left.locator < right.locator ? -1 : 1;
|
|
135
|
-
if (left.path !== right.path) return left.path < right.path ? -1 : 1;
|
|
188
|
+
function snapshotRoomDrafts() {
|
|
189
|
+
const records = [...roomDrafts.values()].flatMap((room) => [...room.values()]);
|
|
190
|
+
records.sort((left, right) => {
|
|
191
|
+
if (left.locator !== right.locator)
|
|
192
|
+
return left.locator < right.locator ? -1 : 1;
|
|
193
|
+
if (left.path !== right.path)
|
|
194
|
+
return left.path < right.path ? -1 : 1;
|
|
136
195
|
return 0;
|
|
137
196
|
});
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
async function readDrafts(opened) {
|
|
141
|
-
await hydrate(opened);
|
|
142
|
-
return snapshotDrafts();
|
|
197
|
+
return records.map(roomDraftOf);
|
|
143
198
|
}
|
|
144
|
-
function
|
|
145
|
-
if (closed)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
199
|
+
function publishRoomDrafts(waitingOnly) {
|
|
200
|
+
if (closed)
|
|
201
|
+
return;
|
|
202
|
+
const value = snapshotRoomDrafts();
|
|
203
|
+
for (const listener of roomDraftListeners) {
|
|
204
|
+
if (waitingOnly && listener.ready)
|
|
205
|
+
continue;
|
|
149
206
|
listener.ready = true;
|
|
150
207
|
listener.notify(value);
|
|
151
208
|
}
|
|
152
209
|
}
|
|
153
|
-
function emitDrafts() {
|
|
154
|
-
publishDrafts(false);
|
|
155
|
-
}
|
|
156
|
-
function releaseWaitingDrafts() {
|
|
157
|
-
publishDrafts(true);
|
|
158
|
-
}
|
|
159
210
|
function presentationOf(app) {
|
|
160
211
|
const value = presentations.get(app);
|
|
161
212
|
return value === undefined ? null : structuredClone(value);
|
|
162
213
|
}
|
|
163
214
|
function emitPresentation(app) {
|
|
164
|
-
if (closed)
|
|
165
|
-
|
|
166
|
-
|
|
215
|
+
if (closed)
|
|
216
|
+
return;
|
|
217
|
+
for (const listener of presentationListeners) {
|
|
218
|
+
if (listener.app === app && listener.ready)
|
|
219
|
+
listener.notify(presentationOf(app));
|
|
167
220
|
}
|
|
168
221
|
}
|
|
169
|
-
function commitDraft(work) {
|
|
170
|
-
return exclusive(async ()=>{
|
|
171
|
-
const local = await openLocal();
|
|
172
|
-
await hydrate(local);
|
|
173
|
-
await work(local);
|
|
174
|
-
}).then(()=>{
|
|
175
|
-
emitDrafts();
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
222
|
function pending() {
|
|
179
|
-
return [
|
|
180
|
-
...replicas.values()
|
|
181
|
-
].some((replica)=>replica.queue.length > 0);
|
|
223
|
+
return [...replicas.values()].some((replica) => replica.queue.length > 0);
|
|
182
224
|
}
|
|
183
225
|
function attention() {
|
|
184
226
|
const attentionRequired = [];
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const head = replica.queue[0];
|
|
188
|
-
if (head === undefined) continue;
|
|
189
|
-
const outcome = outcomes.get(head.id);
|
|
190
|
-
if (outcome?.kind === 'retryable-refusal') {
|
|
191
|
-
if (outcome.reset !== undefined && (reset === null || outcome.reset < reset)) {
|
|
192
|
-
reset = outcome.reset;
|
|
193
|
-
}
|
|
227
|
+
for (const record of [...roomDrafts.values()].flatMap((room) => [...room.values()])) {
|
|
228
|
+
if (record.returned === null)
|
|
194
229
|
continue;
|
|
195
|
-
}
|
|
196
|
-
if (!refused(outcome)) continue;
|
|
197
230
|
attentionRequired.push({
|
|
198
|
-
locator,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
refusal: refusalOf(outcome),
|
|
202
|
-
...outcome.kind === 'conflicted' && outcome.roomVersion !== undefined ? {
|
|
203
|
-
roomVersion: outcome.roomVersion
|
|
204
|
-
} : {}
|
|
231
|
+
room: record.locator,
|
|
232
|
+
path: record.path,
|
|
233
|
+
returned: structuredClone(record.returned),
|
|
205
234
|
});
|
|
206
235
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const stored = {};
|
|
214
|
-
for (const batch of batches){
|
|
215
|
-
const outcome = queuedOutcome(outcomes.get(batch.id));
|
|
216
|
-
if (outcome.kind !== 'pending') stored[batch.id] = outcome;
|
|
217
|
-
}
|
|
218
|
-
return stored;
|
|
219
|
-
}
|
|
220
|
-
async function persistQueue(local, locator, batches) {
|
|
221
|
-
await local.writeQueue({
|
|
222
|
-
locator,
|
|
223
|
-
batches,
|
|
224
|
-
outcomes: storedOutcomes(batches)
|
|
236
|
+
attentionRequired.sort((left, right) => {
|
|
237
|
+
if (left.room !== right.room)
|
|
238
|
+
return left.room < right.room ? -1 : 1;
|
|
239
|
+
if (left.path !== right.path)
|
|
240
|
+
return left.path < right.path ? -1 : 1;
|
|
241
|
+
return 0;
|
|
225
242
|
});
|
|
243
|
+
let reset = null;
|
|
244
|
+
for (const { submission } of pendings.values()) {
|
|
245
|
+
if (submission.state !== 'reset')
|
|
246
|
+
continue;
|
|
247
|
+
if (reset === null || submission.reset < reset)
|
|
248
|
+
reset = submission.reset;
|
|
249
|
+
}
|
|
250
|
+
return { attentionRequired, reset };
|
|
226
251
|
}
|
|
227
252
|
function listed() {
|
|
228
|
-
return structuredClone([
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
253
|
+
return structuredClone([...rooms.values()]);
|
|
254
|
+
}
|
|
255
|
+
function slotted() {
|
|
256
|
+
return structuredClone(slots.map(({ slot, entries }) => ({
|
|
257
|
+
slot,
|
|
258
|
+
entries: entries.flatMap(({ locator, segment }) => {
|
|
259
|
+
const room = rooms.get(locator);
|
|
260
|
+
return room === undefined ? [] : [{ room, segment }];
|
|
261
|
+
}),
|
|
262
|
+
})));
|
|
263
|
+
}
|
|
264
|
+
function roomFacts() {
|
|
265
|
+
const held = [...rooms.values()].map((room) => {
|
|
266
|
+
const attached = [
|
|
267
|
+
modelOf(room.locator),
|
|
268
|
+
...(room.typedSelection === null ? [] : [modelOf(room.locator, 'typed-selection')]),
|
|
269
|
+
];
|
|
270
|
+
return {
|
|
271
|
+
locator: room.locator,
|
|
272
|
+
available: attached.every((model) => model.status !== 'unavailable'),
|
|
273
|
+
types: attached.flatMap((model) => model.status === 'unavailable' ? [] : Object.values(model.types)),
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
return { listed: roomsListed, rooms: held };
|
|
242
277
|
}
|
|
243
278
|
function emitRooms() {
|
|
244
279
|
const value = listed();
|
|
245
|
-
for (const listener of roomListeners)
|
|
280
|
+
for (const listener of roomListeners)
|
|
281
|
+
listener(value);
|
|
246
282
|
}
|
|
247
283
|
function emitRefusal() {
|
|
248
|
-
for (const listener of refusalListeners)
|
|
284
|
+
for (const listener of refusalListeners)
|
|
285
|
+
listener(refusal);
|
|
249
286
|
}
|
|
250
287
|
function emitStorageLoss() {
|
|
251
|
-
for (const listener of storageLossListeners)
|
|
288
|
+
for (const listener of storageLossListeners)
|
|
289
|
+
listener(storageLoss);
|
|
252
290
|
}
|
|
253
291
|
function moveTo(next) {
|
|
254
|
-
if (closed
|
|
292
|
+
if (closed)
|
|
293
|
+
return;
|
|
255
294
|
status = next;
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
function visible(request) {
|
|
259
|
-
if (options.queryBinding === null) {
|
|
260
|
-
if (Object.keys(request).length > 0) throw new Error('query-binding-unavailable');
|
|
261
|
-
return rawQuery(queryRooms());
|
|
262
|
-
}
|
|
263
|
-
return options.queryBinding(queryRooms(), {
|
|
264
|
-
...request,
|
|
265
|
-
selector: {
|
|
266
|
-
$and: [
|
|
267
|
-
PRESENTED,
|
|
268
|
-
request.selector ?? {}
|
|
269
|
-
]
|
|
270
|
-
}
|
|
271
|
-
});
|
|
295
|
+
show();
|
|
272
296
|
}
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
297
|
+
function show() {
|
|
298
|
+
if (closed)
|
|
299
|
+
return;
|
|
300
|
+
const next = status.state === 'synced' && !conversations.current()
|
|
301
|
+
? { ...status, state: 'syncing' }
|
|
302
|
+
: status;
|
|
303
|
+
if (same(shown, next))
|
|
304
|
+
return;
|
|
305
|
+
shown = next;
|
|
306
|
+
for (const listener of statusListeners)
|
|
307
|
+
listener(shown);
|
|
308
|
+
}
|
|
309
|
+
function submissionOf(batch) {
|
|
310
|
+
return pendings.get(batch.id)?.submission ?? { state: 'waiting' };
|
|
277
311
|
}
|
|
278
312
|
function entriesOf(locator) {
|
|
279
313
|
const replica = replicas.get(locator);
|
|
280
|
-
if (replica === undefined)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
outcome: queuedOutcome(outcomes.get(batch.id))
|
|
284
|
-
}));
|
|
314
|
+
if (replica === undefined)
|
|
315
|
+
return [];
|
|
316
|
+
return replica.queue.map((batch) => Object.freeze({ batch, ...submissionOf(batch) }));
|
|
285
317
|
}
|
|
286
318
|
function emitQueue(locator) {
|
|
287
319
|
const entries = entriesOf(locator);
|
|
288
|
-
for (const subscription of queueSubscriptions){
|
|
289
|
-
if (subscription.locator === locator)
|
|
320
|
+
for (const subscription of queueSubscriptions) {
|
|
321
|
+
if (subscription.locator === locator)
|
|
322
|
+
subscription.notify(entries);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function applyChangeSet(set) {
|
|
326
|
+
readSide.apply(set);
|
|
327
|
+
searchIndex.changed(set);
|
|
328
|
+
}
|
|
329
|
+
function publishChangeSet(set) {
|
|
330
|
+
applyChangeSet(set);
|
|
331
|
+
if (set.rooms.length > 0)
|
|
332
|
+
emitRooms();
|
|
333
|
+
for (const locator of set.queues)
|
|
334
|
+
emitQueue(locator);
|
|
335
|
+
if (set.drafts.length > 0 ||
|
|
336
|
+
set.documents.some((change) => roomDrafts.get(change.locator)?.has(change.path) === true)) {
|
|
337
|
+
publishRoomDrafts(false);
|
|
338
|
+
}
|
|
339
|
+
for (const app of set.presentations)
|
|
340
|
+
emitPresentation(app);
|
|
341
|
+
conversations.changed(set);
|
|
342
|
+
const cards = declaredCards();
|
|
343
|
+
if (cards !== undefined && set.documents.some((change) => change.locator === cards.locator)) {
|
|
344
|
+
scheduleCurrency();
|
|
290
345
|
}
|
|
291
346
|
}
|
|
292
347
|
function stopTimer() {
|
|
293
|
-
if (timer === undefined)
|
|
348
|
+
if (timer === undefined)
|
|
349
|
+
return;
|
|
294
350
|
clearInterval(timer);
|
|
295
351
|
timer = undefined;
|
|
296
352
|
}
|
|
297
353
|
function startTimer() {
|
|
298
|
-
if (timer !== undefined)
|
|
299
|
-
|
|
354
|
+
if (timer !== undefined)
|
|
355
|
+
return;
|
|
356
|
+
timer = setInterval(() => {
|
|
300
357
|
void enqueue();
|
|
301
358
|
}, period);
|
|
302
359
|
}
|
|
303
|
-
function refuse(reason, provider = null) {
|
|
304
|
-
if (reason === 'revoked-authorization') {
|
|
305
|
-
const revoked = {
|
|
306
|
-
status: 'unavailable',
|
|
307
|
-
reason: 'revoked'
|
|
308
|
-
};
|
|
309
|
-
const attachments = [
|
|
310
|
-
'collection',
|
|
311
|
-
'typed-selection'
|
|
312
|
-
];
|
|
313
|
-
const keys = new Set(models.keys());
|
|
314
|
-
for (const locator of rooms.keys()){
|
|
315
|
-
for (const attached of attachments)keys.add(modelKey(locator, attached));
|
|
316
|
-
}
|
|
317
|
-
for (const key of keys){
|
|
318
|
-
invalidModels.add(key);
|
|
319
|
-
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
320
|
-
models.set(key, revoked);
|
|
321
|
-
liveModels.delete(key);
|
|
322
|
-
}
|
|
323
|
-
for (const [locator, room] of rooms)rooms.set(locator, {
|
|
324
|
-
...room,
|
|
325
|
-
model: modelTypes(revoked),
|
|
326
|
-
typedSelection: room.typedSelection === null ? null : {
|
|
327
|
-
...room.typedSelection,
|
|
328
|
-
model: modelTypes(revoked)
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
emitRooms();
|
|
332
|
-
void exclusive(async ()=>{
|
|
333
|
-
const local = await openLocal();
|
|
334
|
-
for (const record of (await local.all()))await local.publish({
|
|
335
|
-
...record,
|
|
336
|
-
model: revoked,
|
|
337
|
-
selectionModel: revoked
|
|
338
|
-
});
|
|
339
|
-
}).catch(()=>{
|
|
340
|
-
journal.record('model invalidation durable-write failure');
|
|
341
|
-
});
|
|
342
|
-
}
|
|
360
|
+
async function refuse(reason, provider = null) {
|
|
343
361
|
stopTimer();
|
|
344
362
|
discoveryPaused = true;
|
|
345
|
-
|
|
346
|
-
reason
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
363
|
+
try {
|
|
364
|
+
if (reason === 'revoked-authorization') {
|
|
365
|
+
await exclusive(async () => {
|
|
366
|
+
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
367
|
+
try {
|
|
368
|
+
const local = await openLocal();
|
|
369
|
+
await local.revokeModels([...replicas.values()].map((replica) => ({
|
|
370
|
+
record: replica.confirmed.record,
|
|
371
|
+
queue: replica.queue,
|
|
372
|
+
})));
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
journal.record('model revocation persistence failed');
|
|
376
|
+
}
|
|
377
|
+
const keys = new Set(models.keys());
|
|
378
|
+
for (const locator of rooms.keys()) {
|
|
379
|
+
keys.add(modelKey(locator, 'collection'));
|
|
380
|
+
keys.add(modelKey(locator, 'typed-selection'));
|
|
381
|
+
}
|
|
382
|
+
for (const key of keys) {
|
|
383
|
+
invalidModels.add(key);
|
|
384
|
+
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
385
|
+
models.set(key, revoked);
|
|
386
|
+
liveModels.delete(key);
|
|
387
|
+
}
|
|
388
|
+
const changed = [];
|
|
389
|
+
for (const [locator, room] of rooms) {
|
|
390
|
+
const before = replicas.get(locator) ?? null;
|
|
391
|
+
if (before !== null) {
|
|
392
|
+
const record = {
|
|
393
|
+
...before.confirmed.record,
|
|
394
|
+
model: revoked,
|
|
395
|
+
selectionModel: revoked,
|
|
396
|
+
};
|
|
397
|
+
const read = readConfirmed(record, before.confirmed);
|
|
398
|
+
replicas.set(locator, read.kind === 'staged'
|
|
399
|
+
? replicaOf(read.confirmed, before.queue, rewriting(), before)
|
|
400
|
+
: { ...before, confirmed: { ...before.confirmed, record } });
|
|
401
|
+
}
|
|
402
|
+
rooms.set(locator, {
|
|
403
|
+
...room,
|
|
404
|
+
model: modelTypes(revoked),
|
|
405
|
+
typedSelection: room.typedSelection === null
|
|
406
|
+
? null
|
|
407
|
+
: { ...room.typedSelection, model: modelTypes(revoked) },
|
|
408
|
+
});
|
|
409
|
+
changed.push(changeSet(locator, before, replicas.get(locator) ?? null, { room: true }));
|
|
410
|
+
}
|
|
411
|
+
publishChangeSet(mergeChangeSets(changed));
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
finally {
|
|
416
|
+
refusal = {
|
|
417
|
+
reason,
|
|
418
|
+
reauthorizationOffered: reason === 'revoked-authorization',
|
|
419
|
+
refusal: provider,
|
|
420
|
+
};
|
|
421
|
+
journal.record(`connection refused ${reason}`);
|
|
422
|
+
emitRefusal();
|
|
423
|
+
}
|
|
352
424
|
}
|
|
353
425
|
function list(room, synchronizationAvailable) {
|
|
354
426
|
const selection = room.typedSelection ?? null;
|
|
355
427
|
rooms.set(room.locator, {
|
|
356
428
|
name: room.name,
|
|
357
|
-
collectionRoot: room.collectionRoot,
|
|
358
429
|
locator: room.locator,
|
|
359
430
|
operations: room.operations,
|
|
360
431
|
replica: room.replica,
|
|
361
432
|
attachedModel: room.attachedModel,
|
|
362
433
|
model: modelTypes(modelOf(room.locator)),
|
|
363
|
-
typedSelection: selection === null
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
434
|
+
typedSelection: selection === null
|
|
435
|
+
? null
|
|
436
|
+
: {
|
|
437
|
+
roots: selection.roots,
|
|
438
|
+
attachedModel: selection.attachedModel,
|
|
439
|
+
model: modelTypes(modelOf(room.locator, 'typed-selection')),
|
|
440
|
+
},
|
|
441
|
+
untypedFolders: room.untypedFolders ?? [],
|
|
368
442
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
369
443
|
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
444
|
+
attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
|
|
370
445
|
synchronizationAvailable,
|
|
371
|
-
refusal: scopedRefusals.get(room.locator) ?? null
|
|
446
|
+
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
372
447
|
});
|
|
373
448
|
}
|
|
374
|
-
function applyDiscovery(listed) {
|
|
375
|
-
const listedLocators = new Set(listed.map((room)=>room.locator));
|
|
376
|
-
for (const locator of listedLocators)
|
|
449
|
+
async function applyDiscovery(listed, local) {
|
|
450
|
+
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
451
|
+
for (const locator of listedLocators)
|
|
452
|
+
scopedRefusals.delete(locator);
|
|
377
453
|
discovered.clear();
|
|
378
|
-
for (const room of listed)
|
|
379
|
-
|
|
454
|
+
for (const room of listed)
|
|
455
|
+
discovered.set(room.locator, room);
|
|
456
|
+
for (const locator of wanted.keys())
|
|
457
|
+
if (!listedLocators.has(locator))
|
|
458
|
+
wanted.delete(locator);
|
|
459
|
+
await dropUnlisted(listedLocators, local);
|
|
460
|
+
for (const room of listed) {
|
|
380
461
|
const held = replicas.get(room.locator)?.confirmed.record;
|
|
381
462
|
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
382
463
|
list(held, false);
|
|
@@ -384,23 +465,64 @@ export function createMdRuntime(options) {
|
|
|
384
465
|
}
|
|
385
466
|
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
386
467
|
}
|
|
387
|
-
|
|
388
|
-
if (listedLocators.has(locator)) continue;
|
|
389
|
-
rooms.set(locator, {
|
|
390
|
-
...room,
|
|
391
|
-
synchronizationAvailable: false
|
|
392
|
-
});
|
|
393
|
-
}
|
|
468
|
+
roomsListed = true;
|
|
394
469
|
emitRooms();
|
|
395
|
-
|
|
470
|
+
}
|
|
471
|
+
function unlistedHeld(listed) {
|
|
472
|
+
return [...new Set([...rooms.keys(), ...roomDrafts.keys()])].filter((locator) => !listed.has(locator));
|
|
473
|
+
}
|
|
474
|
+
async function dropUnlisted(listed, local) {
|
|
475
|
+
if (unlistedHeld(listed).length === 0)
|
|
476
|
+
return;
|
|
477
|
+
await exclusive(async () => {
|
|
478
|
+
const unlisted = unlistedHeld(listed);
|
|
479
|
+
await local.dropRooms(unlisted);
|
|
480
|
+
for (const [batch, locator] of outcomeRooms) {
|
|
481
|
+
if (listed.has(locator))
|
|
482
|
+
continue;
|
|
483
|
+
outcomeRooms.delete(batch);
|
|
484
|
+
outcomes.delete(batch);
|
|
485
|
+
}
|
|
486
|
+
const removed = unlisted.map((locator) => changeSet(locator, replicas.get(locator) ?? null, null, {
|
|
487
|
+
room: true,
|
|
488
|
+
queue: true,
|
|
489
|
+
drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
|
|
490
|
+
}));
|
|
491
|
+
for (const locator of unlisted) {
|
|
492
|
+
for (const batch of replicas.get(locator)?.queue ?? [])
|
|
493
|
+
pendings.delete(batch.id);
|
|
494
|
+
replicas.delete(locator);
|
|
495
|
+
rooms.delete(locator);
|
|
496
|
+
roomDrafts.delete(locator);
|
|
497
|
+
unreadable.delete(locator);
|
|
498
|
+
scopedRefusals.delete(locator);
|
|
499
|
+
sourceRefusals.delete(locator);
|
|
500
|
+
declarationRefusals.delete(locator);
|
|
501
|
+
for (const attached of ['collection', 'typed-selection']) {
|
|
502
|
+
const key = modelKey(locator, attached);
|
|
503
|
+
models.delete(key);
|
|
504
|
+
liveModels.delete(key);
|
|
505
|
+
invalidModels.delete(key);
|
|
506
|
+
modelEpoch.delete(key);
|
|
507
|
+
}
|
|
508
|
+
journal.record(`room unlisted ${locator}`);
|
|
509
|
+
}
|
|
510
|
+
publishChangeSet(mergeChangeSets(removed));
|
|
511
|
+
moveTo({ ...status, ...attention() });
|
|
512
|
+
});
|
|
396
513
|
}
|
|
397
514
|
function restored(key, persisted) {
|
|
398
515
|
const prior = models.get(key);
|
|
399
|
-
if (prior !== undefined &&
|
|
516
|
+
if (prior !== undefined &&
|
|
517
|
+
prior.status !== 'unavailable' &&
|
|
518
|
+
(persisted.status === 'unavailable' || !sameModel(prior.snapshot, persisted.snapshot))) {
|
|
400
519
|
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
401
520
|
invalidModels.add(key);
|
|
402
521
|
}
|
|
403
|
-
const model = persisted.status === 'unavailable' ||
|
|
522
|
+
const model = persisted.status === 'unavailable' ||
|
|
523
|
+
liveModels.get(key) === JSON.stringify(persisted.snapshot)
|
|
524
|
+
? persisted
|
|
525
|
+
: cached(persisted);
|
|
404
526
|
models.set(key, model);
|
|
405
527
|
if (model.status === 'unavailable') {
|
|
406
528
|
liveModels.delete(key);
|
|
@@ -409,76 +531,97 @@ export function createMdRuntime(options) {
|
|
|
409
531
|
return model;
|
|
410
532
|
}
|
|
411
533
|
async function hydrate(opened) {
|
|
412
|
-
const [records, queues, kept, presented,
|
|
534
|
+
const [records, queues, recordedOutcomes, kept, presented, held, chatDrafts, lastCompleted, declared, named, listedSlots,] = await Promise.all([
|
|
413
535
|
opened.all(),
|
|
414
536
|
opened.queues(),
|
|
415
|
-
opened.
|
|
537
|
+
opened.outcomes(),
|
|
538
|
+
opened.roomDrafts(),
|
|
416
539
|
opened.presentation(),
|
|
417
540
|
opened.conversations(),
|
|
418
|
-
opened.
|
|
541
|
+
opened.chatDrafts(),
|
|
419
542
|
opened.lastCompleted(),
|
|
420
|
-
opened.providerLimits()
|
|
543
|
+
opened.providerLimits(),
|
|
544
|
+
opened.capabilities(),
|
|
545
|
+
opened.slots(),
|
|
421
546
|
]);
|
|
422
547
|
limits = declared;
|
|
423
|
-
|
|
424
|
-
|
|
548
|
+
capabilities = named;
|
|
549
|
+
slots = listedSlots;
|
|
550
|
+
roomDrafts.clear();
|
|
551
|
+
for (const record of kept)
|
|
552
|
+
rememberRoomDraft(record);
|
|
425
553
|
presentations.clear();
|
|
426
|
-
for (const record of presented)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
554
|
+
for (const record of presented)
|
|
555
|
+
presentations.set(record.app, record.value);
|
|
556
|
+
conversations.hydrate(held, chatDrafts);
|
|
557
|
+
const queuedBatches = new Map(queues.map((queue) => [queue.locator, queue.pending.map(({ batch }) => batch)]));
|
|
558
|
+
pendings.clear();
|
|
559
|
+
for (const queue of queues) {
|
|
560
|
+
for (const { batch, state } of queue.pending)
|
|
561
|
+
pendings.set(batch.id, state);
|
|
562
|
+
}
|
|
563
|
+
outcomes.clear();
|
|
564
|
+
outcomeRooms.clear();
|
|
565
|
+
for (const record of recordedOutcomes) {
|
|
566
|
+
outcomes.set(record.id, record.outcome);
|
|
567
|
+
outcomeRooms.set(record.id, record.locator);
|
|
568
|
+
}
|
|
569
|
+
const previousReplicas = new Map(replicas);
|
|
437
570
|
replicas.clear();
|
|
438
|
-
for (const record of records){
|
|
571
|
+
for (const record of records) {
|
|
439
572
|
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
440
573
|
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
574
|
+
const prior = previousReplicas.get(record.locator);
|
|
575
|
+
const retyped = { ...record, model, selectionModel };
|
|
576
|
+
const read = readConfirmed(retyped, prior?.confirmed);
|
|
577
|
+
const confirmed = read.kind === 'staged'
|
|
578
|
+
? read.confirmed
|
|
579
|
+
: {
|
|
580
|
+
record: retyped,
|
|
581
|
+
collection: parseCollection(retyped.documents, compositionOf(retyped)),
|
|
582
|
+
};
|
|
583
|
+
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], rewriting(), prior));
|
|
448
584
|
const existing = rooms.get(record.locator);
|
|
449
|
-
if (existing === undefined)
|
|
585
|
+
if (existing === undefined)
|
|
586
|
+
list(record, false);
|
|
450
587
|
else {
|
|
451
588
|
rooms.set(record.locator, {
|
|
452
589
|
...existing,
|
|
453
590
|
roomVersion: record.roomVersion,
|
|
591
|
+
attachments: record.attachments,
|
|
454
592
|
operations: record.operations,
|
|
455
593
|
replica: record.replica,
|
|
456
594
|
attachedModel: record.attachedModel,
|
|
457
595
|
model: modelTypes(model),
|
|
458
|
-
typedSelection: record.typedSelection === null
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
596
|
+
typedSelection: record.typedSelection === null
|
|
597
|
+
? null
|
|
598
|
+
: {
|
|
599
|
+
roots: record.typedSelection.roots,
|
|
600
|
+
attachedModel: record.typedSelection.attachedModel,
|
|
601
|
+
model: modelTypes(selectionModel),
|
|
602
|
+
},
|
|
603
|
+
untypedFolders: record.untypedFolders,
|
|
463
604
|
});
|
|
464
605
|
}
|
|
606
|
+
if (read.kind === 'refused') {
|
|
607
|
+
refusedSource(record, read);
|
|
608
|
+
declarationRefusals.add(record.locator);
|
|
609
|
+
}
|
|
465
610
|
}
|
|
466
611
|
return {
|
|
467
612
|
lastCompleted,
|
|
468
|
-
locators: new Set([
|
|
469
|
-
|
|
470
|
-
...replicas.keys()
|
|
471
|
-
])
|
|
613
|
+
locators: new Set([...queuedBatches.keys(), ...replicas.keys()]),
|
|
614
|
+
read: mergeChangeSets([...new Set([...previousReplicas.keys(), ...replicas.keys()])].map((locator) => changeSet(locator, previousReplicas.get(locator) ?? null, replicas.get(locator) ?? null))),
|
|
472
615
|
};
|
|
473
616
|
}
|
|
474
617
|
async function readState(opened) {
|
|
475
|
-
const { lastCompleted, locators } = await hydrate(opened);
|
|
476
|
-
|
|
618
|
+
const { lastCompleted, locators, read } = await hydrate(opened);
|
|
619
|
+
applyChangeSet(read);
|
|
620
|
+
if (replicas.size > 0)
|
|
477
621
|
emitRooms();
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
emitDrafts();
|
|
622
|
+
for (const locator of locators)
|
|
623
|
+
emitQueue(locator);
|
|
624
|
+
publishRoomDrafts(false);
|
|
482
625
|
return lastCompleted;
|
|
483
626
|
}
|
|
484
627
|
async function open() {
|
|
@@ -488,29 +631,26 @@ export function createMdRuntime(options) {
|
|
|
488
631
|
const storedGeneration = await opened.generation();
|
|
489
632
|
const loss = detectStorageLoss(storedGeneration, kept);
|
|
490
633
|
const generation = nextGeneration(storedGeneration, kept);
|
|
491
|
-
if (generation !== storedGeneration)
|
|
492
|
-
|
|
634
|
+
if (generation !== storedGeneration)
|
|
635
|
+
await opened.rememberGeneration(generation);
|
|
636
|
+
if (generation !== kept)
|
|
637
|
+
await options.shell.keepGeneration(generation);
|
|
493
638
|
if (loss !== null) {
|
|
494
639
|
storageLoss = loss;
|
|
495
640
|
emitStorageLoss();
|
|
496
641
|
}
|
|
497
642
|
const lastCompleted = await readState(opened);
|
|
498
|
-
moveTo({
|
|
499
|
-
...status,
|
|
500
|
-
lastCompleted,
|
|
501
|
-
...attention()
|
|
502
|
-
});
|
|
643
|
+
moveTo({ ...status, lastCompleted, ...attention() });
|
|
503
644
|
return opened;
|
|
504
645
|
}
|
|
505
646
|
function durably(work) {
|
|
506
|
-
return exclusive(async ()=>{
|
|
647
|
+
return exclusive(async () => {
|
|
507
648
|
const local = await openLocal();
|
|
508
|
-
await hydrate(local);
|
|
509
649
|
return work(local);
|
|
510
650
|
});
|
|
511
651
|
}
|
|
512
652
|
function openLocal() {
|
|
513
|
-
opening ??= open().catch((error)=>{
|
|
653
|
+
opening ??= open().catch((error) => {
|
|
514
654
|
opening = undefined;
|
|
515
655
|
throw error;
|
|
516
656
|
});
|
|
@@ -518,247 +658,338 @@ export function createMdRuntime(options) {
|
|
|
518
658
|
}
|
|
519
659
|
function exclusive(work) {
|
|
520
660
|
const next = mutations.then(work, work);
|
|
521
|
-
mutations = next.then(()=>undefined, ()=>undefined);
|
|
661
|
+
mutations = next.then(() => undefined, () => undefined);
|
|
522
662
|
return next;
|
|
523
663
|
}
|
|
664
|
+
function withdrawable(batch) {
|
|
665
|
+
const state = pendings.get(batch.id);
|
|
666
|
+
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
667
|
+
}
|
|
668
|
+
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
669
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable, rewriting());
|
|
670
|
+
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
671
|
+
const returning = [
|
|
672
|
+
...withdrawn.map((batch) => ({ batch, returned: { kind: 'withdrawn' } })).reverse(),
|
|
673
|
+
...(settling.leaving === undefined ? [] : [settling.leaving]),
|
|
674
|
+
];
|
|
675
|
+
const written = new Map();
|
|
676
|
+
for (const { batch, returned } of returning) {
|
|
677
|
+
if (returned === null)
|
|
678
|
+
continue;
|
|
679
|
+
for (const saved of pendings.get(batch.id)?.drafts ?? []) {
|
|
680
|
+
const existing = written.get(saved.path) ?? roomDrafts.get(locator)?.get(saved.path);
|
|
681
|
+
written.set(saved.path, {
|
|
682
|
+
locator,
|
|
683
|
+
path: saved.path,
|
|
684
|
+
revision: roomDraftRevision(),
|
|
685
|
+
parts: returnedInto(existing?.parts, saved.parts),
|
|
686
|
+
returned,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const settledOutcomes = [
|
|
691
|
+
...(settling.accepted === undefined ? [] : [settling.accepted]),
|
|
692
|
+
...(settling.leaving === undefined
|
|
693
|
+
? []
|
|
694
|
+
: [{ id: settling.leaving.batch.id, outcome: settling.leaving.outcome }]),
|
|
695
|
+
...withdrawn.map((batch) => ({ id: batch.id, outcome: { kind: 'withdrawn' } })),
|
|
696
|
+
];
|
|
697
|
+
await local.write({
|
|
698
|
+
next: confirmed.record,
|
|
699
|
+
previous: before?.confirmed.record ?? null,
|
|
700
|
+
queue: kept,
|
|
701
|
+
previousQueue: before?.queue ?? [],
|
|
702
|
+
outcomes: settledOutcomes,
|
|
703
|
+
roomDrafts: [...written.values()],
|
|
704
|
+
});
|
|
705
|
+
for (const { id, outcome } of settledOutcomes) {
|
|
706
|
+
outcomes.set(id, outcome);
|
|
707
|
+
outcomeRooms.set(id, locator);
|
|
708
|
+
pendings.delete(id);
|
|
709
|
+
}
|
|
710
|
+
for (const record of written.values())
|
|
711
|
+
rememberRoomDraft(record);
|
|
712
|
+
for (const batch of withdrawn)
|
|
713
|
+
journal.record(`outcome ${locator} ${batch.id} withdrawn`);
|
|
714
|
+
replicas.set(locator, replicaOf(confirmed, kept, rewriting(), before));
|
|
715
|
+
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
716
|
+
room: settling.room,
|
|
717
|
+
queue: kept.length !== (before?.queue.length ?? 0),
|
|
718
|
+
drafts: [...written.keys()],
|
|
719
|
+
});
|
|
720
|
+
}
|
|
524
721
|
function publish(local, room, confirmed) {
|
|
525
|
-
return exclusive(async ()=>{
|
|
526
|
-
|
|
722
|
+
return exclusive(async () => {
|
|
723
|
+
if (!continuing())
|
|
724
|
+
return;
|
|
725
|
+
const prior = replicas.get(room.locator);
|
|
527
726
|
const retained = readConfirmed({
|
|
528
727
|
...confirmed.record,
|
|
529
728
|
model: modelOf(room.locator),
|
|
530
|
-
selectionModel: modelOf(room.locator, 'typed-selection')
|
|
729
|
+
selectionModel: modelOf(room.locator, 'typed-selection'),
|
|
730
|
+
}, prior?.confirmed);
|
|
731
|
+
if (retained.kind !== 'staged')
|
|
732
|
+
throw new Error(`Cannot retype staged Room ${room.locator}`);
|
|
733
|
+
const queue = prior?.queue ?? [];
|
|
734
|
+
const roomVersion = retained.confirmed.record.roomVersion;
|
|
735
|
+
const accepted = queue.filter((batch) => {
|
|
736
|
+
const outcome = outcomes.get(batch.id);
|
|
737
|
+
return (outcome?.kind === 'accepted' &&
|
|
738
|
+
options.adapter.reached(room.locator, roomVersion, outcome.roomVersion));
|
|
531
739
|
});
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
740
|
+
const set = await settle(local, room.locator, prior, retained.confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true });
|
|
741
|
+
for (const batch of accepted)
|
|
742
|
+
pendings.delete(batch.id);
|
|
743
|
+
const target = wanted.get(room.locator);
|
|
744
|
+
if (target !== undefined && options.adapter.reached(room.locator, roomVersion, target)) {
|
|
745
|
+
wanted.delete(room.locator);
|
|
536
746
|
}
|
|
747
|
+
unreadable.delete(room.locator);
|
|
748
|
+
sourceRefusals.delete(room.locator);
|
|
749
|
+
declarationRefusals.delete(room.locator);
|
|
537
750
|
list(room, true);
|
|
538
|
-
|
|
539
|
-
|
|
751
|
+
publishChangeSet(set);
|
|
752
|
+
moveTo({ ...status, ...attention() });
|
|
540
753
|
});
|
|
541
754
|
}
|
|
542
|
-
async function
|
|
755
|
+
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
543
756
|
const local = await openLocal();
|
|
544
|
-
await hydrate(local);
|
|
545
757
|
const replica = replicas.get(locator);
|
|
546
758
|
const room = rooms.get(locator);
|
|
547
759
|
if (replica === undefined || room === undefined) {
|
|
548
760
|
throw new Error(`No Local Replica for ${locator}`);
|
|
549
761
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
762
|
+
const saved = [];
|
|
763
|
+
for (const { path, revision } of named) {
|
|
764
|
+
const record = roomDrafts.get(locator)?.get(path);
|
|
765
|
+
if (record === undefined || record.revision !== revision) {
|
|
766
|
+
return { outcome: 'refused', rule: 'stale', file: path };
|
|
767
|
+
}
|
|
768
|
+
if (!saved.some((draft) => draft.path === path))
|
|
769
|
+
saved.push({ path, parts: record.parts });
|
|
770
|
+
}
|
|
771
|
+
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
772
|
+
if (changed.length > 0)
|
|
773
|
+
return { outcome: 'refused', rule: 'changed-underneath', parts: changed };
|
|
774
|
+
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
554
775
|
const built = buildBatch({
|
|
555
776
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
556
777
|
composition: compositionFor(replica.confirmed.record),
|
|
557
778
|
operations: room.operations,
|
|
558
|
-
binding: options.modelBinding,
|
|
559
779
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
560
780
|
providerLimits: limits,
|
|
781
|
+
rewriting: rewriting(),
|
|
561
782
|
visible: replica.visible,
|
|
562
|
-
changes
|
|
783
|
+
changes: parts.flatMap(({ changes }) => changes),
|
|
784
|
+
bases: parts.flatMap(({ bases }) => bases),
|
|
785
|
+
writtenBy,
|
|
563
786
|
});
|
|
564
|
-
if (built.kind === 'refused')
|
|
565
|
-
outcome: 'refused',
|
|
566
|
-
|
|
567
|
-
};
|
|
568
|
-
const head = replica.queue[0];
|
|
569
|
-
if (replacing !== undefined) {
|
|
570
|
-
if (head === undefined || head.id !== replacing || !refused(outcomes.get(head.id))) {
|
|
571
|
-
return {
|
|
572
|
-
outcome: 'refused',
|
|
573
|
-
rule: 'no-refused-head'
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
const batches = replacing === undefined ? [
|
|
578
|
-
...replica.queue,
|
|
579
|
-
built.batch
|
|
580
|
-
] : [
|
|
581
|
-
built.batch,
|
|
582
|
-
...replica.queue.slice(1)
|
|
583
|
-
];
|
|
584
|
-
const consumed = [
|
|
585
|
-
...new Set(changes.map((change)=>change.path))
|
|
586
|
-
];
|
|
787
|
+
if (built.kind === 'refused')
|
|
788
|
+
return { outcome: 'refused', ...built.refusal };
|
|
789
|
+
const batches = [...replica.queue, built.batch];
|
|
790
|
+
const consumed = saved.map(({ path }) => path);
|
|
587
791
|
try {
|
|
588
|
-
await local.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
rule: 'durable-write'
|
|
597
|
-
};
|
|
792
|
+
await local.write({
|
|
793
|
+
next: replica.confirmed.record,
|
|
794
|
+
previous: replica.confirmed.record,
|
|
795
|
+
queue: batches,
|
|
796
|
+
previousQueue: replica.queue,
|
|
797
|
+
saved,
|
|
798
|
+
consumedRoomDrafts: consumed,
|
|
799
|
+
});
|
|
598
800
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
801
|
+
catch {
|
|
802
|
+
return { outcome: 'refused', rule: 'durable-write' };
|
|
803
|
+
}
|
|
804
|
+
pendings.set(built.batch.id, { drafts: saved, submission: { state: 'waiting' }, lost: false });
|
|
805
|
+
for (const path of consumed)
|
|
806
|
+
forgetRoomDraft(locator, path);
|
|
807
|
+
replicas.set(locator, replicaOf(replica.confirmed, batches, rewriting(), replica));
|
|
808
|
+
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
809
|
+
queue: true,
|
|
810
|
+
drafts: consumed,
|
|
811
|
+
}));
|
|
812
|
+
moveTo({ ...saving(status), ...attention() });
|
|
609
813
|
void enqueue();
|
|
610
|
-
return {
|
|
611
|
-
outcome: 'saved',
|
|
612
|
-
id: built.batch.id
|
|
613
|
-
};
|
|
814
|
+
return { outcome: 'saved', identifier: built.batch.id };
|
|
614
815
|
}
|
|
615
|
-
async function
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (replica === undefined || head === undefined || head.id !== identifier || !refused(outcomes.get(head.id))) {
|
|
816
|
+
async function mark(local, locator, identifier, submission, lost) {
|
|
817
|
+
const state = pendings.get(identifier);
|
|
818
|
+
if (!continuing() ||
|
|
819
|
+
state === undefined ||
|
|
820
|
+
(JSON.stringify(state.submission) === JSON.stringify(submission) && state.lost === lost)) {
|
|
621
821
|
return;
|
|
622
822
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
823
|
+
await local.submit(locator, identifier, submission, lost);
|
|
824
|
+
pendings.set(identifier, { ...state, submission, lost });
|
|
825
|
+
const replica = replicas.get(locator);
|
|
826
|
+
if (replica !== undefined)
|
|
827
|
+
publishChangeSet(changeSet(locator, replica, replica, { queue: true }));
|
|
828
|
+
moveTo({ ...status, ...attention() });
|
|
829
|
+
}
|
|
830
|
+
async function keepUpload(local, locator, identifier, upload) {
|
|
831
|
+
if (!continuing())
|
|
832
|
+
throw new Error('connection refused');
|
|
833
|
+
await local.keepUpload(locator, identifier, upload);
|
|
834
|
+
pendings.set(identifier, { ...pendings.get(identifier), upload });
|
|
835
|
+
if (!continuing())
|
|
836
|
+
throw new Error('connection refused');
|
|
635
837
|
}
|
|
636
838
|
async function readHistory(locator, path, query = {}) {
|
|
637
|
-
if (closed || refusal !== null)
|
|
638
|
-
kind: 'transport-uncertainty'
|
|
639
|
-
};
|
|
839
|
+
if (closed || refusal !== null)
|
|
840
|
+
return { kind: 'transport-uncertainty' };
|
|
640
841
|
try {
|
|
641
842
|
await ensureAuthorization();
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
};
|
|
843
|
+
}
|
|
844
|
+
catch {
|
|
845
|
+
return { kind: 'transport-uncertainty' };
|
|
646
846
|
}
|
|
647
847
|
if (closed || authorization === undefined || refusal !== null) {
|
|
648
|
-
return {
|
|
649
|
-
kind: 'transport-uncertainty'
|
|
650
|
-
};
|
|
848
|
+
return { kind: 'transport-uncertainty' };
|
|
651
849
|
}
|
|
652
|
-
const transfer = [
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
];
|
|
658
|
-
if (query.revision !== undefined) transfer.push(query.revision);
|
|
659
|
-
if (query.pageToken !== undefined) transfer.push(query.pageToken);
|
|
850
|
+
const transfer = ['transfer', 'history', locator, path];
|
|
851
|
+
if (query.revision !== undefined)
|
|
852
|
+
transfer.push(query.revision);
|
|
853
|
+
if (query.pageToken !== undefined)
|
|
854
|
+
transfer.push(query.pageToken);
|
|
660
855
|
journal.record(transfer.join(' '));
|
|
661
856
|
try {
|
|
662
857
|
const result = await options.adapter.history(authorization, {
|
|
663
858
|
locator,
|
|
664
859
|
path,
|
|
665
|
-
...query.revision === undefined ? {} : {
|
|
666
|
-
|
|
667
|
-
},
|
|
668
|
-
...query.pageToken === undefined ? {} : {
|
|
669
|
-
pageToken: query.pageToken
|
|
670
|
-
}
|
|
860
|
+
...(query.revision === undefined ? {} : { revision: query.revision }),
|
|
861
|
+
...(query.pageToken === undefined ? {} : { pageToken: query.pageToken }),
|
|
671
862
|
});
|
|
672
863
|
journal.record(`outcome history ${result.kind}`);
|
|
673
864
|
if (result.kind === 'scoped-refusal') {
|
|
674
865
|
const room = replicas.get(result.locator)?.confirmed.record;
|
|
675
|
-
if (room !== undefined)
|
|
866
|
+
if (room !== undefined)
|
|
867
|
+
await scoped(result, room, await openLocal());
|
|
676
868
|
}
|
|
677
|
-
if (result.kind === 'revoked-authorization')
|
|
869
|
+
if (result.kind === 'revoked-authorization')
|
|
870
|
+
await refuse('revoked-authorization');
|
|
678
871
|
return result;
|
|
679
|
-
}
|
|
872
|
+
}
|
|
873
|
+
catch {
|
|
680
874
|
journal.record('outcome history transport-uncertainty');
|
|
681
|
-
return {
|
|
682
|
-
kind: 'transport-uncertainty'
|
|
683
|
-
};
|
|
875
|
+
return { kind: 'transport-uncertainty' };
|
|
684
876
|
}
|
|
685
877
|
}
|
|
686
878
|
function declaredCards() {
|
|
687
|
-
for (const capability of capabilities){
|
|
688
|
-
if (capability.conversationCards !== undefined)
|
|
879
|
+
for (const capability of capabilities) {
|
|
880
|
+
if (capability.conversationCards !== undefined)
|
|
881
|
+
return capability.conversationCards;
|
|
689
882
|
}
|
|
690
883
|
return undefined;
|
|
691
884
|
}
|
|
692
885
|
function declaredAction(name) {
|
|
693
|
-
for (const capability of capabilities){
|
|
694
|
-
for (const action of capability.actions ?? []){
|
|
695
|
-
if (action.name === name)
|
|
886
|
+
for (const capability of capabilities) {
|
|
887
|
+
for (const action of capability.actions ?? []) {
|
|
888
|
+
if (action.name === name)
|
|
889
|
+
return action;
|
|
696
890
|
}
|
|
697
891
|
}
|
|
698
892
|
return undefined;
|
|
699
893
|
}
|
|
700
894
|
async function invokeAction(name, input) {
|
|
701
895
|
await ready;
|
|
702
|
-
if (closed || refusal !== null)
|
|
703
|
-
kind: 'transport-uncertainty'
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
kind: 'undeclared'
|
|
707
|
-
};
|
|
896
|
+
if (closed || refusal !== null)
|
|
897
|
+
return { kind: 'transport-uncertainty' };
|
|
898
|
+
if (declaredAction(name) === undefined)
|
|
899
|
+
return { kind: 'undeclared' };
|
|
708
900
|
try {
|
|
709
901
|
await ensureAuthorization();
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
};
|
|
902
|
+
}
|
|
903
|
+
catch {
|
|
904
|
+
return { kind: 'transport-uncertainty' };
|
|
714
905
|
}
|
|
715
906
|
if (closed || authorization === undefined || refusal !== null) {
|
|
716
|
-
return {
|
|
717
|
-
kind: 'transport-uncertainty'
|
|
718
|
-
};
|
|
907
|
+
return { kind: 'transport-uncertainty' };
|
|
719
908
|
}
|
|
720
909
|
journal.record(`transfer action ${name}`);
|
|
721
910
|
try {
|
|
722
|
-
const result = await options.adapter.action(authorization, {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
});
|
|
911
|
+
const result = await options.adapter.action(authorization, { name, input });
|
|
912
|
+
if (result.kind === 'action' && result.at !== undefined)
|
|
913
|
+
want(result.at);
|
|
726
914
|
if (result.kind === 'scoped-refusal') {
|
|
727
915
|
const room = replicas.get(result.locator)?.confirmed.record;
|
|
728
|
-
if (room !== undefined)
|
|
916
|
+
if (room !== undefined)
|
|
917
|
+
await scoped(result, room, await openLocal());
|
|
729
918
|
}
|
|
730
|
-
if (result.kind === 'revoked-authorization')
|
|
919
|
+
if (result.kind === 'revoked-authorization')
|
|
920
|
+
await refuse('revoked-authorization');
|
|
731
921
|
journal.record(`outcome action ${result.kind}`);
|
|
732
|
-
if (result.kind === 'rejected')
|
|
922
|
+
if (result.kind === 'rejected')
|
|
923
|
+
journal.record(`refusal rejected ${result.code}`);
|
|
733
924
|
return result;
|
|
734
|
-
}
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
735
927
|
journal.record('outcome action transport-uncertainty');
|
|
736
|
-
return {
|
|
737
|
-
|
|
738
|
-
|
|
928
|
+
return { kind: 'transport-uncertainty' };
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function continuing() {
|
|
932
|
+
return !closed && !discoveryPaused;
|
|
933
|
+
}
|
|
934
|
+
function reachedBy(locator, roomVersion) {
|
|
935
|
+
const held = replicas.get(locator);
|
|
936
|
+
return (held !== undefined &&
|
|
937
|
+
options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion));
|
|
938
|
+
}
|
|
939
|
+
function writtenAfter(locator, roomVersion, path) {
|
|
940
|
+
const written = replicas.get(locator)?.confirmed.record.written[path];
|
|
941
|
+
return (written !== undefined &&
|
|
942
|
+
options.adapter.reached(locator, written, roomVersion) &&
|
|
943
|
+
!options.adapter.reached(locator, roomVersion, written));
|
|
944
|
+
}
|
|
945
|
+
function want({ locator, roomVersion }) {
|
|
946
|
+
if (reachedBy(locator, roomVersion))
|
|
947
|
+
return;
|
|
948
|
+
const held = wanted.get(locator);
|
|
949
|
+
if (held === undefined || options.adapter.reached(locator, roomVersion, held)) {
|
|
950
|
+
wanted.set(locator, roomVersion);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function reachOf(locator) {
|
|
954
|
+
let target = wanted.get(locator);
|
|
955
|
+
const versions = (replicas.get(locator)?.queue ?? []).flatMap((batch) => {
|
|
956
|
+
const outcome = outcomes.get(batch.id);
|
|
957
|
+
return outcome?.kind === 'accepted' ? [outcome.roomVersion] : [];
|
|
958
|
+
});
|
|
959
|
+
for (const version of versions) {
|
|
960
|
+
if (target === undefined || options.adapter.reached(locator, version, target)) {
|
|
961
|
+
target = version;
|
|
962
|
+
}
|
|
739
963
|
}
|
|
964
|
+
return target === undefined || reachedBy(locator, target) ? undefined : target;
|
|
740
965
|
}
|
|
741
|
-
async function pullRefresh(authorized, confirmed) {
|
|
966
|
+
async function pullRefresh(authorized, confirmed, minPosition) {
|
|
967
|
+
if (!continuing())
|
|
968
|
+
return unconfirmed;
|
|
742
969
|
try {
|
|
743
|
-
const result = await refreshRoom(options.adapter, authorized, confirmed,
|
|
970
|
+
const result = await refreshRoom(options.adapter, authorized, confirmed, continuing, minPosition);
|
|
744
971
|
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
745
972
|
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
746
973
|
return result;
|
|
747
|
-
}
|
|
974
|
+
}
|
|
975
|
+
catch (error) {
|
|
748
976
|
journal.record(`transfer refresh ${confirmed.record.locator} transport-uncertainty`);
|
|
749
977
|
throw error;
|
|
750
978
|
}
|
|
751
979
|
}
|
|
752
980
|
async function pullBootstrap(authorized, room) {
|
|
981
|
+
if (!continuing())
|
|
982
|
+
return unconfirmed;
|
|
753
983
|
try {
|
|
754
984
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
755
985
|
collection: modelOf(room.locator),
|
|
756
|
-
selection: modelOf(room.locator, 'typed-selection')
|
|
757
|
-
},
|
|
986
|
+
selection: modelOf(room.locator, 'typed-selection'),
|
|
987
|
+
}, continuing);
|
|
758
988
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
759
989
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
760
990
|
return result;
|
|
761
|
-
}
|
|
991
|
+
}
|
|
992
|
+
catch (error) {
|
|
762
993
|
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
763
994
|
throw error;
|
|
764
995
|
}
|
|
@@ -766,8 +997,12 @@ export function createMdRuntime(options) {
|
|
|
766
997
|
async function advance(room, local, authorized) {
|
|
767
998
|
const held = replicas.get(room.locator);
|
|
768
999
|
if (held !== undefined) {
|
|
769
|
-
const refreshed = await pullRefresh(authorized, held.confirmed);
|
|
1000
|
+
const refreshed = await pullRefresh(authorized, held.confirmed, reachOf(room.locator));
|
|
1001
|
+
if (!continuing())
|
|
1002
|
+
return unconfirmed;
|
|
770
1003
|
if (refreshed.kind === 'still-current') {
|
|
1004
|
+
if (sourceRefusals.has(room.locator))
|
|
1005
|
+
return unconfirmed;
|
|
771
1006
|
if (unreadable.delete(room.locator)) {
|
|
772
1007
|
list(room, true);
|
|
773
1008
|
emitRooms();
|
|
@@ -775,268 +1010,431 @@ export function createMdRuntime(options) {
|
|
|
775
1010
|
return null;
|
|
776
1011
|
}
|
|
777
1012
|
if (refreshed.kind === 'advanced') {
|
|
778
|
-
if (closed) return unconfirmed;
|
|
779
1013
|
await publish(local, room, refreshed.confirmed);
|
|
780
1014
|
return null;
|
|
781
1015
|
}
|
|
782
|
-
if (refreshed.kind !== 'beyond-horizon')
|
|
1016
|
+
if (refreshed.kind !== 'beyond-horizon')
|
|
1017
|
+
return refreshed;
|
|
783
1018
|
}
|
|
784
1019
|
const bootstrapped = await pullBootstrap(authorized, room);
|
|
785
|
-
if (
|
|
786
|
-
|
|
1020
|
+
if (!continuing())
|
|
1021
|
+
return unconfirmed;
|
|
1022
|
+
if (bootstrapped.kind !== 'staged')
|
|
1023
|
+
return bootstrapped;
|
|
787
1024
|
await publish(local, room, bootstrapped.confirmed);
|
|
788
1025
|
return null;
|
|
789
1026
|
}
|
|
790
|
-
function
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1027
|
+
async function withhold(withheld, room, local, progress) {
|
|
1028
|
+
progress.complete = false;
|
|
1029
|
+
if (withheld.kind === 'scoped-refusal') {
|
|
1030
|
+
await scoped(withheld, room, local);
|
|
1031
|
+
}
|
|
1032
|
+
else if (withheld.kind === 'transport-uncertainty') {
|
|
1033
|
+
progress.reachable = false;
|
|
1034
|
+
}
|
|
1035
|
+
else if (withheld.kind === 'retryable-refusal') {
|
|
1036
|
+
journal.record(`refusal ${withheld.kind} ${withheld.code}`);
|
|
1037
|
+
}
|
|
1038
|
+
else if (withheld.kind === 'room-not-readable') {
|
|
1039
|
+
unreadable.add(room.locator);
|
|
1040
|
+
list(room, false);
|
|
1041
|
+
emitRooms();
|
|
1042
|
+
}
|
|
1043
|
+
else if (withheld.kind === 'refused') {
|
|
1044
|
+
refusedSource(room, withheld);
|
|
1045
|
+
}
|
|
1046
|
+
else if (withheld.kind === 'revoked-authorization') {
|
|
1047
|
+
await refuse('revoked-authorization');
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
function refusedSource(room, { refusal }) {
|
|
1051
|
+
sourceRefusals.set(room.locator, {
|
|
1052
|
+
code: refusal.rule,
|
|
1053
|
+
fields: {
|
|
1054
|
+
file: refusal.file,
|
|
1055
|
+
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
1056
|
+
...(refusal.referrers === undefined ? {} : { referrers: refusal.referrers }),
|
|
1057
|
+
},
|
|
1058
|
+
});
|
|
1059
|
+
declarationRefusals.delete(room.locator);
|
|
1060
|
+
unreadable.add(room.locator);
|
|
1061
|
+
list(room, false);
|
|
1062
|
+
emitRooms();
|
|
1063
|
+
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
1064
|
+
}
|
|
1065
|
+
function leaving(batch, result) {
|
|
1066
|
+
if (result.kind === 'expired') {
|
|
1067
|
+
return { batch, outcome: { kind: 'expired' }, returned: { kind: 'expired' } };
|
|
803
1068
|
}
|
|
1069
|
+
const refusal = { code: result.code, fields: result.fields };
|
|
804
1070
|
if (result.kind === 'conflicted') {
|
|
805
1071
|
return {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1072
|
+
batch,
|
|
1073
|
+
outcome: {
|
|
1074
|
+
kind: 'conflicted',
|
|
1075
|
+
...(result.roomVersion === undefined ? {} : { roomVersion: result.roomVersion }),
|
|
1076
|
+
affected: result.affected,
|
|
1077
|
+
...refusal,
|
|
809
1078
|
},
|
|
810
|
-
|
|
811
|
-
code: result.code,
|
|
812
|
-
fields: result.fields
|
|
1079
|
+
returned: { kind: 'conflicted', ...refusal },
|
|
813
1080
|
};
|
|
814
1081
|
}
|
|
815
1082
|
return {
|
|
816
|
-
|
|
817
|
-
affected: result.affected,
|
|
818
|
-
|
|
819
|
-
|
|
1083
|
+
batch,
|
|
1084
|
+
outcome: { kind: 'rejected', affected: result.affected, ...refusal },
|
|
1085
|
+
returned: { kind: 'rejected', ...refusal },
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function acceptedOf(accepted) {
|
|
1089
|
+
return {
|
|
1090
|
+
kind: 'accepted',
|
|
1091
|
+
roomVersion: accepted.roomVersion,
|
|
1092
|
+
...(accepted.author === undefined ? {} : { author: accepted.author }),
|
|
1093
|
+
...(accepted.time === undefined ? {} : { time: accepted.time }),
|
|
820
1094
|
};
|
|
821
1095
|
}
|
|
1096
|
+
function logAccepted(locator, submitted, accepted) {
|
|
1097
|
+
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
1098
|
+
(accepted.author === undefined ? '' : ` author ${accepted.author}`) +
|
|
1099
|
+
(accepted.time === undefined ? '' : ` time ${accepted.time}`));
|
|
1100
|
+
}
|
|
1101
|
+
async function recordAccepted(room, local, submitted, accepted) {
|
|
1102
|
+
await exclusive(async () => {
|
|
1103
|
+
if (!continuing())
|
|
1104
|
+
return;
|
|
1105
|
+
const current = replicas.get(room.locator);
|
|
1106
|
+
if (current === undefined || !current.queue.some((batch) => batch.id === submitted.id)) {
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const outcome = acceptedOf(accepted);
|
|
1110
|
+
await local.write({
|
|
1111
|
+
next: current.confirmed.record,
|
|
1112
|
+
previous: current.confirmed.record,
|
|
1113
|
+
queue: current.queue,
|
|
1114
|
+
previousQueue: current.queue,
|
|
1115
|
+
outcomes: [{ id: submitted.id, outcome }],
|
|
1116
|
+
});
|
|
1117
|
+
outcomes.set(submitted.id, outcome);
|
|
1118
|
+
outcomeRooms.set(submitted.id, room.locator);
|
|
1119
|
+
logAccepted(room.locator, submitted, accepted);
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
822
1122
|
async function commitAccept(room, local, submitted, accepted, base, overlay) {
|
|
823
|
-
|
|
1123
|
+
if (!continuing())
|
|
1124
|
+
return unconfirmed;
|
|
824
1125
|
const current = replicas.get(room.locator);
|
|
825
|
-
if (current === undefined || !current.queue.some((batch)=>batch.id === submitted.id)) {
|
|
826
|
-
return
|
|
1126
|
+
if (current === undefined || !current.queue.some((batch) => batch.id === submitted.id)) {
|
|
1127
|
+
return null;
|
|
827
1128
|
}
|
|
828
|
-
const remaining = current.queue.filter((batch)=>batch.id !== submitted.id);
|
|
1129
|
+
const remaining = current.queue.filter((batch) => batch.id !== submitted.id);
|
|
829
1130
|
const retainedBase = readConfirmed({
|
|
830
1131
|
...base.record,
|
|
831
1132
|
model: modelOf(room.locator),
|
|
832
|
-
selectionModel: modelOf(room.locator, 'typed-selection')
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
...accepted.author === undefined ? {} : {
|
|
843
|
-
author: accepted.author
|
|
844
|
-
},
|
|
845
|
-
...accepted.time === undefined ? {} : {
|
|
846
|
-
time: accepted.time
|
|
847
|
-
}
|
|
1133
|
+
selectionModel: modelOf(room.locator, 'typed-selection'),
|
|
1134
|
+
}, base);
|
|
1135
|
+
const applied = overlay && retainedBase.kind === 'staged'
|
|
1136
|
+
? applyReturned(retainedBase.confirmed, accepted.documents, accepted.revisions, accepted.removals, accepted.roomVersion)
|
|
1137
|
+
: retainedBase;
|
|
1138
|
+
if (applied.kind !== 'staged')
|
|
1139
|
+
return applied;
|
|
1140
|
+
const set = await settle(local, room.locator, current, applied.confirmed, remaining, {
|
|
1141
|
+
accepted: { id: submitted.id, outcome: acceptedOf(accepted) },
|
|
1142
|
+
room: true,
|
|
848
1143
|
});
|
|
849
1144
|
unreadable.delete(room.locator);
|
|
850
|
-
|
|
1145
|
+
sourceRefusals.delete(room.locator);
|
|
1146
|
+
declarationRefusals.delete(room.locator);
|
|
851
1147
|
list(room, true);
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1148
|
+
publishChangeSet(set);
|
|
1149
|
+
moveTo({ ...status, ...attention() });
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
async function accepting(room, local, submitted, accepted, pulled, fresh) {
|
|
1153
|
+
const roomVersion = pulled.record.roomVersion;
|
|
1154
|
+
if (options.adapter.reached(room.locator, roomVersion, accepted.roomVersion)) {
|
|
1155
|
+
return committed(room, local, submitted, accepted, pulled, roomVersion === accepted.roomVersion);
|
|
1156
|
+
}
|
|
1157
|
+
if (fresh && continuing())
|
|
1158
|
+
await publish(local, room, pulled);
|
|
1159
|
+
return false;
|
|
1160
|
+
}
|
|
1161
|
+
async function committed(room, local, submitted, accepted, base, overlay) {
|
|
1162
|
+
const failed = await exclusive(() => commitAccept(room, local, submitted, accepted, base, overlay));
|
|
1163
|
+
if (failed === null)
|
|
1164
|
+
return true;
|
|
1165
|
+
if (failed.kind === 'refused' && continuing())
|
|
1166
|
+
refusedSource(room, failed);
|
|
1167
|
+
return false;
|
|
857
1168
|
}
|
|
858
1169
|
async function submitRoom(room, local, authorized, progress) {
|
|
859
|
-
while(
|
|
1170
|
+
while (continuing() && authorization !== undefined) {
|
|
860
1171
|
const replica = replicas.get(room.locator);
|
|
861
1172
|
const head = replica?.queue[0];
|
|
862
|
-
if (replica === undefined || head === undefined)
|
|
863
|
-
|
|
864
|
-
if (
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
});
|
|
1173
|
+
if (replica === undefined || head === undefined)
|
|
1174
|
+
return;
|
|
1175
|
+
if (outcomes.get(head.id)?.kind === 'accepted') {
|
|
1176
|
+
progress.complete = false;
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const state = pendings.get(head.id);
|
|
1180
|
+
const { submission, lost: earlier } = state ?? {
|
|
1181
|
+
submission: { state: 'waiting' },
|
|
1182
|
+
lost: false,
|
|
1183
|
+
};
|
|
1184
|
+
if (submission.state === 'reset' && Date.now() < Date.parse(submission.reset)) {
|
|
1185
|
+
progress.complete = false;
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (writesHeld()) {
|
|
1189
|
+
progress.complete = false;
|
|
1190
|
+
journal.record(`submission ${room.locator} ${head.id} held`);
|
|
1191
|
+
return;
|
|
882
1192
|
}
|
|
1193
|
+
const lost = earlier || submission.state === 'submitted';
|
|
883
1194
|
let result;
|
|
884
1195
|
journal.record(`submission ${room.locator} ${head.id}`);
|
|
1196
|
+
await exclusive(() => mark(local, room.locator, head.id, { state: 'submitted' }, lost));
|
|
1197
|
+
if (discoveryPaused)
|
|
1198
|
+
return;
|
|
1199
|
+
if (writesHeld()) {
|
|
1200
|
+
await exclusive(() => mark(local, room.locator, head.id, submission, earlier));
|
|
1201
|
+
progress.complete = false;
|
|
1202
|
+
journal.record(`submission ${room.locator} ${head.id} held`);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
const opensBefore = openSignals;
|
|
885
1206
|
try {
|
|
886
1207
|
result = await options.adapter.mutate(authorized, {
|
|
887
1208
|
locator: room.locator,
|
|
888
|
-
changeBatch: head
|
|
889
|
-
|
|
890
|
-
|
|
1209
|
+
changeBatch: head,
|
|
1210
|
+
...(state?.upload === undefined ? {} : { upload: state.upload }),
|
|
1211
|
+
}, (upload) => exclusive(() => keepUpload(local, room.locator, head.id, upload)));
|
|
1212
|
+
}
|
|
1213
|
+
catch {
|
|
891
1214
|
progress.complete = false;
|
|
892
|
-
|
|
893
|
-
|
|
1215
|
+
if (continuing())
|
|
1216
|
+
progress.reachable = false;
|
|
1217
|
+
return;
|
|
894
1218
|
}
|
|
895
|
-
if (
|
|
1219
|
+
if (!continuing())
|
|
1220
|
+
return;
|
|
896
1221
|
if (result.kind === 'transport-uncertainty') {
|
|
897
1222
|
progress.complete = false;
|
|
898
1223
|
progress.reachable = false;
|
|
899
|
-
return
|
|
1224
|
+
return;
|
|
900
1225
|
}
|
|
901
1226
|
if (result.kind === 'room-not-readable') {
|
|
902
1227
|
progress.complete = false;
|
|
903
1228
|
unreadable.add(room.locator);
|
|
904
1229
|
list(room, false);
|
|
905
1230
|
emitRooms();
|
|
906
|
-
return
|
|
1231
|
+
return;
|
|
907
1232
|
}
|
|
908
1233
|
if (result.kind === 'revoked-authorization') {
|
|
909
|
-
refuse('revoked-authorization');
|
|
910
|
-
return
|
|
1234
|
+
await refuse('revoked-authorization');
|
|
1235
|
+
return;
|
|
911
1236
|
}
|
|
912
1237
|
if (result.kind === 'scoped-refusal') {
|
|
913
1238
|
await scoped(result, room, local);
|
|
914
1239
|
progress.complete = false;
|
|
915
|
-
return
|
|
1240
|
+
return;
|
|
916
1241
|
}
|
|
917
|
-
if (result.kind === '
|
|
918
|
-
journal.record(`outcome ${room.locator} ${head.id}
|
|
919
|
-
|
|
920
|
-
if (
|
|
921
|
-
|
|
922
|
-
|
|
1242
|
+
if (result.kind === 'write-halted') {
|
|
1243
|
+
journal.record(`outcome ${room.locator} ${head.id} write-halted`);
|
|
1244
|
+
await exclusive(() => mark(local, room.locator, head.id, { state: 'waiting' }, lost));
|
|
1245
|
+
if (serviceMode === 'open' && openSignals !== opensBefore)
|
|
1246
|
+
continue;
|
|
1247
|
+
haltedRefusal = true;
|
|
1248
|
+
progress.complete = false;
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
if (result.kind === 'conflicted' ||
|
|
1252
|
+
result.kind === 'rejected' ||
|
|
1253
|
+
result.kind === 'expired' ||
|
|
1254
|
+
result.kind === 'retryable-refusal') {
|
|
1255
|
+
if (result.kind === 'expired') {
|
|
1256
|
+
const withheld = await advance(room, local, authorized);
|
|
1257
|
+
if (withheld !== null)
|
|
1258
|
+
return withhold(withheld, room, local, progress);
|
|
1259
|
+
}
|
|
1260
|
+
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` +
|
|
1261
|
+
(result.kind === 'conflicted' && result.roomVersion !== undefined
|
|
1262
|
+
? ` ${result.roomVersion}`
|
|
1263
|
+
: ''));
|
|
1264
|
+
if (result.kind !== 'expired')
|
|
1265
|
+
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
1266
|
+
}
|
|
1267
|
+
if (result.kind === 'retryable-refusal') {
|
|
1268
|
+
const reset = result.reset;
|
|
1269
|
+
progress.complete = false;
|
|
1270
|
+
await exclusive(() => mark(local, room.locator, head.id, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
1274
|
+
const left = leaving(head, result);
|
|
1275
|
+
await exclusive(async () => {
|
|
1276
|
+
if (!continuing())
|
|
1277
|
+
return;
|
|
923
1278
|
const current = replicas.get(room.locator);
|
|
924
|
-
if (current === undefined || !current.queue.some((batch)=>batch.id === head.id)) {
|
|
1279
|
+
if (current === undefined || !current.queue.some((batch) => batch.id === head.id)) {
|
|
925
1280
|
return;
|
|
926
1281
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
...status,
|
|
932
|
-
...attention()
|
|
1282
|
+
const set = await settle(local, room.locator, current, current.confirmed, current.queue.filter((batch) => batch.id !== head.id), {
|
|
1283
|
+
leaving: result.kind === 'expired' && landed(head, current.confirmed)
|
|
1284
|
+
? { ...left, returned: null }
|
|
1285
|
+
: left,
|
|
933
1286
|
});
|
|
1287
|
+
publishChangeSet(set);
|
|
1288
|
+
moveTo({ ...status, ...attention() });
|
|
934
1289
|
});
|
|
935
|
-
|
|
1290
|
+
if (result.kind === 'expired')
|
|
1291
|
+
continue;
|
|
1292
|
+
const withheld = await advance(room, local, authorized);
|
|
1293
|
+
if (withheld !== null)
|
|
1294
|
+
return withhold(withheld, room, local, progress);
|
|
1295
|
+
continue;
|
|
936
1296
|
}
|
|
1297
|
+
await recordAccepted(room, local, head, result);
|
|
937
1298
|
const from = replicas.get(room.locator)?.confirmed;
|
|
938
|
-
if (from === undefined)
|
|
939
|
-
|
|
1299
|
+
if (from === undefined)
|
|
1300
|
+
return;
|
|
1301
|
+
const pulled = await pullRefresh(authorized, from, reachOf(room.locator));
|
|
1302
|
+
if (!continuing())
|
|
1303
|
+
return;
|
|
940
1304
|
if (pulled.kind === 'revoked-authorization') {
|
|
941
|
-
refuse('revoked-authorization');
|
|
942
|
-
return
|
|
1305
|
+
await refuse('revoked-authorization');
|
|
1306
|
+
return;
|
|
943
1307
|
}
|
|
944
1308
|
if (pulled.kind === 'scoped-refusal') {
|
|
945
1309
|
await scoped(pulled, room, local);
|
|
946
1310
|
progress.complete = false;
|
|
947
|
-
return
|
|
948
|
-
}
|
|
949
|
-
if (pulled.kind === 'still-current') {
|
|
950
|
-
const committed = await exclusive(()=>commitAccept(room, local, head, result, from, true));
|
|
951
|
-
if (!committed) {
|
|
952
|
-
progress.complete = false;
|
|
953
|
-
return null;
|
|
954
|
-
}
|
|
955
|
-
continue;
|
|
1311
|
+
return;
|
|
956
1312
|
}
|
|
957
|
-
if (pulled.kind === 'advanced') {
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
if (!committed) {
|
|
1313
|
+
if (pulled.kind === 'still-current' || pulled.kind === 'advanced') {
|
|
1314
|
+
const fresh = pulled.kind === 'advanced';
|
|
1315
|
+
if (!(await accepting(room, local, head, result, fresh ? pulled.confirmed : from, fresh))) {
|
|
961
1316
|
progress.complete = false;
|
|
962
|
-
return
|
|
1317
|
+
return;
|
|
963
1318
|
}
|
|
964
1319
|
continue;
|
|
965
1320
|
}
|
|
966
1321
|
if (pulled.kind === 'beyond-horizon') {
|
|
967
1322
|
const bootstrapped = await pullBootstrap(authorized, room);
|
|
1323
|
+
if (!continuing())
|
|
1324
|
+
return;
|
|
968
1325
|
if (bootstrapped.kind === 'revoked-authorization') {
|
|
969
|
-
refuse('revoked-authorization');
|
|
970
|
-
return
|
|
1326
|
+
await refuse('revoked-authorization');
|
|
1327
|
+
return;
|
|
971
1328
|
}
|
|
972
1329
|
if (bootstrapped.kind === 'scoped-refusal') {
|
|
973
1330
|
await scoped(bootstrapped, room, local);
|
|
974
1331
|
progress.complete = false;
|
|
975
|
-
return
|
|
1332
|
+
return;
|
|
976
1333
|
}
|
|
977
1334
|
if (bootstrapped.kind === 'staged') {
|
|
978
|
-
|
|
979
|
-
const committed = await exclusive(()=>commitAccept(room, local, head, result, bootstrapped.confirmed, overlay));
|
|
980
|
-
if (!committed) {
|
|
1335
|
+
if (!(await accepting(room, local, head, result, bootstrapped.confirmed, true))) {
|
|
981
1336
|
progress.complete = false;
|
|
982
|
-
return
|
|
1337
|
+
return;
|
|
983
1338
|
}
|
|
984
1339
|
continue;
|
|
985
1340
|
}
|
|
986
1341
|
progress.complete = false;
|
|
987
1342
|
if (bootstrapped.kind === 'transport-uncertainty') {
|
|
988
1343
|
progress.reachable = false;
|
|
989
|
-
}
|
|
1344
|
+
}
|
|
1345
|
+
else if (bootstrapped.kind === 'room-not-readable') {
|
|
990
1346
|
unreadable.add(room.locator);
|
|
991
1347
|
list(room, false);
|
|
992
1348
|
emitRooms();
|
|
993
1349
|
}
|
|
994
|
-
|
|
1350
|
+
else if (bootstrapped.kind === 'refused') {
|
|
1351
|
+
refusedSource(room, bootstrapped);
|
|
1352
|
+
}
|
|
1353
|
+
return;
|
|
995
1354
|
}
|
|
996
1355
|
progress.complete = false;
|
|
997
1356
|
if (pulled.kind === 'transport-uncertainty') {
|
|
998
1357
|
progress.reachable = false;
|
|
999
|
-
}
|
|
1358
|
+
}
|
|
1359
|
+
else if (pulled.kind === 'room-not-readable') {
|
|
1000
1360
|
unreadable.add(room.locator);
|
|
1001
1361
|
list(room, false);
|
|
1002
1362
|
emitRooms();
|
|
1003
1363
|
}
|
|
1004
|
-
|
|
1364
|
+
else if (pulled.kind === 'retryable-refusal') {
|
|
1365
|
+
journal.record(`refusal ${pulled.kind} ${pulled.code}`);
|
|
1366
|
+
}
|
|
1367
|
+
else if (pulled.kind === 'refused') {
|
|
1368
|
+
refusedSource(room, pulled);
|
|
1369
|
+
}
|
|
1370
|
+
return;
|
|
1005
1371
|
}
|
|
1006
|
-
return
|
|
1372
|
+
return;
|
|
1007
1373
|
}
|
|
1008
1374
|
async function retainModel(local, room, attached, next) {
|
|
1009
1375
|
const key = modelKey(room.locator, attached);
|
|
1010
|
-
if (next.status === 'unavailable')
|
|
1376
|
+
if (next.status === 'unavailable')
|
|
1377
|
+
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
1011
1378
|
const epoch = modelEpoch.get(key) ?? 0;
|
|
1012
1379
|
const previous = modelOf(room.locator, attached);
|
|
1013
1380
|
const heldBefore = replicas.get(room.locator)?.confirmed.record;
|
|
1014
|
-
if (JSON.stringify(previous) === JSON.stringify(next) &&
|
|
1015
|
-
|
|
1381
|
+
if (JSON.stringify(previous) === JSON.stringify(next) &&
|
|
1382
|
+
(heldBefore === undefined ||
|
|
1383
|
+
(JSON.stringify(heldBefore.operations) === JSON.stringify(room.operations) &&
|
|
1384
|
+
JSON.stringify(heldBefore.replica) === JSON.stringify(room.replica) &&
|
|
1385
|
+
JSON.stringify(heldBefore.attachedModel) === JSON.stringify(room.attachedModel) &&
|
|
1386
|
+
JSON.stringify(heldBefore.typedSelection) ===
|
|
1387
|
+
JSON.stringify(room.typedSelection ?? null) &&
|
|
1388
|
+
JSON.stringify(heldBefore.untypedFolders) === JSON.stringify(room.untypedFolders ?? []))))
|
|
1389
|
+
return;
|
|
1016
1390
|
if (next.status === 'unavailable') {
|
|
1017
1391
|
invalidModels.add(key);
|
|
1018
1392
|
liveModels.delete(key);
|
|
1019
1393
|
}
|
|
1020
|
-
await exclusive(async ()=>{
|
|
1021
|
-
|
|
1022
|
-
|
|
1394
|
+
await exclusive(async () => {
|
|
1395
|
+
if (!continuing())
|
|
1396
|
+
return;
|
|
1397
|
+
if (next.status !== 'unavailable' && epoch !== (modelEpoch.get(key) ?? 0))
|
|
1398
|
+
return;
|
|
1023
1399
|
const held = replicas.get(room.locator);
|
|
1024
1400
|
if (held !== undefined) {
|
|
1025
|
-
const
|
|
1401
|
+
const declared = {
|
|
1026
1402
|
...held.confirmed.record,
|
|
1027
1403
|
operations: room.operations,
|
|
1028
1404
|
replica: room.replica,
|
|
1029
1405
|
attachedModel: room.attachedModel,
|
|
1030
1406
|
typedSelection: room.typedSelection ?? null,
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
} : {
|
|
1034
|
-
selectionModel: next
|
|
1035
|
-
}
|
|
1407
|
+
untypedFolders: room.untypedFolders ?? [],
|
|
1408
|
+
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1036
1409
|
};
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1410
|
+
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
1411
|
+
? readConfirmed(declared, held.confirmed)
|
|
1412
|
+
: readReclassified(declared, held.confirmed);
|
|
1413
|
+
const record = reclassified.kind === 'staged'
|
|
1414
|
+
? declared
|
|
1415
|
+
: {
|
|
1416
|
+
...declared,
|
|
1417
|
+
typedSelection: held.confirmed.record.typedSelection,
|
|
1418
|
+
untypedFolders: held.confirmed.record.untypedFolders,
|
|
1419
|
+
};
|
|
1420
|
+
const read = reclassified.kind === 'deferred' ? readConfirmed(record, held.confirmed) : reclassified;
|
|
1421
|
+
await local.write({
|
|
1422
|
+
next: record,
|
|
1423
|
+
previous: held.confirmed.record,
|
|
1424
|
+
queue: held.queue,
|
|
1425
|
+
});
|
|
1426
|
+
if (read.kind === 'staged') {
|
|
1427
|
+
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, rewriting(), held));
|
|
1428
|
+
if (declarationRefusals.delete(room.locator)) {
|
|
1429
|
+
sourceRefusals.delete(room.locator);
|
|
1430
|
+
unreadable.delete(room.locator);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
else if (read.kind === 'refused') {
|
|
1434
|
+
replicas.set(room.locator, { ...held, confirmed: { ...held.confirmed, record } });
|
|
1435
|
+
refusedSource(room, read);
|
|
1436
|
+
declarationRefusals.add(room.locator);
|
|
1437
|
+
}
|
|
1040
1438
|
}
|
|
1041
1439
|
models.set(key, next);
|
|
1042
1440
|
if (next.status === 'current') {
|
|
@@ -1044,8 +1442,10 @@ export function createMdRuntime(options) {
|
|
|
1044
1442
|
invalidModels.delete(key);
|
|
1045
1443
|
}
|
|
1046
1444
|
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
1047
|
-
|
|
1048
|
-
|
|
1445
|
+
if (held === undefined)
|
|
1446
|
+
emitRooms();
|
|
1447
|
+
else
|
|
1448
|
+
publishChangeSet(changeSet(room.locator, held, replicas.get(room.locator) ?? null, { room: true }));
|
|
1049
1449
|
});
|
|
1050
1450
|
}
|
|
1051
1451
|
async function acquireModel(local, room, authorized, selection) {
|
|
@@ -1053,39 +1453,32 @@ export function createMdRuntime(options) {
|
|
|
1053
1453
|
const key = modelKey(room.locator, attached);
|
|
1054
1454
|
const declared = selection === null ? room.attachedModel : selection.attachedModel;
|
|
1055
1455
|
let previous = modelOf(room.locator, attached);
|
|
1056
|
-
if (previous.status !== 'unavailable' &&
|
|
1057
|
-
previous
|
|
1058
|
-
|
|
1059
|
-
reason: 'version-changed'
|
|
1060
|
-
};
|
|
1456
|
+
if (previous.status !== 'unavailable' &&
|
|
1457
|
+
(declared === null || !sameModel(previous.snapshot, declared))) {
|
|
1458
|
+
previous = { status: 'unavailable', reason: 'version-changed' };
|
|
1061
1459
|
await retainModel(local, room, attached, previous);
|
|
1460
|
+
if (discoveryPaused)
|
|
1461
|
+
return;
|
|
1062
1462
|
}
|
|
1063
1463
|
const epoch = modelEpoch.get(key) ?? 0;
|
|
1064
1464
|
let answer;
|
|
1065
1465
|
try {
|
|
1066
|
-
answer = await options.adapter.model(authorized, selection === null
|
|
1067
|
-
locator: room.locator,
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
} catch {
|
|
1078
|
-
answer = {
|
|
1079
|
-
kind: 'transport-uncertainty'
|
|
1080
|
-
};
|
|
1466
|
+
answer = await options.adapter.model(authorized, selection === null
|
|
1467
|
+
? { locator: room.locator, attached: 'collection', model: declared }
|
|
1468
|
+
: {
|
|
1469
|
+
locator: room.locator,
|
|
1470
|
+
attached: 'typed-selection',
|
|
1471
|
+
roots: selection.roots,
|
|
1472
|
+
model: declared,
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
catch {
|
|
1476
|
+
answer = { kind: 'transport-uncertainty' };
|
|
1081
1477
|
}
|
|
1082
|
-
if (epoch !== (modelEpoch.get(key) ?? 0))
|
|
1478
|
+
if (discoveryPaused || epoch !== (modelEpoch.get(key) ?? 0))
|
|
1479
|
+
return;
|
|
1083
1480
|
if (answer.kind === 'revoked-authorization') {
|
|
1084
|
-
await
|
|
1085
|
-
status: 'unavailable',
|
|
1086
|
-
reason: 'revoked'
|
|
1087
|
-
});
|
|
1088
|
-
refuse('revoked-authorization');
|
|
1481
|
+
await refuse('revoked-authorization');
|
|
1089
1482
|
return;
|
|
1090
1483
|
}
|
|
1091
1484
|
if (answer.kind === 'transport-uncertainty') {
|
|
@@ -1095,123 +1488,116 @@ export function createMdRuntime(options) {
|
|
|
1095
1488
|
if (answer.kind === 'model-unavailable') {
|
|
1096
1489
|
await retainModel(local, room, attached, {
|
|
1097
1490
|
status: 'unavailable',
|
|
1098
|
-
reason: answer.reason
|
|
1491
|
+
reason: answer.reason,
|
|
1099
1492
|
});
|
|
1100
1493
|
return;
|
|
1101
1494
|
}
|
|
1102
1495
|
if (declared === null || !sameModel(answer.snapshot, declared)) {
|
|
1103
1496
|
await retainModel(local, room, attached, {
|
|
1104
1497
|
status: 'unavailable',
|
|
1105
|
-
reason: 'version-changed'
|
|
1498
|
+
reason: 'version-changed',
|
|
1106
1499
|
});
|
|
1107
1500
|
return;
|
|
1108
1501
|
}
|
|
1109
|
-
if (previous.status !== 'unavailable' &&
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
});
|
|
1502
|
+
if (previous.status !== 'unavailable' &&
|
|
1503
|
+
sameModel(previous.snapshot, answer.snapshot) &&
|
|
1504
|
+
previous.snapshot.schema !== answer.snapshot.schema) {
|
|
1505
|
+
await retainModel(local, room, attached, { status: 'unavailable', reason: 'invalid' });
|
|
1114
1506
|
return;
|
|
1115
1507
|
}
|
|
1116
|
-
const verified =
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1508
|
+
const verified = answer.snapshot.schema === null ? EMPTY_SCHEMA : verify(answer.snapshot.schema);
|
|
1509
|
+
if (verified.verdict === 'refuse') {
|
|
1510
|
+
const where = verified.location === undefined ? '' : ` ${verified.location}`;
|
|
1511
|
+
journal.record(`model invalid ${room.locator} ${attached} ${answer.snapshot.identity} ${verified.rule}${where}`);
|
|
1512
|
+
}
|
|
1513
|
+
await retainModel(local, room, attached, verified.verdict === 'accept'
|
|
1514
|
+
? {
|
|
1515
|
+
status: 'current',
|
|
1516
|
+
snapshot: structuredClone(answer.snapshot),
|
|
1517
|
+
verifiedWith: MD_MODEL,
|
|
1518
|
+
types: verified.model.types,
|
|
1519
|
+
}
|
|
1520
|
+
: { status: 'unavailable', reason: 'invalid' });
|
|
1126
1521
|
}
|
|
1127
1522
|
async function scoped(result, room, local) {
|
|
1128
|
-
scopedRefusals.set(room.locator, {
|
|
1129
|
-
code: result.code,
|
|
1130
|
-
fields: result.fields
|
|
1131
|
-
});
|
|
1523
|
+
scopedRefusals.set(room.locator, { code: result.code, fields: result.fields });
|
|
1132
1524
|
let scopedAway = room;
|
|
1133
|
-
if (result.scope === 'model')
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1525
|
+
if (result.scope === 'model')
|
|
1526
|
+
await retainModel(local, room, 'collection', {
|
|
1527
|
+
status: 'unavailable',
|
|
1528
|
+
reason: 'not-readable',
|
|
1529
|
+
});
|
|
1137
1530
|
else if (result.scope !== 'operation') {
|
|
1138
1531
|
unreadable.add(room.locator);
|
|
1139
1532
|
scopedAway = {
|
|
1140
1533
|
...room,
|
|
1141
|
-
replica: {
|
|
1142
|
-
...room.replica,
|
|
1143
|
-
admission: 'unavailable'
|
|
1144
|
-
},
|
|
1534
|
+
replica: { ...room.replica, admission: 'unavailable' },
|
|
1145
1535
|
operations: {
|
|
1146
1536
|
...room.operations,
|
|
1147
|
-
...result.scope === 'content' ? {
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
...result.scope === 'sync' ? {
|
|
1151
|
-
sync: []
|
|
1152
|
-
} : {}
|
|
1153
|
-
}
|
|
1537
|
+
...(result.scope === 'content' ? { read: [] } : {}),
|
|
1538
|
+
...(result.scope === 'sync' ? { sync: [] } : {}),
|
|
1539
|
+
},
|
|
1154
1540
|
};
|
|
1155
1541
|
await retainModel(local, scopedAway, 'collection', modelOf(room.locator));
|
|
1156
1542
|
}
|
|
1543
|
+
if (!continuing())
|
|
1544
|
+
return;
|
|
1157
1545
|
list(scopedAway, scopedAway.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
1158
1546
|
emitRooms();
|
|
1159
1547
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
1160
1548
|
}
|
|
1161
1549
|
async function synchronize(discovered, local, progress) {
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1550
|
+
const prepared = await Promise.allSettled(discovered.map((room) => prepareRoom(room, local, progress)));
|
|
1551
|
+
const ready = discovered.filter((_, index) => {
|
|
1552
|
+
const outcome = prepared[index];
|
|
1553
|
+
return outcome?.status === 'fulfilled' && outcome.value;
|
|
1554
|
+
});
|
|
1555
|
+
const rooms = await Promise.allSettled(ready.map((room) => synchronizeRoom(room, local, progress)));
|
|
1556
|
+
const failed = [...prepared, ...rooms].find((room) => room.status === 'rejected');
|
|
1557
|
+
if (failed !== undefined)
|
|
1558
|
+
throw failed.reason;
|
|
1559
|
+
}
|
|
1560
|
+
async function prepareRoom(room, local, progress) {
|
|
1561
|
+
if (closed || authorization === undefined) {
|
|
1562
|
+
progress.complete = false;
|
|
1563
|
+
return false;
|
|
1564
|
+
}
|
|
1565
|
+
const prior = replicas.get(room.locator)?.confirmed.record;
|
|
1566
|
+
if (prior !== undefined && prior.replica.selection !== room.replica.selection) {
|
|
1567
|
+
list(prior, false);
|
|
1568
|
+
emitRooms();
|
|
1569
|
+
journal.record(`selection changed ${room.locator}`);
|
|
1570
|
+
progress.complete = false;
|
|
1571
|
+
return false;
|
|
1572
|
+
}
|
|
1573
|
+
const selection = room.typedSelection;
|
|
1574
|
+
await Promise.all([
|
|
1575
|
+
acquireModel(local, room, authorization, null),
|
|
1576
|
+
...(selection === undefined ? [] : [acquireModel(local, room, authorization, selection)]),
|
|
1577
|
+
]);
|
|
1578
|
+
if (discoveryPaused || authorization === undefined)
|
|
1579
|
+
return false;
|
|
1580
|
+
if (room.replica.admission !== 'admitted') {
|
|
1581
|
+
if (replicas.has(room.locator))
|
|
1192
1582
|
progress.complete = false;
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
return;
|
|
1210
|
-
}
|
|
1211
|
-
continue;
|
|
1212
|
-
}
|
|
1213
|
-
if (await submitRoom(room, local, authorization, progress) === 'stop') return;
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
const held = replicas.get(room.locator);
|
|
1586
|
+
if (held !== undefined && held.confirmed.record.replica.selection !== room.replica.selection) {
|
|
1587
|
+
progress.complete = false;
|
|
1588
|
+
return false;
|
|
1589
|
+
}
|
|
1590
|
+
return true;
|
|
1591
|
+
}
|
|
1592
|
+
async function synchronizeRoom(room, local, progress) {
|
|
1593
|
+
if (discoveryPaused || authorization === undefined)
|
|
1594
|
+
return;
|
|
1595
|
+
const withheld = await advance(room, local, authorization);
|
|
1596
|
+
if (withheld !== null) {
|
|
1597
|
+
await withhold(withheld, room, local, progress);
|
|
1598
|
+
return;
|
|
1214
1599
|
}
|
|
1600
|
+
await submitRoom(room, local, authorization, progress);
|
|
1215
1601
|
}
|
|
1216
1602
|
async function cycle(local, progress) {
|
|
1217
1603
|
if (closed || authorization === undefined) {
|
|
@@ -1222,10 +1608,9 @@ export function createMdRuntime(options) {
|
|
|
1222
1608
|
let result;
|
|
1223
1609
|
try {
|
|
1224
1610
|
result = await options.adapter.discover(authorization);
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
};
|
|
1611
|
+
}
|
|
1612
|
+
catch {
|
|
1613
|
+
result = { kind: 'transport-uncertainty' };
|
|
1229
1614
|
}
|
|
1230
1615
|
if (closed) {
|
|
1231
1616
|
notifyReady();
|
|
@@ -1233,7 +1618,7 @@ export function createMdRuntime(options) {
|
|
|
1233
1618
|
}
|
|
1234
1619
|
journal.record(`connection discover ${result.kind}`);
|
|
1235
1620
|
if (result.kind === 'transport-uncertainty') {
|
|
1236
|
-
for (const room of rooms.values()){
|
|
1621
|
+
for (const room of rooms.values()) {
|
|
1237
1622
|
await retainModel(local, room, 'collection', cached(modelOf(room.locator)));
|
|
1238
1623
|
if (room.typedSelection !== null) {
|
|
1239
1624
|
await retainModel(local, room, 'typed-selection', cached(modelOf(room.locator, 'typed-selection')));
|
|
@@ -1245,66 +1630,57 @@ export function createMdRuntime(options) {
|
|
|
1245
1630
|
}
|
|
1246
1631
|
if (result.kind === 'contract-unavailable') {
|
|
1247
1632
|
journal.record(`connection contract unavailable ${result.code}`);
|
|
1248
|
-
refuse('missing-required-guarantee', {
|
|
1249
|
-
code: result.code,
|
|
1250
|
-
fields: result.fields
|
|
1251
|
-
});
|
|
1633
|
+
await refuse('missing-required-guarantee', { code: result.code, fields: result.fields });
|
|
1252
1634
|
notifyReady();
|
|
1253
1635
|
return;
|
|
1254
1636
|
}
|
|
1255
1637
|
if (result.kind === 'revoked-authorization') {
|
|
1256
|
-
refuse('revoked-authorization');
|
|
1638
|
+
await refuse('revoked-authorization');
|
|
1257
1639
|
notifyReady();
|
|
1258
1640
|
return;
|
|
1259
1641
|
}
|
|
1260
1642
|
if (!result.supportedVersions.includes(ROOM_CONTRACT_VERSION)) {
|
|
1261
|
-
refuse('no-matching-version');
|
|
1643
|
+
await refuse('no-matching-version');
|
|
1262
1644
|
notifyReady();
|
|
1263
1645
|
return;
|
|
1264
1646
|
}
|
|
1265
|
-
if (result.mdModel.identity !== MD_MODEL.identity ||
|
|
1266
|
-
|
|
1647
|
+
if (result.mdModel.identity !== MD_MODEL.identity ||
|
|
1648
|
+
result.mdModel.version !== MD_MODEL.version) {
|
|
1649
|
+
await refuse('md-model-version-mismatch');
|
|
1267
1650
|
notifyReady();
|
|
1268
1651
|
return;
|
|
1269
1652
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
'
|
|
1276
|
-
|
|
1277
|
-
'
|
|
1278
|
-
|
|
1279
|
-
].every((operation)=>Array.isArray(room.operations[operation]) && room.operations[operation].every((scope)=>typeof scope === 'string')) || room.replica === undefined || typeof room.replica.selection !== 'string' || room.replica.selection.length === 0 || ![
|
|
1280
|
-
'admitted',
|
|
1281
|
-
'unavailable'
|
|
1282
|
-
].includes(room.replica.admission) || !declaredIdentity(room.attachedModel)) {
|
|
1653
|
+
for (const room of result.rooms) {
|
|
1654
|
+
if (room.operations === undefined ||
|
|
1655
|
+
!['read', 'sync', 'write', 'delete'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
1656
|
+
room.operations[operation].every((scope) => typeof scope === 'string')) ||
|
|
1657
|
+
room.replica === undefined ||
|
|
1658
|
+
typeof room.replica.selection !== 'string' ||
|
|
1659
|
+
room.replica.selection.length === 0 ||
|
|
1660
|
+
!['admitted', 'unavailable'].includes(room.replica.admission) ||
|
|
1661
|
+
!declaredIdentity(room.attachedModel)) {
|
|
1283
1662
|
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
1284
|
-
refuse('missing-required-guarantee');
|
|
1663
|
+
await refuse('missing-required-guarantee');
|
|
1285
1664
|
notifyReady();
|
|
1286
1665
|
return;
|
|
1287
1666
|
}
|
|
1288
1667
|
if (!declaredSelection(room)) {
|
|
1289
1668
|
journal.record(`invalid typed selection declaration ${room.locator}`);
|
|
1290
|
-
refuse('missing-required-guarantee');
|
|
1669
|
+
await refuse('missing-required-guarantee');
|
|
1291
1670
|
notifyReady();
|
|
1292
1671
|
return;
|
|
1293
1672
|
}
|
|
1294
|
-
if (
|
|
1295
|
-
|
|
1296
|
-
|
|
1673
|
+
if (!declaredUntypedFolders(room)) {
|
|
1674
|
+
journal.record(`invalid untyped folder declaration ${room.locator}`);
|
|
1675
|
+
await refuse('missing-required-guarantee');
|
|
1676
|
+
notifyReady();
|
|
1677
|
+
return;
|
|
1297
1678
|
}
|
|
1298
1679
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
notifyReady();
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
const declared = new Set(result.capabilities.map((capability)=>capability.name));
|
|
1305
|
-
for (const required of options.requiredGuarantees){
|
|
1680
|
+
const declared = new Set(result.capabilities.map((capability) => capability.name));
|
|
1681
|
+
for (const required of options.requiredGuarantees) {
|
|
1306
1682
|
if (!knownCapabilities.has(required) || !declared.has(required)) {
|
|
1307
|
-
refuse('missing-required-guarantee');
|
|
1683
|
+
await refuse('missing-required-guarantee');
|
|
1308
1684
|
notifyReady();
|
|
1309
1685
|
return;
|
|
1310
1686
|
}
|
|
@@ -1313,58 +1689,74 @@ export function createMdRuntime(options) {
|
|
|
1313
1689
|
refusal = null;
|
|
1314
1690
|
emitRefusal();
|
|
1315
1691
|
}
|
|
1692
|
+
const previousCapabilities = capabilities;
|
|
1316
1693
|
capabilities = result.capabilities;
|
|
1317
1694
|
notifyReady();
|
|
1318
|
-
if (declared.has('room-version-notifications'))
|
|
1319
|
-
|
|
1320
|
-
|
|
1695
|
+
if (declared.has('room-version-notifications'))
|
|
1696
|
+
observeRoomVersions();
|
|
1697
|
+
const discoveredSlots = result.slots ?? [];
|
|
1698
|
+
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
1699
|
+
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities) ||
|
|
1700
|
+
JSON.stringify(discoveredSlots) !== JSON.stringify(slots)) {
|
|
1321
1701
|
limits = result.providerLimits;
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1702
|
+
slots = discoveredSlots;
|
|
1703
|
+
await local.declare(limits, capabilities, slots);
|
|
1704
|
+
}
|
|
1705
|
+
await applyDiscovery(result.rooms, local);
|
|
1706
|
+
handSelected(result.rooms
|
|
1707
|
+
.filter((room) => room.replica.admission === 'admitted')
|
|
1708
|
+
.map((room) => room.locator)
|
|
1709
|
+
.sort());
|
|
1710
|
+
progress.complete = true;
|
|
1329
1711
|
await synchronize(result.rooms, local, progress);
|
|
1712
|
+
if (closed || discoveryPaused)
|
|
1713
|
+
return;
|
|
1714
|
+
await conversations.reconcile();
|
|
1715
|
+
if (status.providerUnreachable)
|
|
1716
|
+
conversations.reconnected();
|
|
1717
|
+
await conversations.catchUp(progress);
|
|
1330
1718
|
}
|
|
1331
1719
|
async function ensureAuthorization() {
|
|
1332
|
-
if (authorization !== undefined)
|
|
1720
|
+
if (authorization !== undefined)
|
|
1721
|
+
return;
|
|
1333
1722
|
try {
|
|
1334
1723
|
authorization = await options.adapter.authorize();
|
|
1335
1724
|
journal.record('connection authorize');
|
|
1336
|
-
}
|
|
1725
|
+
}
|
|
1726
|
+
catch (error) {
|
|
1337
1727
|
journal.record('connection authorize transport-uncertainty');
|
|
1338
1728
|
throw error;
|
|
1339
1729
|
}
|
|
1340
1730
|
}
|
|
1341
1731
|
async function connect() {
|
|
1342
|
-
if (closed || discoveryPaused) {
|
|
1732
|
+
if (closed || stopping || discoveryPaused) {
|
|
1343
1733
|
notifyReady();
|
|
1344
1734
|
return;
|
|
1345
1735
|
}
|
|
1346
1736
|
startTimer();
|
|
1347
|
-
const progress = {
|
|
1348
|
-
complete: false,
|
|
1349
|
-
reachable: true
|
|
1350
|
-
};
|
|
1737
|
+
const progress = { complete: false, reachable: true };
|
|
1351
1738
|
let completedAt = new Date().toISOString();
|
|
1352
1739
|
try {
|
|
1353
1740
|
const local = await openLocal();
|
|
1354
|
-
if (closed)
|
|
1741
|
+
if (closed)
|
|
1742
|
+
return;
|
|
1355
1743
|
await ensureAuthorization();
|
|
1356
|
-
if (closed)
|
|
1744
|
+
if (closed)
|
|
1745
|
+
return;
|
|
1357
1746
|
await cycle(local, progress);
|
|
1358
|
-
if (closed)
|
|
1747
|
+
if (closed)
|
|
1748
|
+
return;
|
|
1359
1749
|
if (progress.complete) {
|
|
1360
1750
|
completedAt = new Date().toISOString();
|
|
1361
1751
|
await local.complete(completedAt);
|
|
1362
1752
|
}
|
|
1363
|
-
}
|
|
1753
|
+
}
|
|
1754
|
+
catch {
|
|
1364
1755
|
progress.complete = false;
|
|
1365
1756
|
notifyReady();
|
|
1366
1757
|
return;
|
|
1367
|
-
}
|
|
1758
|
+
}
|
|
1759
|
+
finally {
|
|
1368
1760
|
notifyReady();
|
|
1369
1761
|
moveTo(settled(status, progress, completedAt, pending(), attention()));
|
|
1370
1762
|
}
|
|
@@ -1372,7 +1764,8 @@ export function createMdRuntime(options) {
|
|
|
1372
1764
|
async function run() {
|
|
1373
1765
|
try {
|
|
1374
1766
|
await connect();
|
|
1375
|
-
}
|
|
1767
|
+
}
|
|
1768
|
+
finally {
|
|
1376
1769
|
inflight = false;
|
|
1377
1770
|
if (queued && !closed) {
|
|
1378
1771
|
queued = false;
|
|
@@ -1382,50 +1775,124 @@ export function createMdRuntime(options) {
|
|
|
1382
1775
|
}
|
|
1383
1776
|
function observeRoomVersions() {
|
|
1384
1777
|
const path = options.adapter.roomVersions;
|
|
1385
|
-
if (path === undefined || withdrawNotifications !== undefined)
|
|
1386
|
-
|
|
1387
|
-
|
|
1778
|
+
if (path === undefined || withdrawNotifications !== undefined)
|
|
1779
|
+
return;
|
|
1780
|
+
withdrawNotifications = path.observe((notification) => {
|
|
1781
|
+
if (closed || stopping || discoveryPaused)
|
|
1782
|
+
return;
|
|
1783
|
+
if (notification.modelChanged === true && handed?.includes(notification.locator) === true) {
|
|
1784
|
+
void enqueue();
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1388
1787
|
notices.set(notification.locator, notification.roomVersion);
|
|
1389
1788
|
chain = chain.then(runNotified, runNotified);
|
|
1390
1789
|
});
|
|
1391
1790
|
}
|
|
1791
|
+
function writesHeld() {
|
|
1792
|
+
return serviceMode === 'write-halted' || haltedRefusal;
|
|
1793
|
+
}
|
|
1794
|
+
function signalled(mode) {
|
|
1795
|
+
if (closed)
|
|
1796
|
+
return;
|
|
1797
|
+
if (mode === 'open')
|
|
1798
|
+
openSignals += 1;
|
|
1799
|
+
const releasing = mode === 'open' && (haltedRefusal || serviceMode === 'write-halted');
|
|
1800
|
+
if (mode === 'open')
|
|
1801
|
+
haltedRefusal = false;
|
|
1802
|
+
if (mode !== serviceMode) {
|
|
1803
|
+
serviceMode = mode;
|
|
1804
|
+
journal.record(`service mode ${mode}`);
|
|
1805
|
+
for (const listener of serviceModeListeners)
|
|
1806
|
+
listener(mode);
|
|
1807
|
+
}
|
|
1808
|
+
if (releasing)
|
|
1809
|
+
void enqueue();
|
|
1810
|
+
}
|
|
1392
1811
|
function observeConversations() {
|
|
1393
|
-
const
|
|
1394
|
-
if (
|
|
1395
|
-
|
|
1812
|
+
const stream = options.adapter.conversations;
|
|
1813
|
+
if (stream === undefined || withdrawConversations !== undefined)
|
|
1814
|
+
return;
|
|
1815
|
+
withdrawConversations = stream.observe((event) => {
|
|
1396
1816
|
conversations.receive(event);
|
|
1817
|
+
if (event.kind !== 'gap' || conversations.current())
|
|
1818
|
+
return;
|
|
1819
|
+
show();
|
|
1820
|
+
scheduleCurrency();
|
|
1397
1821
|
});
|
|
1398
1822
|
}
|
|
1823
|
+
function scheduleCurrency() {
|
|
1824
|
+
if (currencyQueued || closed || stopping)
|
|
1825
|
+
return;
|
|
1826
|
+
currencyQueued = true;
|
|
1827
|
+
chain = chain.then(runCurrency, runCurrency);
|
|
1828
|
+
}
|
|
1829
|
+
async function runCurrency() {
|
|
1830
|
+
currencyQueued = false;
|
|
1831
|
+
if (closed || stopping || discoveryPaused)
|
|
1832
|
+
return;
|
|
1833
|
+
await conversations.reconcile();
|
|
1834
|
+
if (conversations.current()) {
|
|
1835
|
+
show();
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
const resting = status;
|
|
1839
|
+
const progress = { complete: false, reachable: !resting.providerUnreachable };
|
|
1840
|
+
moveTo(transferring(resting));
|
|
1841
|
+
try {
|
|
1842
|
+
await conversations.catchUp(progress);
|
|
1843
|
+
}
|
|
1844
|
+
catch {
|
|
1845
|
+
progress.reachable = false;
|
|
1846
|
+
}
|
|
1847
|
+
finally {
|
|
1848
|
+
if (!closed) {
|
|
1849
|
+
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1399
1853
|
function handSelected(locators) {
|
|
1400
1854
|
const path = options.adapter.roomVersions;
|
|
1401
|
-
if (path === undefined || withdrawNotifications === undefined)
|
|
1402
|
-
|
|
1855
|
+
if (path === undefined || withdrawNotifications === undefined)
|
|
1856
|
+
return;
|
|
1857
|
+
if (handed !== undefined && JSON.stringify(locators) === JSON.stringify(handed))
|
|
1858
|
+
return;
|
|
1403
1859
|
handed = locators;
|
|
1404
1860
|
path.select(locators);
|
|
1405
1861
|
}
|
|
1406
1862
|
async function runNotified() {
|
|
1407
|
-
const announced = [
|
|
1408
|
-
...notices
|
|
1409
|
-
];
|
|
1863
|
+
const announced = [...notices];
|
|
1410
1864
|
notices.clear();
|
|
1411
|
-
if (announced.length === 0 || closed || discoveryPaused)
|
|
1865
|
+
if (announced.length === 0 || closed || stopping || discoveryPaused)
|
|
1866
|
+
return;
|
|
1412
1867
|
const resting = status;
|
|
1413
|
-
const progress = {
|
|
1414
|
-
complete: false,
|
|
1415
|
-
reachable: !resting.providerUnreachable
|
|
1416
|
-
};
|
|
1868
|
+
const progress = { complete: false, reachable: !resting.providerUnreachable };
|
|
1417
1869
|
moveTo(transferring(resting));
|
|
1418
1870
|
try {
|
|
1419
1871
|
const local = await openLocal();
|
|
1420
|
-
for (const [locator, roomVersion] of announced){
|
|
1421
|
-
if (closed || discoveryPaused || authorization === undefined)
|
|
1422
|
-
|
|
1872
|
+
for (const [locator, roomVersion] of announced) {
|
|
1873
|
+
if (closed || discoveryPaused || authorization === undefined)
|
|
1874
|
+
return;
|
|
1875
|
+
if (handed?.includes(locator) !== true)
|
|
1876
|
+
continue;
|
|
1423
1877
|
const room = discovered.get(locator);
|
|
1424
1878
|
const held = replicas.get(locator);
|
|
1425
|
-
if (room === undefined
|
|
1426
|
-
|
|
1879
|
+
if (room === undefined)
|
|
1880
|
+
continue;
|
|
1881
|
+
if (sourceRefusals.has(locator)) {
|
|
1882
|
+
void enqueue();
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
if (held === undefined)
|
|
1886
|
+
continue;
|
|
1887
|
+
if (held.confirmed.record.roomVersion === roomVersion)
|
|
1888
|
+
continue;
|
|
1427
1889
|
const withheld = await advance(room, local, authorization);
|
|
1428
|
-
if (withheld === null)
|
|
1890
|
+
if (withheld === null)
|
|
1891
|
+
continue;
|
|
1892
|
+
if (withheld.kind === 'refused') {
|
|
1893
|
+
void enqueue();
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1429
1896
|
if (withheld.kind === 'scoped-refusal') {
|
|
1430
1897
|
await scoped(withheld, room, local);
|
|
1431
1898
|
continue;
|
|
@@ -1441,19 +1908,23 @@ export function createMdRuntime(options) {
|
|
|
1441
1908
|
continue;
|
|
1442
1909
|
}
|
|
1443
1910
|
if (withheld.kind === 'revoked-authorization') {
|
|
1444
|
-
refuse('revoked-authorization');
|
|
1911
|
+
await refuse('revoked-authorization');
|
|
1445
1912
|
return;
|
|
1446
1913
|
}
|
|
1447
1914
|
}
|
|
1448
|
-
}
|
|
1915
|
+
}
|
|
1916
|
+
catch {
|
|
1449
1917
|
progress.reachable = false;
|
|
1450
|
-
}
|
|
1918
|
+
}
|
|
1919
|
+
finally {
|
|
1451
1920
|
if (!closed) {
|
|
1452
1921
|
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1453
1922
|
}
|
|
1454
1923
|
}
|
|
1455
1924
|
}
|
|
1456
1925
|
function enqueue() {
|
|
1926
|
+
if (stopping)
|
|
1927
|
+
return chain;
|
|
1457
1928
|
if (inflight) {
|
|
1458
1929
|
queued = true;
|
|
1459
1930
|
return chain;
|
|
@@ -1463,242 +1934,282 @@ export function createMdRuntime(options) {
|
|
|
1463
1934
|
return chain;
|
|
1464
1935
|
}
|
|
1465
1936
|
function close() {
|
|
1937
|
+
if (closed)
|
|
1938
|
+
return;
|
|
1466
1939
|
closed = true;
|
|
1467
1940
|
withdrawNotifications?.();
|
|
1468
1941
|
withdrawNotifications = undefined;
|
|
1469
1942
|
withdrawConversations?.();
|
|
1470
1943
|
withdrawConversations = undefined;
|
|
1944
|
+
withdrawServiceMode?.();
|
|
1471
1945
|
conversations.close();
|
|
1472
1946
|
stopTimer();
|
|
1473
1947
|
store?.close();
|
|
1474
|
-
void opening?.then((opened)=>{
|
|
1948
|
+
void opening?.then((opened) => {
|
|
1475
1949
|
opened.close();
|
|
1476
|
-
}, ()=>{});
|
|
1950
|
+
}, () => { });
|
|
1477
1951
|
roomListeners.clear();
|
|
1478
1952
|
refusalListeners.clear();
|
|
1479
1953
|
storageLossListeners.clear();
|
|
1480
1954
|
statusListeners.clear();
|
|
1481
|
-
|
|
1955
|
+
serviceModeListeners.clear();
|
|
1956
|
+
readSide.close();
|
|
1482
1957
|
queueSubscriptions.clear();
|
|
1483
|
-
|
|
1958
|
+
roomDraftListeners.clear();
|
|
1484
1959
|
presentationListeners.clear();
|
|
1485
1960
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1961
|
+
async function stop() {
|
|
1962
|
+
stopping = true;
|
|
1963
|
+
stopTimer();
|
|
1964
|
+
await chain.catch(() => undefined);
|
|
1965
|
+
await mutations;
|
|
1966
|
+
close();
|
|
1967
|
+
}
|
|
1968
|
+
const withdrawServiceMode = options.adapter.serviceMode?.observe(signalled);
|
|
1969
|
+
observeConversations();
|
|
1970
|
+
options.signal?.addEventListener('abort', close, { once: true });
|
|
1971
|
+
if (options.signal?.aborted)
|
|
1972
|
+
close();
|
|
1973
|
+
void openLocal().catch(() => {
|
|
1491
1974
|
notifyReady();
|
|
1492
1975
|
});
|
|
1493
1976
|
void enqueue();
|
|
1494
|
-
|
|
1495
|
-
|
|
1977
|
+
function watch(topic, input, deliver) {
|
|
1978
|
+
if (topic === 'room-drafts') {
|
|
1979
|
+
const listener = { notify: deliver, ready: false };
|
|
1980
|
+
roomDraftListeners.add(listener);
|
|
1981
|
+
void exclusive(async () => {
|
|
1982
|
+
await openLocal();
|
|
1983
|
+
if (closed || !roomDraftListeners.has(listener))
|
|
1984
|
+
return;
|
|
1985
|
+
listener.ready = true;
|
|
1986
|
+
}).then(() => {
|
|
1987
|
+
if (closed || !listener.ready || !roomDraftListeners.has(listener))
|
|
1988
|
+
return;
|
|
1989
|
+
listener.notify(snapshotRoomDrafts());
|
|
1990
|
+
}, () => { });
|
|
1991
|
+
return () => {
|
|
1992
|
+
roomDraftListeners.delete(listener);
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
if (topic === 'conversation') {
|
|
1996
|
+
return conversations.watch(input[0], input[1], deliver);
|
|
1997
|
+
}
|
|
1998
|
+
if (topic === 'presentation') {
|
|
1999
|
+
const listener = {
|
|
2000
|
+
app: input[0],
|
|
2001
|
+
notify: deliver,
|
|
2002
|
+
ready: false,
|
|
2003
|
+
};
|
|
2004
|
+
presentationListeners.add(listener);
|
|
2005
|
+
void exclusive(async () => {
|
|
2006
|
+
await openLocal();
|
|
2007
|
+
}).then(() => {
|
|
2008
|
+
if (closed || !presentationListeners.has(listener))
|
|
2009
|
+
return;
|
|
2010
|
+
listener.ready = true;
|
|
2011
|
+
listener.notify(presentationOf(listener.app));
|
|
2012
|
+
}, () => { });
|
|
2013
|
+
return () => {
|
|
2014
|
+
presentationListeners.delete(listener);
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
if (topic === 'rooms') {
|
|
2018
|
+
roomListeners.add(deliver);
|
|
2019
|
+
deliver(listed());
|
|
2020
|
+
return () => {
|
|
2021
|
+
roomListeners.delete(deliver);
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
if (topic === 'connection-refusal') {
|
|
2025
|
+
refusalListeners.add(deliver);
|
|
2026
|
+
deliver(refusal);
|
|
2027
|
+
return () => {
|
|
2028
|
+
refusalListeners.delete(deliver);
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
if (topic === 'storage-loss') {
|
|
2032
|
+
storageLossListeners.add(deliver);
|
|
2033
|
+
deliver(storageLoss);
|
|
2034
|
+
return () => {
|
|
2035
|
+
storageLossListeners.delete(deliver);
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
if (topic === 'sync-status') {
|
|
2039
|
+
statusListeners.add(deliver);
|
|
2040
|
+
deliver(shown);
|
|
2041
|
+
return () => {
|
|
2042
|
+
statusListeners.delete(deliver);
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
if (topic === 'service-mode') {
|
|
2046
|
+
serviceModeListeners.add(deliver);
|
|
2047
|
+
deliver(serviceMode);
|
|
2048
|
+
return () => {
|
|
2049
|
+
serviceModeListeners.delete(deliver);
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
if (topic === 'queue') {
|
|
2053
|
+
const queue = { locator: input[0], notify: deliver };
|
|
2054
|
+
queueSubscriptions.add(queue);
|
|
2055
|
+
queue.notify(entriesOf(queue.locator));
|
|
2056
|
+
return () => {
|
|
2057
|
+
queueSubscriptions.delete(queue);
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
const plan = planOf(input[0], roomFacts());
|
|
2061
|
+
return readSide.watch(plan, identityOf(plan), deliver);
|
|
2062
|
+
}
|
|
2063
|
+
const md = {
|
|
2064
|
+
loaded() {
|
|
2065
|
+
return openLocal().then(() => undefined);
|
|
2066
|
+
},
|
|
2067
|
+
rooms() {
|
|
1496
2068
|
return listed();
|
|
1497
2069
|
},
|
|
1498
|
-
|
|
1499
|
-
return
|
|
2070
|
+
slots() {
|
|
2071
|
+
return slotted();
|
|
1500
2072
|
},
|
|
1501
|
-
|
|
2073
|
+
query: ((input) => readSide.query(planOf(input, roomFacts()))),
|
|
2074
|
+
async search(input) {
|
|
2075
|
+
const plan = searchPlanOf(input);
|
|
2076
|
+
const selected = plan.documents === undefined
|
|
2077
|
+
? null
|
|
2078
|
+
: (await readSide.query(filterOf(plan.documents, roomFacts())))
|
|
2079
|
+
.documents;
|
|
2080
|
+
return searchIndex.search(plan, selected);
|
|
2081
|
+
},
|
|
2082
|
+
types(locator) {
|
|
1502
2083
|
return {
|
|
1503
2084
|
collection: modelTypes(modelOf(locator)),
|
|
1504
|
-
typedSelection: rooms.get(locator)?.typedSelection === undefined ||
|
|
2085
|
+
typedSelection: rooms.get(locator)?.typedSelection === undefined ||
|
|
2086
|
+
rooms.get(locator)?.typedSelection === null
|
|
2087
|
+
? null
|
|
2088
|
+
: modelTypes(modelOf(locator, 'typed-selection')),
|
|
1505
2089
|
};
|
|
1506
2090
|
},
|
|
1507
|
-
syncStatus
|
|
1508
|
-
return
|
|
2091
|
+
syncStatus() {
|
|
2092
|
+
return shown;
|
|
1509
2093
|
},
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
return
|
|
2094
|
+
serviceMode() {
|
|
2095
|
+
return serviceMode;
|
|
2096
|
+
},
|
|
2097
|
+
roomDraft: {
|
|
2098
|
+
list() {
|
|
2099
|
+
return exclusive(async () => {
|
|
2100
|
+
await openLocal();
|
|
2101
|
+
return snapshotRoomDrafts();
|
|
2102
|
+
}).then((listed) => {
|
|
2103
|
+
publishRoomDrafts(true);
|
|
2104
|
+
return listed;
|
|
1518
2105
|
});
|
|
1519
2106
|
},
|
|
1520
|
-
read
|
|
1521
|
-
const
|
|
1522
|
-
return
|
|
2107
|
+
read(room, path) {
|
|
2108
|
+
const record = roomDrafts.get(room)?.get(path);
|
|
2109
|
+
return record === undefined ? null : roomDraftOf(record);
|
|
1523
2110
|
},
|
|
1524
|
-
|
|
1525
|
-
return
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
2111
|
+
write(room, path, revision, input) {
|
|
2112
|
+
return exclusive(async () => {
|
|
2113
|
+
const parts = readParts(input);
|
|
2114
|
+
if (path === '' || normalize(path) !== path)
|
|
2115
|
+
throw new Error('room-draft-malformed');
|
|
2116
|
+
const local = await openLocal();
|
|
2117
|
+
const current = roomDrafts.get(room)?.get(path);
|
|
2118
|
+
if ((current?.revision ?? null) !== revision) {
|
|
2119
|
+
return {
|
|
2120
|
+
outcome: 'stale',
|
|
2121
|
+
current: current === undefined ? null : roomDraftOf(current),
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
const record = {
|
|
2125
|
+
locator: room,
|
|
1529
2126
|
path,
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
2127
|
+
revision: roomDraftRevision(),
|
|
2128
|
+
parts: held(current?.parts, parts),
|
|
2129
|
+
returned: current?.returned ?? null,
|
|
2130
|
+
};
|
|
2131
|
+
await local.keepRoomDraft(record);
|
|
2132
|
+
rememberRoomDraft(record);
|
|
2133
|
+
publishChangeSet(changeSet(room, null, null, { drafts: [path] }));
|
|
2134
|
+
return { outcome: 'written', roomDraft: roomDraftOf(record) };
|
|
1533
2135
|
});
|
|
1534
2136
|
},
|
|
1535
|
-
discard
|
|
1536
|
-
return
|
|
1537
|
-
await
|
|
1538
|
-
|
|
2137
|
+
discard(room, path, revision) {
|
|
2138
|
+
return exclusive(async () => {
|
|
2139
|
+
const local = await openLocal();
|
|
2140
|
+
const current = roomDrafts.get(room)?.get(path);
|
|
2141
|
+
if (current === undefined || current.revision !== revision) {
|
|
2142
|
+
return {
|
|
2143
|
+
outcome: 'stale',
|
|
2144
|
+
current: current === undefined ? null : roomDraftOf(current),
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
await local.discardRoomDraft(room, path);
|
|
2148
|
+
forgetRoomDraft(room, path);
|
|
2149
|
+
publishChangeSet(changeSet(room, null, null, { drafts: [path] }));
|
|
2150
|
+
moveTo({ ...status, ...attention() });
|
|
2151
|
+
return { outcome: 'written', roomDraft: null };
|
|
1539
2152
|
});
|
|
1540
|
-
}
|
|
2153
|
+
},
|
|
1541
2154
|
},
|
|
1542
2155
|
conversation: conversations.surface,
|
|
2156
|
+
chatDraft: conversations.chatDraft,
|
|
1543
2157
|
presentation: {
|
|
1544
|
-
read
|
|
1545
|
-
return exclusive(async ()=>{
|
|
1546
|
-
await
|
|
2158
|
+
read(app) {
|
|
2159
|
+
return exclusive(async () => {
|
|
2160
|
+
await openLocal();
|
|
1547
2161
|
return presentationOf(app);
|
|
1548
2162
|
});
|
|
1549
2163
|
},
|
|
1550
|
-
write
|
|
1551
|
-
return exclusive(async ()=>{
|
|
2164
|
+
write(app, value) {
|
|
2165
|
+
return exclusive(async () => {
|
|
1552
2166
|
const local = await openLocal();
|
|
1553
|
-
await hydrate(local);
|
|
1554
2167
|
const stored = structuredClone(value);
|
|
1555
|
-
await local.present({
|
|
1556
|
-
app,
|
|
1557
|
-
value: stored
|
|
1558
|
-
});
|
|
2168
|
+
await local.present({ app, value: stored });
|
|
1559
2169
|
presentations.set(app, stored);
|
|
1560
|
-
|
|
1561
|
-
emitPresentation(app);
|
|
2170
|
+
publishChangeSet(changeSet('', null, null, { presentation: app }));
|
|
1562
2171
|
});
|
|
1563
|
-
}
|
|
1564
|
-
},
|
|
1565
|
-
save (locator, changes, replacing) {
|
|
1566
|
-
return exclusive(()=>write(locator, changes, replacing));
|
|
2172
|
+
},
|
|
1567
2173
|
},
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
if (recorded !== undefined) return recorded;
|
|
1571
|
-
for (const replica of replicas.values()){
|
|
1572
|
-
if (replica.queue.some((batch)=>batch.id === identifier)) return {
|
|
1573
|
-
kind: 'pending'
|
|
1574
|
-
};
|
|
1575
|
-
}
|
|
1576
|
-
return null;
|
|
2174
|
+
save(room, named, writtenBy) {
|
|
2175
|
+
return exclusive(() => saveRoomDrafts(room, named, writtenBy));
|
|
1577
2176
|
},
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
return exclusive(()=>discardHead(locator, identifier));
|
|
1581
|
-
}
|
|
2177
|
+
outcome(identifier) {
|
|
2178
|
+
return outcomes.get(identifier) ?? null;
|
|
1582
2179
|
},
|
|
1583
|
-
history
|
|
2180
|
+
history(locator, path, query) {
|
|
1584
2181
|
return readHistory(locator, path, query);
|
|
1585
2182
|
},
|
|
1586
|
-
call
|
|
2183
|
+
call(name, input) {
|
|
1587
2184
|
return invokeAction(name, input);
|
|
1588
2185
|
},
|
|
1589
|
-
|
|
2186
|
+
reached(locator, roomVersion, path) {
|
|
2187
|
+
return path === undefined
|
|
2188
|
+
? reachedBy(locator, roomVersion)
|
|
2189
|
+
: writtenAfter(locator, roomVersion, path);
|
|
2190
|
+
},
|
|
2191
|
+
log() {
|
|
1590
2192
|
return journal.export();
|
|
1591
2193
|
},
|
|
1592
|
-
storageLoss
|
|
2194
|
+
storageLoss() {
|
|
1593
2195
|
return storageLoss;
|
|
1594
2196
|
},
|
|
1595
|
-
persistence
|
|
2197
|
+
persistence() {
|
|
1596
2198
|
return options.shell.persistentStorage;
|
|
1597
2199
|
},
|
|
1598
|
-
observe (topic,
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
};
|
|
1605
|
-
draftListeners.add(listener);
|
|
1606
|
-
void exclusive(async ()=>{
|
|
1607
|
-
const local = await openLocal();
|
|
1608
|
-
await readDrafts(local);
|
|
1609
|
-
if (closed || !draftListeners.has(listener)) return;
|
|
1610
|
-
listener.ready = true;
|
|
1611
|
-
}).then(()=>{
|
|
1612
|
-
if (closed || !listener.ready || !draftListeners.has(listener)) return;
|
|
1613
|
-
listener.notify(snapshotDrafts());
|
|
1614
|
-
}, ()=>{});
|
|
1615
|
-
return ()=>{
|
|
1616
|
-
draftListeners.delete(listener);
|
|
1617
|
-
};
|
|
1618
|
-
}
|
|
1619
|
-
if (topic === 'conversation') {
|
|
1620
|
-
return conversations.observe(second, third);
|
|
1621
|
-
}
|
|
1622
|
-
if (topic === 'presentation') {
|
|
1623
|
-
const listener = {
|
|
1624
|
-
app: second,
|
|
1625
|
-
notify: third,
|
|
1626
|
-
ready: false
|
|
1627
|
-
};
|
|
1628
|
-
presentationListeners.add(listener);
|
|
1629
|
-
void exclusive(async ()=>{
|
|
1630
|
-
await hydrate(await openLocal());
|
|
1631
|
-
}).then(()=>{
|
|
1632
|
-
if (closed || !presentationListeners.has(listener)) return;
|
|
1633
|
-
listener.ready = true;
|
|
1634
|
-
listener.notify(presentationOf(listener.app));
|
|
1635
|
-
}, ()=>{});
|
|
1636
|
-
return ()=>{
|
|
1637
|
-
presentationListeners.delete(listener);
|
|
1638
|
-
};
|
|
1639
|
-
}
|
|
1640
|
-
if (topic === 'rooms') {
|
|
1641
|
-
const notify = second;
|
|
1642
|
-
roomListeners.add(notify);
|
|
1643
|
-
notify(listed());
|
|
1644
|
-
return ()=>{
|
|
1645
|
-
roomListeners.delete(notify);
|
|
1646
|
-
};
|
|
1647
|
-
}
|
|
1648
|
-
if (topic === 'connection-refusal') {
|
|
1649
|
-
const notify = second;
|
|
1650
|
-
refusalListeners.add(notify);
|
|
1651
|
-
notify(refusal);
|
|
1652
|
-
return ()=>{
|
|
1653
|
-
refusalListeners.delete(notify);
|
|
1654
|
-
};
|
|
1655
|
-
}
|
|
1656
|
-
if (topic === 'storage-loss') {
|
|
1657
|
-
const notify = second;
|
|
1658
|
-
storageLossListeners.add(notify);
|
|
1659
|
-
notify(storageLoss);
|
|
1660
|
-
return ()=>{
|
|
1661
|
-
storageLossListeners.delete(notify);
|
|
1662
|
-
};
|
|
1663
|
-
}
|
|
1664
|
-
if (topic === 'sync-status') {
|
|
1665
|
-
const notify = second;
|
|
1666
|
-
statusListeners.add(notify);
|
|
1667
|
-
notify(status);
|
|
1668
|
-
return ()=>{
|
|
1669
|
-
statusListeners.delete(notify);
|
|
1670
|
-
};
|
|
1671
|
-
}
|
|
1672
|
-
if (topic === 'queue') {
|
|
1673
|
-
const queue = {
|
|
1674
|
-
locator: second,
|
|
1675
|
-
notify: third
|
|
1676
|
-
};
|
|
1677
|
-
queueSubscriptions.add(queue);
|
|
1678
|
-
queue.notify(entriesOf(queue.locator));
|
|
1679
|
-
return ()=>{
|
|
1680
|
-
queueSubscriptions.delete(queue);
|
|
1681
|
-
};
|
|
1682
|
-
}
|
|
1683
|
-
const subscription = {
|
|
1684
|
-
query: second,
|
|
1685
|
-
notify: third
|
|
1686
|
-
};
|
|
1687
|
-
const first = visible(subscription.query);
|
|
1688
|
-
querySubscriptions.add(subscription);
|
|
1689
|
-
subscription.notify(first);
|
|
1690
|
-
return ()=>{
|
|
1691
|
-
querySubscriptions.delete(subscription);
|
|
1692
|
-
};
|
|
1693
|
-
},
|
|
1694
|
-
async reauthorize () {
|
|
1695
|
-
if (refusal === null || !refusal.reauthorizationOffered) return;
|
|
2200
|
+
observe: ((topic, ...input) => opened((deliver) => watch(topic, input, (message) => {
|
|
2201
|
+
deliver(structuredClone(message));
|
|
2202
|
+
}), topic === 'query')),
|
|
2203
|
+
async reauthorize() {
|
|
2204
|
+
if (refusal === null || !refusal.reauthorizationOffered)
|
|
2205
|
+
return;
|
|
1696
2206
|
journal.record('connection reauthorize');
|
|
2207
|
+
await chain.catch(() => undefined);
|
|
2208
|
+
await mutations;
|
|
1697
2209
|
authorization = undefined;
|
|
1698
2210
|
discoveryPaused = false;
|
|
1699
2211
|
await enqueue();
|
|
1700
|
-
}
|
|
2212
|
+
},
|
|
1701
2213
|
};
|
|
2214
|
+
return { md, stop, watch };
|
|
1702
2215
|
}
|
|
1703
|
-
|
|
1704
|
-
//# sourceMappingURL=connection.js.map
|