@agentshouse/mdruntime 0.1.0-alpha.23 → 0.1.0-alpha.24
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 +34 -18
- 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 +9 -12
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +15 -20
- package/dist/client.js.map +1 -1
- package/dist/connection.d.ts +14 -18
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +390 -291
- package/dist/connection.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 +3 -2
- 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/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/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 +1 -1
package/dist/connection.js
CHANGED
|
@@ -7,13 +7,14 @@ import { changeSet, mergeChangeSets } from './change-set.js';
|
|
|
7
7
|
import { createConversationStore } from './conversation.js';
|
|
8
8
|
import { MD_MODEL, PRESENTED, validRoot } from './collection.js';
|
|
9
9
|
import { createLog } from './log.js';
|
|
10
|
-
import { queuedOutcome, refusalOf, refused, withheldUntilReset } from './outcome.js';
|
|
11
10
|
import { refreshRoom } from './refresh.js';
|
|
12
11
|
import { buildBatch } from './save.js';
|
|
13
|
-
import { applyReturned, compositionOf, readConfirmed, replicaOf } from './replica.js';
|
|
12
|
+
import { applyReturned, compositionOf, readConfirmed, replicaOf, withdrawals } from './replica.js';
|
|
13
|
+
import { changedParts, changesOf, held, readParts, returnedInto, underneath } from './room-draft.js';
|
|
14
14
|
import { settled, same, saving, STARTING, transferring } from './status.js';
|
|
15
15
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
16
|
-
import {
|
|
16
|
+
import { roomDraftRevision } from './identity.js';
|
|
17
|
+
import { beneath, normalize } from './paths.js';
|
|
17
18
|
import { openRuntimeStore } from './store.js';
|
|
18
19
|
import { unconfirmed } from './transfer.js';
|
|
19
20
|
const DEFAULT_DISCOVERY_INTERVAL_MS = 60_000;
|
|
@@ -39,10 +40,11 @@ export function createMdRuntime(options) {
|
|
|
39
40
|
let opening;
|
|
40
41
|
let store;
|
|
41
42
|
const replicas = new Map();
|
|
42
|
-
const
|
|
43
|
+
const pendings = new Map();
|
|
44
|
+
const roomDrafts = new Map();
|
|
43
45
|
const presentations = new Map();
|
|
44
46
|
const outcomes = new Map();
|
|
45
|
-
const
|
|
47
|
+
const outcomeRooms = new Map();
|
|
46
48
|
let limits = [];
|
|
47
49
|
let slots = [];
|
|
48
50
|
let capabilities = [];
|
|
@@ -97,7 +99,7 @@ export function createMdRuntime(options) {
|
|
|
97
99
|
const statusListeners = new Set();
|
|
98
100
|
const querySubscriptions = new Set();
|
|
99
101
|
const queueSubscriptions = new Set();
|
|
100
|
-
const
|
|
102
|
+
const roomDraftListeners = new Set();
|
|
101
103
|
const presentationListeners = new Set();
|
|
102
104
|
let withdrawConversations;
|
|
103
105
|
let currencyQueued = false;
|
|
@@ -115,53 +117,49 @@ export function createMdRuntime(options) {
|
|
|
115
117
|
const ready = new Promise((resolve)=>{
|
|
116
118
|
notifyReady = resolve;
|
|
117
119
|
});
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
+
function rememberRoomDraft(record) {
|
|
121
|
+
const room = roomDrafts.get(record.locator) ?? new Map();
|
|
122
|
+
room.set(record.path, record);
|
|
123
|
+
roomDrafts.set(record.locator, room);
|
|
120
124
|
}
|
|
121
|
-
function
|
|
122
|
-
const room =
|
|
123
|
-
room.set(path, content);
|
|
124
|
-
drafts.set(locator, room);
|
|
125
|
-
}
|
|
126
|
-
function forgetDraft(locator, path) {
|
|
127
|
-
const room = drafts.get(locator);
|
|
125
|
+
function forgetRoomDraft(locator, path) {
|
|
126
|
+
const room = roomDrafts.get(locator);
|
|
128
127
|
if (room === undefined) return;
|
|
129
128
|
room.delete(path);
|
|
130
|
-
if (room.size === 0)
|
|
129
|
+
if (room.size === 0) roomDrafts.delete(locator);
|
|
130
|
+
}
|
|
131
|
+
function roomDraftOf(record) {
|
|
132
|
+
return structuredClone({
|
|
133
|
+
room: record.locator,
|
|
134
|
+
path: record.path,
|
|
135
|
+
revision: record.revision,
|
|
136
|
+
parts: record.parts,
|
|
137
|
+
changedUnderneath: underneath(record.parts, record.path, replicas.get(record.locator)?.visible),
|
|
138
|
+
returned: record.returned
|
|
139
|
+
});
|
|
131
140
|
}
|
|
132
|
-
function
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
content: structuredClone(content)
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
entries.sort((left, right)=>{
|
|
141
|
+
function snapshotRoomDrafts() {
|
|
142
|
+
const records = [
|
|
143
|
+
...roomDrafts.values()
|
|
144
|
+
].flatMap((room)=>[
|
|
145
|
+
...room.values()
|
|
146
|
+
]);
|
|
147
|
+
records.sort((left, right)=>{
|
|
144
148
|
if (left.locator !== right.locator) return left.locator < right.locator ? -1 : 1;
|
|
145
149
|
if (left.path !== right.path) return left.path < right.path ? -1 : 1;
|
|
146
150
|
return 0;
|
|
147
151
|
});
|
|
148
|
-
return
|
|
152
|
+
return records.map(roomDraftOf);
|
|
149
153
|
}
|
|
150
|
-
function
|
|
154
|
+
function publishRoomDrafts(waitingOnly) {
|
|
151
155
|
if (closed) return;
|
|
152
|
-
const value =
|
|
153
|
-
for (const listener of
|
|
156
|
+
const value = snapshotRoomDrafts();
|
|
157
|
+
for (const listener of roomDraftListeners){
|
|
154
158
|
if (waitingOnly && listener.ready) continue;
|
|
155
159
|
listener.ready = true;
|
|
156
160
|
listener.notify(value);
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
|
-
function emitDrafts() {
|
|
160
|
-
publishDrafts(false);
|
|
161
|
-
}
|
|
162
|
-
function releaseWaitingDrafts() {
|
|
163
|
-
publishDrafts(true);
|
|
164
|
-
}
|
|
165
163
|
function presentationOf(app) {
|
|
166
164
|
const value = presentations.get(app);
|
|
167
165
|
return value === undefined ? null : structuredClone(value);
|
|
@@ -172,17 +170,6 @@ export function createMdRuntime(options) {
|
|
|
172
170
|
if (listener.app === app && listener.ready) listener.notify(presentationOf(app));
|
|
173
171
|
}
|
|
174
172
|
}
|
|
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
173
|
function pending() {
|
|
187
174
|
return [
|
|
188
175
|
...replicas.values()
|
|
@@ -190,42 +177,33 @@ export function createMdRuntime(options) {
|
|
|
190
177
|
}
|
|
191
178
|
function attention() {
|
|
192
179
|
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;
|
|
180
|
+
for (const record of [
|
|
181
|
+
...roomDrafts.values()
|
|
182
|
+
].flatMap((room)=>[
|
|
183
|
+
...room.values()
|
|
184
|
+
])){
|
|
185
|
+
if (record.returned === null) continue;
|
|
205
186
|
attentionRequired.push({
|
|
206
|
-
locator,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
refusal: refusalOf(outcome),
|
|
210
|
-
...outcome.kind === 'conflicted' && outcome.roomVersion !== undefined ? {
|
|
211
|
-
roomVersion: outcome.roomVersion
|
|
212
|
-
} : {}
|
|
187
|
+
room: record.locator,
|
|
188
|
+
path: record.path,
|
|
189
|
+
returned: structuredClone(record.returned)
|
|
213
190
|
});
|
|
214
191
|
}
|
|
192
|
+
attentionRequired.sort((left, right)=>{
|
|
193
|
+
if (left.room !== right.room) return left.room < right.room ? -1 : 1;
|
|
194
|
+
if (left.path !== right.path) return left.path < right.path ? -1 : 1;
|
|
195
|
+
return 0;
|
|
196
|
+
});
|
|
197
|
+
let reset = null;
|
|
198
|
+
for (const { submission } of pendings.values()){
|
|
199
|
+
if (submission.state !== 'reset') continue;
|
|
200
|
+
if (reset === null || submission.reset < reset) reset = submission.reset;
|
|
201
|
+
}
|
|
215
202
|
return {
|
|
216
203
|
attentionRequired,
|
|
217
204
|
reset
|
|
218
205
|
};
|
|
219
206
|
}
|
|
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
207
|
function listed() {
|
|
230
208
|
return structuredClone([
|
|
231
209
|
...rooms.values()
|
|
@@ -301,12 +279,17 @@ export function createMdRuntime(options) {
|
|
|
301
279
|
subscription.notify(visible(subscription.query));
|
|
302
280
|
}
|
|
303
281
|
}
|
|
282
|
+
function submissionOf(batch) {
|
|
283
|
+
return pendings.get(batch.id)?.submission ?? {
|
|
284
|
+
state: 'waiting'
|
|
285
|
+
};
|
|
286
|
+
}
|
|
304
287
|
function entriesOf(locator) {
|
|
305
288
|
const replica = replicas.get(locator);
|
|
306
289
|
if (replica === undefined) return [];
|
|
307
290
|
return replica.queue.map((batch)=>Object.freeze({
|
|
308
291
|
batch,
|
|
309
|
-
|
|
292
|
+
...submissionOf(batch)
|
|
310
293
|
}));
|
|
311
294
|
}
|
|
312
295
|
function emitQueue(locator) {
|
|
@@ -319,7 +302,9 @@ export function createMdRuntime(options) {
|
|
|
319
302
|
if (set.rooms.length > 0) emitRooms();
|
|
320
303
|
if (set.documents.length > 0 || set.query) emitVisible();
|
|
321
304
|
for (const locator of set.queues)emitQueue(locator);
|
|
322
|
-
if (set.drafts.length > 0
|
|
305
|
+
if (set.drafts.length > 0 || set.documents.some((change)=>roomDrafts.get(change.locator)?.has(change.path) === true)) {
|
|
306
|
+
publishRoomDrafts(false);
|
|
307
|
+
}
|
|
323
308
|
for (const app of set.presentations)emitPresentation(app);
|
|
324
309
|
conversations.changed(set);
|
|
325
310
|
const cards = declaredCards();
|
|
@@ -450,7 +435,7 @@ export function createMdRuntime(options) {
|
|
|
450
435
|
return [
|
|
451
436
|
...new Set([
|
|
452
437
|
...rooms.keys(),
|
|
453
|
-
...
|
|
438
|
+
...roomDrafts.keys()
|
|
454
439
|
])
|
|
455
440
|
].filter((locator)=>!listed.has(locator));
|
|
456
441
|
}
|
|
@@ -459,9 +444,9 @@ export function createMdRuntime(options) {
|
|
|
459
444
|
await exclusive(async ()=>{
|
|
460
445
|
const unlisted = unlistedHeld(listed);
|
|
461
446
|
await local.dropRooms(unlisted);
|
|
462
|
-
for (const [batch, locator] of
|
|
447
|
+
for (const [batch, locator] of outcomeRooms){
|
|
463
448
|
if (listed.has(locator)) continue;
|
|
464
|
-
|
|
449
|
+
outcomeRooms.delete(batch);
|
|
465
450
|
outcomes.delete(batch);
|
|
466
451
|
}
|
|
467
452
|
const removed = unlisted.map((locator)=>changeSet(locator, replicas.get(locator) ?? null, null, {
|
|
@@ -469,14 +454,14 @@ export function createMdRuntime(options) {
|
|
|
469
454
|
queue: true,
|
|
470
455
|
query: true,
|
|
471
456
|
drafts: [
|
|
472
|
-
...
|
|
457
|
+
...roomDrafts.get(locator)?.keys() ?? []
|
|
473
458
|
]
|
|
474
459
|
}));
|
|
475
460
|
for (const locator of unlisted){
|
|
476
|
-
for (const batch of replicas.get(locator)?.queue ?? [])
|
|
461
|
+
for (const batch of replicas.get(locator)?.queue ?? [])pendings.delete(batch.id);
|
|
477
462
|
replicas.delete(locator);
|
|
478
463
|
rooms.delete(locator);
|
|
479
|
-
|
|
464
|
+
roomDrafts.delete(locator);
|
|
480
465
|
unreadable.delete(locator);
|
|
481
466
|
scopedRefusals.delete(locator);
|
|
482
467
|
for (const attached of [
|
|
@@ -517,7 +502,7 @@ export function createMdRuntime(options) {
|
|
|
517
502
|
opened.all(),
|
|
518
503
|
opened.queues(),
|
|
519
504
|
opened.outcomes(),
|
|
520
|
-
opened.
|
|
505
|
+
opened.roomDrafts(),
|
|
521
506
|
opened.presentation(),
|
|
522
507
|
opened.conversations(),
|
|
523
508
|
opened.chatDrafts(),
|
|
@@ -529,20 +514,24 @@ export function createMdRuntime(options) {
|
|
|
529
514
|
limits = declared;
|
|
530
515
|
capabilities = named;
|
|
531
516
|
slots = listedSlots;
|
|
532
|
-
|
|
533
|
-
for (const
|
|
517
|
+
roomDrafts.clear();
|
|
518
|
+
for (const record of kept)rememberRoomDraft(record);
|
|
534
519
|
presentations.clear();
|
|
535
520
|
for (const record of presented)presentations.set(record.app, record.value);
|
|
536
521
|
conversations.hydrate(held, chatDrafts);
|
|
537
522
|
const queuedBatches = new Map(queues.map((queue)=>[
|
|
538
523
|
queue.locator,
|
|
539
|
-
queue.
|
|
524
|
+
queue.pending.map(({ batch })=>batch)
|
|
540
525
|
]));
|
|
526
|
+
pendings.clear();
|
|
527
|
+
for (const queue of queues){
|
|
528
|
+
for (const { batch, state } of queue.pending)pendings.set(batch.id, state);
|
|
529
|
+
}
|
|
541
530
|
outcomes.clear();
|
|
542
|
-
|
|
531
|
+
outcomeRooms.clear();
|
|
543
532
|
for (const record of recordedOutcomes){
|
|
544
533
|
outcomes.set(record.id, record.outcome);
|
|
545
|
-
|
|
534
|
+
outcomeRooms.set(record.id, record.locator);
|
|
546
535
|
}
|
|
547
536
|
const previousReplicas = new Map(replicas);
|
|
548
537
|
replicas.clear();
|
|
@@ -590,7 +579,7 @@ export function createMdRuntime(options) {
|
|
|
590
579
|
emitVisible();
|
|
591
580
|
}
|
|
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,81 @@ 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
|
+
query: settling.query,
|
|
693
|
+
queue: kept.length !== (before?.queue.length ?? 0),
|
|
694
|
+
drafts: [
|
|
695
|
+
...written.keys()
|
|
696
|
+
]
|
|
697
|
+
});
|
|
698
|
+
}
|
|
635
699
|
function publish(local, room, confirmed) {
|
|
636
700
|
return exclusive(async ()=>{
|
|
637
701
|
const prior = replicas.get(room.locator);
|
|
@@ -640,23 +704,28 @@ export function createMdRuntime(options) {
|
|
|
640
704
|
model: modelOf(room.locator),
|
|
641
705
|
selectionModel: modelOf(room.locator, 'typed-selection')
|
|
642
706
|
}, prior?.confirmed);
|
|
643
|
-
if (retained
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
replicas.set(room.locator, replicaOf(retained, prior?.queue ?? [], rewriting(), prior));
|
|
707
|
+
if (retained === null) {
|
|
708
|
+
list(room, true);
|
|
709
|
+
publishChangeSet(changeSet(room.locator, prior ?? null, prior ?? null, {
|
|
710
|
+
room: true,
|
|
711
|
+
query: true
|
|
712
|
+
}));
|
|
713
|
+
return;
|
|
651
714
|
}
|
|
652
|
-
|
|
653
|
-
publishChangeSet(changeSet(room.locator, prior ?? null, replicas.get(room.locator) ?? null, {
|
|
715
|
+
const set = await settle(local, room.locator, prior, retained, prior?.queue ?? [], {
|
|
654
716
|
room: true,
|
|
655
717
|
query: true
|
|
656
|
-
})
|
|
718
|
+
});
|
|
719
|
+
unreadable.delete(room.locator);
|
|
720
|
+
list(room, true);
|
|
721
|
+
publishChangeSet(set);
|
|
722
|
+
moveTo({
|
|
723
|
+
...status,
|
|
724
|
+
...attention()
|
|
725
|
+
});
|
|
657
726
|
});
|
|
658
727
|
}
|
|
659
|
-
async function
|
|
728
|
+
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
660
729
|
const local = await openLocal();
|
|
661
730
|
const replica = replicas.get(locator);
|
|
662
731
|
const room = rooms.get(locator);
|
|
@@ -667,6 +736,28 @@ export function createMdRuntime(options) {
|
|
|
667
736
|
outcome: 'refused',
|
|
668
737
|
rule: 'collection-root-mismatch'
|
|
669
738
|
};
|
|
739
|
+
const saved = [];
|
|
740
|
+
for (const { path, revision } of named){
|
|
741
|
+
const record = roomDrafts.get(locator)?.get(path);
|
|
742
|
+
if (record === undefined || record.revision !== revision) {
|
|
743
|
+
return {
|
|
744
|
+
outcome: 'refused',
|
|
745
|
+
rule: 'stale',
|
|
746
|
+
file: path
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
if (!saved.some((draft)=>draft.path === path)) saved.push({
|
|
750
|
+
path,
|
|
751
|
+
parts: record.parts
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
const changed = saved.flatMap(({ path, parts })=>changedParts(path, underneath(parts, path, replica.visible)));
|
|
755
|
+
if (changed.length > 0) return {
|
|
756
|
+
outcome: 'refused',
|
|
757
|
+
rule: 'changed-underneath',
|
|
758
|
+
parts: changed
|
|
759
|
+
};
|
|
760
|
+
const parts = saved.map(({ path, parts })=>changesOf(path, parts));
|
|
670
761
|
const built = buildBatch({
|
|
671
762
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
672
763
|
composition: compositionFor(replica.confirmed.record),
|
|
@@ -675,45 +766,27 @@ export function createMdRuntime(options) {
|
|
|
675
766
|
providerLimits: limits,
|
|
676
767
|
rewriting: rewriting(),
|
|
677
768
|
visible: replica.visible,
|
|
678
|
-
changes,
|
|
769
|
+
changes: parts.flatMap(({ changes })=>changes),
|
|
770
|
+
bases: parts.flatMap(({ bases })=>bases),
|
|
679
771
|
writtenBy
|
|
680
772
|
});
|
|
681
773
|
if (built.kind === 'refused') return {
|
|
682
774
|
outcome: 'refused',
|
|
683
775
|
...built.refusal
|
|
684
776
|
};
|
|
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 ? [
|
|
777
|
+
const batches = [
|
|
695
778
|
...replica.queue,
|
|
696
779
|
built.batch
|
|
697
|
-
] : [
|
|
698
|
-
built.batch,
|
|
699
|
-
...replica.queue.slice(1)
|
|
700
|
-
];
|
|
701
|
-
const consumed = [
|
|
702
|
-
...new Set(changes.map((change)=>change.path))
|
|
703
780
|
];
|
|
781
|
+
const consumed = saved.map(({ path })=>path);
|
|
704
782
|
try {
|
|
705
783
|
await local.write({
|
|
706
784
|
next: replica.confirmed.record,
|
|
707
785
|
previous: replica.confirmed.record,
|
|
708
786
|
queue: batches,
|
|
709
787
|
previousQueue: replica.queue,
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
id: replacing,
|
|
713
|
-
outcome: null
|
|
714
|
-
}
|
|
715
|
-
],
|
|
716
|
-
consumedDrafts: consumed
|
|
788
|
+
saved,
|
|
789
|
+
consumedRoomDrafts: consumed
|
|
717
790
|
});
|
|
718
791
|
} catch {
|
|
719
792
|
return {
|
|
@@ -721,8 +794,14 @@ export function createMdRuntime(options) {
|
|
|
721
794
|
rule: 'durable-write'
|
|
722
795
|
};
|
|
723
796
|
}
|
|
724
|
-
|
|
725
|
-
|
|
797
|
+
pendings.set(built.batch.id, {
|
|
798
|
+
drafts: saved,
|
|
799
|
+
submission: {
|
|
800
|
+
state: 'waiting'
|
|
801
|
+
},
|
|
802
|
+
lost: false
|
|
803
|
+
});
|
|
804
|
+
for (const path of consumed)forgetRoomDraft(locator, path);
|
|
726
805
|
replicas.set(locator, replicaOf(replica.confirmed, batches, rewriting(), replica));
|
|
727
806
|
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
728
807
|
queue: true,
|
|
@@ -735,34 +814,28 @@ export function createMdRuntime(options) {
|
|
|
735
814
|
void enqueue();
|
|
736
815
|
return {
|
|
737
816
|
outcome: 'saved',
|
|
738
|
-
|
|
817
|
+
identifier: built.batch.id
|
|
739
818
|
};
|
|
740
819
|
}
|
|
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))) {
|
|
820
|
+
async function mark(local, locator, identifier, submission, lost) {
|
|
821
|
+
const state = pendings.get(identifier);
|
|
822
|
+
if (state === undefined || JSON.stringify(state.submission) === JSON.stringify(submission) && state.lost === lost) {
|
|
746
823
|
return;
|
|
747
824
|
}
|
|
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, {
|
|
825
|
+
await local.submit(locator, identifier, submission, lost);
|
|
826
|
+
pendings.set(identifier, {
|
|
827
|
+
...state,
|
|
828
|
+
submission,
|
|
829
|
+
lost
|
|
830
|
+
});
|
|
831
|
+
const replica = replicas.get(locator);
|
|
832
|
+
if (replica !== undefined) publishChangeSet(changeSet(locator, replica, replica, {
|
|
758
833
|
queue: true
|
|
759
834
|
}));
|
|
760
835
|
moveTo({
|
|
761
836
|
...status,
|
|
762
|
-
state: pending() ? 'pending' : status.state,
|
|
763
837
|
...attention()
|
|
764
838
|
});
|
|
765
|
-
void enqueue();
|
|
766
839
|
}
|
|
767
840
|
async function readHistory(locator, path, query = {}) {
|
|
768
841
|
if (closed || refusal !== null) return {
|
|
@@ -918,36 +991,66 @@ export function createMdRuntime(options) {
|
|
|
918
991
|
await publish(local, room, bootstrapped.confirmed);
|
|
919
992
|
return null;
|
|
920
993
|
}
|
|
921
|
-
function
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
if (
|
|
994
|
+
async function withhold(withheld, room, local, progress) {
|
|
995
|
+
progress.complete = false;
|
|
996
|
+
if (withheld.kind === 'scoped-refusal') {
|
|
997
|
+
await scoped(withheld, room, local);
|
|
998
|
+
} else if (withheld.kind === 'transport-uncertainty') {
|
|
999
|
+
progress.reachable = false;
|
|
1000
|
+
} else if (withheld.kind === 'room-not-readable') {
|
|
1001
|
+
unreadable.add(room.locator);
|
|
1002
|
+
list(room, false);
|
|
1003
|
+
emitRooms();
|
|
1004
|
+
} else if (withheld.kind === 'revoked-authorization') {
|
|
1005
|
+
await refuse('revoked-authorization');
|
|
1006
|
+
return 'stop';
|
|
1007
|
+
}
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
function leaving(batch, result) {
|
|
1011
|
+
if (result.kind === 'expired') {
|
|
926
1012
|
return {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1013
|
+
batch,
|
|
1014
|
+
outcome: {
|
|
1015
|
+
kind: 'expired'
|
|
1016
|
+
},
|
|
1017
|
+
returned: {
|
|
1018
|
+
kind: 'expired'
|
|
932
1019
|
}
|
|
933
1020
|
};
|
|
934
1021
|
}
|
|
1022
|
+
const refusal = {
|
|
1023
|
+
code: result.code,
|
|
1024
|
+
fields: result.fields
|
|
1025
|
+
};
|
|
935
1026
|
if (result.kind === 'conflicted') {
|
|
936
1027
|
return {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1028
|
+
batch,
|
|
1029
|
+
outcome: {
|
|
1030
|
+
kind: 'conflicted',
|
|
1031
|
+
...result.roomVersion === undefined ? {} : {
|
|
1032
|
+
roomVersion: result.roomVersion
|
|
1033
|
+
},
|
|
1034
|
+
affected: result.affected,
|
|
1035
|
+
...refusal
|
|
940
1036
|
},
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1037
|
+
returned: {
|
|
1038
|
+
kind: 'conflicted',
|
|
1039
|
+
...refusal
|
|
1040
|
+
}
|
|
944
1041
|
};
|
|
945
1042
|
}
|
|
946
1043
|
return {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1044
|
+
batch,
|
|
1045
|
+
outcome: {
|
|
1046
|
+
kind: 'rejected',
|
|
1047
|
+
affected: result.affected,
|
|
1048
|
+
...refusal
|
|
1049
|
+
},
|
|
1050
|
+
returned: {
|
|
1051
|
+
kind: 'rejected',
|
|
1052
|
+
...refusal
|
|
1053
|
+
}
|
|
951
1054
|
};
|
|
952
1055
|
}
|
|
953
1056
|
async function commitAccept(room, local, submitted, accepted, base, overlay) {
|
|
@@ -974,28 +1077,21 @@ export function createMdRuntime(options) {
|
|
|
974
1077
|
time: accepted.time
|
|
975
1078
|
}
|
|
976
1079
|
};
|
|
977
|
-
await local.
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
id: submitted.id,
|
|
985
|
-
outcome: acceptedOutcome
|
|
986
|
-
}
|
|
987
|
-
]
|
|
1080
|
+
const set = await settle(local, room.locator, current, applied, remaining, {
|
|
1081
|
+
accepted: {
|
|
1082
|
+
id: submitted.id,
|
|
1083
|
+
outcome: acceptedOutcome
|
|
1084
|
+
},
|
|
1085
|
+
room: true,
|
|
1086
|
+
query: true
|
|
988
1087
|
});
|
|
989
|
-
acceptedIn.set(submitted.id, room.locator);
|
|
990
|
-
outcomes.set(submitted.id, acceptedOutcome);
|
|
991
1088
|
unreadable.delete(room.locator);
|
|
992
|
-
replicas.set(room.locator, replicaOf(applied, remaining, rewriting(), current));
|
|
993
1089
|
list(room, true);
|
|
994
|
-
publishChangeSet(
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
})
|
|
1090
|
+
publishChangeSet(set);
|
|
1091
|
+
moveTo({
|
|
1092
|
+
...status,
|
|
1093
|
+
...attention()
|
|
1094
|
+
});
|
|
999
1095
|
journal.record(`outcome ${room.locator} ${submitted.id} accepted ${accepted.roomVersion}` + (accepted.author === undefined ? '' : ` author ${accepted.author}`) + (accepted.time === undefined ? '' : ` time ${accepted.time}`));
|
|
1000
1096
|
return true;
|
|
1001
1097
|
}
|
|
@@ -1004,39 +1100,22 @@ export function createMdRuntime(options) {
|
|
|
1004
1100
|
const replica = replicas.get(room.locator);
|
|
1005
1101
|
const head = replica?.queue[0];
|
|
1006
1102
|
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
|
-
});
|
|
1103
|
+
const { submission, lost: earlier } = pendings.get(head.id) ?? {
|
|
1104
|
+
submission: {
|
|
1105
|
+
state: 'waiting'
|
|
1106
|
+
},
|
|
1107
|
+
lost: false
|
|
1108
|
+
};
|
|
1109
|
+
if (submission.state === 'reset' && Date.now() < Date.parse(submission.reset)) {
|
|
1110
|
+
progress.complete = false;
|
|
1111
|
+
return null;
|
|
1032
1112
|
}
|
|
1113
|
+
const lost = earlier || submission.state === 'submitted';
|
|
1033
1114
|
let result;
|
|
1034
1115
|
journal.record(`submission ${room.locator} ${head.id}`);
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
}));
|
|
1039
|
-
}
|
|
1116
|
+
await exclusive(()=>mark(local, room.locator, head.id, {
|
|
1117
|
+
state: 'submitted'
|
|
1118
|
+
}, lost));
|
|
1040
1119
|
try {
|
|
1041
1120
|
result = await options.adapter.mutate(authorized, {
|
|
1042
1121
|
locator: room.locator,
|
|
@@ -1072,29 +1151,37 @@ export function createMdRuntime(options) {
|
|
|
1072
1151
|
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired' || result.kind === 'retryable-refusal') {
|
|
1073
1152
|
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` + (result.kind === 'conflicted' && result.roomVersion !== undefined ? ` ${result.roomVersion}` : ''));
|
|
1074
1153
|
if (result.kind !== 'expired') journal.record(`refusal ${result.kind} ${result.code}`);
|
|
1075
|
-
|
|
1154
|
+
}
|
|
1155
|
+
if (result.kind === 'retryable-refusal') {
|
|
1156
|
+
const reset = result.reset;
|
|
1157
|
+
progress.complete = false;
|
|
1158
|
+
await exclusive(()=>mark(local, room.locator, head.id, reset === undefined ? {
|
|
1159
|
+
state: 'waiting'
|
|
1160
|
+
} : {
|
|
1161
|
+
state: 'reset',
|
|
1162
|
+
reset
|
|
1163
|
+
}, lost));
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
1167
|
+
const left = leaving(head, result);
|
|
1076
1168
|
await exclusive(async ()=>{
|
|
1077
1169
|
const current = replicas.get(room.locator);
|
|
1078
1170
|
if (current === undefined || !current.queue.some((batch)=>batch.id === head.id)) {
|
|
1079
1171
|
return;
|
|
1080
1172
|
}
|
|
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
|
-
}));
|
|
1173
|
+
const set = await settle(local, room.locator, current, current.confirmed, current.queue.filter((batch)=>batch.id !== head.id), {
|
|
1174
|
+
leaving: left
|
|
1175
|
+
});
|
|
1176
|
+
publishChangeSet(set);
|
|
1092
1177
|
moveTo({
|
|
1093
1178
|
...status,
|
|
1094
1179
|
...attention()
|
|
1095
1180
|
});
|
|
1096
1181
|
});
|
|
1097
|
-
|
|
1182
|
+
const withheld = await advance(room, local, authorized);
|
|
1183
|
+
if (withheld !== null) return withhold(withheld, room, local, progress);
|
|
1184
|
+
continue;
|
|
1098
1185
|
}
|
|
1099
1186
|
const from = replicas.get(room.locator)?.confirmed;
|
|
1100
1187
|
if (from === undefined) return null;
|
|
@@ -1356,25 +1443,7 @@ export function createMdRuntime(options) {
|
|
|
1356
1443
|
}
|
|
1357
1444
|
const withheld = await advance(room, local, authorization);
|
|
1358
1445
|
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
|
-
}
|
|
1446
|
+
if (await withhold(withheld, room, local, progress) === 'stop') return;
|
|
1378
1447
|
continue;
|
|
1379
1448
|
}
|
|
1380
1449
|
if (await submitRoom(room, local, authorization, progress) === 'stop') return;
|
|
@@ -1684,7 +1753,7 @@ export function createMdRuntime(options) {
|
|
|
1684
1753
|
statusListeners.clear();
|
|
1685
1754
|
querySubscriptions.clear();
|
|
1686
1755
|
queueSubscriptions.clear();
|
|
1687
|
-
|
|
1756
|
+
roomDraftListeners.clear();
|
|
1688
1757
|
presentationListeners.clear();
|
|
1689
1758
|
}
|
|
1690
1759
|
options.signal?.addEventListener('abort', close, {
|
|
@@ -1717,35 +1786,77 @@ export function createMdRuntime(options) {
|
|
|
1717
1786
|
syncStatus () {
|
|
1718
1787
|
return shown;
|
|
1719
1788
|
},
|
|
1720
|
-
|
|
1789
|
+
roomDraft: {
|
|
1721
1790
|
list () {
|
|
1722
1791
|
return exclusive(async ()=>{
|
|
1723
1792
|
await openLocal();
|
|
1724
|
-
return
|
|
1725
|
-
}).then((
|
|
1726
|
-
|
|
1727
|
-
return
|
|
1793
|
+
return snapshotRoomDrafts();
|
|
1794
|
+
}).then((listed)=>{
|
|
1795
|
+
publishRoomDrafts(true);
|
|
1796
|
+
return listed;
|
|
1728
1797
|
});
|
|
1729
1798
|
},
|
|
1730
|
-
read (
|
|
1731
|
-
const
|
|
1732
|
-
return
|
|
1799
|
+
read (room, path) {
|
|
1800
|
+
const record = roomDrafts.get(room)?.get(path);
|
|
1801
|
+
return record === undefined ? null : roomDraftOf(record);
|
|
1733
1802
|
},
|
|
1734
|
-
|
|
1735
|
-
return
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
|
|
1803
|
+
write (room, path, revision, input) {
|
|
1804
|
+
return exclusive(async ()=>{
|
|
1805
|
+
const parts = readParts(input);
|
|
1806
|
+
if (path === '' || normalize(path) !== path) throw new Error('room-draft-malformed');
|
|
1807
|
+
const local = await openLocal();
|
|
1808
|
+
const current = roomDrafts.get(room)?.get(path);
|
|
1809
|
+
if ((current?.revision ?? null) !== revision) {
|
|
1810
|
+
return {
|
|
1811
|
+
outcome: 'stale',
|
|
1812
|
+
current: current === undefined ? null : roomDraftOf(current)
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
const record = {
|
|
1816
|
+
locator: room,
|
|
1739
1817
|
path,
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1818
|
+
revision: roomDraftRevision(),
|
|
1819
|
+
parts: held(current?.parts, parts),
|
|
1820
|
+
returned: current?.returned ?? null
|
|
1821
|
+
};
|
|
1822
|
+
await local.keepRoomDraft(record);
|
|
1823
|
+
rememberRoomDraft(record);
|
|
1824
|
+
publishChangeSet(changeSet(room, null, null, {
|
|
1825
|
+
drafts: [
|
|
1826
|
+
path
|
|
1827
|
+
]
|
|
1828
|
+
}));
|
|
1829
|
+
return {
|
|
1830
|
+
outcome: 'written',
|
|
1831
|
+
roomDraft: roomDraftOf(record)
|
|
1832
|
+
};
|
|
1743
1833
|
});
|
|
1744
1834
|
},
|
|
1745
|
-
discard (
|
|
1746
|
-
return
|
|
1747
|
-
await
|
|
1748
|
-
|
|
1835
|
+
discard (room, path, revision) {
|
|
1836
|
+
return exclusive(async ()=>{
|
|
1837
|
+
const local = await openLocal();
|
|
1838
|
+
const current = roomDrafts.get(room)?.get(path);
|
|
1839
|
+
if (current === undefined || current.revision !== revision) {
|
|
1840
|
+
return {
|
|
1841
|
+
outcome: 'stale',
|
|
1842
|
+
current: current === undefined ? null : roomDraftOf(current)
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
await local.discardRoomDraft(room, path);
|
|
1846
|
+
forgetRoomDraft(room, path);
|
|
1847
|
+
publishChangeSet(changeSet(room, null, null, {
|
|
1848
|
+
drafts: [
|
|
1849
|
+
path
|
|
1850
|
+
]
|
|
1851
|
+
}));
|
|
1852
|
+
moveTo({
|
|
1853
|
+
...status,
|
|
1854
|
+
...attention()
|
|
1855
|
+
});
|
|
1856
|
+
return {
|
|
1857
|
+
outcome: 'written',
|
|
1858
|
+
roomDraft: null
|
|
1859
|
+
};
|
|
1749
1860
|
});
|
|
1750
1861
|
}
|
|
1751
1862
|
},
|
|
@@ -1773,23 +1884,11 @@ export function createMdRuntime(options) {
|
|
|
1773
1884
|
});
|
|
1774
1885
|
}
|
|
1775
1886
|
},
|
|
1776
|
-
save (
|
|
1777
|
-
return exclusive(()=>
|
|
1887
|
+
save (room, named, writtenBy) {
|
|
1888
|
+
return exclusive(()=>saveRoomDrafts(room, named, writtenBy));
|
|
1778
1889
|
},
|
|
1779
1890
|
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
|
-
}
|
|
1891
|
+
return outcomes.get(identifier) ?? null;
|
|
1793
1892
|
},
|
|
1794
1893
|
history (locator, path, query) {
|
|
1795
1894
|
return readHistory(locator, path, query);
|
|
@@ -1807,23 +1906,23 @@ export function createMdRuntime(options) {
|
|
|
1807
1906
|
return options.shell.persistentStorage;
|
|
1808
1907
|
},
|
|
1809
1908
|
observe (topic, second, third, fourth) {
|
|
1810
|
-
if (topic === 'drafts') {
|
|
1909
|
+
if (topic === 'room-drafts') {
|
|
1811
1910
|
const notify = second;
|
|
1812
1911
|
const listener = {
|
|
1813
1912
|
notify,
|
|
1814
1913
|
ready: false
|
|
1815
1914
|
};
|
|
1816
|
-
|
|
1915
|
+
roomDraftListeners.add(listener);
|
|
1817
1916
|
void exclusive(async ()=>{
|
|
1818
1917
|
await openLocal();
|
|
1819
|
-
if (closed || !
|
|
1918
|
+
if (closed || !roomDraftListeners.has(listener)) return;
|
|
1820
1919
|
listener.ready = true;
|
|
1821
1920
|
}).then(()=>{
|
|
1822
|
-
if (closed || !listener.ready || !
|
|
1823
|
-
listener.notify(
|
|
1921
|
+
if (closed || !listener.ready || !roomDraftListeners.has(listener)) return;
|
|
1922
|
+
listener.notify(snapshotRoomDrafts());
|
|
1824
1923
|
}, ()=>{});
|
|
1825
1924
|
return ()=>{
|
|
1826
|
-
|
|
1925
|
+
roomDraftListeners.delete(listener);
|
|
1827
1926
|
};
|
|
1828
1927
|
}
|
|
1829
1928
|
if (topic === 'conversation') {
|