@agentshouse/mdruntime 0.1.0-c6e4c19c13638 → 0.1.0-c70658e32ec08
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 +149 -91
- package/README.md +20 -10
- package/dist/adapter.d.ts +47 -14
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +15 -2
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -0
- package/dist/binary.d.ts +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +17 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +88 -38
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +19 -12
- package/dist/change.d.ts +15 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +26 -1
- package/dist/chat/acts.d.ts +34 -0
- package/dist/chat/acts.d.ts.map +1 -0
- package/dist/chat/acts.js +36 -0
- package/dist/chat/context.d.ts +3 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +10 -0
- package/dist/chat/input.d.ts +9 -0
- package/dist/chat/input.d.ts.map +1 -0
- package/dist/chat/input.js +99 -0
- package/dist/chat/kit.d.ts +60 -0
- package/dist/chat/kit.d.ts.map +1 -0
- package/dist/chat/kit.js +123 -0
- package/dist/chat/lasting.d.ts +6 -0
- package/dist/chat/lasting.d.ts.map +1 -0
- package/dist/chat/lasting.js +19 -0
- package/dist/chat/list.d.ts +26 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +62 -0
- package/dist/chat/markdown.d.ts +20 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +188 -0
- package/dist/chat/message.d.ts +6 -0
- package/dist/chat/message.d.ts.map +1 -0
- package/dist/chat/message.js +192 -0
- package/dist/chat/messages.d.ts +21 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +151 -0
- package/dist/chat/name.d.ts +6 -0
- package/dist/chat/name.d.ts.map +1 -0
- package/dist/chat/name.js +37 -0
- package/dist/chat/opened.d.ts +11 -0
- package/dist/chat/opened.d.ts.map +1 -0
- package/dist/chat/opened.js +17 -0
- package/dist/chat/overlay.d.ts +31 -0
- package/dist/chat/overlay.d.ts.map +1 -0
- package/dist/chat/overlay.js +97 -0
- package/dist/chat/parts.d.ts +57 -0
- package/dist/chat/parts.d.ts.map +1 -0
- package/dist/chat/parts.js +119 -0
- package/dist/chat/process.d.ts +20 -0
- package/dist/chat/process.d.ts.map +1 -0
- package/dist/chat/process.js +32 -0
- package/dist/chat/provider.d.ts +63 -0
- package/dist/chat/provider.d.ts.map +1 -0
- package/dist/chat/provider.js +645 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +80 -0
- package/dist/chat/questions.d.ts +31 -0
- package/dist/chat/questions.d.ts.map +1 -0
- package/dist/chat/questions.js +111 -0
- package/dist/chat/read.d.ts +5 -0
- package/dist/chat/read.d.ts.map +1 -0
- package/dist/chat/read.js +13 -0
- package/dist/chat/ready.d.ts +33 -0
- package/dist/chat/ready.d.ts.map +1 -0
- package/dist/chat/ready.js +91 -0
- package/dist/chat/style.d.ts +4 -0
- package/dist/chat/style.d.ts.map +1 -0
- package/dist/chat/style.js +302 -0
- package/dist/chat.d.ts +14 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +8 -0
- package/dist/client.d.ts +14 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +65 -15
- package/dist/collection.d.ts +6 -11
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +56 -19
- package/dist/connection.d.ts +21 -9
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +469 -142
- package/dist/conversation.d.ts +11 -3
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +47 -7
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/host.d.ts +2 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +12 -11
- package/dist/identity.d.ts +3 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +13 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/memory.d.ts +4 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +60 -20
- package/dist/model.d.ts +10 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +2 -0
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/outcome.d.ts +28 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +41 -1
- package/dist/overlay.d.ts +14 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +75 -37
- package/dist/query.d.ts +4 -0
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +5 -31
- package/dist/reach.d.ts +2 -0
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +43 -19
- package/dist/react.d.ts +26 -16
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +10 -8
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +9 -2
- package/dist/refresh.d.ts +10 -0
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +42 -38
- package/dist/replica.d.ts +8 -5
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +76 -56
- package/dist/room-draft.d.ts +26 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +132 -23
- package/dist/rows.d.ts +5 -1
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +10 -2
- package/dist/save.d.ts +26 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +345 -37
- package/dist/search.d.ts +3 -2
- package/dist/search.d.ts.map +1 -1
- package/dist/status.d.ts +2 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +36 -10
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +310 -164
- package/dist/wire.d.ts +7 -3
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +15 -2
- package/package.json +22 -14
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
package/dist/connection.js
CHANGED
|
@@ -3,17 +3,22 @@ import { cached, modelTypes, NO_MODEL, sameModel, } from './model.js';
|
|
|
3
3
|
import { observing } from './observation.js';
|
|
4
4
|
import { filterOf, identityOf, planOf, } from './query.js';
|
|
5
5
|
import { createReadSide } from './read-side.js';
|
|
6
|
+
import { markdownPath, roomOfKey } from './rows.js';
|
|
6
7
|
import { createSearchIndex, searchPlanOf, } from './search.js';
|
|
7
8
|
import { bootstrapRoom } from './bootstrap.js';
|
|
8
9
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
9
10
|
import { createConversationStore, } from './conversation.js';
|
|
10
|
-
import {
|
|
11
|
+
import { queuedFile } from './change.js';
|
|
12
|
+
import { validRoot } from './collection.js';
|
|
13
|
+
import { runtimeError } from './failure.js';
|
|
11
14
|
import { createLog } from './log.js';
|
|
15
|
+
import { binaryRecord } from './binary.js';
|
|
16
|
+
import { absentBinary, binaryAnswer, calledResult, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
|
|
12
17
|
import { refreshRoom } from './refresh.js';
|
|
13
|
-
import { buildBatch } from './save.js';
|
|
14
|
-
import { applyReturned, compositionOf,
|
|
15
|
-
import { changedParts, changesOf, held, readParts, returnedInto, underneath, } from './room-draft.js';
|
|
16
|
-
import {
|
|
18
|
+
import { buildBatch, joined, limitedIn, requeued } from './save.js';
|
|
19
|
+
import { applyReturned, compositionOf, confirmedOf, landed, readReclassified, replicaOf, sameMembership, withdrawals, } from './replica.js';
|
|
20
|
+
import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
|
|
21
|
+
import { same, saving, STARTING, transferring, } from './status.js';
|
|
17
22
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
18
23
|
import { roomDraftRevision } from './identity.js';
|
|
19
24
|
import { beneath, normalize } from './paths.js';
|
|
@@ -34,16 +39,19 @@ function declaredSelection(room) {
|
|
|
34
39
|
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
35
40
|
}
|
|
36
41
|
export function composeRuntime(options) {
|
|
42
|
+
const localSaves = options.adapter.localSaves === true;
|
|
37
43
|
let authorization = options.authorization;
|
|
38
44
|
let opening;
|
|
39
45
|
let store;
|
|
40
46
|
const replicas = new Map();
|
|
47
|
+
const queuesWithoutReplica = new Map();
|
|
41
48
|
const pendings = new Map();
|
|
42
49
|
const roomDrafts = new Map();
|
|
43
50
|
const presentations = new Map();
|
|
44
51
|
let limits = [];
|
|
45
52
|
let capabilities = [];
|
|
46
53
|
const rooms = new Map();
|
|
54
|
+
const counted = new Map();
|
|
47
55
|
const unreadable = new Set();
|
|
48
56
|
const models = new Map();
|
|
49
57
|
const liveModels = new Map();
|
|
@@ -82,6 +90,8 @@ export function composeRuntime(options) {
|
|
|
82
90
|
let withdrawStream;
|
|
83
91
|
const announced = new Map();
|
|
84
92
|
let underway = null;
|
|
93
|
+
let streamed = null;
|
|
94
|
+
let handed = false;
|
|
85
95
|
const submitting = new Map();
|
|
86
96
|
const answering = new Map();
|
|
87
97
|
let complete = false;
|
|
@@ -104,9 +114,14 @@ export function composeRuntime(options) {
|
|
|
104
114
|
let haltedRefusal = false;
|
|
105
115
|
let openSignals = 0;
|
|
106
116
|
const serviceModeListeners = new Set();
|
|
117
|
+
let member = null;
|
|
118
|
+
const memberListeners = new Set();
|
|
119
|
+
const materialized = new Set();
|
|
120
|
+
let everyRoom = false;
|
|
121
|
+
const transfers = new Map();
|
|
107
122
|
const conversations = createConversationStore({
|
|
108
123
|
agents: declaredAgents,
|
|
109
|
-
documents: (locator) =>
|
|
124
|
+
documents: (locator) => rowsQuery(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
110
125
|
call: (name, input) => invokeAction(name, input),
|
|
111
126
|
durably,
|
|
112
127
|
stream: () => options.adapter.conversations,
|
|
@@ -182,7 +197,9 @@ export function composeRuntime(options) {
|
|
|
182
197
|
}
|
|
183
198
|
}
|
|
184
199
|
function pending() {
|
|
185
|
-
return
|
|
200
|
+
return (!localSaves &&
|
|
201
|
+
(queuesWithoutReplica.size > 0 ||
|
|
202
|
+
[...replicas.values()].some((replica) => replica.queue.length > 0)));
|
|
186
203
|
}
|
|
187
204
|
function attention() {
|
|
188
205
|
const attentionRequired = [];
|
|
@@ -211,8 +228,23 @@ export function composeRuntime(options) {
|
|
|
211
228
|
}
|
|
212
229
|
return { attentionRequired, reset };
|
|
213
230
|
}
|
|
231
|
+
function documentsIn(locator) {
|
|
232
|
+
const replica = replicas.get(locator);
|
|
233
|
+
if (replica === undefined)
|
|
234
|
+
return 0;
|
|
235
|
+
const roots = replica.confirmed.record.typedSelection?.roots ?? [];
|
|
236
|
+
const paths = [
|
|
237
|
+
...Object.keys(replica.visible.documents).filter(markdownPath),
|
|
238
|
+
...Object.keys(replica.visible.binaries),
|
|
239
|
+
];
|
|
240
|
+
return paths.filter((path) => !roots.some((root) => beneath(root, path))).length;
|
|
241
|
+
}
|
|
214
242
|
function listed() {
|
|
215
|
-
return structuredClone([...rooms.values()])
|
|
243
|
+
return structuredClone([...rooms.values()].map((room) => {
|
|
244
|
+
const documents = documentsIn(room.locator);
|
|
245
|
+
counted.set(room.locator, documents);
|
|
246
|
+
return { ...room, documents };
|
|
247
|
+
}));
|
|
216
248
|
}
|
|
217
249
|
function roomFacts(reach = null) {
|
|
218
250
|
const held = [...rooms.values()]
|
|
@@ -244,6 +276,10 @@ export function composeRuntime(options) {
|
|
|
244
276
|
for (const listener of storageLossListeners)
|
|
245
277
|
listener(storageLoss);
|
|
246
278
|
}
|
|
279
|
+
function emitMember() {
|
|
280
|
+
for (const listener of memberListeners)
|
|
281
|
+
listener(member);
|
|
282
|
+
}
|
|
247
283
|
function moveTo(next) {
|
|
248
284
|
if (closed)
|
|
249
285
|
return;
|
|
@@ -263,6 +299,8 @@ export function composeRuntime(options) {
|
|
|
263
299
|
listener(shown);
|
|
264
300
|
}
|
|
265
301
|
function entryOf(batch) {
|
|
302
|
+
if (localSaves)
|
|
303
|
+
return Object.freeze({ batch, state: 'local' });
|
|
266
304
|
const state = pendings.get(batch.id);
|
|
267
305
|
if (state?.accepted !== undefined) {
|
|
268
306
|
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
@@ -270,10 +308,7 @@ export function composeRuntime(options) {
|
|
|
270
308
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
271
309
|
}
|
|
272
310
|
function entriesOf(locator) {
|
|
273
|
-
|
|
274
|
-
if (replica === undefined)
|
|
275
|
-
return [];
|
|
276
|
-
return replica.queue.map(entryOf);
|
|
311
|
+
return (replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []).map(entryOf);
|
|
277
312
|
}
|
|
278
313
|
function emitQueue(locator) {
|
|
279
314
|
const entries = entriesOf(locator);
|
|
@@ -283,12 +318,47 @@ export function composeRuntime(options) {
|
|
|
283
318
|
}
|
|
284
319
|
}
|
|
285
320
|
function applyChangeSet(set) {
|
|
321
|
+
const held = everyRoom
|
|
322
|
+
? set
|
|
323
|
+
: { ...set, documents: set.documents.filter(({ locator }) => materialized.has(locator)) };
|
|
324
|
+
readSide.apply(held);
|
|
325
|
+
searchIndex.changed(held);
|
|
326
|
+
}
|
|
327
|
+
function materialize(locators) {
|
|
328
|
+
if (everyRoom)
|
|
329
|
+
return;
|
|
330
|
+
if (locators === null)
|
|
331
|
+
everyRoom = true;
|
|
332
|
+
const sets = [];
|
|
333
|
+
for (const locator of locators ?? [...replicas.keys()]) {
|
|
334
|
+
if (materialized.has(locator))
|
|
335
|
+
continue;
|
|
336
|
+
materialized.add(locator);
|
|
337
|
+
const replica = replicas.get(locator);
|
|
338
|
+
if (replica !== undefined)
|
|
339
|
+
sets.push(changeSet(locator, null, replica));
|
|
340
|
+
}
|
|
341
|
+
if (sets.length === 0)
|
|
342
|
+
return;
|
|
343
|
+
const set = mergeChangeSets(sets);
|
|
286
344
|
readSide.apply(set);
|
|
287
345
|
searchIndex.changed(set);
|
|
288
346
|
}
|
|
347
|
+
function reachedRooms(plan) {
|
|
348
|
+
if (plan.rooms !== null)
|
|
349
|
+
return plan.rooms;
|
|
350
|
+
if (plan.reach === null)
|
|
351
|
+
return null;
|
|
352
|
+
return [...plan.reach.rooms, ...plan.reach.documents.map(roomOfKey)];
|
|
353
|
+
}
|
|
354
|
+
function rowsQuery(plan) {
|
|
355
|
+
materialize(reachedRooms(plan));
|
|
356
|
+
return readSide.query(plan);
|
|
357
|
+
}
|
|
289
358
|
function publishChangeSet(set) {
|
|
290
359
|
applyChangeSet(set);
|
|
291
|
-
if (set.rooms.length > 0
|
|
360
|
+
if (set.rooms.length > 0 ||
|
|
361
|
+
[...new Set(set.documents.map(({ locator }) => locator))].some((locator) => rooms.has(locator) && counted.get(locator) !== documentsIn(locator)))
|
|
292
362
|
emitRooms();
|
|
293
363
|
for (const locator of set.queues)
|
|
294
364
|
emitQueue(locator);
|
|
@@ -335,10 +405,7 @@ export function composeRuntime(options) {
|
|
|
335
405
|
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
336
406
|
try {
|
|
337
407
|
const local = await openLocal();
|
|
338
|
-
await local.revokeModels([...replicas.values()].map((replica) =>
|
|
339
|
-
record: replica.confirmed.record,
|
|
340
|
-
queue: replica.queue,
|
|
341
|
-
})));
|
|
408
|
+
await local.revokeModels([...replicas.values()].map((replica) => replica.confirmed.record));
|
|
342
409
|
}
|
|
343
410
|
catch {
|
|
344
411
|
journal.record('model revocation persistence failed');
|
|
@@ -362,10 +429,7 @@ export function composeRuntime(options) {
|
|
|
362
429
|
model: revoked,
|
|
363
430
|
selectionModel: revoked,
|
|
364
431
|
};
|
|
365
|
-
|
|
366
|
-
replicas.set(locator, read.kind === 'staged'
|
|
367
|
-
? replicaOf(read.confirmed, before.queue, before)
|
|
368
|
-
: { ...before, confirmed: { ...before.confirmed, record } });
|
|
432
|
+
replicas.set(locator, replicaOf(confirmedOf(record, before.confirmed), before.queue, before));
|
|
369
433
|
}
|
|
370
434
|
rooms.set(locator, {
|
|
371
435
|
...room,
|
|
@@ -382,7 +446,8 @@ export function composeRuntime(options) {
|
|
|
382
446
|
}
|
|
383
447
|
finally {
|
|
384
448
|
refusal = {
|
|
385
|
-
reason,
|
|
449
|
+
code: reason,
|
|
450
|
+
retryable: false,
|
|
386
451
|
reauthorizationOffered: reason === 'revoked-authorization',
|
|
387
452
|
refusal: provider,
|
|
388
453
|
};
|
|
@@ -407,7 +472,6 @@ export function composeRuntime(options) {
|
|
|
407
472
|
},
|
|
408
473
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
409
474
|
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
410
|
-
attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
|
|
411
475
|
synchronizationAvailable,
|
|
412
476
|
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
413
477
|
});
|
|
@@ -432,7 +496,14 @@ export function composeRuntime(options) {
|
|
|
432
496
|
emitRooms();
|
|
433
497
|
}
|
|
434
498
|
function unlistedHeld(listed) {
|
|
435
|
-
return [
|
|
499
|
+
return [
|
|
500
|
+
...new Set([
|
|
501
|
+
...rooms.keys(),
|
|
502
|
+
...roomDrafts.keys(),
|
|
503
|
+
...transfers.keys(),
|
|
504
|
+
...queuesWithoutReplica.keys(),
|
|
505
|
+
]),
|
|
506
|
+
].filter((locator) => !listed.has(locator));
|
|
436
507
|
}
|
|
437
508
|
async function dropUnlisted(listed, local) {
|
|
438
509
|
if (unlistedHeld(listed).length === 0)
|
|
@@ -446,11 +517,13 @@ export function composeRuntime(options) {
|
|
|
446
517
|
drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
|
|
447
518
|
}));
|
|
448
519
|
for (const locator of unlisted) {
|
|
449
|
-
for (const batch of replicas.get(locator)?.queue ?? [])
|
|
520
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
450
521
|
pendings.delete(batch.id);
|
|
451
522
|
replicas.delete(locator);
|
|
523
|
+
queuesWithoutReplica.delete(locator);
|
|
452
524
|
rooms.delete(locator);
|
|
453
525
|
roomDrafts.delete(locator);
|
|
526
|
+
transfers.delete(locator);
|
|
454
527
|
unreadable.delete(locator);
|
|
455
528
|
stale.delete(locator);
|
|
456
529
|
scopedRefusals.delete(locator);
|
|
@@ -487,7 +560,7 @@ export function composeRuntime(options) {
|
|
|
487
560
|
return model;
|
|
488
561
|
}
|
|
489
562
|
async function hydrate(opened) {
|
|
490
|
-
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named] = await Promise.all([
|
|
563
|
+
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known, staged,] = await Promise.all([
|
|
491
564
|
opened.all(),
|
|
492
565
|
opened.queues(),
|
|
493
566
|
opened.roomDrafts(),
|
|
@@ -497,9 +570,15 @@ export function composeRuntime(options) {
|
|
|
497
570
|
opened.lastCompleted(),
|
|
498
571
|
opened.providerLimits(),
|
|
499
572
|
opened.capabilities(),
|
|
573
|
+
opened.member(),
|
|
574
|
+
opened.transfers(),
|
|
500
575
|
]);
|
|
576
|
+
transfers.clear();
|
|
577
|
+
for (const record of staged)
|
|
578
|
+
transfers.set(record.locator, record);
|
|
501
579
|
limits = declared;
|
|
502
580
|
capabilities = named;
|
|
581
|
+
member = known;
|
|
503
582
|
roomDrafts.clear();
|
|
504
583
|
for (const record of kept)
|
|
505
584
|
rememberRoomDraft(record);
|
|
@@ -515,18 +594,17 @@ export function composeRuntime(options) {
|
|
|
515
594
|
}
|
|
516
595
|
const previousReplicas = new Map(replicas);
|
|
517
596
|
replicas.clear();
|
|
597
|
+
queuesWithoutReplica.clear();
|
|
598
|
+
const replicated = new Set(records.map((record) => record.locator));
|
|
599
|
+
for (const [locator, queue] of queuedBatches) {
|
|
600
|
+
if (!replicated.has(locator))
|
|
601
|
+
queuesWithoutReplica.set(locator, queue);
|
|
602
|
+
}
|
|
518
603
|
for (const record of records) {
|
|
519
604
|
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
520
605
|
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
521
606
|
const prior = previousReplicas.get(record.locator);
|
|
522
|
-
const
|
|
523
|
-
const read = readConfirmed(retyped, prior?.confirmed);
|
|
524
|
-
const confirmed = read.kind === 'staged'
|
|
525
|
-
? read.confirmed
|
|
526
|
-
: {
|
|
527
|
-
record: retyped,
|
|
528
|
-
collection: parseCollection(retyped.documents, compositionOf(retyped)),
|
|
529
|
-
};
|
|
607
|
+
const confirmed = confirmedOf({ ...record, model, selectionModel }, prior?.confirmed);
|
|
530
608
|
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
531
609
|
const existing = rooms.get(record.locator);
|
|
532
610
|
if (existing === undefined)
|
|
@@ -535,7 +613,6 @@ export function composeRuntime(options) {
|
|
|
535
613
|
rooms.set(record.locator, {
|
|
536
614
|
...existing,
|
|
537
615
|
roomVersion: record.roomVersion,
|
|
538
|
-
attachments: record.attachments,
|
|
539
616
|
operations: record.operations,
|
|
540
617
|
replica: record.replica,
|
|
541
618
|
model: modelTypes(model),
|
|
@@ -547,10 +624,6 @@ export function composeRuntime(options) {
|
|
|
547
624
|
},
|
|
548
625
|
});
|
|
549
626
|
}
|
|
550
|
-
if (read.kind === 'refused') {
|
|
551
|
-
refusedSource(record, read);
|
|
552
|
-
declarationRefusals.add(record.locator);
|
|
553
|
-
}
|
|
554
627
|
}
|
|
555
628
|
return {
|
|
556
629
|
lastCompleted,
|
|
@@ -563,6 +636,8 @@ export function composeRuntime(options) {
|
|
|
563
636
|
applyChangeSet(read);
|
|
564
637
|
if (replicas.size > 0)
|
|
565
638
|
emitRooms();
|
|
639
|
+
if (member !== null)
|
|
640
|
+
emitMember();
|
|
566
641
|
for (const locator of locators)
|
|
567
642
|
emitQueue(locator);
|
|
568
643
|
publishRoomDrafts(false);
|
|
@@ -610,10 +685,15 @@ export function composeRuntime(options) {
|
|
|
610
685
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
611
686
|
}
|
|
612
687
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
613
|
-
const
|
|
688
|
+
const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
689
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
|
|
614
690
|
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
691
|
+
const unqueued = withdrawn.map((batch) => ({
|
|
692
|
+
batch,
|
|
693
|
+
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
694
|
+
}));
|
|
615
695
|
const returning = [
|
|
616
|
-
...
|
|
696
|
+
...[...unqueued].reverse(),
|
|
617
697
|
...(settling.leaving === undefined ? [] : [settling.leaving]),
|
|
618
698
|
];
|
|
619
699
|
const written = new Map();
|
|
@@ -631,25 +711,44 @@ export function composeRuntime(options) {
|
|
|
631
711
|
});
|
|
632
712
|
}
|
|
633
713
|
}
|
|
634
|
-
await local.
|
|
635
|
-
|
|
636
|
-
previous: before?.confirmed.record ?? null,
|
|
637
|
-
queue: kept,
|
|
638
|
-
previousQueue: before?.queue ?? [],
|
|
639
|
-
roomDrafts: [...written.values()],
|
|
640
|
-
});
|
|
641
|
-
for (const batch of before?.queue ?? []) {
|
|
714
|
+
await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
|
|
715
|
+
for (const batch of previousQueue) {
|
|
642
716
|
if (!kept.includes(batch))
|
|
643
717
|
pendings.delete(batch.id);
|
|
644
718
|
}
|
|
645
719
|
for (const record of written.values())
|
|
646
720
|
rememberRoomDraft(record);
|
|
647
|
-
|
|
648
|
-
|
|
721
|
+
try {
|
|
722
|
+
await local.write({
|
|
723
|
+
next: confirmed.record,
|
|
724
|
+
previous: before?.confirmed.record ?? null,
|
|
725
|
+
...(settling.stored === undefined ? {} : { stored: settling.stored }),
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
catch (error) {
|
|
729
|
+
const held = before === undefined ? null : replicaOf(before.confirmed, kept, before);
|
|
730
|
+
if (held !== null)
|
|
731
|
+
replicas.set(locator, held);
|
|
732
|
+
else if (kept.length > 0)
|
|
733
|
+
queuesWithoutReplica.set(locator, kept);
|
|
734
|
+
else
|
|
735
|
+
queuesWithoutReplica.delete(locator);
|
|
736
|
+
publishChangeSet(changeSet(locator, before ?? null, held, {
|
|
737
|
+
queue: kept.length !== previousQueue.length,
|
|
738
|
+
drafts: [...written.keys()],
|
|
739
|
+
}));
|
|
740
|
+
moveTo({ ...status, ...attention() });
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
743
|
+
queuesWithoutReplica.delete(locator);
|
|
744
|
+
for (const { batch, returned } of unqueued) {
|
|
745
|
+
const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
|
|
746
|
+
journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
|
|
747
|
+
}
|
|
649
748
|
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
650
749
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
651
750
|
room: settling.room,
|
|
652
|
-
queue: kept.length !==
|
|
751
|
+
queue: kept.length !== previousQueue.length,
|
|
653
752
|
drafts: [...written.keys()],
|
|
654
753
|
});
|
|
655
754
|
}
|
|
@@ -684,7 +783,8 @@ export function composeRuntime(options) {
|
|
|
684
783
|
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
685
784
|
};
|
|
686
785
|
}
|
|
687
|
-
function publish(local, room, confirmed,
|
|
786
|
+
function publish(local, room, confirmed, landing = {}) {
|
|
787
|
+
const { base, stored } = landing;
|
|
688
788
|
return exclusive(async () => {
|
|
689
789
|
if (!continuing())
|
|
690
790
|
return true;
|
|
@@ -695,13 +795,15 @@ export function composeRuntime(options) {
|
|
|
695
795
|
return false;
|
|
696
796
|
}
|
|
697
797
|
adoptModels(room.locator, confirmed.record);
|
|
698
|
-
const queue = prior?.queue ?? [];
|
|
798
|
+
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
699
799
|
const roomVersion = confirmed.record.roomVersion;
|
|
700
800
|
const accepted = queue.filter((batch) => {
|
|
701
801
|
const accepted = pendings.get(batch.id)?.accepted;
|
|
702
802
|
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
703
803
|
});
|
|
704
|
-
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true });
|
|
804
|
+
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true, ...(stored === undefined ? {} : { stored }) });
|
|
805
|
+
if (stored !== undefined)
|
|
806
|
+
transfers.delete(room.locator);
|
|
705
807
|
for (const batch of accepted)
|
|
706
808
|
pendings.delete(batch.id);
|
|
707
809
|
stale.delete(room.locator);
|
|
@@ -720,22 +822,40 @@ export function composeRuntime(options) {
|
|
|
720
822
|
const replica = replicas.get(locator);
|
|
721
823
|
const room = rooms.get(locator);
|
|
722
824
|
if (replica === undefined || room === undefined) {
|
|
723
|
-
throw
|
|
825
|
+
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
724
826
|
}
|
|
725
827
|
const saved = [];
|
|
726
828
|
for (const { path, revision } of named) {
|
|
727
829
|
const record = roomDrafts.get(locator)?.get(path);
|
|
728
830
|
if (record === undefined || record.revision !== revision) {
|
|
729
|
-
return { outcome: 'refused',
|
|
831
|
+
return { outcome: 'refused', code: 'stale', retryable: false, file: path };
|
|
730
832
|
}
|
|
731
833
|
if (!saved.some((draft) => draft.path === path))
|
|
732
834
|
saved.push({ path, parts: record.parts });
|
|
733
835
|
}
|
|
734
836
|
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
735
837
|
if (changed.length > 0)
|
|
736
|
-
return { outcome: 'refused',
|
|
838
|
+
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
737
839
|
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
738
|
-
const
|
|
840
|
+
const changes = parts.flatMap(({ changes }) => changes);
|
|
841
|
+
const requeue = requeued(replica.queue, changes, replica.visible, withdrawable, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
842
|
+
if (requeue.kind === 'refused')
|
|
843
|
+
return { outcome: 'refused', ...requeue.refusal };
|
|
844
|
+
const foreign = [...requeue.withdrawn.keys()].some((origin) => {
|
|
845
|
+
const writer = replica.queue.find((batch) => batch.id === origin)?.writtenBy;
|
|
846
|
+
return writer?.app !== writtenBy?.app || writer?.release !== writtenBy?.release;
|
|
847
|
+
});
|
|
848
|
+
if (foreign) {
|
|
849
|
+
return {
|
|
850
|
+
outcome: 'refused',
|
|
851
|
+
code: 'unknown-revision',
|
|
852
|
+
retryable: false,
|
|
853
|
+
file: [...requeue.queued].at(-1),
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
const withdrawing = requeue.withdrawn.size > 0;
|
|
857
|
+
const input = {
|
|
858
|
+
local: localSaves,
|
|
739
859
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
740
860
|
composition: compositionFor(replica.confirmed.record),
|
|
741
861
|
operations: room.operations,
|
|
@@ -743,29 +863,86 @@ export function composeRuntime(options) {
|
|
|
743
863
|
providerLimits: limits,
|
|
744
864
|
handle: room.handle,
|
|
745
865
|
visible: replica.visible,
|
|
746
|
-
changes
|
|
866
|
+
changes,
|
|
747
867
|
bases: parts.flatMap(({ bases }) => bases),
|
|
748
868
|
writtenBy,
|
|
869
|
+
};
|
|
870
|
+
const checked = withdrawing ? buildBatch({ ...input, queued: requeue.queued }) : null;
|
|
871
|
+
if (checked?.kind === 'refused')
|
|
872
|
+
return { outcome: 'refused', ...checked.refusal };
|
|
873
|
+
const owners = [...requeue.owners].flatMap((origin) => {
|
|
874
|
+
const batch = requeue.withdrawn.get(origin);
|
|
875
|
+
return batch === undefined || batch === null ? [] : [{ origin, batch }];
|
|
749
876
|
});
|
|
750
|
-
if (
|
|
877
|
+
if (owners.length > 1) {
|
|
878
|
+
return {
|
|
879
|
+
outcome: 'refused',
|
|
880
|
+
code: 'unknown-revision',
|
|
881
|
+
retryable: false,
|
|
882
|
+
file: [...requeue.queued].at(-1),
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
const built = !withdrawing
|
|
886
|
+
? buildBatch(input)
|
|
887
|
+
: requeue.changes.length === 0
|
|
888
|
+
? null
|
|
889
|
+
: buildBatch({
|
|
890
|
+
...input,
|
|
891
|
+
visible: replicaOf(replica.confirmed, requeue.queue, replica).visible,
|
|
892
|
+
changes: requeue.changes,
|
|
893
|
+
});
|
|
894
|
+
if (built?.kind === 'refused')
|
|
751
895
|
return { outcome: 'refused', ...built.refusal };
|
|
752
|
-
const
|
|
896
|
+
const [owner] = owners;
|
|
897
|
+
const join = built === null || owner === undefined
|
|
898
|
+
? null
|
|
899
|
+
: joined(requeue.queue, owner.batch, built.batch);
|
|
900
|
+
if (join?.kind === 'refused')
|
|
901
|
+
return { outcome: 'refused', ...join.refusal };
|
|
902
|
+
const withdrawn = new Map(requeue.withdrawn);
|
|
903
|
+
if (join !== null && owner !== undefined)
|
|
904
|
+
withdrawn.set(owner.origin, join.batch);
|
|
905
|
+
const drafted = new Map();
|
|
906
|
+
for (const [origin, replaced] of withdrawn) {
|
|
907
|
+
if (replaced === null)
|
|
908
|
+
continue;
|
|
909
|
+
const carried = withdrawnDrafts(pendings.get(origin)?.drafts ?? [], changes);
|
|
910
|
+
drafted.set(replaced.id, replaced === join?.batch
|
|
911
|
+
? [
|
|
912
|
+
...carried.map((held) => joinedDraft(held, saved)),
|
|
913
|
+
...saved.filter((draft) => !carried.some((held) => held.path === draft.path)),
|
|
914
|
+
]
|
|
915
|
+
: carried);
|
|
916
|
+
}
|
|
917
|
+
if (built !== null && join === null)
|
|
918
|
+
drafted.set(built.batch.id, saved);
|
|
919
|
+
const batches = join !== null ? join.queue : built === null ? requeue.queue : [...requeue.queue, built.batch];
|
|
920
|
+
for (const replaced of withdrawn.values()) {
|
|
921
|
+
if (replaced === null || !batches.includes(replaced))
|
|
922
|
+
continue;
|
|
923
|
+
const limited = limitedIn(batches, replaced, limits, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
924
|
+
if (limited !== null)
|
|
925
|
+
return { outcome: 'refused', ...limited };
|
|
926
|
+
}
|
|
753
927
|
const consumed = saved.map(({ path }) => path);
|
|
754
928
|
try {
|
|
755
|
-
await local.
|
|
756
|
-
|
|
757
|
-
previous: replica.confirmed.record,
|
|
929
|
+
await local.keepAuthored({
|
|
930
|
+
locator,
|
|
758
931
|
queue: batches,
|
|
759
|
-
|
|
760
|
-
saved,
|
|
932
|
+
saved: drafted,
|
|
761
933
|
consumedRoomDrafts: consumed,
|
|
762
934
|
});
|
|
763
935
|
}
|
|
764
936
|
catch {
|
|
765
|
-
return { outcome: 'refused',
|
|
937
|
+
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
938
|
+
}
|
|
939
|
+
for (const origin of withdrawn.keys())
|
|
940
|
+
pendings.delete(origin);
|
|
941
|
+
for (const [id, drafts] of drafted) {
|
|
942
|
+
pendings.set(id, { drafts, submission: { state: 'waiting' }, lost: false });
|
|
766
943
|
}
|
|
767
|
-
|
|
768
|
-
|
|
944
|
+
if (!localSaves)
|
|
945
|
+
recorded = false;
|
|
769
946
|
for (const path of consumed)
|
|
770
947
|
forgetRoomDraft(locator, path);
|
|
771
948
|
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
@@ -773,9 +950,12 @@ export function composeRuntime(options) {
|
|
|
773
950
|
queue: true,
|
|
774
951
|
drafts: consumed,
|
|
775
952
|
}));
|
|
776
|
-
moveTo({ ...saving(status), ...attention() });
|
|
953
|
+
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
777
954
|
submitLater(locator);
|
|
778
|
-
|
|
955
|
+
const identifier = join?.batch.id ??
|
|
956
|
+
built?.batch.id ??
|
|
957
|
+
[...requeue.withdrawn].map(([origin, replaced]) => replaced?.id ?? origin).at(-1);
|
|
958
|
+
return { outcome: 'saved', identifier };
|
|
779
959
|
}
|
|
780
960
|
async function mark(local, locator, identifier, submission, lost) {
|
|
781
961
|
const state = pendings.get(identifier);
|
|
@@ -801,16 +981,15 @@ export function composeRuntime(options) {
|
|
|
801
981
|
}
|
|
802
982
|
async function readHistory(locator, path, query = {}) {
|
|
803
983
|
if (closed || refusal !== null)
|
|
804
|
-
return
|
|
984
|
+
return UNCERTAIN;
|
|
805
985
|
try {
|
|
806
986
|
await ensureAuthorization();
|
|
807
987
|
}
|
|
808
988
|
catch {
|
|
809
|
-
return
|
|
810
|
-
}
|
|
811
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
812
|
-
return { kind: 'transport-uncertainty' };
|
|
989
|
+
return UNCERTAIN;
|
|
813
990
|
}
|
|
991
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
992
|
+
return UNCERTAIN;
|
|
814
993
|
const transfer = ['transfer', 'history', locator, path];
|
|
815
994
|
if (query.revision !== undefined)
|
|
816
995
|
transfer.push(query.revision);
|
|
@@ -832,13 +1011,97 @@ export function composeRuntime(options) {
|
|
|
832
1011
|
}
|
|
833
1012
|
if (result.kind === 'revoked-authorization')
|
|
834
1013
|
await refuse('revoked-authorization');
|
|
835
|
-
return result;
|
|
1014
|
+
return historyAnswer(result);
|
|
836
1015
|
}
|
|
837
1016
|
catch {
|
|
838
1017
|
journal.record('outcome history transport-uncertainty');
|
|
839
|
-
return
|
|
1018
|
+
return UNCERTAIN;
|
|
840
1019
|
}
|
|
841
1020
|
}
|
|
1021
|
+
function queuedBlob(locator, path, localId) {
|
|
1022
|
+
for (const batch of replicas.get(locator)?.queue ?? []) {
|
|
1023
|
+
const change = batch.changes[queuedFile(batch, path, localId)];
|
|
1024
|
+
if (change?.kind === 'binary')
|
|
1025
|
+
return change.blob;
|
|
1026
|
+
}
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
async function readBinary(request) {
|
|
1030
|
+
if (closed || refusal !== null)
|
|
1031
|
+
return UNCERTAIN;
|
|
1032
|
+
try {
|
|
1033
|
+
await ensureAuthorization();
|
|
1034
|
+
}
|
|
1035
|
+
catch {
|
|
1036
|
+
return UNCERTAIN;
|
|
1037
|
+
}
|
|
1038
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1039
|
+
return UNCERTAIN;
|
|
1040
|
+
journal.record(request.kind === 'path'
|
|
1041
|
+
? `transfer binary ${request.locator} ${request.path}`
|
|
1042
|
+
: `transfer binary ${request.fileRef}`);
|
|
1043
|
+
try {
|
|
1044
|
+
const result = await options.adapter.binary(authorization, request);
|
|
1045
|
+
journal.record(`outcome binary ${result.kind}`);
|
|
1046
|
+
if (result.kind === 'scoped-refusal') {
|
|
1047
|
+
const room = replicas.get(result.locator)?.confirmed.record;
|
|
1048
|
+
if (room !== undefined)
|
|
1049
|
+
await scoped(result, room, await openLocal());
|
|
1050
|
+
}
|
|
1051
|
+
if (result.kind === 'revoked-authorization')
|
|
1052
|
+
await refuse('revoked-authorization');
|
|
1053
|
+
return binaryAnswer(result);
|
|
1054
|
+
}
|
|
1055
|
+
catch {
|
|
1056
|
+
journal.record('outcome binary transport-uncertainty');
|
|
1057
|
+
return UNCERTAIN;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async function readBinaryPath(locator, path, revision) {
|
|
1061
|
+
await ready;
|
|
1062
|
+
if (revision !== undefined)
|
|
1063
|
+
return readBinary({ kind: 'path', locator, path, revision });
|
|
1064
|
+
const visible = replicas.get(locator)?.visible;
|
|
1065
|
+
const held = visible?.binaries[path];
|
|
1066
|
+
if (visible === undefined || held === undefined)
|
|
1067
|
+
return absentBinary(path);
|
|
1068
|
+
if (held.state === 'pending') {
|
|
1069
|
+
const blob = queuedBlob(locator, path, held.localId);
|
|
1070
|
+
return blob === null ? absentBinary(path) : { kind: 'binary', blob };
|
|
1071
|
+
}
|
|
1072
|
+
const record = binaryRecord(path, held, visible.revisions[path]);
|
|
1073
|
+
if (record.state !== 'accepted')
|
|
1074
|
+
return absentBinary(path);
|
|
1075
|
+
const replica = replicas.get(locator);
|
|
1076
|
+
const queued = (replica?.queue ?? []).flatMap((batch) => batch.changes);
|
|
1077
|
+
const holds = (at) => {
|
|
1078
|
+
const kept = replica?.confirmed.record.binaries[at];
|
|
1079
|
+
return (kept?.state === 'accepted' &&
|
|
1080
|
+
kept.fileRef === record.fileRef &&
|
|
1081
|
+
replica?.confirmed.record.revisions[at] === record.revision);
|
|
1082
|
+
};
|
|
1083
|
+
let confirmed = path;
|
|
1084
|
+
for (let index = queued.length - 1; index >= 0 && !holds(confirmed); index -= 1) {
|
|
1085
|
+
const change = queued[index];
|
|
1086
|
+
if (change.kind === 'move' && change.to === confirmed)
|
|
1087
|
+
confirmed = change.path;
|
|
1088
|
+
}
|
|
1089
|
+
return readBinary({
|
|
1090
|
+
kind: 'path',
|
|
1091
|
+
locator,
|
|
1092
|
+
path: confirmed,
|
|
1093
|
+
revision: record.revision,
|
|
1094
|
+
fileRef: record.fileRef,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
async function readBinaryReference(reference) {
|
|
1098
|
+
await ready;
|
|
1099
|
+
return readBinary({
|
|
1100
|
+
kind: 'reference',
|
|
1101
|
+
fileRef: reference.fileRef,
|
|
1102
|
+
referrer: reference.referrer,
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
842
1105
|
function declaredAgents() {
|
|
843
1106
|
for (const capability of capabilities) {
|
|
844
1107
|
if (capability.conversationAgents !== undefined)
|
|
@@ -858,18 +1121,17 @@ export function composeRuntime(options) {
|
|
|
858
1121
|
async function invokeAction(name, input) {
|
|
859
1122
|
await ready;
|
|
860
1123
|
if (closed || refusal !== null)
|
|
861
|
-
return
|
|
1124
|
+
return UNCERTAIN;
|
|
862
1125
|
if (declaredAction(name) === undefined)
|
|
863
|
-
return
|
|
1126
|
+
return undeclared(name);
|
|
864
1127
|
try {
|
|
865
1128
|
await ensureAuthorization();
|
|
866
1129
|
}
|
|
867
1130
|
catch {
|
|
868
|
-
return
|
|
869
|
-
}
|
|
870
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
871
|
-
return { kind: 'transport-uncertainty' };
|
|
1131
|
+
return UNCERTAIN;
|
|
872
1132
|
}
|
|
1133
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1134
|
+
return UNCERTAIN;
|
|
873
1135
|
journal.record(`transfer action ${name}`);
|
|
874
1136
|
try {
|
|
875
1137
|
const result = await options.adapter.action(authorization, { name, input });
|
|
@@ -883,11 +1145,11 @@ export function composeRuntime(options) {
|
|
|
883
1145
|
journal.record(`outcome action ${result.kind}`);
|
|
884
1146
|
if (result.kind === 'rejected')
|
|
885
1147
|
journal.record(`refusal rejected ${result.code}`);
|
|
886
|
-
return result;
|
|
1148
|
+
return calledResult(result);
|
|
887
1149
|
}
|
|
888
1150
|
catch {
|
|
889
1151
|
journal.record('outcome action transport-uncertainty');
|
|
890
|
-
return
|
|
1152
|
+
return UNCERTAIN;
|
|
891
1153
|
}
|
|
892
1154
|
}
|
|
893
1155
|
function continuing() {
|
|
@@ -918,14 +1180,33 @@ export function composeRuntime(options) {
|
|
|
918
1180
|
throw error;
|
|
919
1181
|
}
|
|
920
1182
|
}
|
|
921
|
-
|
|
1183
|
+
function staging(local) {
|
|
1184
|
+
return {
|
|
1185
|
+
held: (locator) => transfers.get(locator) ?? null,
|
|
1186
|
+
staged: (record) => local.staged(record),
|
|
1187
|
+
stage: (record, page) => exclusive(async () => {
|
|
1188
|
+
if (!continuing())
|
|
1189
|
+
return false;
|
|
1190
|
+
await local.stage(record, page);
|
|
1191
|
+
transfers.set(record.locator, record);
|
|
1192
|
+
return true;
|
|
1193
|
+
}),
|
|
1194
|
+
abandon: (locator) => exclusive(async () => {
|
|
1195
|
+
if (!continuing())
|
|
1196
|
+
return;
|
|
1197
|
+
await local.abandon(locator);
|
|
1198
|
+
transfers.delete(locator);
|
|
1199
|
+
}),
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
async function pullBootstrap(authorized, room, local) {
|
|
922
1203
|
if (!continuing())
|
|
923
1204
|
return unconfirmed;
|
|
924
1205
|
try {
|
|
925
1206
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
926
1207
|
collection: modelOf(room.locator),
|
|
927
1208
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
928
|
-
}, (models, held) => remodel(room.locator, models, held), continuing);
|
|
1209
|
+
}, (models, held) => remodel(room.locator, models, held), continuing, staging(local));
|
|
929
1210
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
930
1211
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
931
1212
|
return result;
|
|
@@ -953,7 +1234,7 @@ export function composeRuntime(options) {
|
|
|
953
1234
|
return null;
|
|
954
1235
|
}
|
|
955
1236
|
if (refreshed.kind === 'advanced') {
|
|
956
|
-
if (!(await publish(local, room, refreshed.confirmed, held.confirmed))) {
|
|
1237
|
+
if (!(await publish(local, room, refreshed.confirmed, { base: held.confirmed }))) {
|
|
957
1238
|
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
958
1239
|
const reached = now !== undefined &&
|
|
959
1240
|
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
@@ -965,12 +1246,12 @@ export function composeRuntime(options) {
|
|
|
965
1246
|
if (refreshed.kind !== 'beyond-horizon')
|
|
966
1247
|
return refreshed;
|
|
967
1248
|
}
|
|
968
|
-
const bootstrapped = await pullBootstrap(authorized, room);
|
|
1249
|
+
const bootstrapped = await pullBootstrap(authorized, room, local);
|
|
969
1250
|
if (!continuing())
|
|
970
1251
|
return unconfirmed;
|
|
971
1252
|
if (bootstrapped.kind !== 'staged')
|
|
972
1253
|
return bootstrapped;
|
|
973
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1254
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
974
1255
|
return null;
|
|
975
1256
|
}
|
|
976
1257
|
async function withhold(withheld, room, local, progress) {
|
|
@@ -1002,6 +1283,7 @@ export function composeRuntime(options) {
|
|
|
1002
1283
|
function refusedSource(room, { refusal }) {
|
|
1003
1284
|
sourceRefusals.set(room.locator, {
|
|
1004
1285
|
code: refusal.rule,
|
|
1286
|
+
retryable: false,
|
|
1005
1287
|
fields: {
|
|
1006
1288
|
file: refusal.file,
|
|
1007
1289
|
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
@@ -1016,8 +1298,16 @@ export function composeRuntime(options) {
|
|
|
1016
1298
|
}
|
|
1017
1299
|
function leaving(batch, result) {
|
|
1018
1300
|
if (result.kind === 'expired')
|
|
1019
|
-
return { batch, returned:
|
|
1020
|
-
return {
|
|
1301
|
+
return { batch, returned: returnedBy('expired') };
|
|
1302
|
+
return {
|
|
1303
|
+
batch,
|
|
1304
|
+
returned: {
|
|
1305
|
+
kind: result.kind,
|
|
1306
|
+
code: result.code,
|
|
1307
|
+
fields: result.fields,
|
|
1308
|
+
retryable: result.retryable,
|
|
1309
|
+
},
|
|
1310
|
+
};
|
|
1021
1311
|
}
|
|
1022
1312
|
function logAccepted(locator, submitted, accepted) {
|
|
1023
1313
|
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
@@ -1035,7 +1325,7 @@ export function composeRuntime(options) {
|
|
|
1035
1325
|
logAccepted(room.locator, submitted, accepted);
|
|
1036
1326
|
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1037
1327
|
? { kind: 'staged', confirmed: current.confirmed }
|
|
1038
|
-
: applyReturned(current.confirmed,
|
|
1328
|
+
: applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1039
1329
|
? accepted.roomVersion
|
|
1040
1330
|
: held, accepted.roomVersion);
|
|
1041
1331
|
if (applied.kind !== 'staged') {
|
|
@@ -1181,7 +1471,8 @@ export function composeRuntime(options) {
|
|
|
1181
1471
|
return;
|
|
1182
1472
|
}
|
|
1183
1473
|
const set = await settle(local, room.locator, current, current.confirmed, current.queue.filter((batch) => batch.id !== head.id), {
|
|
1184
|
-
leaving: result.kind === 'expired' && landed(head, current.confirmed)
|
|
1474
|
+
leaving: (result.kind === 'expired' && landed(head, current.confirmed)) ||
|
|
1475
|
+
(result.kind === 'conflicted' && landed(head, current.confirmed, result.affected))
|
|
1185
1476
|
? { ...left, returned: null }
|
|
1186
1477
|
: left,
|
|
1187
1478
|
});
|
|
@@ -1242,7 +1533,7 @@ export function composeRuntime(options) {
|
|
|
1242
1533
|
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1243
1534
|
};
|
|
1244
1535
|
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
1245
|
-
?
|
|
1536
|
+
? { kind: 'staged', confirmed: confirmedOf(declared, held.confirmed) }
|
|
1246
1537
|
: readReclassified(declared, held.confirmed);
|
|
1247
1538
|
const record = reclassified.kind === 'staged'
|
|
1248
1539
|
? declared
|
|
@@ -1250,12 +1541,10 @@ export function composeRuntime(options) {
|
|
|
1250
1541
|
...declared,
|
|
1251
1542
|
typedSelection: held.confirmed.record.typedSelection,
|
|
1252
1543
|
};
|
|
1253
|
-
const read = reclassified.kind === 'deferred'
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
queue: held.queue,
|
|
1258
|
-
});
|
|
1544
|
+
const read = reclassified.kind === 'deferred'
|
|
1545
|
+
? { kind: 'staged', confirmed: confirmedOf(record, held.confirmed) }
|
|
1546
|
+
: reclassified;
|
|
1547
|
+
await local.write({ next: record, previous: held.confirmed.record });
|
|
1259
1548
|
if (read.kind === 'staged') {
|
|
1260
1549
|
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
|
|
1261
1550
|
if (declarationRefusals.delete(room.locator)) {
|
|
@@ -1282,7 +1571,11 @@ export function composeRuntime(options) {
|
|
|
1282
1571
|
});
|
|
1283
1572
|
}
|
|
1284
1573
|
async function scoped(result, room, local) {
|
|
1285
|
-
scopedRefusals.set(room.locator, {
|
|
1574
|
+
scopedRefusals.set(room.locator, {
|
|
1575
|
+
code: result.code,
|
|
1576
|
+
fields: result.fields,
|
|
1577
|
+
retryable: result.retryable,
|
|
1578
|
+
});
|
|
1286
1579
|
let scopedAway = room;
|
|
1287
1580
|
if (result.scope === 'model')
|
|
1288
1581
|
await retainModel(local, room, 'collection', {
|
|
@@ -1319,7 +1612,6 @@ export function composeRuntime(options) {
|
|
|
1319
1612
|
roomVersion: record.roomVersion,
|
|
1320
1613
|
documents: record.documents,
|
|
1321
1614
|
revisions: record.revisions,
|
|
1322
|
-
attachments: record.attachments,
|
|
1323
1615
|
},
|
|
1324
1616
|
];
|
|
1325
1617
|
});
|
|
@@ -1330,8 +1622,10 @@ export function composeRuntime(options) {
|
|
|
1330
1622
|
const outcome = prepared[index];
|
|
1331
1623
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
1332
1624
|
});
|
|
1333
|
-
if (attempt === 'opened')
|
|
1625
|
+
if (attempt === 'opened') {
|
|
1334
1626
|
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
1627
|
+
handed = true;
|
|
1628
|
+
}
|
|
1335
1629
|
const loaded = await Promise.allSettled(ready
|
|
1336
1630
|
.filter((room) => attempt === 'unstreamed' || !replicas.has(room.locator))
|
|
1337
1631
|
.map((room) => loadRoom(room, local, progress)));
|
|
@@ -1397,14 +1691,14 @@ export function composeRuntime(options) {
|
|
|
1397
1691
|
async function reload(room, local, progress) {
|
|
1398
1692
|
if (discoveryPaused || authorization === undefined)
|
|
1399
1693
|
return;
|
|
1400
|
-
const bootstrapped = await pullBootstrap(authorization, room);
|
|
1694
|
+
const bootstrapped = await pullBootstrap(authorization, room, local);
|
|
1401
1695
|
if (!continuing())
|
|
1402
1696
|
return;
|
|
1403
1697
|
if (bootstrapped.kind !== 'staged') {
|
|
1404
1698
|
await withhold(bootstrapped, room, local, progress);
|
|
1405
1699
|
return;
|
|
1406
1700
|
}
|
|
1407
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1701
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
1408
1702
|
await caughtUp(room, local, progress);
|
|
1409
1703
|
}
|
|
1410
1704
|
async function discoverRooms(local, progress, attempt) {
|
|
@@ -1434,15 +1728,16 @@ export function composeRuntime(options) {
|
|
|
1434
1728
|
}
|
|
1435
1729
|
progress.reachable = false;
|
|
1436
1730
|
progress.complete = false;
|
|
1437
|
-
if (attempt === 'opened') {
|
|
1438
|
-
options.adapter.roomStream?.hold(heldRooms([...discovered.values()]));
|
|
1439
|
-
}
|
|
1440
1731
|
notifyReady();
|
|
1441
1732
|
return;
|
|
1442
1733
|
}
|
|
1443
1734
|
if (result.kind === 'contract-unavailable') {
|
|
1444
1735
|
journal.record(`connection contract unavailable ${result.code}`);
|
|
1445
|
-
await refuse('missing-required-guarantee', {
|
|
1736
|
+
await refuse('missing-required-guarantee', {
|
|
1737
|
+
code: result.code,
|
|
1738
|
+
fields: result.fields,
|
|
1739
|
+
retryable: result.retryable,
|
|
1740
|
+
});
|
|
1446
1741
|
notifyReady();
|
|
1447
1742
|
return;
|
|
1448
1743
|
}
|
|
@@ -1483,6 +1778,12 @@ export function composeRuntime(options) {
|
|
|
1483
1778
|
limits = result.providerLimits;
|
|
1484
1779
|
await local.declare(limits, capabilities);
|
|
1485
1780
|
}
|
|
1781
|
+
const signedIn = result.member ?? null;
|
|
1782
|
+
if (JSON.stringify(signedIn) !== JSON.stringify(member)) {
|
|
1783
|
+
await local.keepMember(signedIn);
|
|
1784
|
+
member = signedIn;
|
|
1785
|
+
emitMember();
|
|
1786
|
+
}
|
|
1486
1787
|
await applyDiscovery(result.rooms, local);
|
|
1487
1788
|
await synchronize(result.rooms, local, progress, attempt);
|
|
1488
1789
|
}
|
|
@@ -1512,9 +1813,12 @@ export function composeRuntime(options) {
|
|
|
1512
1813
|
}
|
|
1513
1814
|
const progress = { complete: true, reachable: true };
|
|
1514
1815
|
underway = attempt === 'opened' ? progress : null;
|
|
1816
|
+
if (attempt === 'opened')
|
|
1817
|
+
streamed = progress;
|
|
1515
1818
|
complete = false;
|
|
1516
1819
|
recorded = false;
|
|
1517
1820
|
moveTo(transferring(status));
|
|
1821
|
+
handed = false;
|
|
1518
1822
|
try {
|
|
1519
1823
|
const local = await openLocal();
|
|
1520
1824
|
if (closed)
|
|
@@ -1526,16 +1830,22 @@ export function composeRuntime(options) {
|
|
|
1526
1830
|
}
|
|
1527
1831
|
catch {
|
|
1528
1832
|
progress.complete = false;
|
|
1529
|
-
if (attempt === 'opened')
|
|
1530
|
-
|
|
1531
|
-
}
|
|
1833
|
+
if (attempt === 'opened' && !handed)
|
|
1834
|
+
progress.reachable = false;
|
|
1532
1835
|
}
|
|
1533
1836
|
finally {
|
|
1534
1837
|
notifyReady();
|
|
1535
1838
|
}
|
|
1536
|
-
if (attempt
|
|
1839
|
+
if (attempt !== 'opened') {
|
|
1840
|
+
await finishOpening(progress);
|
|
1537
1841
|
return;
|
|
1538
|
-
|
|
1842
|
+
}
|
|
1843
|
+
if (handed || closed || progress.reachable)
|
|
1844
|
+
return;
|
|
1845
|
+
underway = null;
|
|
1846
|
+
journal.record('connection reopen');
|
|
1847
|
+
await rest(progress);
|
|
1848
|
+
options.adapter.roomStream?.reopen();
|
|
1539
1849
|
});
|
|
1540
1850
|
}
|
|
1541
1851
|
function closedSocket(revoked) {
|
|
@@ -1545,9 +1855,14 @@ export function composeRuntime(options) {
|
|
|
1545
1855
|
void refuse('revoked-authorization');
|
|
1546
1856
|
return;
|
|
1547
1857
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1858
|
+
complete = false;
|
|
1859
|
+
if (streamed !== null)
|
|
1860
|
+
streamed.complete = false;
|
|
1861
|
+
moveTo({
|
|
1862
|
+
...status,
|
|
1863
|
+
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
1864
|
+
providerUnreachable: status.providerUnreachable || !opened,
|
|
1865
|
+
});
|
|
1551
1866
|
}
|
|
1552
1867
|
function applyOpening(expired) {
|
|
1553
1868
|
return task(async () => {
|
|
@@ -1602,6 +1917,8 @@ export function composeRuntime(options) {
|
|
|
1602
1917
|
}
|
|
1603
1918
|
}
|
|
1604
1919
|
function submitLater(locator) {
|
|
1920
|
+
if (localSaves)
|
|
1921
|
+
return;
|
|
1605
1922
|
const running = submitting.get(locator);
|
|
1606
1923
|
if (running !== undefined) {
|
|
1607
1924
|
running.again = true;
|
|
@@ -1681,7 +1998,7 @@ export function composeRuntime(options) {
|
|
|
1681
1998
|
}
|
|
1682
1999
|
}
|
|
1683
2000
|
moveTo({
|
|
1684
|
-
state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
|
|
2001
|
+
state: pending() ? 'pending' : synced && complete ? 'synced' : 'syncing',
|
|
1685
2002
|
lastCompleted,
|
|
1686
2003
|
providerUnreachable: !progress.reachable,
|
|
1687
2004
|
...attention(),
|
|
@@ -1790,20 +2107,15 @@ export function composeRuntime(options) {
|
|
|
1790
2107
|
show();
|
|
1791
2108
|
return;
|
|
1792
2109
|
}
|
|
1793
|
-
const
|
|
1794
|
-
|
|
1795
|
-
moveTo(transferring(resting));
|
|
2110
|
+
const progress = { complete: false, reachable: !status.providerUnreachable };
|
|
2111
|
+
moveTo(transferring(status));
|
|
1796
2112
|
try {
|
|
1797
2113
|
await conversations.catchUp(progress);
|
|
1798
2114
|
}
|
|
1799
2115
|
catch {
|
|
1800
2116
|
progress.reachable = false;
|
|
1801
2117
|
}
|
|
1802
|
-
|
|
1803
|
-
if (!closed) {
|
|
1804
|
-
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
2118
|
+
await rest(progress);
|
|
1807
2119
|
}
|
|
1808
2120
|
function close() {
|
|
1809
2121
|
if (closed)
|
|
@@ -1825,6 +2137,7 @@ export function composeRuntime(options) {
|
|
|
1825
2137
|
storageLossListeners.clear();
|
|
1826
2138
|
statusListeners.clear();
|
|
1827
2139
|
serviceModeListeners.clear();
|
|
2140
|
+
memberListeners.clear();
|
|
1828
2141
|
readSide.close();
|
|
1829
2142
|
queueSubscriptions.clear();
|
|
1830
2143
|
roomDraftListeners.clear();
|
|
@@ -1856,13 +2169,16 @@ export function composeRuntime(options) {
|
|
|
1856
2169
|
return planOf(input, roomFacts(reach), planReach(reach));
|
|
1857
2170
|
}
|
|
1858
2171
|
function watchPlan(plan, deliver) {
|
|
2172
|
+
materialize(reachedRooms(plan));
|
|
1859
2173
|
return readSide.watch(plan, identityOf(plan), deliver);
|
|
1860
2174
|
}
|
|
1861
2175
|
async function searched(input, reach) {
|
|
1862
2176
|
const plan = searchPlanOf(input);
|
|
1863
2177
|
const selected = plan.documents === undefined && reach === null
|
|
1864
2178
|
? null
|
|
1865
|
-
: (await
|
|
2179
|
+
: (await rowsQuery(filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)))).documents;
|
|
2180
|
+
if (selected === null)
|
|
2181
|
+
materialize(null);
|
|
1866
2182
|
return searchIndex.search(reach === null || reach.conversations ? plan : { ...plan, conversations: [] }, selected);
|
|
1867
2183
|
}
|
|
1868
2184
|
const watch = (topic, input, deliver) => {
|
|
@@ -1888,6 +2204,8 @@ export function composeRuntime(options) {
|
|
|
1888
2204
|
}
|
|
1889
2205
|
if (topic === 'conversations')
|
|
1890
2206
|
return conversations.watchList(deliver);
|
|
2207
|
+
if (topic === 'agents')
|
|
2208
|
+
return conversations.watchAgents(deliver);
|
|
1891
2209
|
if (topic === 'presentation') {
|
|
1892
2210
|
const listener = {
|
|
1893
2211
|
app: input[0],
|
|
@@ -1942,6 +2260,13 @@ export function composeRuntime(options) {
|
|
|
1942
2260
|
serviceModeListeners.delete(deliver);
|
|
1943
2261
|
};
|
|
1944
2262
|
}
|
|
2263
|
+
if (topic === 'member') {
|
|
2264
|
+
memberListeners.add(deliver);
|
|
2265
|
+
deliver(member);
|
|
2266
|
+
return () => {
|
|
2267
|
+
memberListeners.delete(deliver);
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
1945
2270
|
if (topic === 'queue') {
|
|
1946
2271
|
const queue = { locator: input[0], notify: deliver };
|
|
1947
2272
|
queueSubscriptions.add(queue);
|
|
@@ -1959,7 +2284,7 @@ export function composeRuntime(options) {
|
|
|
1959
2284
|
rooms() {
|
|
1960
2285
|
return listed();
|
|
1961
2286
|
},
|
|
1962
|
-
query: ((input) =>
|
|
2287
|
+
query: ((input) => rowsQuery(planned(input, null))),
|
|
1963
2288
|
search: (input) => searched(input, null),
|
|
1964
2289
|
types(locator) {
|
|
1965
2290
|
return {
|
|
@@ -1988,20 +2313,20 @@ export function composeRuntime(options) {
|
|
|
1988
2313
|
return exclusive(async () => {
|
|
1989
2314
|
const parts = readParts(input);
|
|
1990
2315
|
if (path === '' || normalize(path) !== path)
|
|
1991
|
-
throw
|
|
2316
|
+
throw runtimeError('room-draft-malformed');
|
|
1992
2317
|
const local = await openLocal();
|
|
1993
2318
|
const current = roomDrafts.get(room)?.get(path);
|
|
1994
2319
|
if ((current?.revision ?? null) !== revision) {
|
|
1995
|
-
return
|
|
1996
|
-
outcome: 'stale',
|
|
1997
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
1998
|
-
};
|
|
2320
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
1999
2321
|
}
|
|
2322
|
+
const file = parts.file === undefined
|
|
2323
|
+
? undefined
|
|
2324
|
+
: await heldFile(parts.file.blob, binaryBase(replicas.get(room)?.visible, path));
|
|
2000
2325
|
const record = {
|
|
2001
2326
|
locator: room,
|
|
2002
2327
|
path,
|
|
2003
2328
|
revision: roomDraftRevision(),
|
|
2004
|
-
parts: held(current?.parts, parts),
|
|
2329
|
+
parts: held(current?.parts, parts, file),
|
|
2005
2330
|
returned: current?.returned ?? null,
|
|
2006
2331
|
};
|
|
2007
2332
|
await local.keepRoomDraft(record);
|
|
@@ -2015,10 +2340,7 @@ export function composeRuntime(options) {
|
|
|
2015
2340
|
const local = await openLocal();
|
|
2016
2341
|
const current = roomDrafts.get(room)?.get(path);
|
|
2017
2342
|
if (current === undefined || current.revision !== revision) {
|
|
2018
|
-
return
|
|
2019
|
-
outcome: 'stale',
|
|
2020
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2021
|
-
};
|
|
2343
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2022
2344
|
}
|
|
2023
2345
|
await local.discardRoomDraft(room, path);
|
|
2024
2346
|
forgetRoomDraft(room, path);
|
|
@@ -2053,6 +2375,10 @@ export function composeRuntime(options) {
|
|
|
2053
2375
|
history(locator, path, query) {
|
|
2054
2376
|
return readHistory(locator, path, query);
|
|
2055
2377
|
},
|
|
2378
|
+
binaryFile: {
|
|
2379
|
+
read: readBinaryPath,
|
|
2380
|
+
readReference: readBinaryReference,
|
|
2381
|
+
},
|
|
2056
2382
|
call(name, input) {
|
|
2057
2383
|
return invokeAction(name, input);
|
|
2058
2384
|
},
|
|
@@ -2085,8 +2411,9 @@ export function composeRuntime(options) {
|
|
|
2085
2411
|
const reachable = {
|
|
2086
2412
|
md,
|
|
2087
2413
|
watch,
|
|
2414
|
+
referrerScope: (referrer) => options.adapter.referrerScope(referrer),
|
|
2088
2415
|
plan: planned,
|
|
2089
|
-
query: (plan) =>
|
|
2416
|
+
query: (plan) => rowsQuery(plan),
|
|
2090
2417
|
watchPlan,
|
|
2091
2418
|
search: searched,
|
|
2092
2419
|
};
|