@agentshouse/mdruntime 0.1.0-cf6d5f0a5c9c8 → 0.1.0-cfb0d45b48da0
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 +146 -100
- package/README.md +11 -1
- package/dist/adapter.d.ts +41 -14
- package/dist/adapter.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/bootstrap.js +5 -8
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +8 -7
- 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 +12 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +30 -15
- package/dist/connection.d.ts +19 -9
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +312 -96
- 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 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +12 -12
- package/dist/identity.d.ts +2 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +6 -0
- 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 +3 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +47 -16
- package/dist/model.d.ts +3 -2
- package/dist/model.d.ts.map +1 -1
- 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 +3 -0
- 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 +4 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +3 -2
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +9 -2
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +12 -17
- package/dist/replica.d.ts +6 -3
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +46 -32
- package/dist/room-draft.d.ts +26 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +131 -22
- package/dist/rows.d.ts +3 -1
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +3 -1
- package/dist/save.d.ts +27 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +323 -22
- 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 +16 -13
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +186 -156
- package/dist/wire.d.ts +6 -3
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +14 -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
|
@@ -8,13 +8,17 @@ import { createSearchIndex, searchPlanOf, } from './search.js';
|
|
|
8
8
|
import { bootstrapRoom } from './bootstrap.js';
|
|
9
9
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
10
10
|
import { createConversationStore, } from './conversation.js';
|
|
11
|
+
import { queuedFile } from './change.js';
|
|
11
12
|
import { validRoot } from './collection.js';
|
|
13
|
+
import { runtimeError } from './failure.js';
|
|
12
14
|
import { createLog } from './log.js';
|
|
15
|
+
import { binaryRecord } from './binary.js';
|
|
16
|
+
import { absentBinary, binaryAnswer, calledResult, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
|
|
13
17
|
import { refreshRoom } from './refresh.js';
|
|
14
|
-
import { buildBatch } from './save.js';
|
|
18
|
+
import { buildBatch, joined, limitedIn, requeued } from './save.js';
|
|
15
19
|
import { applyReturned, compositionOf, confirmedOf, landed, readReclassified, replicaOf, sameMembership, withdrawals, } from './replica.js';
|
|
16
|
-
import { changedParts, changesOf, held, readParts, returnedInto, underneath, } from './room-draft.js';
|
|
17
|
-
import {
|
|
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';
|
|
18
22
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
19
23
|
import { roomDraftRevision } from './identity.js';
|
|
20
24
|
import { beneath, normalize } from './paths.js';
|
|
@@ -35,10 +39,12 @@ function declaredSelection(room) {
|
|
|
35
39
|
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
36
40
|
}
|
|
37
41
|
export function composeRuntime(options) {
|
|
42
|
+
const localSaves = options.adapter.localSaves === true;
|
|
38
43
|
let authorization = options.authorization;
|
|
39
44
|
let opening;
|
|
40
45
|
let store;
|
|
41
46
|
const replicas = new Map();
|
|
47
|
+
const queuesWithoutReplica = new Map();
|
|
42
48
|
const pendings = new Map();
|
|
43
49
|
const roomDrafts = new Map();
|
|
44
50
|
const presentations = new Map();
|
|
@@ -84,6 +90,7 @@ export function composeRuntime(options) {
|
|
|
84
90
|
let withdrawStream;
|
|
85
91
|
const announced = new Map();
|
|
86
92
|
let underway = null;
|
|
93
|
+
let streamed = null;
|
|
87
94
|
let handed = false;
|
|
88
95
|
const submitting = new Map();
|
|
89
96
|
const answering = new Map();
|
|
@@ -190,7 +197,9 @@ export function composeRuntime(options) {
|
|
|
190
197
|
}
|
|
191
198
|
}
|
|
192
199
|
function pending() {
|
|
193
|
-
return
|
|
200
|
+
return (!localSaves &&
|
|
201
|
+
(queuesWithoutReplica.size > 0 ||
|
|
202
|
+
[...replicas.values()].some((replica) => replica.queue.length > 0)));
|
|
194
203
|
}
|
|
195
204
|
function attention() {
|
|
196
205
|
const attentionRequired = [];
|
|
@@ -291,6 +300,8 @@ export function composeRuntime(options) {
|
|
|
291
300
|
listener(shown);
|
|
292
301
|
}
|
|
293
302
|
function entryOf(batch) {
|
|
303
|
+
if (localSaves)
|
|
304
|
+
return Object.freeze({ batch, state: 'local' });
|
|
294
305
|
const state = pendings.get(batch.id);
|
|
295
306
|
if (state?.accepted !== undefined) {
|
|
296
307
|
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
@@ -298,10 +309,7 @@ export function composeRuntime(options) {
|
|
|
298
309
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
299
310
|
}
|
|
300
311
|
function entriesOf(locator) {
|
|
301
|
-
|
|
302
|
-
if (replica === undefined)
|
|
303
|
-
return [];
|
|
304
|
-
return replica.queue.map(entryOf);
|
|
312
|
+
return (replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []).map(entryOf);
|
|
305
313
|
}
|
|
306
314
|
function emitQueue(locator) {
|
|
307
315
|
const entries = entriesOf(locator);
|
|
@@ -398,10 +406,7 @@ export function composeRuntime(options) {
|
|
|
398
406
|
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
399
407
|
try {
|
|
400
408
|
const local = await openLocal();
|
|
401
|
-
await local.revokeModels([...replicas.values()].map((replica) =>
|
|
402
|
-
record: replica.confirmed.record,
|
|
403
|
-
queue: replica.queue,
|
|
404
|
-
})));
|
|
409
|
+
await local.revokeModels([...replicas.values()].map((replica) => replica.confirmed.record));
|
|
405
410
|
}
|
|
406
411
|
catch {
|
|
407
412
|
journal.record('model revocation persistence failed');
|
|
@@ -442,7 +447,8 @@ export function composeRuntime(options) {
|
|
|
442
447
|
}
|
|
443
448
|
finally {
|
|
444
449
|
refusal = {
|
|
445
|
-
reason,
|
|
450
|
+
code: reason,
|
|
451
|
+
retryable: false,
|
|
446
452
|
reauthorizationOffered: reason === 'revoked-authorization',
|
|
447
453
|
refusal: provider,
|
|
448
454
|
};
|
|
@@ -467,7 +473,6 @@ export function composeRuntime(options) {
|
|
|
467
473
|
},
|
|
468
474
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
469
475
|
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
470
|
-
attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
|
|
471
476
|
synchronizationAvailable,
|
|
472
477
|
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
473
478
|
});
|
|
@@ -492,7 +497,14 @@ export function composeRuntime(options) {
|
|
|
492
497
|
emitRooms();
|
|
493
498
|
}
|
|
494
499
|
function unlistedHeld(listed) {
|
|
495
|
-
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));
|
|
496
508
|
}
|
|
497
509
|
async function dropUnlisted(listed, local) {
|
|
498
510
|
if (unlistedHeld(listed).length === 0)
|
|
@@ -506,9 +518,10 @@ export function composeRuntime(options) {
|
|
|
506
518
|
drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
|
|
507
519
|
}));
|
|
508
520
|
for (const locator of unlisted) {
|
|
509
|
-
for (const batch of replicas.get(locator)?.queue ?? [])
|
|
521
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
510
522
|
pendings.delete(batch.id);
|
|
511
523
|
replicas.delete(locator);
|
|
524
|
+
queuesWithoutReplica.delete(locator);
|
|
512
525
|
rooms.delete(locator);
|
|
513
526
|
roomDrafts.delete(locator);
|
|
514
527
|
transfers.delete(locator);
|
|
@@ -582,6 +595,12 @@ export function composeRuntime(options) {
|
|
|
582
595
|
}
|
|
583
596
|
const previousReplicas = new Map(replicas);
|
|
584
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
|
+
}
|
|
585
604
|
for (const record of records) {
|
|
586
605
|
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
587
606
|
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
@@ -595,7 +614,6 @@ export function composeRuntime(options) {
|
|
|
595
614
|
rooms.set(record.locator, {
|
|
596
615
|
...existing,
|
|
597
616
|
roomVersion: record.roomVersion,
|
|
598
|
-
attachments: record.attachments,
|
|
599
617
|
operations: record.operations,
|
|
600
618
|
replica: record.replica,
|
|
601
619
|
model: modelTypes(model),
|
|
@@ -668,11 +686,12 @@ export function composeRuntime(options) {
|
|
|
668
686
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
669
687
|
}
|
|
670
688
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
671
|
-
const
|
|
689
|
+
const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
690
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
|
|
672
691
|
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
673
692
|
const unqueued = withdrawn.map((batch) => ({
|
|
674
693
|
batch,
|
|
675
|
-
returned: landed(batch, confirmed) ? null :
|
|
694
|
+
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
676
695
|
}));
|
|
677
696
|
const returning = [
|
|
678
697
|
...[...unqueued].reverse(),
|
|
@@ -693,27 +712,44 @@ export function composeRuntime(options) {
|
|
|
693
712
|
});
|
|
694
713
|
}
|
|
695
714
|
}
|
|
696
|
-
await local.
|
|
697
|
-
|
|
698
|
-
previous: before?.confirmed.record ?? null,
|
|
699
|
-
...(settling.stored === undefined ? {} : { stored: settling.stored }),
|
|
700
|
-
queue: kept,
|
|
701
|
-
previousQueue: before?.queue ?? [],
|
|
702
|
-
roomDrafts: [...written.values()],
|
|
703
|
-
});
|
|
704
|
-
for (const batch of before?.queue ?? []) {
|
|
715
|
+
await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
|
|
716
|
+
for (const batch of previousQueue) {
|
|
705
717
|
if (!kept.includes(batch))
|
|
706
718
|
pendings.delete(batch.id);
|
|
707
719
|
}
|
|
708
720
|
for (const record of written.values())
|
|
709
721
|
rememberRoomDraft(record);
|
|
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);
|
|
710
745
|
for (const { batch, returned } of unqueued) {
|
|
711
|
-
|
|
746
|
+
const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
|
|
747
|
+
journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
|
|
712
748
|
}
|
|
713
749
|
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
714
750
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
715
751
|
room: settling.room,
|
|
716
|
-
queue: kept.length !==
|
|
752
|
+
queue: kept.length !== previousQueue.length,
|
|
717
753
|
drafts: [...written.keys()],
|
|
718
754
|
});
|
|
719
755
|
}
|
|
@@ -760,7 +796,7 @@ export function composeRuntime(options) {
|
|
|
760
796
|
return false;
|
|
761
797
|
}
|
|
762
798
|
adoptModels(room.locator, confirmed.record);
|
|
763
|
-
const queue = prior?.queue ?? [];
|
|
799
|
+
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
764
800
|
const roomVersion = confirmed.record.roomVersion;
|
|
765
801
|
const accepted = queue.filter((batch) => {
|
|
766
802
|
const accepted = pendings.get(batch.id)?.accepted;
|
|
@@ -787,22 +823,40 @@ export function composeRuntime(options) {
|
|
|
787
823
|
const replica = replicas.get(locator);
|
|
788
824
|
const room = rooms.get(locator);
|
|
789
825
|
if (replica === undefined || room === undefined) {
|
|
790
|
-
throw
|
|
826
|
+
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
791
827
|
}
|
|
792
828
|
const saved = [];
|
|
793
829
|
for (const { path, revision } of named) {
|
|
794
830
|
const record = roomDrafts.get(locator)?.get(path);
|
|
795
831
|
if (record === undefined || record.revision !== revision) {
|
|
796
|
-
return { outcome: 'refused',
|
|
832
|
+
return { outcome: 'refused', code: 'stale', retryable: false, file: path };
|
|
797
833
|
}
|
|
798
834
|
if (!saved.some((draft) => draft.path === path))
|
|
799
835
|
saved.push({ path, parts: record.parts });
|
|
800
836
|
}
|
|
801
837
|
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
802
838
|
if (changed.length > 0)
|
|
803
|
-
return { outcome: 'refused',
|
|
839
|
+
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
804
840
|
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
805
|
-
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,
|
|
806
860
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
807
861
|
composition: compositionFor(replica.confirmed.record),
|
|
808
862
|
operations: room.operations,
|
|
@@ -810,29 +864,86 @@ export function composeRuntime(options) {
|
|
|
810
864
|
providerLimits: limits,
|
|
811
865
|
handle: room.handle,
|
|
812
866
|
visible: replica.visible,
|
|
813
|
-
changes
|
|
867
|
+
changes,
|
|
814
868
|
bases: parts.flatMap(({ bases }) => bases),
|
|
815
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 }];
|
|
816
877
|
});
|
|
817
|
-
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')
|
|
818
896
|
return { outcome: 'refused', ...built.refusal };
|
|
819
|
-
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
|
+
}
|
|
820
928
|
const consumed = saved.map(({ path }) => path);
|
|
821
929
|
try {
|
|
822
|
-
await local.
|
|
823
|
-
|
|
824
|
-
previous: replica.confirmed.record,
|
|
930
|
+
await local.keepAuthored({
|
|
931
|
+
locator,
|
|
825
932
|
queue: batches,
|
|
826
|
-
|
|
827
|
-
saved,
|
|
933
|
+
saved: drafted,
|
|
828
934
|
consumedRoomDrafts: consumed,
|
|
829
935
|
});
|
|
830
936
|
}
|
|
831
937
|
catch {
|
|
832
|
-
return { outcome: 'refused',
|
|
938
|
+
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
833
939
|
}
|
|
834
|
-
|
|
835
|
-
|
|
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 });
|
|
944
|
+
}
|
|
945
|
+
if (!localSaves)
|
|
946
|
+
recorded = false;
|
|
836
947
|
for (const path of consumed)
|
|
837
948
|
forgetRoomDraft(locator, path);
|
|
838
949
|
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
@@ -840,9 +951,12 @@ export function composeRuntime(options) {
|
|
|
840
951
|
queue: true,
|
|
841
952
|
drafts: consumed,
|
|
842
953
|
}));
|
|
843
|
-
moveTo({ ...saving(status), ...attention() });
|
|
954
|
+
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
844
955
|
submitLater(locator);
|
|
845
|
-
|
|
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 };
|
|
846
960
|
}
|
|
847
961
|
async function mark(local, locator, identifier, submission, lost) {
|
|
848
962
|
const state = pendings.get(identifier);
|
|
@@ -868,16 +982,15 @@ export function composeRuntime(options) {
|
|
|
868
982
|
}
|
|
869
983
|
async function readHistory(locator, path, query = {}) {
|
|
870
984
|
if (closed || refusal !== null)
|
|
871
|
-
return
|
|
985
|
+
return UNCERTAIN;
|
|
872
986
|
try {
|
|
873
987
|
await ensureAuthorization();
|
|
874
988
|
}
|
|
875
989
|
catch {
|
|
876
|
-
return
|
|
877
|
-
}
|
|
878
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
879
|
-
return { kind: 'transport-uncertainty' };
|
|
990
|
+
return UNCERTAIN;
|
|
880
991
|
}
|
|
992
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
993
|
+
return UNCERTAIN;
|
|
881
994
|
const transfer = ['transfer', 'history', locator, path];
|
|
882
995
|
if (query.revision !== undefined)
|
|
883
996
|
transfer.push(query.revision);
|
|
@@ -899,13 +1012,97 @@ export function composeRuntime(options) {
|
|
|
899
1012
|
}
|
|
900
1013
|
if (result.kind === 'revoked-authorization')
|
|
901
1014
|
await refuse('revoked-authorization');
|
|
902
|
-
return result;
|
|
1015
|
+
return historyAnswer(result);
|
|
903
1016
|
}
|
|
904
1017
|
catch {
|
|
905
1018
|
journal.record('outcome history transport-uncertainty');
|
|
906
|
-
return
|
|
1019
|
+
return UNCERTAIN;
|
|
907
1020
|
}
|
|
908
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
|
+
}
|
|
909
1106
|
function declaredAgents() {
|
|
910
1107
|
for (const capability of capabilities) {
|
|
911
1108
|
if (capability.conversationAgents !== undefined)
|
|
@@ -925,18 +1122,17 @@ export function composeRuntime(options) {
|
|
|
925
1122
|
async function invokeAction(name, input) {
|
|
926
1123
|
await ready;
|
|
927
1124
|
if (closed || refusal !== null)
|
|
928
|
-
return
|
|
1125
|
+
return UNCERTAIN;
|
|
929
1126
|
if (declaredAction(name) === undefined)
|
|
930
|
-
return
|
|
1127
|
+
return undeclared(name);
|
|
931
1128
|
try {
|
|
932
1129
|
await ensureAuthorization();
|
|
933
1130
|
}
|
|
934
1131
|
catch {
|
|
935
|
-
return
|
|
936
|
-
}
|
|
937
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
938
|
-
return { kind: 'transport-uncertainty' };
|
|
1132
|
+
return UNCERTAIN;
|
|
939
1133
|
}
|
|
1134
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1135
|
+
return UNCERTAIN;
|
|
940
1136
|
journal.record(`transfer action ${name}`);
|
|
941
1137
|
try {
|
|
942
1138
|
const result = await options.adapter.action(authorization, { name, input });
|
|
@@ -950,11 +1146,11 @@ export function composeRuntime(options) {
|
|
|
950
1146
|
journal.record(`outcome action ${result.kind}`);
|
|
951
1147
|
if (result.kind === 'rejected')
|
|
952
1148
|
journal.record(`refusal rejected ${result.code}`);
|
|
953
|
-
return result;
|
|
1149
|
+
return calledResult(result);
|
|
954
1150
|
}
|
|
955
1151
|
catch {
|
|
956
1152
|
journal.record('outcome action transport-uncertainty');
|
|
957
|
-
return
|
|
1153
|
+
return UNCERTAIN;
|
|
958
1154
|
}
|
|
959
1155
|
}
|
|
960
1156
|
function continuing() {
|
|
@@ -1088,6 +1284,7 @@ export function composeRuntime(options) {
|
|
|
1088
1284
|
function refusedSource(room, { refusal }) {
|
|
1089
1285
|
sourceRefusals.set(room.locator, {
|
|
1090
1286
|
code: refusal.rule,
|
|
1287
|
+
retryable: false,
|
|
1091
1288
|
fields: {
|
|
1092
1289
|
file: refusal.file,
|
|
1093
1290
|
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
@@ -1102,8 +1299,16 @@ export function composeRuntime(options) {
|
|
|
1102
1299
|
}
|
|
1103
1300
|
function leaving(batch, result) {
|
|
1104
1301
|
if (result.kind === 'expired')
|
|
1105
|
-
return { batch, returned:
|
|
1106
|
-
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
|
+
};
|
|
1107
1312
|
}
|
|
1108
1313
|
function logAccepted(locator, submitted, accepted) {
|
|
1109
1314
|
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
@@ -1121,7 +1326,7 @@ export function composeRuntime(options) {
|
|
|
1121
1326
|
logAccepted(room.locator, submitted, accepted);
|
|
1122
1327
|
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1123
1328
|
? { kind: 'staged', confirmed: current.confirmed }
|
|
1124
|
-
: applyReturned(current.confirmed,
|
|
1329
|
+
: applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1125
1330
|
? accepted.roomVersion
|
|
1126
1331
|
: held, accepted.roomVersion);
|
|
1127
1332
|
if (applied.kind !== 'staged') {
|
|
@@ -1340,11 +1545,7 @@ export function composeRuntime(options) {
|
|
|
1340
1545
|
const read = reclassified.kind === 'deferred'
|
|
1341
1546
|
? { kind: 'staged', confirmed: confirmedOf(record, held.confirmed) }
|
|
1342
1547
|
: reclassified;
|
|
1343
|
-
await local.write({
|
|
1344
|
-
next: record,
|
|
1345
|
-
previous: held.confirmed.record,
|
|
1346
|
-
queue: held.queue,
|
|
1347
|
-
});
|
|
1548
|
+
await local.write({ next: record, previous: held.confirmed.record });
|
|
1348
1549
|
if (read.kind === 'staged') {
|
|
1349
1550
|
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
|
|
1350
1551
|
if (declarationRefusals.delete(room.locator)) {
|
|
@@ -1371,7 +1572,11 @@ export function composeRuntime(options) {
|
|
|
1371
1572
|
});
|
|
1372
1573
|
}
|
|
1373
1574
|
async function scoped(result, room, local) {
|
|
1374
|
-
scopedRefusals.set(room.locator, {
|
|
1575
|
+
scopedRefusals.set(room.locator, {
|
|
1576
|
+
code: result.code,
|
|
1577
|
+
fields: result.fields,
|
|
1578
|
+
retryable: result.retryable,
|
|
1579
|
+
});
|
|
1375
1580
|
let scopedAway = room;
|
|
1376
1581
|
if (result.scope === 'model')
|
|
1377
1582
|
await retainModel(local, room, 'collection', {
|
|
@@ -1408,7 +1613,6 @@ export function composeRuntime(options) {
|
|
|
1408
1613
|
roomVersion: record.roomVersion,
|
|
1409
1614
|
documents: record.documents,
|
|
1410
1615
|
revisions: record.revisions,
|
|
1411
|
-
attachments: record.attachments,
|
|
1412
1616
|
},
|
|
1413
1617
|
];
|
|
1414
1618
|
});
|
|
@@ -1530,7 +1734,11 @@ export function composeRuntime(options) {
|
|
|
1530
1734
|
}
|
|
1531
1735
|
if (result.kind === 'contract-unavailable') {
|
|
1532
1736
|
journal.record(`connection contract unavailable ${result.code}`);
|
|
1533
|
-
await refuse('missing-required-guarantee', {
|
|
1737
|
+
await refuse('missing-required-guarantee', {
|
|
1738
|
+
code: result.code,
|
|
1739
|
+
fields: result.fields,
|
|
1740
|
+
retryable: result.retryable,
|
|
1741
|
+
});
|
|
1534
1742
|
notifyReady();
|
|
1535
1743
|
return;
|
|
1536
1744
|
}
|
|
@@ -1606,6 +1814,8 @@ export function composeRuntime(options) {
|
|
|
1606
1814
|
}
|
|
1607
1815
|
const progress = { complete: true, reachable: true };
|
|
1608
1816
|
underway = attempt === 'opened' ? progress : null;
|
|
1817
|
+
if (attempt === 'opened')
|
|
1818
|
+
streamed = progress;
|
|
1609
1819
|
complete = false;
|
|
1610
1820
|
recorded = false;
|
|
1611
1821
|
moveTo(transferring(status));
|
|
@@ -1646,9 +1856,14 @@ export function composeRuntime(options) {
|
|
|
1646
1856
|
void refuse('revoked-authorization');
|
|
1647
1857
|
return;
|
|
1648
1858
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
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
|
+
});
|
|
1652
1867
|
}
|
|
1653
1868
|
function applyOpening(expired) {
|
|
1654
1869
|
return task(async () => {
|
|
@@ -1703,6 +1918,8 @@ export function composeRuntime(options) {
|
|
|
1703
1918
|
}
|
|
1704
1919
|
}
|
|
1705
1920
|
function submitLater(locator) {
|
|
1921
|
+
if (localSaves)
|
|
1922
|
+
return;
|
|
1706
1923
|
const running = submitting.get(locator);
|
|
1707
1924
|
if (running !== undefined) {
|
|
1708
1925
|
running.again = true;
|
|
@@ -1782,7 +1999,7 @@ export function composeRuntime(options) {
|
|
|
1782
1999
|
}
|
|
1783
2000
|
}
|
|
1784
2001
|
moveTo({
|
|
1785
|
-
state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
|
|
2002
|
+
state: pending() ? 'pending' : synced && complete ? 'synced' : 'syncing',
|
|
1786
2003
|
lastCompleted,
|
|
1787
2004
|
providerUnreachable: !progress.reachable,
|
|
1788
2005
|
...attention(),
|
|
@@ -1891,20 +2108,15 @@ export function composeRuntime(options) {
|
|
|
1891
2108
|
show();
|
|
1892
2109
|
return;
|
|
1893
2110
|
}
|
|
1894
|
-
const
|
|
1895
|
-
|
|
1896
|
-
moveTo(transferring(resting));
|
|
2111
|
+
const progress = { complete: false, reachable: !status.providerUnreachable };
|
|
2112
|
+
moveTo(transferring(status));
|
|
1897
2113
|
try {
|
|
1898
2114
|
await conversations.catchUp(progress);
|
|
1899
2115
|
}
|
|
1900
2116
|
catch {
|
|
1901
2117
|
progress.reachable = false;
|
|
1902
2118
|
}
|
|
1903
|
-
|
|
1904
|
-
if (!closed) {
|
|
1905
|
-
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
2119
|
+
await rest(progress);
|
|
1908
2120
|
}
|
|
1909
2121
|
function close() {
|
|
1910
2122
|
if (closed)
|
|
@@ -1993,6 +2205,8 @@ export function composeRuntime(options) {
|
|
|
1993
2205
|
}
|
|
1994
2206
|
if (topic === 'conversations')
|
|
1995
2207
|
return conversations.watchList(deliver);
|
|
2208
|
+
if (topic === 'agents')
|
|
2209
|
+
return conversations.watchAgents(deliver);
|
|
1996
2210
|
if (topic === 'presentation') {
|
|
1997
2211
|
const listener = {
|
|
1998
2212
|
app: input[0],
|
|
@@ -2100,20 +2314,20 @@ export function composeRuntime(options) {
|
|
|
2100
2314
|
return exclusive(async () => {
|
|
2101
2315
|
const parts = readParts(input);
|
|
2102
2316
|
if (path === '' || normalize(path) !== path)
|
|
2103
|
-
throw
|
|
2317
|
+
throw runtimeError('room-draft-malformed');
|
|
2104
2318
|
const local = await openLocal();
|
|
2105
2319
|
const current = roomDrafts.get(room)?.get(path);
|
|
2106
2320
|
if ((current?.revision ?? null) !== revision) {
|
|
2107
|
-
return
|
|
2108
|
-
outcome: 'stale',
|
|
2109
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2110
|
-
};
|
|
2321
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2111
2322
|
}
|
|
2323
|
+
const file = parts.file === undefined
|
|
2324
|
+
? undefined
|
|
2325
|
+
: await heldFile(parts.file.blob, binaryBase(replicas.get(room)?.visible, path));
|
|
2112
2326
|
const record = {
|
|
2113
2327
|
locator: room,
|
|
2114
2328
|
path,
|
|
2115
2329
|
revision: roomDraftRevision(),
|
|
2116
|
-
parts: held(current?.parts, parts),
|
|
2330
|
+
parts: held(current?.parts, parts, file),
|
|
2117
2331
|
returned: current?.returned ?? null,
|
|
2118
2332
|
};
|
|
2119
2333
|
await local.keepRoomDraft(record);
|
|
@@ -2127,10 +2341,7 @@ export function composeRuntime(options) {
|
|
|
2127
2341
|
const local = await openLocal();
|
|
2128
2342
|
const current = roomDrafts.get(room)?.get(path);
|
|
2129
2343
|
if (current === undefined || current.revision !== revision) {
|
|
2130
|
-
return
|
|
2131
|
-
outcome: 'stale',
|
|
2132
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2133
|
-
};
|
|
2344
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2134
2345
|
}
|
|
2135
2346
|
await local.discardRoomDraft(room, path);
|
|
2136
2347
|
forgetRoomDraft(room, path);
|
|
@@ -2165,6 +2376,10 @@ export function composeRuntime(options) {
|
|
|
2165
2376
|
history(locator, path, query) {
|
|
2166
2377
|
return readHistory(locator, path, query);
|
|
2167
2378
|
},
|
|
2379
|
+
binaryFile: {
|
|
2380
|
+
read: readBinaryPath,
|
|
2381
|
+
readReference: readBinaryReference,
|
|
2382
|
+
},
|
|
2168
2383
|
call(name, input) {
|
|
2169
2384
|
return invokeAction(name, input);
|
|
2170
2385
|
},
|
|
@@ -2197,6 +2412,7 @@ export function composeRuntime(options) {
|
|
|
2197
2412
|
const reachable = {
|
|
2198
2413
|
md,
|
|
2199
2414
|
watch,
|
|
2415
|
+
referrerScope: (referrer) => options.adapter.referrerScope(referrer),
|
|
2200
2416
|
plan: planned,
|
|
2201
2417
|
query: (plan) => rowsQuery(plan),
|
|
2202
2418
|
watchPlan,
|