@agentshouse/mdruntime 0.1.0-alpha.22 → 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/bootstrap.d.ts +1 -2
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +2 -2
- package/dist/bootstrap.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 +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/collection.d.ts +2 -11
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +11 -16
- package/dist/collection.js.map +1 -1
- package/dist/connection.d.ts +15 -20
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +399 -299
- 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 +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +10 -24
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -1
- package/dist/model.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/refresh.d.ts +1 -2
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +2 -2
- package/dist/refresh.js.map +1 -1
- package/dist/replica.d.ts +3 -3
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +59 -5
- 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 -4
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +41 -130
- 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 +3 -3
package/dist/connection.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { rawQuery } from './query.js';
|
|
2
|
+
import { verify } from '@agentshouse/mdmodel';
|
|
2
3
|
import { cached, modelTypes, NO_MODEL, sameModel } from './model.js';
|
|
3
4
|
import { ROOM_CONTRACT_VERSION, VERSION_1_CAPABILITIES } from './adapter.js';
|
|
4
5
|
import { bootstrapRoom } from './bootstrap.js';
|
|
@@ -6,13 +7,14 @@ import { changeSet, mergeChangeSets } from './change-set.js';
|
|
|
6
7
|
import { createConversationStore } from './conversation.js';
|
|
7
8
|
import { MD_MODEL, PRESENTED, validRoot } from './collection.js';
|
|
8
9
|
import { createLog } from './log.js';
|
|
9
|
-
import { queuedOutcome, refusalOf, refused, withheldUntilReset } from './outcome.js';
|
|
10
10
|
import { refreshRoom } from './refresh.js';
|
|
11
11
|
import { buildBatch } from './save.js';
|
|
12
|
-
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';
|
|
13
14
|
import { settled, same, saving, STARTING, transferring } from './status.js';
|
|
14
15
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
15
|
-
import {
|
|
16
|
+
import { roomDraftRevision } from './identity.js';
|
|
17
|
+
import { beneath, normalize } from './paths.js';
|
|
16
18
|
import { openRuntimeStore } from './store.js';
|
|
17
19
|
import { unconfirmed } from './transfer.js';
|
|
18
20
|
const DEFAULT_DISCOVERY_INTERVAL_MS = 60_000;
|
|
@@ -38,10 +40,11 @@ export function createMdRuntime(options) {
|
|
|
38
40
|
let opening;
|
|
39
41
|
let store;
|
|
40
42
|
const replicas = new Map();
|
|
41
|
-
const
|
|
43
|
+
const pendings = new Map();
|
|
44
|
+
const roomDrafts = new Map();
|
|
42
45
|
const presentations = new Map();
|
|
43
46
|
const outcomes = new Map();
|
|
44
|
-
const
|
|
47
|
+
const outcomeRooms = new Map();
|
|
45
48
|
let limits = [];
|
|
46
49
|
let slots = [];
|
|
47
50
|
let capabilities = [];
|
|
@@ -96,7 +99,7 @@ export function createMdRuntime(options) {
|
|
|
96
99
|
const statusListeners = new Set();
|
|
97
100
|
const querySubscriptions = new Set();
|
|
98
101
|
const queueSubscriptions = new Set();
|
|
99
|
-
const
|
|
102
|
+
const roomDraftListeners = new Set();
|
|
100
103
|
const presentationListeners = new Set();
|
|
101
104
|
let withdrawConversations;
|
|
102
105
|
let currencyQueued = false;
|
|
@@ -114,53 +117,49 @@ export function createMdRuntime(options) {
|
|
|
114
117
|
const ready = new Promise((resolve)=>{
|
|
115
118
|
notifyReady = resolve;
|
|
116
119
|
});
|
|
117
|
-
function
|
|
118
|
-
|
|
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);
|
|
119
124
|
}
|
|
120
|
-
function
|
|
121
|
-
const room =
|
|
122
|
-
room.set(path, content);
|
|
123
|
-
drafts.set(locator, room);
|
|
124
|
-
}
|
|
125
|
-
function forgetDraft(locator, path) {
|
|
126
|
-
const room = drafts.get(locator);
|
|
125
|
+
function forgetRoomDraft(locator, path) {
|
|
126
|
+
const room = roomDrafts.get(locator);
|
|
127
127
|
if (room === undefined) return;
|
|
128
128
|
room.delete(path);
|
|
129
|
-
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
|
+
});
|
|
130
140
|
}
|
|
131
|
-
function
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
content: structuredClone(content)
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
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)=>{
|
|
143
148
|
if (left.locator !== right.locator) return left.locator < right.locator ? -1 : 1;
|
|
144
149
|
if (left.path !== right.path) return left.path < right.path ? -1 : 1;
|
|
145
150
|
return 0;
|
|
146
151
|
});
|
|
147
|
-
return
|
|
152
|
+
return records.map(roomDraftOf);
|
|
148
153
|
}
|
|
149
|
-
function
|
|
154
|
+
function publishRoomDrafts(waitingOnly) {
|
|
150
155
|
if (closed) return;
|
|
151
|
-
const value =
|
|
152
|
-
for (const listener of
|
|
156
|
+
const value = snapshotRoomDrafts();
|
|
157
|
+
for (const listener of roomDraftListeners){
|
|
153
158
|
if (waitingOnly && listener.ready) continue;
|
|
154
159
|
listener.ready = true;
|
|
155
160
|
listener.notify(value);
|
|
156
161
|
}
|
|
157
162
|
}
|
|
158
|
-
function emitDrafts() {
|
|
159
|
-
publishDrafts(false);
|
|
160
|
-
}
|
|
161
|
-
function releaseWaitingDrafts() {
|
|
162
|
-
publishDrafts(true);
|
|
163
|
-
}
|
|
164
163
|
function presentationOf(app) {
|
|
165
164
|
const value = presentations.get(app);
|
|
166
165
|
return value === undefined ? null : structuredClone(value);
|
|
@@ -171,17 +170,6 @@ export function createMdRuntime(options) {
|
|
|
171
170
|
if (listener.app === app && listener.ready) listener.notify(presentationOf(app));
|
|
172
171
|
}
|
|
173
172
|
}
|
|
174
|
-
function commitDraft(locator, path, work) {
|
|
175
|
-
return exclusive(async ()=>{
|
|
176
|
-
const local = await openLocal();
|
|
177
|
-
await work(local);
|
|
178
|
-
publishChangeSet(changeSet(locator, null, null, {
|
|
179
|
-
drafts: [
|
|
180
|
-
path
|
|
181
|
-
]
|
|
182
|
-
}));
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
173
|
function pending() {
|
|
186
174
|
return [
|
|
187
175
|
...replicas.values()
|
|
@@ -189,42 +177,33 @@ export function createMdRuntime(options) {
|
|
|
189
177
|
}
|
|
190
178
|
function attention() {
|
|
191
179
|
const attentionRequired = [];
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
if (outcome.reset !== undefined && (reset === null || outcome.reset < reset)) {
|
|
199
|
-
reset = outcome.reset;
|
|
200
|
-
}
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
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;
|
|
204
186
|
attentionRequired.push({
|
|
205
|
-
locator,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
refusal: refusalOf(outcome),
|
|
209
|
-
...outcome.kind === 'conflicted' && outcome.roomVersion !== undefined ? {
|
|
210
|
-
roomVersion: outcome.roomVersion
|
|
211
|
-
} : {}
|
|
187
|
+
room: record.locator,
|
|
188
|
+
path: record.path,
|
|
189
|
+
returned: structuredClone(record.returned)
|
|
212
190
|
});
|
|
213
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
|
+
}
|
|
214
202
|
return {
|
|
215
203
|
attentionRequired,
|
|
216
204
|
reset
|
|
217
205
|
};
|
|
218
206
|
}
|
|
219
|
-
async function persistQueue(local, replica, batches, changedOutcomes = []) {
|
|
220
|
-
await local.write({
|
|
221
|
-
next: replica.confirmed.record,
|
|
222
|
-
previous: replica.confirmed.record,
|
|
223
|
-
queue: batches,
|
|
224
|
-
previousQueue: replica.queue,
|
|
225
|
-
outcomes: changedOutcomes
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
207
|
function listed() {
|
|
229
208
|
return structuredClone([
|
|
230
209
|
...rooms.values()
|
|
@@ -300,12 +279,17 @@ export function createMdRuntime(options) {
|
|
|
300
279
|
subscription.notify(visible(subscription.query));
|
|
301
280
|
}
|
|
302
281
|
}
|
|
282
|
+
function submissionOf(batch) {
|
|
283
|
+
return pendings.get(batch.id)?.submission ?? {
|
|
284
|
+
state: 'waiting'
|
|
285
|
+
};
|
|
286
|
+
}
|
|
303
287
|
function entriesOf(locator) {
|
|
304
288
|
const replica = replicas.get(locator);
|
|
305
289
|
if (replica === undefined) return [];
|
|
306
290
|
return replica.queue.map((batch)=>Object.freeze({
|
|
307
291
|
batch,
|
|
308
|
-
|
|
292
|
+
...submissionOf(batch)
|
|
309
293
|
}));
|
|
310
294
|
}
|
|
311
295
|
function emitQueue(locator) {
|
|
@@ -318,7 +302,9 @@ export function createMdRuntime(options) {
|
|
|
318
302
|
if (set.rooms.length > 0) emitRooms();
|
|
319
303
|
if (set.documents.length > 0 || set.query) emitVisible();
|
|
320
304
|
for (const locator of set.queues)emitQueue(locator);
|
|
321
|
-
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
|
+
}
|
|
322
308
|
for (const app of set.presentations)emitPresentation(app);
|
|
323
309
|
conversations.changed(set);
|
|
324
310
|
const cards = declaredCards();
|
|
@@ -449,7 +435,7 @@ export function createMdRuntime(options) {
|
|
|
449
435
|
return [
|
|
450
436
|
...new Set([
|
|
451
437
|
...rooms.keys(),
|
|
452
|
-
...
|
|
438
|
+
...roomDrafts.keys()
|
|
453
439
|
])
|
|
454
440
|
].filter((locator)=>!listed.has(locator));
|
|
455
441
|
}
|
|
@@ -458,9 +444,9 @@ export function createMdRuntime(options) {
|
|
|
458
444
|
await exclusive(async ()=>{
|
|
459
445
|
const unlisted = unlistedHeld(listed);
|
|
460
446
|
await local.dropRooms(unlisted);
|
|
461
|
-
for (const [batch, locator] of
|
|
447
|
+
for (const [batch, locator] of outcomeRooms){
|
|
462
448
|
if (listed.has(locator)) continue;
|
|
463
|
-
|
|
449
|
+
outcomeRooms.delete(batch);
|
|
464
450
|
outcomes.delete(batch);
|
|
465
451
|
}
|
|
466
452
|
const removed = unlisted.map((locator)=>changeSet(locator, replicas.get(locator) ?? null, null, {
|
|
@@ -468,14 +454,14 @@ export function createMdRuntime(options) {
|
|
|
468
454
|
queue: true,
|
|
469
455
|
query: true,
|
|
470
456
|
drafts: [
|
|
471
|
-
...
|
|
457
|
+
...roomDrafts.get(locator)?.keys() ?? []
|
|
472
458
|
]
|
|
473
459
|
}));
|
|
474
460
|
for (const locator of unlisted){
|
|
475
|
-
for (const batch of replicas.get(locator)?.queue ?? [])
|
|
461
|
+
for (const batch of replicas.get(locator)?.queue ?? [])pendings.delete(batch.id);
|
|
476
462
|
replicas.delete(locator);
|
|
477
463
|
rooms.delete(locator);
|
|
478
|
-
|
|
464
|
+
roomDrafts.delete(locator);
|
|
479
465
|
unreadable.delete(locator);
|
|
480
466
|
scopedRefusals.delete(locator);
|
|
481
467
|
for (const attached of [
|
|
@@ -516,7 +502,7 @@ export function createMdRuntime(options) {
|
|
|
516
502
|
opened.all(),
|
|
517
503
|
opened.queues(),
|
|
518
504
|
opened.outcomes(),
|
|
519
|
-
opened.
|
|
505
|
+
opened.roomDrafts(),
|
|
520
506
|
opened.presentation(),
|
|
521
507
|
opened.conversations(),
|
|
522
508
|
opened.chatDrafts(),
|
|
@@ -528,20 +514,24 @@ export function createMdRuntime(options) {
|
|
|
528
514
|
limits = declared;
|
|
529
515
|
capabilities = named;
|
|
530
516
|
slots = listedSlots;
|
|
531
|
-
|
|
532
|
-
for (const
|
|
517
|
+
roomDrafts.clear();
|
|
518
|
+
for (const record of kept)rememberRoomDraft(record);
|
|
533
519
|
presentations.clear();
|
|
534
520
|
for (const record of presented)presentations.set(record.app, record.value);
|
|
535
521
|
conversations.hydrate(held, chatDrafts);
|
|
536
522
|
const queuedBatches = new Map(queues.map((queue)=>[
|
|
537
523
|
queue.locator,
|
|
538
|
-
queue.
|
|
524
|
+
queue.pending.map(({ batch })=>batch)
|
|
539
525
|
]));
|
|
526
|
+
pendings.clear();
|
|
527
|
+
for (const queue of queues){
|
|
528
|
+
for (const { batch, state } of queue.pending)pendings.set(batch.id, state);
|
|
529
|
+
}
|
|
540
530
|
outcomes.clear();
|
|
541
|
-
|
|
531
|
+
outcomeRooms.clear();
|
|
542
532
|
for (const record of recordedOutcomes){
|
|
543
533
|
outcomes.set(record.id, record.outcome);
|
|
544
|
-
|
|
534
|
+
outcomeRooms.set(record.id, record.locator);
|
|
545
535
|
}
|
|
546
536
|
const previousReplicas = new Map(replicas);
|
|
547
537
|
replicas.clear();
|
|
@@ -589,7 +579,7 @@ export function createMdRuntime(options) {
|
|
|
589
579
|
emitVisible();
|
|
590
580
|
}
|
|
591
581
|
for (const locator of locators)emitQueue(locator);
|
|
592
|
-
|
|
582
|
+
publishRoomDrafts(false);
|
|
593
583
|
return lastCompleted;
|
|
594
584
|
}
|
|
595
585
|
async function open() {
|
|
@@ -631,6 +621,81 @@ export function createMdRuntime(options) {
|
|
|
631
621
|
mutations = next.then(()=>undefined, ()=>undefined);
|
|
632
622
|
return next;
|
|
633
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
|
+
}
|
|
634
699
|
function publish(local, room, confirmed) {
|
|
635
700
|
return exclusive(async ()=>{
|
|
636
701
|
const prior = replicas.get(room.locator);
|
|
@@ -639,23 +704,28 @@ export function createMdRuntime(options) {
|
|
|
639
704
|
model: modelOf(room.locator),
|
|
640
705
|
selectionModel: modelOf(room.locator, 'typed-selection')
|
|
641
706
|
}, prior?.confirmed);
|
|
642
|
-
if (retained
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
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;
|
|
650
714
|
}
|
|
651
|
-
|
|
652
|
-
publishChangeSet(changeSet(room.locator, prior ?? null, replicas.get(room.locator) ?? null, {
|
|
715
|
+
const set = await settle(local, room.locator, prior, retained, prior?.queue ?? [], {
|
|
653
716
|
room: true,
|
|
654
717
|
query: true
|
|
655
|
-
})
|
|
718
|
+
});
|
|
719
|
+
unreadable.delete(room.locator);
|
|
720
|
+
list(room, true);
|
|
721
|
+
publishChangeSet(set);
|
|
722
|
+
moveTo({
|
|
723
|
+
...status,
|
|
724
|
+
...attention()
|
|
725
|
+
});
|
|
656
726
|
});
|
|
657
727
|
}
|
|
658
|
-
async function
|
|
728
|
+
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
659
729
|
const local = await openLocal();
|
|
660
730
|
const replica = replicas.get(locator);
|
|
661
731
|
const room = rooms.get(locator);
|
|
@@ -666,54 +736,57 @@ export function createMdRuntime(options) {
|
|
|
666
736
|
outcome: 'refused',
|
|
667
737
|
rule: 'collection-root-mismatch'
|
|
668
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));
|
|
669
761
|
const built = buildBatch({
|
|
670
762
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
671
763
|
composition: compositionFor(replica.confirmed.record),
|
|
672
764
|
operations: room.operations,
|
|
673
|
-
binding: options.modelBinding,
|
|
674
765
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
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 {
|
|
@@ -871,7 +944,7 @@ export function createMdRuntime(options) {
|
|
|
871
944
|
}
|
|
872
945
|
async function pullRefresh(authorized, confirmed) {
|
|
873
946
|
try {
|
|
874
|
-
const result = await refreshRoom(options.adapter, authorized, confirmed
|
|
947
|
+
const result = await refreshRoom(options.adapter, authorized, confirmed);
|
|
875
948
|
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
876
949
|
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
877
950
|
return result;
|
|
@@ -885,7 +958,7 @@ export function createMdRuntime(options) {
|
|
|
885
958
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
886
959
|
collection: modelOf(room.locator),
|
|
887
960
|
selection: modelOf(room.locator, 'typed-selection')
|
|
888
|
-
}
|
|
961
|
+
});
|
|
889
962
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
890
963
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
891
964
|
return result;
|
|
@@ -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) {
|
|
@@ -962,7 +1065,7 @@ export function createMdRuntime(options) {
|
|
|
962
1065
|
selectionModel: modelOf(room.locator, 'typed-selection')
|
|
963
1066
|
}, base);
|
|
964
1067
|
if (retainedBase === null) return false;
|
|
965
|
-
const applied = overlay ? applyReturned(retainedBase, accepted.documents, accepted.revisions, accepted.removals, accepted.roomVersion
|
|
1068
|
+
const applied = overlay ? applyReturned(retainedBase, accepted.documents, accepted.revisions, accepted.removals, accepted.roomVersion) : retainedBase;
|
|
966
1069
|
if (applied === null) return false;
|
|
967
1070
|
const acceptedOutcome = {
|
|
968
1071
|
kind: 'accepted',
|
|
@@ -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;
|
|
@@ -1279,15 +1366,16 @@ export function createMdRuntime(options) {
|
|
|
1279
1366
|
});
|
|
1280
1367
|
return;
|
|
1281
1368
|
}
|
|
1282
|
-
const verified =
|
|
1283
|
-
|
|
1369
|
+
const verified = verify(answer.snapshot.definitions);
|
|
1370
|
+
if (verified.verdict === 'refuse') journal.record(`model invalid ${room.locator} ${attached} ${verified.rule} ${verified.file}`);
|
|
1371
|
+
await retainModel(local, room, attached, verified.verdict === 'accept' ? {
|
|
1284
1372
|
status: 'current',
|
|
1285
1373
|
snapshot: structuredClone(answer.snapshot),
|
|
1286
1374
|
verifiedWith: MD_MODEL,
|
|
1287
|
-
types: verified.types
|
|
1375
|
+
types: verified.model.types
|
|
1288
1376
|
} : {
|
|
1289
1377
|
status: 'unavailable',
|
|
1290
|
-
reason:
|
|
1378
|
+
reason: 'invalid'
|
|
1291
1379
|
});
|
|
1292
1380
|
}
|
|
1293
1381
|
async function scoped(result, room, local) {
|
|
@@ -1355,25 +1443,7 @@ export function createMdRuntime(options) {
|
|
|
1355
1443
|
}
|
|
1356
1444
|
const withheld = await advance(room, local, authorization);
|
|
1357
1445
|
if (withheld !== null) {
|
|
1358
|
-
progress
|
|
1359
|
-
if (withheld.kind === 'scoped-refusal') {
|
|
1360
|
-
await scoped(withheld, room, local);
|
|
1361
|
-
continue;
|
|
1362
|
-
}
|
|
1363
|
-
if (withheld.kind === 'transport-uncertainty') {
|
|
1364
|
-
progress.reachable = false;
|
|
1365
|
-
continue;
|
|
1366
|
-
}
|
|
1367
|
-
if (withheld.kind === 'room-not-readable') {
|
|
1368
|
-
unreadable.add(room.locator);
|
|
1369
|
-
list(room, false);
|
|
1370
|
-
emitRooms();
|
|
1371
|
-
continue;
|
|
1372
|
-
}
|
|
1373
|
-
if (withheld.kind === 'revoked-authorization') {
|
|
1374
|
-
await refuse('revoked-authorization');
|
|
1375
|
-
return;
|
|
1376
|
-
}
|
|
1446
|
+
if (await withhold(withheld, room, local, progress) === 'stop') return;
|
|
1377
1447
|
continue;
|
|
1378
1448
|
}
|
|
1379
1449
|
if (await submitRoom(room, local, authorization, progress) === 'stop') return;
|
|
@@ -1683,7 +1753,7 @@ export function createMdRuntime(options) {
|
|
|
1683
1753
|
statusListeners.clear();
|
|
1684
1754
|
querySubscriptions.clear();
|
|
1685
1755
|
queueSubscriptions.clear();
|
|
1686
|
-
|
|
1756
|
+
roomDraftListeners.clear();
|
|
1687
1757
|
presentationListeners.clear();
|
|
1688
1758
|
}
|
|
1689
1759
|
options.signal?.addEventListener('abort', close, {
|
|
@@ -1716,35 +1786,77 @@ export function createMdRuntime(options) {
|
|
|
1716
1786
|
syncStatus () {
|
|
1717
1787
|
return shown;
|
|
1718
1788
|
},
|
|
1719
|
-
|
|
1789
|
+
roomDraft: {
|
|
1720
1790
|
list () {
|
|
1721
1791
|
return exclusive(async ()=>{
|
|
1722
1792
|
await openLocal();
|
|
1723
|
-
return
|
|
1724
|
-
}).then((
|
|
1725
|
-
|
|
1726
|
-
return
|
|
1793
|
+
return snapshotRoomDrafts();
|
|
1794
|
+
}).then((listed)=>{
|
|
1795
|
+
publishRoomDrafts(true);
|
|
1796
|
+
return listed;
|
|
1727
1797
|
});
|
|
1728
1798
|
},
|
|
1729
|
-
read (
|
|
1730
|
-
const
|
|
1731
|
-
return
|
|
1799
|
+
read (room, path) {
|
|
1800
|
+
const record = roomDrafts.get(room)?.get(path);
|
|
1801
|
+
return record === undefined ? null : roomDraftOf(record);
|
|
1732
1802
|
},
|
|
1733
|
-
|
|
1734
|
-
return
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1737
|
-
|
|
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,
|
|
1738
1817
|
path,
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
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
|
+
};
|
|
1742
1833
|
});
|
|
1743
1834
|
},
|
|
1744
|
-
discard (
|
|
1745
|
-
return
|
|
1746
|
-
await
|
|
1747
|
-
|
|
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
|
+
};
|
|
1748
1860
|
});
|
|
1749
1861
|
}
|
|
1750
1862
|
},
|
|
@@ -1772,23 +1884,11 @@ export function createMdRuntime(options) {
|
|
|
1772
1884
|
});
|
|
1773
1885
|
}
|
|
1774
1886
|
},
|
|
1775
|
-
save (
|
|
1776
|
-
return exclusive(()=>
|
|
1887
|
+
save (room, named, writtenBy) {
|
|
1888
|
+
return exclusive(()=>saveRoomDrafts(room, named, writtenBy));
|
|
1777
1889
|
},
|
|
1778
1890
|
outcome (identifier) {
|
|
1779
|
-
|
|
1780
|
-
if (recorded !== undefined) return recorded;
|
|
1781
|
-
for (const replica of replicas.values()){
|
|
1782
|
-
if (replica.queue.some((batch)=>batch.id === identifier)) return {
|
|
1783
|
-
kind: 'pending'
|
|
1784
|
-
};
|
|
1785
|
-
}
|
|
1786
|
-
return null;
|
|
1787
|
-
},
|
|
1788
|
-
queue: {
|
|
1789
|
-
discard (locator, identifier) {
|
|
1790
|
-
return exclusive(()=>discardHead(locator, identifier));
|
|
1791
|
-
}
|
|
1891
|
+
return outcomes.get(identifier) ?? null;
|
|
1792
1892
|
},
|
|
1793
1893
|
history (locator, path, query) {
|
|
1794
1894
|
return readHistory(locator, path, query);
|
|
@@ -1806,23 +1906,23 @@ export function createMdRuntime(options) {
|
|
|
1806
1906
|
return options.shell.persistentStorage;
|
|
1807
1907
|
},
|
|
1808
1908
|
observe (topic, second, third, fourth) {
|
|
1809
|
-
if (topic === 'drafts') {
|
|
1909
|
+
if (topic === 'room-drafts') {
|
|
1810
1910
|
const notify = second;
|
|
1811
1911
|
const listener = {
|
|
1812
1912
|
notify,
|
|
1813
1913
|
ready: false
|
|
1814
1914
|
};
|
|
1815
|
-
|
|
1915
|
+
roomDraftListeners.add(listener);
|
|
1816
1916
|
void exclusive(async ()=>{
|
|
1817
1917
|
await openLocal();
|
|
1818
|
-
if (closed || !
|
|
1918
|
+
if (closed || !roomDraftListeners.has(listener)) return;
|
|
1819
1919
|
listener.ready = true;
|
|
1820
1920
|
}).then(()=>{
|
|
1821
|
-
if (closed || !listener.ready || !
|
|
1822
|
-
listener.notify(
|
|
1921
|
+
if (closed || !listener.ready || !roomDraftListeners.has(listener)) return;
|
|
1922
|
+
listener.notify(snapshotRoomDrafts());
|
|
1823
1923
|
}, ()=>{});
|
|
1824
1924
|
return ()=>{
|
|
1825
|
-
|
|
1925
|
+
roomDraftListeners.delete(listener);
|
|
1826
1926
|
};
|
|
1827
1927
|
}
|
|
1828
1928
|
if (topic === 'conversation') {
|