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