@agentshouse/mdruntime 0.1.0-alpha.23 → 0.1.0-alpha.25
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 +70 -18
- package/dist/change-set.d.ts +4 -11
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +8 -16
- package/dist/change-set.js.map +1 -1
- package/dist/change.d.ts +4 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js.map +1 -1
- package/dist/client.d.ts +16 -15
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +17 -22
- package/dist/client.js.map +1 -1
- package/dist/collection.d.ts +0 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +0 -5
- package/dist/collection.js.map +1 -1
- package/dist/connection.d.ts +21 -23
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +435 -352
- package/dist/connection.js.map +1 -1
- package/dist/conversation.d.ts +4 -4
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +12 -6
- package/dist/conversation.js.map +1 -1
- package/dist/durability.js +1 -1
- package/dist/durability.js.map +1 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +12 -14
- package/dist/host.js.map +1 -1
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +7 -0
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/outcome.d.ts +17 -20
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +1 -21
- package/dist/outcome.js.map +1 -1
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +1 -1
- package/dist/overlay.js.map +1 -1
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +8 -0
- package/dist/paths.js.map +1 -1
- package/dist/query.d.ts +199 -10
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +450 -14
- package/dist/query.js.map +1 -1
- package/dist/read-side.d.ts +10 -0
- package/dist/read-side.d.ts.map +1 -0
- package/dist/read-side.js +374 -0
- package/dist/read-side.js.map +1 -0
- package/dist/replica.d.ts +1 -0
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +54 -0
- package/dist/replica.js.map +1 -1
- package/dist/room-draft.d.ts +124 -0
- package/dist/room-draft.d.ts.map +1 -0
- package/dist/room-draft.js +403 -0
- package/dist/room-draft.js.map +1 -0
- package/dist/rows.d.ts +39 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +85 -0
- package/dist/rows.js.map +1 -0
- package/dist/save.d.ts +4 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +34 -100
- package/dist/save.js.map +1 -1
- package/dist/status.d.ts +4 -6
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js.map +1 -1
- package/dist/store.d.ts +18 -12
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +43 -34
- package/dist/store.js.map +1 -1
- package/dist/wire.d.ts +3 -3
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +6 -7
- package/dist/wire.js.map +1 -1
- package/package.json +2 -2
package/dist/connection.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import { rawQuery } from './query.js';
|
|
2
1
|
import { verify } from '@agentshouse/mdmodel';
|
|
3
2
|
import { cached, modelTypes, NO_MODEL, sameModel } from './model.js';
|
|
3
|
+
import { identityOf, planOf } from './query.js';
|
|
4
|
+
import { createReadSide } from './read-side.js';
|
|
4
5
|
import { ROOM_CONTRACT_VERSION, VERSION_1_CAPABILITIES } from './adapter.js';
|
|
5
6
|
import { bootstrapRoom } from './bootstrap.js';
|
|
6
7
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
7
8
|
import { createConversationStore } from './conversation.js';
|
|
8
|
-
import { MD_MODEL,
|
|
9
|
+
import { MD_MODEL, validRoot } from './collection.js';
|
|
9
10
|
import { createLog } from './log.js';
|
|
10
|
-
import { queuedOutcome, refusalOf, refused, withheldUntilReset } from './outcome.js';
|
|
11
11
|
import { refreshRoom } from './refresh.js';
|
|
12
12
|
import { buildBatch } from './save.js';
|
|
13
|
-
import { applyReturned, compositionOf, readConfirmed, replicaOf } from './replica.js';
|
|
13
|
+
import { applyReturned, compositionOf, readConfirmed, replicaOf, withdrawals } from './replica.js';
|
|
14
|
+
import { changedParts, changesOf, held, readParts, returnedInto, underneath } from './room-draft.js';
|
|
14
15
|
import { settled, same, saving, STARTING, transferring } from './status.js';
|
|
15
16
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
16
|
-
import {
|
|
17
|
+
import { roomDraftRevision } from './identity.js';
|
|
18
|
+
import { beneath, normalize } from './paths.js';
|
|
17
19
|
import { openRuntimeStore } from './store.js';
|
|
18
20
|
import { unconfirmed } from './transfer.js';
|
|
19
21
|
const DEFAULT_DISCOVERY_INTERVAL_MS = 60_000;
|
|
@@ -39,10 +41,11 @@ export function createMdRuntime(options) {
|
|
|
39
41
|
let opening;
|
|
40
42
|
let store;
|
|
41
43
|
const replicas = new Map();
|
|
42
|
-
const
|
|
44
|
+
const pendings = new Map();
|
|
45
|
+
const roomDrafts = new Map();
|
|
43
46
|
const presentations = new Map();
|
|
44
47
|
const outcomes = new Map();
|
|
45
|
-
const
|
|
48
|
+
const outcomeRooms = new Map();
|
|
46
49
|
let limits = [];
|
|
47
50
|
let slots = [];
|
|
48
51
|
let capabilities = [];
|
|
@@ -56,6 +59,7 @@ export function createMdRuntime(options) {
|
|
|
56
59
|
const invalidModels = new Set();
|
|
57
60
|
const modelEpoch = new Map();
|
|
58
61
|
const discovered = new Map();
|
|
62
|
+
let roomsListed = false;
|
|
59
63
|
const scopedRefusals = new Map();
|
|
60
64
|
function modelKey(locator, attached) {
|
|
61
65
|
return `${attached} ${locator}`;
|
|
@@ -95,15 +99,20 @@ export function createMdRuntime(options) {
|
|
|
95
99
|
const refusalListeners = new Set();
|
|
96
100
|
const storageLossListeners = new Set();
|
|
97
101
|
const statusListeners = new Set();
|
|
98
|
-
const querySubscriptions = new Set();
|
|
99
102
|
const queueSubscriptions = new Set();
|
|
100
|
-
const
|
|
103
|
+
const roomDraftListeners = new Set();
|
|
101
104
|
const presentationListeners = new Set();
|
|
105
|
+
const readSide = createReadSide();
|
|
102
106
|
let withdrawConversations;
|
|
103
107
|
let currencyQueued = false;
|
|
104
108
|
const conversations = createConversationStore({
|
|
105
109
|
cards: declaredCards,
|
|
106
|
-
documents: ()=>
|
|
110
|
+
documents: (locator)=>readSide.query(planOf({
|
|
111
|
+
rooms: [
|
|
112
|
+
locator
|
|
113
|
+
],
|
|
114
|
+
select: 'all'
|
|
115
|
+
}, roomFacts())),
|
|
107
116
|
call: (name, input)=>invokeAction(name, input),
|
|
108
117
|
durably,
|
|
109
118
|
stream: ()=>options.adapter.conversations,
|
|
@@ -115,53 +124,49 @@ export function createMdRuntime(options) {
|
|
|
115
124
|
const ready = new Promise((resolve)=>{
|
|
116
125
|
notifyReady = resolve;
|
|
117
126
|
});
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const room = drafts.get(locator) ?? new Map();
|
|
123
|
-
room.set(path, content);
|
|
124
|
-
drafts.set(locator, room);
|
|
127
|
+
function rememberRoomDraft(record) {
|
|
128
|
+
const room = roomDrafts.get(record.locator) ?? new Map();
|
|
129
|
+
room.set(record.path, record);
|
|
130
|
+
roomDrafts.set(record.locator, room);
|
|
125
131
|
}
|
|
126
|
-
function
|
|
127
|
-
const room =
|
|
132
|
+
function forgetRoomDraft(locator, path) {
|
|
133
|
+
const room = roomDrafts.get(locator);
|
|
128
134
|
if (room === undefined) return;
|
|
129
135
|
room.delete(path);
|
|
130
|
-
if (room.size === 0)
|
|
136
|
+
if (room.size === 0) roomDrafts.delete(locator);
|
|
137
|
+
}
|
|
138
|
+
function roomDraftOf(record) {
|
|
139
|
+
return structuredClone({
|
|
140
|
+
room: record.locator,
|
|
141
|
+
path: record.path,
|
|
142
|
+
revision: record.revision,
|
|
143
|
+
parts: record.parts,
|
|
144
|
+
changedUnderneath: underneath(record.parts, record.path, replicas.get(record.locator)?.visible),
|
|
145
|
+
returned: record.returned
|
|
146
|
+
});
|
|
131
147
|
}
|
|
132
|
-
function
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
content: structuredClone(content)
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
entries.sort((left, right)=>{
|
|
148
|
+
function snapshotRoomDrafts() {
|
|
149
|
+
const records = [
|
|
150
|
+
...roomDrafts.values()
|
|
151
|
+
].flatMap((room)=>[
|
|
152
|
+
...room.values()
|
|
153
|
+
]);
|
|
154
|
+
records.sort((left, right)=>{
|
|
144
155
|
if (left.locator !== right.locator) return left.locator < right.locator ? -1 : 1;
|
|
145
156
|
if (left.path !== right.path) return left.path < right.path ? -1 : 1;
|
|
146
157
|
return 0;
|
|
147
158
|
});
|
|
148
|
-
return
|
|
159
|
+
return records.map(roomDraftOf);
|
|
149
160
|
}
|
|
150
|
-
function
|
|
161
|
+
function publishRoomDrafts(waitingOnly) {
|
|
151
162
|
if (closed) return;
|
|
152
|
-
const value =
|
|
153
|
-
for (const listener of
|
|
163
|
+
const value = snapshotRoomDrafts();
|
|
164
|
+
for (const listener of roomDraftListeners){
|
|
154
165
|
if (waitingOnly && listener.ready) continue;
|
|
155
166
|
listener.ready = true;
|
|
156
167
|
listener.notify(value);
|
|
157
168
|
}
|
|
158
169
|
}
|
|
159
|
-
function emitDrafts() {
|
|
160
|
-
publishDrafts(false);
|
|
161
|
-
}
|
|
162
|
-
function releaseWaitingDrafts() {
|
|
163
|
-
publishDrafts(true);
|
|
164
|
-
}
|
|
165
170
|
function presentationOf(app) {
|
|
166
171
|
const value = presentations.get(app);
|
|
167
172
|
return value === undefined ? null : structuredClone(value);
|
|
@@ -172,17 +177,6 @@ export function createMdRuntime(options) {
|
|
|
172
177
|
if (listener.app === app && listener.ready) listener.notify(presentationOf(app));
|
|
173
178
|
}
|
|
174
179
|
}
|
|
175
|
-
function commitDraft(locator, path, work) {
|
|
176
|
-
return exclusive(async ()=>{
|
|
177
|
-
const local = await openLocal();
|
|
178
|
-
await work(local);
|
|
179
|
-
publishChangeSet(changeSet(locator, null, null, {
|
|
180
|
-
drafts: [
|
|
181
|
-
path
|
|
182
|
-
]
|
|
183
|
-
}));
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
180
|
function pending() {
|
|
187
181
|
return [
|
|
188
182
|
...replicas.values()
|
|
@@ -190,42 +184,33 @@ export function createMdRuntime(options) {
|
|
|
190
184
|
}
|
|
191
185
|
function attention() {
|
|
192
186
|
const attentionRequired = [];
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
if (outcome.reset !== undefined && (reset === null || outcome.reset < reset)) {
|
|
200
|
-
reset = outcome.reset;
|
|
201
|
-
}
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
if (!refused(outcome)) continue;
|
|
187
|
+
for (const record of [
|
|
188
|
+
...roomDrafts.values()
|
|
189
|
+
].flatMap((room)=>[
|
|
190
|
+
...room.values()
|
|
191
|
+
])){
|
|
192
|
+
if (record.returned === null) continue;
|
|
205
193
|
attentionRequired.push({
|
|
206
|
-
locator,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
refusal: refusalOf(outcome),
|
|
210
|
-
...outcome.kind === 'conflicted' && outcome.roomVersion !== undefined ? {
|
|
211
|
-
roomVersion: outcome.roomVersion
|
|
212
|
-
} : {}
|
|
194
|
+
room: record.locator,
|
|
195
|
+
path: record.path,
|
|
196
|
+
returned: structuredClone(record.returned)
|
|
213
197
|
});
|
|
214
198
|
}
|
|
199
|
+
attentionRequired.sort((left, right)=>{
|
|
200
|
+
if (left.room !== right.room) return left.room < right.room ? -1 : 1;
|
|
201
|
+
if (left.path !== right.path) return left.path < right.path ? -1 : 1;
|
|
202
|
+
return 0;
|
|
203
|
+
});
|
|
204
|
+
let reset = null;
|
|
205
|
+
for (const { submission } of pendings.values()){
|
|
206
|
+
if (submission.state !== 'reset') continue;
|
|
207
|
+
if (reset === null || submission.reset < reset) reset = submission.reset;
|
|
208
|
+
}
|
|
215
209
|
return {
|
|
216
210
|
attentionRequired,
|
|
217
211
|
reset
|
|
218
212
|
};
|
|
219
213
|
}
|
|
220
|
-
async function persistQueue(local, replica, batches, changedOutcomes = []) {
|
|
221
|
-
await local.write({
|
|
222
|
-
next: replica.confirmed.record,
|
|
223
|
-
previous: replica.confirmed.record,
|
|
224
|
-
queue: batches,
|
|
225
|
-
previousQueue: replica.queue,
|
|
226
|
-
outcomes: changedOutcomes
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
214
|
function listed() {
|
|
230
215
|
return structuredClone([
|
|
231
216
|
...rooms.values()
|
|
@@ -245,16 +230,26 @@ export function createMdRuntime(options) {
|
|
|
245
230
|
})
|
|
246
231
|
})));
|
|
247
232
|
}
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
...
|
|
251
|
-
].map((
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
233
|
+
function roomFacts() {
|
|
234
|
+
const held = [
|
|
235
|
+
...rooms.values()
|
|
236
|
+
].map((room)=>{
|
|
237
|
+
const attached = [
|
|
238
|
+
modelOf(room.locator),
|
|
239
|
+
...room.typedSelection === null ? [] : [
|
|
240
|
+
modelOf(room.locator, 'typed-selection')
|
|
241
|
+
]
|
|
242
|
+
];
|
|
243
|
+
return {
|
|
244
|
+
locator: room.locator,
|
|
245
|
+
available: attached.every((model)=>model.status !== 'unavailable'),
|
|
246
|
+
types: attached.flatMap((model)=>model.status === 'unavailable' ? [] : Object.values(model.types))
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
listed: roomsListed,
|
|
251
|
+
rooms: held
|
|
252
|
+
};
|
|
258
253
|
}
|
|
259
254
|
function emitRooms() {
|
|
260
255
|
const value = listed();
|
|
@@ -281,32 +276,17 @@ export function createMdRuntime(options) {
|
|
|
281
276
|
shown = next;
|
|
282
277
|
for (const listener of statusListeners)listener(shown);
|
|
283
278
|
}
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
return options.queryBinding(queryRooms(), {
|
|
290
|
-
...request,
|
|
291
|
-
selector: {
|
|
292
|
-
$and: [
|
|
293
|
-
PRESENTED,
|
|
294
|
-
request.selector ?? {}
|
|
295
|
-
]
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
function emitVisible() {
|
|
300
|
-
for (const subscription of querySubscriptions){
|
|
301
|
-
subscription.notify(visible(subscription.query));
|
|
302
|
-
}
|
|
279
|
+
function submissionOf(batch) {
|
|
280
|
+
return pendings.get(batch.id)?.submission ?? {
|
|
281
|
+
state: 'waiting'
|
|
282
|
+
};
|
|
303
283
|
}
|
|
304
284
|
function entriesOf(locator) {
|
|
305
285
|
const replica = replicas.get(locator);
|
|
306
286
|
if (replica === undefined) return [];
|
|
307
287
|
return replica.queue.map((batch)=>Object.freeze({
|
|
308
288
|
batch,
|
|
309
|
-
|
|
289
|
+
...submissionOf(batch)
|
|
310
290
|
}));
|
|
311
291
|
}
|
|
312
292
|
function emitQueue(locator) {
|
|
@@ -316,10 +296,12 @@ export function createMdRuntime(options) {
|
|
|
316
296
|
}
|
|
317
297
|
}
|
|
318
298
|
function publishChangeSet(set) {
|
|
299
|
+
readSide.apply(set);
|
|
319
300
|
if (set.rooms.length > 0) emitRooms();
|
|
320
|
-
if (set.documents.length > 0 || set.query) emitVisible();
|
|
321
301
|
for (const locator of set.queues)emitQueue(locator);
|
|
322
|
-
if (set.drafts.length > 0
|
|
302
|
+
if (set.drafts.length > 0 || set.documents.some((change)=>roomDrafts.get(change.locator)?.has(change.path) === true)) {
|
|
303
|
+
publishRoomDrafts(false);
|
|
304
|
+
}
|
|
323
305
|
for (const app of set.presentations)emitPresentation(app);
|
|
324
306
|
conversations.changed(set);
|
|
325
307
|
const cards = declaredCards();
|
|
@@ -392,8 +374,7 @@ export function createMdRuntime(options) {
|
|
|
392
374
|
}
|
|
393
375
|
});
|
|
394
376
|
changed.push(changeSet(locator, before, replicas.get(locator) ?? null, {
|
|
395
|
-
room: true
|
|
396
|
-
query: true
|
|
377
|
+
room: true
|
|
397
378
|
}));
|
|
398
379
|
}
|
|
399
380
|
publishChangeSet(mergeChangeSets(changed));
|
|
@@ -444,13 +425,14 @@ export function createMdRuntime(options) {
|
|
|
444
425
|
}
|
|
445
426
|
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
446
427
|
}
|
|
428
|
+
roomsListed = true;
|
|
447
429
|
emitRooms();
|
|
448
430
|
}
|
|
449
431
|
function unlistedHeld(listed) {
|
|
450
432
|
return [
|
|
451
433
|
...new Set([
|
|
452
434
|
...rooms.keys(),
|
|
453
|
-
...
|
|
435
|
+
...roomDrafts.keys()
|
|
454
436
|
])
|
|
455
437
|
].filter((locator)=>!listed.has(locator));
|
|
456
438
|
}
|
|
@@ -459,24 +441,23 @@ export function createMdRuntime(options) {
|
|
|
459
441
|
await exclusive(async ()=>{
|
|
460
442
|
const unlisted = unlistedHeld(listed);
|
|
461
443
|
await local.dropRooms(unlisted);
|
|
462
|
-
for (const [batch, locator] of
|
|
444
|
+
for (const [batch, locator] of outcomeRooms){
|
|
463
445
|
if (listed.has(locator)) continue;
|
|
464
|
-
|
|
446
|
+
outcomeRooms.delete(batch);
|
|
465
447
|
outcomes.delete(batch);
|
|
466
448
|
}
|
|
467
449
|
const removed = unlisted.map((locator)=>changeSet(locator, replicas.get(locator) ?? null, null, {
|
|
468
450
|
room: true,
|
|
469
451
|
queue: true,
|
|
470
|
-
query: true,
|
|
471
452
|
drafts: [
|
|
472
|
-
...
|
|
453
|
+
...roomDrafts.get(locator)?.keys() ?? []
|
|
473
454
|
]
|
|
474
455
|
}));
|
|
475
456
|
for (const locator of unlisted){
|
|
476
|
-
for (const batch of replicas.get(locator)?.queue ?? [])
|
|
457
|
+
for (const batch of replicas.get(locator)?.queue ?? [])pendings.delete(batch.id);
|
|
477
458
|
replicas.delete(locator);
|
|
478
459
|
rooms.delete(locator);
|
|
479
|
-
|
|
460
|
+
roomDrafts.delete(locator);
|
|
480
461
|
unreadable.delete(locator);
|
|
481
462
|
scopedRefusals.delete(locator);
|
|
482
463
|
for (const attached of [
|
|
@@ -517,7 +498,7 @@ export function createMdRuntime(options) {
|
|
|
517
498
|
opened.all(),
|
|
518
499
|
opened.queues(),
|
|
519
500
|
opened.outcomes(),
|
|
520
|
-
opened.
|
|
501
|
+
opened.roomDrafts(),
|
|
521
502
|
opened.presentation(),
|
|
522
503
|
opened.conversations(),
|
|
523
504
|
opened.chatDrafts(),
|
|
@@ -529,20 +510,24 @@ export function createMdRuntime(options) {
|
|
|
529
510
|
limits = declared;
|
|
530
511
|
capabilities = named;
|
|
531
512
|
slots = listedSlots;
|
|
532
|
-
|
|
533
|
-
for (const
|
|
513
|
+
roomDrafts.clear();
|
|
514
|
+
for (const record of kept)rememberRoomDraft(record);
|
|
534
515
|
presentations.clear();
|
|
535
516
|
for (const record of presented)presentations.set(record.app, record.value);
|
|
536
517
|
conversations.hydrate(held, chatDrafts);
|
|
537
518
|
const queuedBatches = new Map(queues.map((queue)=>[
|
|
538
519
|
queue.locator,
|
|
539
|
-
queue.
|
|
520
|
+
queue.pending.map(({ batch })=>batch)
|
|
540
521
|
]));
|
|
522
|
+
pendings.clear();
|
|
523
|
+
for (const queue of queues){
|
|
524
|
+
for (const { batch, state } of queue.pending)pendings.set(batch.id, state);
|
|
525
|
+
}
|
|
541
526
|
outcomes.clear();
|
|
542
|
-
|
|
527
|
+
outcomeRooms.clear();
|
|
543
528
|
for (const record of recordedOutcomes){
|
|
544
529
|
outcomes.set(record.id, record.outcome);
|
|
545
|
-
|
|
530
|
+
outcomeRooms.set(record.id, record.locator);
|
|
546
531
|
}
|
|
547
532
|
const previousReplicas = new Map(replicas);
|
|
548
533
|
replicas.clear();
|
|
@@ -580,17 +565,21 @@ export function createMdRuntime(options) {
|
|
|
580
565
|
locators: new Set([
|
|
581
566
|
...queuedBatches.keys(),
|
|
582
567
|
...replicas.keys()
|
|
583
|
-
])
|
|
568
|
+
]),
|
|
569
|
+
read: mergeChangeSets([
|
|
570
|
+
...new Set([
|
|
571
|
+
...previousReplicas.keys(),
|
|
572
|
+
...replicas.keys()
|
|
573
|
+
])
|
|
574
|
+
].map((locator)=>changeSet(locator, previousReplicas.get(locator) ?? null, replicas.get(locator) ?? null)))
|
|
584
575
|
};
|
|
585
576
|
}
|
|
586
577
|
async function readState(opened) {
|
|
587
|
-
const { lastCompleted, locators } = await hydrate(opened);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
emitVisible();
|
|
591
|
-
}
|
|
578
|
+
const { lastCompleted, locators, read } = await hydrate(opened);
|
|
579
|
+
readSide.apply(read);
|
|
580
|
+
if (replicas.size > 0) emitRooms();
|
|
592
581
|
for (const locator of locators)emitQueue(locator);
|
|
593
|
-
|
|
582
|
+
publishRoomDrafts(false);
|
|
594
583
|
return lastCompleted;
|
|
595
584
|
}
|
|
596
585
|
async function open() {
|
|
@@ -632,6 +621,80 @@ export function createMdRuntime(options) {
|
|
|
632
621
|
mutations = next.then(()=>undefined, ()=>undefined);
|
|
633
622
|
return next;
|
|
634
623
|
}
|
|
624
|
+
function withdrawable(batch) {
|
|
625
|
+
const state = pendings.get(batch.id);
|
|
626
|
+
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
627
|
+
}
|
|
628
|
+
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
629
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable, rewriting());
|
|
630
|
+
const kept = queue.filter((batch)=>!withdrawn.includes(batch));
|
|
631
|
+
const returning = [
|
|
632
|
+
...withdrawn.map((batch)=>({
|
|
633
|
+
batch,
|
|
634
|
+
returned: {
|
|
635
|
+
kind: 'withdrawn'
|
|
636
|
+
}
|
|
637
|
+
})).reverse(),
|
|
638
|
+
...settling.leaving === undefined ? [] : [
|
|
639
|
+
settling.leaving
|
|
640
|
+
]
|
|
641
|
+
];
|
|
642
|
+
const written = new Map();
|
|
643
|
+
for (const { batch, returned } of returning){
|
|
644
|
+
for (const saved of pendings.get(batch.id)?.drafts ?? []){
|
|
645
|
+
const existing = written.get(saved.path) ?? roomDrafts.get(locator)?.get(saved.path);
|
|
646
|
+
written.set(saved.path, {
|
|
647
|
+
locator,
|
|
648
|
+
path: saved.path,
|
|
649
|
+
revision: roomDraftRevision(),
|
|
650
|
+
parts: returnedInto(existing?.parts, saved.parts),
|
|
651
|
+
returned
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const settledOutcomes = [
|
|
656
|
+
...settling.accepted === undefined ? [] : [
|
|
657
|
+
settling.accepted
|
|
658
|
+
],
|
|
659
|
+
...settling.leaving === undefined ? [] : [
|
|
660
|
+
{
|
|
661
|
+
id: settling.leaving.batch.id,
|
|
662
|
+
outcome: settling.leaving.outcome
|
|
663
|
+
}
|
|
664
|
+
],
|
|
665
|
+
...withdrawn.map((batch)=>({
|
|
666
|
+
id: batch.id,
|
|
667
|
+
outcome: {
|
|
668
|
+
kind: 'withdrawn'
|
|
669
|
+
}
|
|
670
|
+
}))
|
|
671
|
+
];
|
|
672
|
+
await local.write({
|
|
673
|
+
next: confirmed.record,
|
|
674
|
+
previous: before?.confirmed.record ?? null,
|
|
675
|
+
queue: kept,
|
|
676
|
+
previousQueue: before?.queue ?? [],
|
|
677
|
+
outcomes: settledOutcomes,
|
|
678
|
+
roomDrafts: [
|
|
679
|
+
...written.values()
|
|
680
|
+
]
|
|
681
|
+
});
|
|
682
|
+
for (const { id, outcome } of settledOutcomes){
|
|
683
|
+
outcomes.set(id, outcome);
|
|
684
|
+
outcomeRooms.set(id, locator);
|
|
685
|
+
pendings.delete(id);
|
|
686
|
+
}
|
|
687
|
+
for (const record of written.values())rememberRoomDraft(record);
|
|
688
|
+
for (const batch of withdrawn)journal.record(`outcome ${locator} ${batch.id} withdrawn`);
|
|
689
|
+
replicas.set(locator, replicaOf(confirmed, kept, rewriting(), before));
|
|
690
|
+
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
691
|
+
room: settling.room,
|
|
692
|
+
queue: kept.length !== (before?.queue.length ?? 0),
|
|
693
|
+
drafts: [
|
|
694
|
+
...written.keys()
|
|
695
|
+
]
|
|
696
|
+
});
|
|
697
|
+
}
|
|
635
698
|
function publish(local, room, confirmed) {
|
|
636
699
|
return exclusive(async ()=>{
|
|
637
700
|
const prior = replicas.get(room.locator);
|
|
@@ -640,23 +703,26 @@ export function createMdRuntime(options) {
|
|
|
640
703
|
model: modelOf(room.locator),
|
|
641
704
|
selectionModel: modelOf(room.locator, 'typed-selection')
|
|
642
705
|
}, prior?.confirmed);
|
|
643
|
-
if (retained
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
unreadable.delete(room.locator);
|
|
650
|
-
replicas.set(room.locator, replicaOf(retained, prior?.queue ?? [], rewriting(), prior));
|
|
706
|
+
if (retained === null) {
|
|
707
|
+
list(room, true);
|
|
708
|
+
publishChangeSet(changeSet(room.locator, prior ?? null, prior ?? null, {
|
|
709
|
+
room: true
|
|
710
|
+
}));
|
|
711
|
+
return;
|
|
651
712
|
}
|
|
713
|
+
const set = await settle(local, room.locator, prior, retained, prior?.queue ?? [], {
|
|
714
|
+
room: true
|
|
715
|
+
});
|
|
716
|
+
unreadable.delete(room.locator);
|
|
652
717
|
list(room, true);
|
|
653
|
-
publishChangeSet(
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
718
|
+
publishChangeSet(set);
|
|
719
|
+
moveTo({
|
|
720
|
+
...status,
|
|
721
|
+
...attention()
|
|
722
|
+
});
|
|
657
723
|
});
|
|
658
724
|
}
|
|
659
|
-
async function
|
|
725
|
+
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
660
726
|
const local = await openLocal();
|
|
661
727
|
const replica = replicas.get(locator);
|
|
662
728
|
const room = rooms.get(locator);
|
|
@@ -667,6 +733,28 @@ export function createMdRuntime(options) {
|
|
|
667
733
|
outcome: 'refused',
|
|
668
734
|
rule: 'collection-root-mismatch'
|
|
669
735
|
};
|
|
736
|
+
const saved = [];
|
|
737
|
+
for (const { path, revision } of named){
|
|
738
|
+
const record = roomDrafts.get(locator)?.get(path);
|
|
739
|
+
if (record === undefined || record.revision !== revision) {
|
|
740
|
+
return {
|
|
741
|
+
outcome: 'refused',
|
|
742
|
+
rule: 'stale',
|
|
743
|
+
file: path
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
if (!saved.some((draft)=>draft.path === path)) saved.push({
|
|
747
|
+
path,
|
|
748
|
+
parts: record.parts
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
const changed = saved.flatMap(({ path, parts })=>changedParts(path, underneath(parts, path, replica.visible)));
|
|
752
|
+
if (changed.length > 0) return {
|
|
753
|
+
outcome: 'refused',
|
|
754
|
+
rule: 'changed-underneath',
|
|
755
|
+
parts: changed
|
|
756
|
+
};
|
|
757
|
+
const parts = saved.map(({ path, parts })=>changesOf(path, parts));
|
|
670
758
|
const built = buildBatch({
|
|
671
759
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
672
760
|
composition: compositionFor(replica.confirmed.record),
|
|
@@ -675,45 +763,27 @@ export function createMdRuntime(options) {
|
|
|
675
763
|
providerLimits: limits,
|
|
676
764
|
rewriting: rewriting(),
|
|
677
765
|
visible: replica.visible,
|
|
678
|
-
changes,
|
|
766
|
+
changes: parts.flatMap(({ changes })=>changes),
|
|
767
|
+
bases: parts.flatMap(({ bases })=>bases),
|
|
679
768
|
writtenBy
|
|
680
769
|
});
|
|
681
770
|
if (built.kind === 'refused') return {
|
|
682
771
|
outcome: 'refused',
|
|
683
772
|
...built.refusal
|
|
684
773
|
};
|
|
685
|
-
const
|
|
686
|
-
if (replacing !== undefined) {
|
|
687
|
-
if (head === undefined || head.id !== replacing || !refused(outcomes.get(head.id))) {
|
|
688
|
-
return {
|
|
689
|
-
outcome: 'refused',
|
|
690
|
-
rule: 'no-refused-head'
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
const batches = replacing === undefined ? [
|
|
774
|
+
const batches = [
|
|
695
775
|
...replica.queue,
|
|
696
776
|
built.batch
|
|
697
|
-
] : [
|
|
698
|
-
built.batch,
|
|
699
|
-
...replica.queue.slice(1)
|
|
700
|
-
];
|
|
701
|
-
const consumed = [
|
|
702
|
-
...new Set(changes.map((change)=>change.path))
|
|
703
777
|
];
|
|
778
|
+
const consumed = saved.map(({ path })=>path);
|
|
704
779
|
try {
|
|
705
780
|
await local.write({
|
|
706
781
|
next: replica.confirmed.record,
|
|
707
782
|
previous: replica.confirmed.record,
|
|
708
783
|
queue: batches,
|
|
709
784
|
previousQueue: replica.queue,
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
id: replacing,
|
|
713
|
-
outcome: null
|
|
714
|
-
}
|
|
715
|
-
],
|
|
716
|
-
consumedDrafts: consumed
|
|
785
|
+
saved,
|
|
786
|
+
consumedRoomDrafts: consumed
|
|
717
787
|
});
|
|
718
788
|
} catch {
|
|
719
789
|
return {
|
|
@@ -721,8 +791,14 @@ export function createMdRuntime(options) {
|
|
|
721
791
|
rule: 'durable-write'
|
|
722
792
|
};
|
|
723
793
|
}
|
|
724
|
-
|
|
725
|
-
|
|
794
|
+
pendings.set(built.batch.id, {
|
|
795
|
+
drafts: saved,
|
|
796
|
+
submission: {
|
|
797
|
+
state: 'waiting'
|
|
798
|
+
},
|
|
799
|
+
lost: false
|
|
800
|
+
});
|
|
801
|
+
for (const path of consumed)forgetRoomDraft(locator, path);
|
|
726
802
|
replicas.set(locator, replicaOf(replica.confirmed, batches, rewriting(), replica));
|
|
727
803
|
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
728
804
|
queue: true,
|
|
@@ -735,34 +811,28 @@ export function createMdRuntime(options) {
|
|
|
735
811
|
void enqueue();
|
|
736
812
|
return {
|
|
737
813
|
outcome: 'saved',
|
|
738
|
-
|
|
814
|
+
identifier: built.batch.id
|
|
739
815
|
};
|
|
740
816
|
}
|
|
741
|
-
async function
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
const head = replica?.queue[0];
|
|
745
|
-
if (replica === undefined || head === undefined || head.id !== identifier || !refused(outcomes.get(head.id))) {
|
|
817
|
+
async function mark(local, locator, identifier, submission, lost) {
|
|
818
|
+
const state = pendings.get(identifier);
|
|
819
|
+
if (state === undefined || JSON.stringify(state.submission) === JSON.stringify(submission) && state.lost === lost) {
|
|
746
820
|
return;
|
|
747
821
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
replicas.set(locator, replicaOf(replica.confirmed, batches, rewriting(), replica));
|
|
757
|
-
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
822
|
+
await local.submit(locator, identifier, submission, lost);
|
|
823
|
+
pendings.set(identifier, {
|
|
824
|
+
...state,
|
|
825
|
+
submission,
|
|
826
|
+
lost
|
|
827
|
+
});
|
|
828
|
+
const replica = replicas.get(locator);
|
|
829
|
+
if (replica !== undefined) publishChangeSet(changeSet(locator, replica, replica, {
|
|
758
830
|
queue: true
|
|
759
831
|
}));
|
|
760
832
|
moveTo({
|
|
761
833
|
...status,
|
|
762
|
-
state: pending() ? 'pending' : status.state,
|
|
763
834
|
...attention()
|
|
764
835
|
});
|
|
765
|
-
void enqueue();
|
|
766
836
|
}
|
|
767
837
|
async function readHistory(locator, path, query = {}) {
|
|
768
838
|
if (closed || refusal !== null) return {
|
|
@@ -918,36 +988,66 @@ export function createMdRuntime(options) {
|
|
|
918
988
|
await publish(local, room, bootstrapped.confirmed);
|
|
919
989
|
return null;
|
|
920
990
|
}
|
|
921
|
-
function
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
if (
|
|
991
|
+
async function withhold(withheld, room, local, progress) {
|
|
992
|
+
progress.complete = false;
|
|
993
|
+
if (withheld.kind === 'scoped-refusal') {
|
|
994
|
+
await scoped(withheld, room, local);
|
|
995
|
+
} else if (withheld.kind === 'transport-uncertainty') {
|
|
996
|
+
progress.reachable = false;
|
|
997
|
+
} else if (withheld.kind === 'room-not-readable') {
|
|
998
|
+
unreadable.add(room.locator);
|
|
999
|
+
list(room, false);
|
|
1000
|
+
emitRooms();
|
|
1001
|
+
} else if (withheld.kind === 'revoked-authorization') {
|
|
1002
|
+
await refuse('revoked-authorization');
|
|
1003
|
+
return 'stop';
|
|
1004
|
+
}
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
function leaving(batch, result) {
|
|
1008
|
+
if (result.kind === 'expired') {
|
|
926
1009
|
return {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1010
|
+
batch,
|
|
1011
|
+
outcome: {
|
|
1012
|
+
kind: 'expired'
|
|
1013
|
+
},
|
|
1014
|
+
returned: {
|
|
1015
|
+
kind: 'expired'
|
|
932
1016
|
}
|
|
933
1017
|
};
|
|
934
1018
|
}
|
|
1019
|
+
const refusal = {
|
|
1020
|
+
code: result.code,
|
|
1021
|
+
fields: result.fields
|
|
1022
|
+
};
|
|
935
1023
|
if (result.kind === 'conflicted') {
|
|
936
1024
|
return {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1025
|
+
batch,
|
|
1026
|
+
outcome: {
|
|
1027
|
+
kind: 'conflicted',
|
|
1028
|
+
...result.roomVersion === undefined ? {} : {
|
|
1029
|
+
roomVersion: result.roomVersion
|
|
1030
|
+
},
|
|
1031
|
+
affected: result.affected,
|
|
1032
|
+
...refusal
|
|
940
1033
|
},
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1034
|
+
returned: {
|
|
1035
|
+
kind: 'conflicted',
|
|
1036
|
+
...refusal
|
|
1037
|
+
}
|
|
944
1038
|
};
|
|
945
1039
|
}
|
|
946
1040
|
return {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1041
|
+
batch,
|
|
1042
|
+
outcome: {
|
|
1043
|
+
kind: 'rejected',
|
|
1044
|
+
affected: result.affected,
|
|
1045
|
+
...refusal
|
|
1046
|
+
},
|
|
1047
|
+
returned: {
|
|
1048
|
+
kind: 'rejected',
|
|
1049
|
+
...refusal
|
|
1050
|
+
}
|
|
951
1051
|
};
|
|
952
1052
|
}
|
|
953
1053
|
async function commitAccept(room, local, submitted, accepted, base, overlay) {
|
|
@@ -974,28 +1074,20 @@ export function createMdRuntime(options) {
|
|
|
974
1074
|
time: accepted.time
|
|
975
1075
|
}
|
|
976
1076
|
};
|
|
977
|
-
await local.
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
{
|
|
984
|
-
id: submitted.id,
|
|
985
|
-
outcome: acceptedOutcome
|
|
986
|
-
}
|
|
987
|
-
]
|
|
1077
|
+
const set = await settle(local, room.locator, current, applied, remaining, {
|
|
1078
|
+
accepted: {
|
|
1079
|
+
id: submitted.id,
|
|
1080
|
+
outcome: acceptedOutcome
|
|
1081
|
+
},
|
|
1082
|
+
room: true
|
|
988
1083
|
});
|
|
989
|
-
acceptedIn.set(submitted.id, room.locator);
|
|
990
|
-
outcomes.set(submitted.id, acceptedOutcome);
|
|
991
1084
|
unreadable.delete(room.locator);
|
|
992
|
-
replicas.set(room.locator, replicaOf(applied, remaining, rewriting(), current));
|
|
993
1085
|
list(room, true);
|
|
994
|
-
publishChangeSet(
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
})
|
|
1086
|
+
publishChangeSet(set);
|
|
1087
|
+
moveTo({
|
|
1088
|
+
...status,
|
|
1089
|
+
...attention()
|
|
1090
|
+
});
|
|
999
1091
|
journal.record(`outcome ${room.locator} ${submitted.id} accepted ${accepted.roomVersion}` + (accepted.author === undefined ? '' : ` author ${accepted.author}`) + (accepted.time === undefined ? '' : ` time ${accepted.time}`));
|
|
1000
1092
|
return true;
|
|
1001
1093
|
}
|
|
@@ -1004,39 +1096,22 @@ export function createMdRuntime(options) {
|
|
|
1004
1096
|
const replica = replicas.get(room.locator);
|
|
1005
1097
|
const head = replica?.queue[0];
|
|
1006
1098
|
if (replica === undefined || head === undefined) return null;
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
if (current === undefined || current.queue[0]?.id !== head.id) return;
|
|
1017
|
-
await persistQueue(local, current, current.queue, [
|
|
1018
|
-
{
|
|
1019
|
-
id: head.id,
|
|
1020
|
-
outcome: null
|
|
1021
|
-
}
|
|
1022
|
-
]);
|
|
1023
|
-
outcomes.delete(head.id);
|
|
1024
|
-
publishChangeSet(changeSet(room.locator, current, current, {
|
|
1025
|
-
queue: true
|
|
1026
|
-
}));
|
|
1027
|
-
moveTo({
|
|
1028
|
-
...status,
|
|
1029
|
-
...attention()
|
|
1030
|
-
});
|
|
1031
|
-
});
|
|
1099
|
+
const { submission, lost: earlier } = pendings.get(head.id) ?? {
|
|
1100
|
+
submission: {
|
|
1101
|
+
state: 'waiting'
|
|
1102
|
+
},
|
|
1103
|
+
lost: false
|
|
1104
|
+
};
|
|
1105
|
+
if (submission.state === 'reset' && Date.now() < Date.parse(submission.reset)) {
|
|
1106
|
+
progress.complete = false;
|
|
1107
|
+
return null;
|
|
1032
1108
|
}
|
|
1109
|
+
const lost = earlier || submission.state === 'submitted';
|
|
1033
1110
|
let result;
|
|
1034
1111
|
journal.record(`submission ${room.locator} ${head.id}`);
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
}));
|
|
1039
|
-
}
|
|
1112
|
+
await exclusive(()=>mark(local, room.locator, head.id, {
|
|
1113
|
+
state: 'submitted'
|
|
1114
|
+
}, lost));
|
|
1040
1115
|
try {
|
|
1041
1116
|
result = await options.adapter.mutate(authorized, {
|
|
1042
1117
|
locator: room.locator,
|
|
@@ -1072,29 +1147,37 @@ export function createMdRuntime(options) {
|
|
|
1072
1147
|
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired' || result.kind === 'retryable-refusal') {
|
|
1073
1148
|
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` + (result.kind === 'conflicted' && result.roomVersion !== undefined ? ` ${result.roomVersion}` : ''));
|
|
1074
1149
|
if (result.kind !== 'expired') journal.record(`refusal ${result.kind} ${result.code}`);
|
|
1075
|
-
|
|
1150
|
+
}
|
|
1151
|
+
if (result.kind === 'retryable-refusal') {
|
|
1152
|
+
const reset = result.reset;
|
|
1153
|
+
progress.complete = false;
|
|
1154
|
+
await exclusive(()=>mark(local, room.locator, head.id, reset === undefined ? {
|
|
1155
|
+
state: 'waiting'
|
|
1156
|
+
} : {
|
|
1157
|
+
state: 'reset',
|
|
1158
|
+
reset
|
|
1159
|
+
}, lost));
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
1163
|
+
const left = leaving(head, result);
|
|
1076
1164
|
await exclusive(async ()=>{
|
|
1077
1165
|
const current = replicas.get(room.locator);
|
|
1078
1166
|
if (current === undefined || !current.queue.some((batch)=>batch.id === head.id)) {
|
|
1079
1167
|
return;
|
|
1080
1168
|
}
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
outcome: recorded
|
|
1086
|
-
}
|
|
1087
|
-
]);
|
|
1088
|
-
outcomes.set(head.id, recorded);
|
|
1089
|
-
publishChangeSet(changeSet(room.locator, current, current, {
|
|
1090
|
-
queue: true
|
|
1091
|
-
}));
|
|
1169
|
+
const set = await settle(local, room.locator, current, current.confirmed, current.queue.filter((batch)=>batch.id !== head.id), {
|
|
1170
|
+
leaving: left
|
|
1171
|
+
});
|
|
1172
|
+
publishChangeSet(set);
|
|
1092
1173
|
moveTo({
|
|
1093
1174
|
...status,
|
|
1094
1175
|
...attention()
|
|
1095
1176
|
});
|
|
1096
1177
|
});
|
|
1097
|
-
|
|
1178
|
+
const withheld = await advance(room, local, authorized);
|
|
1179
|
+
if (withheld !== null) return withhold(withheld, room, local, progress);
|
|
1180
|
+
continue;
|
|
1098
1181
|
}
|
|
1099
1182
|
const from = replicas.get(room.locator)?.confirmed;
|
|
1100
1183
|
if (from === undefined) return null;
|
|
@@ -1174,7 +1257,6 @@ export function createMdRuntime(options) {
|
|
|
1174
1257
|
const previous = modelOf(room.locator, attached);
|
|
1175
1258
|
const heldBefore = replicas.get(room.locator)?.confirmed.record;
|
|
1176
1259
|
if (JSON.stringify(previous) === JSON.stringify(next) && (heldBefore === undefined || JSON.stringify(heldBefore.operations) === JSON.stringify(room.operations) && JSON.stringify(heldBefore.replica) === JSON.stringify(room.replica) && JSON.stringify(heldBefore.attachedModel) === JSON.stringify(room.attachedModel) && JSON.stringify(heldBefore.typedSelection) === JSON.stringify(room.typedSelection ?? null))) return;
|
|
1177
|
-
const queryChanged = previous.status === 'unavailable' || next.status === 'unavailable' ? previous.status !== next.status : JSON.stringify(previous.types) !== JSON.stringify(next.types);
|
|
1178
1260
|
if (next.status === 'unavailable') {
|
|
1179
1261
|
invalidModels.add(key);
|
|
1180
1262
|
liveModels.delete(key);
|
|
@@ -1213,8 +1295,7 @@ export function createMdRuntime(options) {
|
|
|
1213
1295
|
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
1214
1296
|
if (held === undefined) emitRooms();
|
|
1215
1297
|
else publishChangeSet(changeSet(room.locator, held, replicas.get(room.locator) ?? null, {
|
|
1216
|
-
room: true
|
|
1217
|
-
query: queryChanged
|
|
1298
|
+
room: true
|
|
1218
1299
|
}));
|
|
1219
1300
|
});
|
|
1220
1301
|
}
|
|
@@ -1356,25 +1437,7 @@ export function createMdRuntime(options) {
|
|
|
1356
1437
|
}
|
|
1357
1438
|
const withheld = await advance(room, local, authorization);
|
|
1358
1439
|
if (withheld !== null) {
|
|
1359
|
-
progress
|
|
1360
|
-
if (withheld.kind === 'scoped-refusal') {
|
|
1361
|
-
await scoped(withheld, room, local);
|
|
1362
|
-
continue;
|
|
1363
|
-
}
|
|
1364
|
-
if (withheld.kind === 'transport-uncertainty') {
|
|
1365
|
-
progress.reachable = false;
|
|
1366
|
-
continue;
|
|
1367
|
-
}
|
|
1368
|
-
if (withheld.kind === 'room-not-readable') {
|
|
1369
|
-
unreadable.add(room.locator);
|
|
1370
|
-
list(room, false);
|
|
1371
|
-
emitRooms();
|
|
1372
|
-
continue;
|
|
1373
|
-
}
|
|
1374
|
-
if (withheld.kind === 'revoked-authorization') {
|
|
1375
|
-
await refuse('revoked-authorization');
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1440
|
+
if (await withhold(withheld, room, local, progress) === 'stop') return;
|
|
1378
1441
|
continue;
|
|
1379
1442
|
}
|
|
1380
1443
|
if (await submitRoom(room, local, authorization, progress) === 'stop') return;
|
|
@@ -1682,9 +1745,9 @@ export function createMdRuntime(options) {
|
|
|
1682
1745
|
refusalListeners.clear();
|
|
1683
1746
|
storageLossListeners.clear();
|
|
1684
1747
|
statusListeners.clear();
|
|
1685
|
-
|
|
1748
|
+
readSide.close();
|
|
1686
1749
|
queueSubscriptions.clear();
|
|
1687
|
-
|
|
1750
|
+
roomDraftListeners.clear();
|
|
1688
1751
|
presentationListeners.clear();
|
|
1689
1752
|
}
|
|
1690
1753
|
options.signal?.addEventListener('abort', close, {
|
|
@@ -1705,9 +1768,7 @@ export function createMdRuntime(options) {
|
|
|
1705
1768
|
slots () {
|
|
1706
1769
|
return slotted();
|
|
1707
1770
|
},
|
|
1708
|
-
query (
|
|
1709
|
-
return visible(request);
|
|
1710
|
-
},
|
|
1771
|
+
query: (input)=>readSide.query(planOf(input, roomFacts())),
|
|
1711
1772
|
types (locator) {
|
|
1712
1773
|
return {
|
|
1713
1774
|
collection: modelTypes(modelOf(locator)),
|
|
@@ -1717,35 +1778,77 @@ export function createMdRuntime(options) {
|
|
|
1717
1778
|
syncStatus () {
|
|
1718
1779
|
return shown;
|
|
1719
1780
|
},
|
|
1720
|
-
|
|
1781
|
+
roomDraft: {
|
|
1721
1782
|
list () {
|
|
1722
1783
|
return exclusive(async ()=>{
|
|
1723
1784
|
await openLocal();
|
|
1724
|
-
return
|
|
1725
|
-
}).then((
|
|
1726
|
-
|
|
1727
|
-
return
|
|
1785
|
+
return snapshotRoomDrafts();
|
|
1786
|
+
}).then((listed)=>{
|
|
1787
|
+
publishRoomDrafts(true);
|
|
1788
|
+
return listed;
|
|
1728
1789
|
});
|
|
1729
1790
|
},
|
|
1730
|
-
read (
|
|
1731
|
-
const
|
|
1732
|
-
return
|
|
1791
|
+
read (room, path) {
|
|
1792
|
+
const record = roomDrafts.get(room)?.get(path);
|
|
1793
|
+
return record === undefined ? null : roomDraftOf(record);
|
|
1733
1794
|
},
|
|
1734
|
-
|
|
1735
|
-
return
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
|
|
1795
|
+
write (room, path, revision, input) {
|
|
1796
|
+
return exclusive(async ()=>{
|
|
1797
|
+
const parts = readParts(input);
|
|
1798
|
+
if (path === '' || normalize(path) !== path) throw new Error('room-draft-malformed');
|
|
1799
|
+
const local = await openLocal();
|
|
1800
|
+
const current = roomDrafts.get(room)?.get(path);
|
|
1801
|
+
if ((current?.revision ?? null) !== revision) {
|
|
1802
|
+
return {
|
|
1803
|
+
outcome: 'stale',
|
|
1804
|
+
current: current === undefined ? null : roomDraftOf(current)
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
const record = {
|
|
1808
|
+
locator: room,
|
|
1739
1809
|
path,
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1810
|
+
revision: roomDraftRevision(),
|
|
1811
|
+
parts: held(current?.parts, parts),
|
|
1812
|
+
returned: current?.returned ?? null
|
|
1813
|
+
};
|
|
1814
|
+
await local.keepRoomDraft(record);
|
|
1815
|
+
rememberRoomDraft(record);
|
|
1816
|
+
publishChangeSet(changeSet(room, null, null, {
|
|
1817
|
+
drafts: [
|
|
1818
|
+
path
|
|
1819
|
+
]
|
|
1820
|
+
}));
|
|
1821
|
+
return {
|
|
1822
|
+
outcome: 'written',
|
|
1823
|
+
roomDraft: roomDraftOf(record)
|
|
1824
|
+
};
|
|
1743
1825
|
});
|
|
1744
1826
|
},
|
|
1745
|
-
discard (
|
|
1746
|
-
return
|
|
1747
|
-
await
|
|
1748
|
-
|
|
1827
|
+
discard (room, path, revision) {
|
|
1828
|
+
return exclusive(async ()=>{
|
|
1829
|
+
const local = await openLocal();
|
|
1830
|
+
const current = roomDrafts.get(room)?.get(path);
|
|
1831
|
+
if (current === undefined || current.revision !== revision) {
|
|
1832
|
+
return {
|
|
1833
|
+
outcome: 'stale',
|
|
1834
|
+
current: current === undefined ? null : roomDraftOf(current)
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
await local.discardRoomDraft(room, path);
|
|
1838
|
+
forgetRoomDraft(room, path);
|
|
1839
|
+
publishChangeSet(changeSet(room, null, null, {
|
|
1840
|
+
drafts: [
|
|
1841
|
+
path
|
|
1842
|
+
]
|
|
1843
|
+
}));
|
|
1844
|
+
moveTo({
|
|
1845
|
+
...status,
|
|
1846
|
+
...attention()
|
|
1847
|
+
});
|
|
1848
|
+
return {
|
|
1849
|
+
outcome: 'written',
|
|
1850
|
+
roomDraft: null
|
|
1851
|
+
};
|
|
1749
1852
|
});
|
|
1750
1853
|
}
|
|
1751
1854
|
},
|
|
@@ -1773,23 +1876,11 @@ export function createMdRuntime(options) {
|
|
|
1773
1876
|
});
|
|
1774
1877
|
}
|
|
1775
1878
|
},
|
|
1776
|
-
save (
|
|
1777
|
-
return exclusive(()=>
|
|
1879
|
+
save (room, named, writtenBy) {
|
|
1880
|
+
return exclusive(()=>saveRoomDrafts(room, named, writtenBy));
|
|
1778
1881
|
},
|
|
1779
1882
|
outcome (identifier) {
|
|
1780
|
-
|
|
1781
|
-
if (recorded !== undefined) return recorded;
|
|
1782
|
-
for (const replica of replicas.values()){
|
|
1783
|
-
if (replica.queue.some((batch)=>batch.id === identifier)) return {
|
|
1784
|
-
kind: 'pending'
|
|
1785
|
-
};
|
|
1786
|
-
}
|
|
1787
|
-
return null;
|
|
1788
|
-
},
|
|
1789
|
-
queue: {
|
|
1790
|
-
discard (locator, identifier) {
|
|
1791
|
-
return exclusive(()=>discardHead(locator, identifier));
|
|
1792
|
-
}
|
|
1883
|
+
return outcomes.get(identifier) ?? null;
|
|
1793
1884
|
},
|
|
1794
1885
|
history (locator, path, query) {
|
|
1795
1886
|
return readHistory(locator, path, query);
|
|
@@ -1807,23 +1898,23 @@ export function createMdRuntime(options) {
|
|
|
1807
1898
|
return options.shell.persistentStorage;
|
|
1808
1899
|
},
|
|
1809
1900
|
observe (topic, second, third, fourth) {
|
|
1810
|
-
if (topic === 'drafts') {
|
|
1901
|
+
if (topic === 'room-drafts') {
|
|
1811
1902
|
const notify = second;
|
|
1812
1903
|
const listener = {
|
|
1813
1904
|
notify,
|
|
1814
1905
|
ready: false
|
|
1815
1906
|
};
|
|
1816
|
-
|
|
1907
|
+
roomDraftListeners.add(listener);
|
|
1817
1908
|
void exclusive(async ()=>{
|
|
1818
1909
|
await openLocal();
|
|
1819
|
-
if (closed || !
|
|
1910
|
+
if (closed || !roomDraftListeners.has(listener)) return;
|
|
1820
1911
|
listener.ready = true;
|
|
1821
1912
|
}).then(()=>{
|
|
1822
|
-
if (closed || !listener.ready || !
|
|
1823
|
-
listener.notify(
|
|
1913
|
+
if (closed || !listener.ready || !roomDraftListeners.has(listener)) return;
|
|
1914
|
+
listener.notify(snapshotRoomDrafts());
|
|
1824
1915
|
}, ()=>{});
|
|
1825
1916
|
return ()=>{
|
|
1826
|
-
|
|
1917
|
+
roomDraftListeners.delete(listener);
|
|
1827
1918
|
};
|
|
1828
1919
|
}
|
|
1829
1920
|
if (topic === 'conversation') {
|
|
@@ -1890,16 +1981,8 @@ export function createMdRuntime(options) {
|
|
|
1890
1981
|
queueSubscriptions.delete(queue);
|
|
1891
1982
|
};
|
|
1892
1983
|
}
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
notify: third
|
|
1896
|
-
};
|
|
1897
|
-
const first = visible(subscription.query);
|
|
1898
|
-
querySubscriptions.add(subscription);
|
|
1899
|
-
subscription.notify(first);
|
|
1900
|
-
return ()=>{
|
|
1901
|
-
querySubscriptions.delete(subscription);
|
|
1902
|
-
};
|
|
1984
|
+
const plan = planOf(second, roomFacts());
|
|
1985
|
+
return readSide.observe(plan, identityOf(plan), third);
|
|
1903
1986
|
},
|
|
1904
1987
|
async reauthorize () {
|
|
1905
1988
|
if (refusal === null || !refusal.reauthorizationOffered) return;
|