@agentshouse/mdruntime 0.1.0-c7f1d9323e816 → 0.1.0-c967d1ca66fee
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 +27 -11
- package/README.md +2 -2
- package/dist/adapter.d.ts +7 -3
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +17 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +88 -35
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +13 -7
- package/dist/change.d.ts +4 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/client.d.ts +4 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +50 -14
- 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 +10 -5
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +189 -91
- package/dist/conversation.d.ts +2 -1
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +2 -1
- 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.map +1 -1
- package/dist/host.js +3 -7
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +7 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +24 -11
- 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 +22 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +35 -1
- package/dist/overlay.d.ts +1 -1
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +19 -15
- package/dist/query.d.ts +2 -0
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -0
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +19 -17
- package/dist/react.d.ts +22 -15
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +8 -6
- package/dist/refresh.d.ts +10 -0
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +33 -24
- package/dist/replica.d.ts +4 -4
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +42 -27
- package/dist/room-draft.d.ts +8 -8
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +13 -22
- package/dist/rows.d.ts +2 -0
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +7 -1
- package/dist/save.d.ts +3 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +38 -21
- 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 +21 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +194 -89
- package/dist/wire.d.ts +3 -3
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +11 -2
- package/package.json +2 -2
package/dist/connection.js
CHANGED
|
@@ -3,17 +3,20 @@ 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 { validRoot } from './collection.js';
|
|
12
|
+
import { runtimeError } from './failure.js';
|
|
11
13
|
import { createLog } from './log.js';
|
|
14
|
+
import { calledResult, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
|
|
12
15
|
import { refreshRoom } from './refresh.js';
|
|
13
16
|
import { buildBatch } from './save.js';
|
|
14
|
-
import { applyReturned, compositionOf,
|
|
15
|
-
import { changedParts, changesOf, held, readParts, returnedInto, underneath, } from './room-draft.js';
|
|
16
|
-
import {
|
|
17
|
+
import { applyReturned, compositionOf, confirmedOf, landed, readReclassified, replicaOf, sameMembership, withdrawals, } from './replica.js';
|
|
18
|
+
import { changedParts, changesOf, held, readParts, returnedBy, returnedInto, staleWrite, underneath, } from './room-draft.js';
|
|
19
|
+
import { same, saving, STARTING, transferring, } from './status.js';
|
|
17
20
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
18
21
|
import { roomDraftRevision } from './identity.js';
|
|
19
22
|
import { beneath, normalize } from './paths.js';
|
|
@@ -34,6 +37,7 @@ function declaredSelection(room) {
|
|
|
34
37
|
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
35
38
|
}
|
|
36
39
|
export function composeRuntime(options) {
|
|
40
|
+
const localSaves = options.adapter.localSaves === true;
|
|
37
41
|
let authorization = options.authorization;
|
|
38
42
|
let opening;
|
|
39
43
|
let store;
|
|
@@ -44,6 +48,7 @@ export function composeRuntime(options) {
|
|
|
44
48
|
let limits = [];
|
|
45
49
|
let capabilities = [];
|
|
46
50
|
const rooms = new Map();
|
|
51
|
+
const counted = new Map();
|
|
47
52
|
const unreadable = new Set();
|
|
48
53
|
const models = new Map();
|
|
49
54
|
const liveModels = new Map();
|
|
@@ -82,6 +87,7 @@ export function composeRuntime(options) {
|
|
|
82
87
|
let withdrawStream;
|
|
83
88
|
const announced = new Map();
|
|
84
89
|
let underway = null;
|
|
90
|
+
let streamed = null;
|
|
85
91
|
let handed = false;
|
|
86
92
|
const submitting = new Map();
|
|
87
93
|
const answering = new Map();
|
|
@@ -107,9 +113,12 @@ export function composeRuntime(options) {
|
|
|
107
113
|
const serviceModeListeners = new Set();
|
|
108
114
|
let member = null;
|
|
109
115
|
const memberListeners = new Set();
|
|
116
|
+
const materialized = new Set();
|
|
117
|
+
let everyRoom = false;
|
|
118
|
+
const transfers = new Map();
|
|
110
119
|
const conversations = createConversationStore({
|
|
111
120
|
agents: declaredAgents,
|
|
112
|
-
documents: (locator) =>
|
|
121
|
+
documents: (locator) => rowsQuery(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
113
122
|
call: (name, input) => invokeAction(name, input),
|
|
114
123
|
durably,
|
|
115
124
|
stream: () => options.adapter.conversations,
|
|
@@ -185,7 +194,7 @@ export function composeRuntime(options) {
|
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
function pending() {
|
|
188
|
-
return [...replicas.values()].some((replica) => replica.queue.length > 0);
|
|
197
|
+
return !localSaves && [...replicas.values()].some((replica) => replica.queue.length > 0);
|
|
189
198
|
}
|
|
190
199
|
function attention() {
|
|
191
200
|
const attentionRequired = [];
|
|
@@ -214,8 +223,24 @@ export function composeRuntime(options) {
|
|
|
214
223
|
}
|
|
215
224
|
return { attentionRequired, reset };
|
|
216
225
|
}
|
|
226
|
+
function documentsIn(locator) {
|
|
227
|
+
const replica = replicas.get(locator);
|
|
228
|
+
if (replica === undefined)
|
|
229
|
+
return 0;
|
|
230
|
+
const roots = replica.confirmed.record.typedSelection?.roots ?? [];
|
|
231
|
+
let count = 0;
|
|
232
|
+
for (const path of Object.keys(replica.visible.documents)) {
|
|
233
|
+
if (markdownPath(path) && !roots.some((root) => beneath(root, path)))
|
|
234
|
+
count += 1;
|
|
235
|
+
}
|
|
236
|
+
return count;
|
|
237
|
+
}
|
|
217
238
|
function listed() {
|
|
218
|
-
return structuredClone([...rooms.values()])
|
|
239
|
+
return structuredClone([...rooms.values()].map((room) => {
|
|
240
|
+
const documents = documentsIn(room.locator);
|
|
241
|
+
counted.set(room.locator, documents);
|
|
242
|
+
return { ...room, documents };
|
|
243
|
+
}));
|
|
219
244
|
}
|
|
220
245
|
function roomFacts(reach = null) {
|
|
221
246
|
const held = [...rooms.values()]
|
|
@@ -270,6 +295,8 @@ export function composeRuntime(options) {
|
|
|
270
295
|
listener(shown);
|
|
271
296
|
}
|
|
272
297
|
function entryOf(batch) {
|
|
298
|
+
if (localSaves)
|
|
299
|
+
return Object.freeze({ batch, state: 'local' });
|
|
273
300
|
const state = pendings.get(batch.id);
|
|
274
301
|
if (state?.accepted !== undefined) {
|
|
275
302
|
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
@@ -290,12 +317,47 @@ export function composeRuntime(options) {
|
|
|
290
317
|
}
|
|
291
318
|
}
|
|
292
319
|
function applyChangeSet(set) {
|
|
320
|
+
const held = everyRoom
|
|
321
|
+
? set
|
|
322
|
+
: { ...set, documents: set.documents.filter(({ locator }) => materialized.has(locator)) };
|
|
323
|
+
readSide.apply(held);
|
|
324
|
+
searchIndex.changed(held);
|
|
325
|
+
}
|
|
326
|
+
function materialize(locators) {
|
|
327
|
+
if (everyRoom)
|
|
328
|
+
return;
|
|
329
|
+
if (locators === null)
|
|
330
|
+
everyRoom = true;
|
|
331
|
+
const sets = [];
|
|
332
|
+
for (const locator of locators ?? [...replicas.keys()]) {
|
|
333
|
+
if (materialized.has(locator))
|
|
334
|
+
continue;
|
|
335
|
+
materialized.add(locator);
|
|
336
|
+
const replica = replicas.get(locator);
|
|
337
|
+
if (replica !== undefined)
|
|
338
|
+
sets.push(changeSet(locator, null, replica));
|
|
339
|
+
}
|
|
340
|
+
if (sets.length === 0)
|
|
341
|
+
return;
|
|
342
|
+
const set = mergeChangeSets(sets);
|
|
293
343
|
readSide.apply(set);
|
|
294
344
|
searchIndex.changed(set);
|
|
295
345
|
}
|
|
346
|
+
function reachedRooms(plan) {
|
|
347
|
+
if (plan.rooms !== null)
|
|
348
|
+
return plan.rooms;
|
|
349
|
+
if (plan.reach === null)
|
|
350
|
+
return null;
|
|
351
|
+
return [...plan.reach.rooms, ...plan.reach.documents.map(roomOfKey)];
|
|
352
|
+
}
|
|
353
|
+
function rowsQuery(plan) {
|
|
354
|
+
materialize(reachedRooms(plan));
|
|
355
|
+
return readSide.query(plan);
|
|
356
|
+
}
|
|
296
357
|
function publishChangeSet(set) {
|
|
297
358
|
applyChangeSet(set);
|
|
298
|
-
if (set.rooms.length > 0
|
|
359
|
+
if (set.rooms.length > 0 ||
|
|
360
|
+
[...new Set(set.documents.map(({ locator }) => locator))].some((locator) => rooms.has(locator) && counted.get(locator) !== documentsIn(locator)))
|
|
299
361
|
emitRooms();
|
|
300
362
|
for (const locator of set.queues)
|
|
301
363
|
emitQueue(locator);
|
|
@@ -369,10 +431,7 @@ export function composeRuntime(options) {
|
|
|
369
431
|
model: revoked,
|
|
370
432
|
selectionModel: revoked,
|
|
371
433
|
};
|
|
372
|
-
|
|
373
|
-
replicas.set(locator, read.kind === 'staged'
|
|
374
|
-
? replicaOf(read.confirmed, before.queue, before)
|
|
375
|
-
: { ...before, confirmed: { ...before.confirmed, record } });
|
|
434
|
+
replicas.set(locator, replicaOf(confirmedOf(record, before.confirmed), before.queue, before));
|
|
376
435
|
}
|
|
377
436
|
rooms.set(locator, {
|
|
378
437
|
...room,
|
|
@@ -389,7 +448,8 @@ export function composeRuntime(options) {
|
|
|
389
448
|
}
|
|
390
449
|
finally {
|
|
391
450
|
refusal = {
|
|
392
|
-
reason,
|
|
451
|
+
code: reason,
|
|
452
|
+
retryable: false,
|
|
393
453
|
reauthorizationOffered: reason === 'revoked-authorization',
|
|
394
454
|
refusal: provider,
|
|
395
455
|
};
|
|
@@ -439,7 +499,7 @@ export function composeRuntime(options) {
|
|
|
439
499
|
emitRooms();
|
|
440
500
|
}
|
|
441
501
|
function unlistedHeld(listed) {
|
|
442
|
-
return [...new Set([...rooms.keys(), ...roomDrafts.keys()])].filter((locator) => !listed.has(locator));
|
|
502
|
+
return [...new Set([...rooms.keys(), ...roomDrafts.keys(), ...transfers.keys()])].filter((locator) => !listed.has(locator));
|
|
443
503
|
}
|
|
444
504
|
async function dropUnlisted(listed, local) {
|
|
445
505
|
if (unlistedHeld(listed).length === 0)
|
|
@@ -458,6 +518,7 @@ export function composeRuntime(options) {
|
|
|
458
518
|
replicas.delete(locator);
|
|
459
519
|
rooms.delete(locator);
|
|
460
520
|
roomDrafts.delete(locator);
|
|
521
|
+
transfers.delete(locator);
|
|
461
522
|
unreadable.delete(locator);
|
|
462
523
|
stale.delete(locator);
|
|
463
524
|
scopedRefusals.delete(locator);
|
|
@@ -494,7 +555,7 @@ export function composeRuntime(options) {
|
|
|
494
555
|
return model;
|
|
495
556
|
}
|
|
496
557
|
async function hydrate(opened) {
|
|
497
|
-
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known,] = await Promise.all([
|
|
558
|
+
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known, staged,] = await Promise.all([
|
|
498
559
|
opened.all(),
|
|
499
560
|
opened.queues(),
|
|
500
561
|
opened.roomDrafts(),
|
|
@@ -505,7 +566,11 @@ export function composeRuntime(options) {
|
|
|
505
566
|
opened.providerLimits(),
|
|
506
567
|
opened.capabilities(),
|
|
507
568
|
opened.member(),
|
|
569
|
+
opened.transfers(),
|
|
508
570
|
]);
|
|
571
|
+
transfers.clear();
|
|
572
|
+
for (const record of staged)
|
|
573
|
+
transfers.set(record.locator, record);
|
|
509
574
|
limits = declared;
|
|
510
575
|
capabilities = named;
|
|
511
576
|
member = known;
|
|
@@ -528,14 +593,7 @@ export function composeRuntime(options) {
|
|
|
528
593
|
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
529
594
|
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
530
595
|
const prior = previousReplicas.get(record.locator);
|
|
531
|
-
const
|
|
532
|
-
const read = readConfirmed(retyped, prior?.confirmed);
|
|
533
|
-
const confirmed = read.kind === 'staged'
|
|
534
|
-
? read.confirmed
|
|
535
|
-
: {
|
|
536
|
-
record: retyped,
|
|
537
|
-
collection: parseCollection(retyped.documents, compositionOf(retyped)),
|
|
538
|
-
};
|
|
596
|
+
const confirmed = confirmedOf({ ...record, model, selectionModel }, prior?.confirmed);
|
|
539
597
|
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
540
598
|
const existing = rooms.get(record.locator);
|
|
541
599
|
if (existing === undefined)
|
|
@@ -556,10 +614,6 @@ export function composeRuntime(options) {
|
|
|
556
614
|
},
|
|
557
615
|
});
|
|
558
616
|
}
|
|
559
|
-
if (read.kind === 'refused') {
|
|
560
|
-
refusedSource(record, read);
|
|
561
|
-
declarationRefusals.add(record.locator);
|
|
562
|
-
}
|
|
563
617
|
}
|
|
564
618
|
return {
|
|
565
619
|
lastCompleted,
|
|
@@ -621,11 +675,11 @@ export function composeRuntime(options) {
|
|
|
621
675
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
622
676
|
}
|
|
623
677
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
624
|
-
const withdrawn = withdrawals(confirmed, queue, withdrawable);
|
|
678
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
|
|
625
679
|
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
626
680
|
const unqueued = withdrawn.map((batch) => ({
|
|
627
681
|
batch,
|
|
628
|
-
returned: landed(batch, confirmed) ? null :
|
|
682
|
+
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
629
683
|
}));
|
|
630
684
|
const returning = [
|
|
631
685
|
...[...unqueued].reverse(),
|
|
@@ -649,6 +703,7 @@ export function composeRuntime(options) {
|
|
|
649
703
|
await local.write({
|
|
650
704
|
next: confirmed.record,
|
|
651
705
|
previous: before?.confirmed.record ?? null,
|
|
706
|
+
...(settling.stored === undefined ? {} : { stored: settling.stored }),
|
|
652
707
|
queue: kept,
|
|
653
708
|
previousQueue: before?.queue ?? [],
|
|
654
709
|
roomDrafts: [...written.values()],
|
|
@@ -660,7 +715,8 @@ export function composeRuntime(options) {
|
|
|
660
715
|
for (const record of written.values())
|
|
661
716
|
rememberRoomDraft(record);
|
|
662
717
|
for (const { batch, returned } of unqueued) {
|
|
663
|
-
|
|
718
|
+
const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
|
|
719
|
+
journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
|
|
664
720
|
}
|
|
665
721
|
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
666
722
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
@@ -700,7 +756,8 @@ export function composeRuntime(options) {
|
|
|
700
756
|
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
701
757
|
};
|
|
702
758
|
}
|
|
703
|
-
function publish(local, room, confirmed,
|
|
759
|
+
function publish(local, room, confirmed, landing = {}) {
|
|
760
|
+
const { base, stored } = landing;
|
|
704
761
|
return exclusive(async () => {
|
|
705
762
|
if (!continuing())
|
|
706
763
|
return true;
|
|
@@ -717,7 +774,9 @@ export function composeRuntime(options) {
|
|
|
717
774
|
const accepted = pendings.get(batch.id)?.accepted;
|
|
718
775
|
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
719
776
|
});
|
|
720
|
-
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true });
|
|
777
|
+
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true, ...(stored === undefined ? {} : { stored }) });
|
|
778
|
+
if (stored !== undefined)
|
|
779
|
+
transfers.delete(room.locator);
|
|
721
780
|
for (const batch of accepted)
|
|
722
781
|
pendings.delete(batch.id);
|
|
723
782
|
stale.delete(room.locator);
|
|
@@ -736,22 +795,23 @@ export function composeRuntime(options) {
|
|
|
736
795
|
const replica = replicas.get(locator);
|
|
737
796
|
const room = rooms.get(locator);
|
|
738
797
|
if (replica === undefined || room === undefined) {
|
|
739
|
-
throw
|
|
798
|
+
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
740
799
|
}
|
|
741
800
|
const saved = [];
|
|
742
801
|
for (const { path, revision } of named) {
|
|
743
802
|
const record = roomDrafts.get(locator)?.get(path);
|
|
744
803
|
if (record === undefined || record.revision !== revision) {
|
|
745
|
-
return { outcome: 'refused',
|
|
804
|
+
return { outcome: 'refused', code: 'stale', retryable: false, file: path };
|
|
746
805
|
}
|
|
747
806
|
if (!saved.some((draft) => draft.path === path))
|
|
748
807
|
saved.push({ path, parts: record.parts });
|
|
749
808
|
}
|
|
750
809
|
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
751
810
|
if (changed.length > 0)
|
|
752
|
-
return { outcome: 'refused',
|
|
811
|
+
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
753
812
|
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
754
813
|
const built = buildBatch({
|
|
814
|
+
local: localSaves,
|
|
755
815
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
756
816
|
composition: compositionFor(replica.confirmed.record),
|
|
757
817
|
operations: room.operations,
|
|
@@ -778,10 +838,11 @@ export function composeRuntime(options) {
|
|
|
778
838
|
});
|
|
779
839
|
}
|
|
780
840
|
catch {
|
|
781
|
-
return { outcome: 'refused',
|
|
841
|
+
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
782
842
|
}
|
|
783
843
|
pendings.set(built.batch.id, { drafts: saved, submission: { state: 'waiting' }, lost: false });
|
|
784
|
-
|
|
844
|
+
if (!localSaves)
|
|
845
|
+
recorded = false;
|
|
785
846
|
for (const path of consumed)
|
|
786
847
|
forgetRoomDraft(locator, path);
|
|
787
848
|
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
@@ -789,7 +850,7 @@ export function composeRuntime(options) {
|
|
|
789
850
|
queue: true,
|
|
790
851
|
drafts: consumed,
|
|
791
852
|
}));
|
|
792
|
-
moveTo({ ...saving(status), ...attention() });
|
|
853
|
+
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
793
854
|
submitLater(locator);
|
|
794
855
|
return { outcome: 'saved', identifier: built.batch.id };
|
|
795
856
|
}
|
|
@@ -817,16 +878,15 @@ export function composeRuntime(options) {
|
|
|
817
878
|
}
|
|
818
879
|
async function readHistory(locator, path, query = {}) {
|
|
819
880
|
if (closed || refusal !== null)
|
|
820
|
-
return
|
|
881
|
+
return UNCERTAIN;
|
|
821
882
|
try {
|
|
822
883
|
await ensureAuthorization();
|
|
823
884
|
}
|
|
824
885
|
catch {
|
|
825
|
-
return
|
|
826
|
-
}
|
|
827
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
828
|
-
return { kind: 'transport-uncertainty' };
|
|
886
|
+
return UNCERTAIN;
|
|
829
887
|
}
|
|
888
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
889
|
+
return UNCERTAIN;
|
|
830
890
|
const transfer = ['transfer', 'history', locator, path];
|
|
831
891
|
if (query.revision !== undefined)
|
|
832
892
|
transfer.push(query.revision);
|
|
@@ -848,11 +908,11 @@ export function composeRuntime(options) {
|
|
|
848
908
|
}
|
|
849
909
|
if (result.kind === 'revoked-authorization')
|
|
850
910
|
await refuse('revoked-authorization');
|
|
851
|
-
return result;
|
|
911
|
+
return historyAnswer(result);
|
|
852
912
|
}
|
|
853
913
|
catch {
|
|
854
914
|
journal.record('outcome history transport-uncertainty');
|
|
855
|
-
return
|
|
915
|
+
return UNCERTAIN;
|
|
856
916
|
}
|
|
857
917
|
}
|
|
858
918
|
function declaredAgents() {
|
|
@@ -874,18 +934,17 @@ export function composeRuntime(options) {
|
|
|
874
934
|
async function invokeAction(name, input) {
|
|
875
935
|
await ready;
|
|
876
936
|
if (closed || refusal !== null)
|
|
877
|
-
return
|
|
937
|
+
return UNCERTAIN;
|
|
878
938
|
if (declaredAction(name) === undefined)
|
|
879
|
-
return
|
|
939
|
+
return undeclared(name);
|
|
880
940
|
try {
|
|
881
941
|
await ensureAuthorization();
|
|
882
942
|
}
|
|
883
943
|
catch {
|
|
884
|
-
return
|
|
885
|
-
}
|
|
886
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
887
|
-
return { kind: 'transport-uncertainty' };
|
|
944
|
+
return UNCERTAIN;
|
|
888
945
|
}
|
|
946
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
947
|
+
return UNCERTAIN;
|
|
889
948
|
journal.record(`transfer action ${name}`);
|
|
890
949
|
try {
|
|
891
950
|
const result = await options.adapter.action(authorization, { name, input });
|
|
@@ -899,11 +958,11 @@ export function composeRuntime(options) {
|
|
|
899
958
|
journal.record(`outcome action ${result.kind}`);
|
|
900
959
|
if (result.kind === 'rejected')
|
|
901
960
|
journal.record(`refusal rejected ${result.code}`);
|
|
902
|
-
return result;
|
|
961
|
+
return calledResult(result);
|
|
903
962
|
}
|
|
904
963
|
catch {
|
|
905
964
|
journal.record('outcome action transport-uncertainty');
|
|
906
|
-
return
|
|
965
|
+
return UNCERTAIN;
|
|
907
966
|
}
|
|
908
967
|
}
|
|
909
968
|
function continuing() {
|
|
@@ -934,14 +993,33 @@ export function composeRuntime(options) {
|
|
|
934
993
|
throw error;
|
|
935
994
|
}
|
|
936
995
|
}
|
|
937
|
-
|
|
996
|
+
function staging(local) {
|
|
997
|
+
return {
|
|
998
|
+
held: (locator) => transfers.get(locator) ?? null,
|
|
999
|
+
staged: (record) => local.staged(record),
|
|
1000
|
+
stage: (record, page) => exclusive(async () => {
|
|
1001
|
+
if (!continuing())
|
|
1002
|
+
return false;
|
|
1003
|
+
await local.stage(record, page);
|
|
1004
|
+
transfers.set(record.locator, record);
|
|
1005
|
+
return true;
|
|
1006
|
+
}),
|
|
1007
|
+
abandon: (locator) => exclusive(async () => {
|
|
1008
|
+
if (!continuing())
|
|
1009
|
+
return;
|
|
1010
|
+
await local.abandon(locator);
|
|
1011
|
+
transfers.delete(locator);
|
|
1012
|
+
}),
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
async function pullBootstrap(authorized, room, local) {
|
|
938
1016
|
if (!continuing())
|
|
939
1017
|
return unconfirmed;
|
|
940
1018
|
try {
|
|
941
1019
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
942
1020
|
collection: modelOf(room.locator),
|
|
943
1021
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
944
|
-
}, (models, held) => remodel(room.locator, models, held), continuing);
|
|
1022
|
+
}, (models, held) => remodel(room.locator, models, held), continuing, staging(local));
|
|
945
1023
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
946
1024
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
947
1025
|
return result;
|
|
@@ -969,7 +1047,7 @@ export function composeRuntime(options) {
|
|
|
969
1047
|
return null;
|
|
970
1048
|
}
|
|
971
1049
|
if (refreshed.kind === 'advanced') {
|
|
972
|
-
if (!(await publish(local, room, refreshed.confirmed, held.confirmed))) {
|
|
1050
|
+
if (!(await publish(local, room, refreshed.confirmed, { base: held.confirmed }))) {
|
|
973
1051
|
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
974
1052
|
const reached = now !== undefined &&
|
|
975
1053
|
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
@@ -981,12 +1059,12 @@ export function composeRuntime(options) {
|
|
|
981
1059
|
if (refreshed.kind !== 'beyond-horizon')
|
|
982
1060
|
return refreshed;
|
|
983
1061
|
}
|
|
984
|
-
const bootstrapped = await pullBootstrap(authorized, room);
|
|
1062
|
+
const bootstrapped = await pullBootstrap(authorized, room, local);
|
|
985
1063
|
if (!continuing())
|
|
986
1064
|
return unconfirmed;
|
|
987
1065
|
if (bootstrapped.kind !== 'staged')
|
|
988
1066
|
return bootstrapped;
|
|
989
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1067
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
990
1068
|
return null;
|
|
991
1069
|
}
|
|
992
1070
|
async function withhold(withheld, room, local, progress) {
|
|
@@ -1018,6 +1096,7 @@ export function composeRuntime(options) {
|
|
|
1018
1096
|
function refusedSource(room, { refusal }) {
|
|
1019
1097
|
sourceRefusals.set(room.locator, {
|
|
1020
1098
|
code: refusal.rule,
|
|
1099
|
+
retryable: false,
|
|
1021
1100
|
fields: {
|
|
1022
1101
|
file: refusal.file,
|
|
1023
1102
|
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
@@ -1032,8 +1111,16 @@ export function composeRuntime(options) {
|
|
|
1032
1111
|
}
|
|
1033
1112
|
function leaving(batch, result) {
|
|
1034
1113
|
if (result.kind === 'expired')
|
|
1035
|
-
return { batch, returned:
|
|
1036
|
-
return {
|
|
1114
|
+
return { batch, returned: returnedBy('expired') };
|
|
1115
|
+
return {
|
|
1116
|
+
batch,
|
|
1117
|
+
returned: {
|
|
1118
|
+
kind: result.kind,
|
|
1119
|
+
code: result.code,
|
|
1120
|
+
fields: result.fields,
|
|
1121
|
+
retryable: result.retryable,
|
|
1122
|
+
},
|
|
1123
|
+
};
|
|
1037
1124
|
}
|
|
1038
1125
|
function logAccepted(locator, submitted, accepted) {
|
|
1039
1126
|
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
@@ -1051,7 +1138,7 @@ export function composeRuntime(options) {
|
|
|
1051
1138
|
logAccepted(room.locator, submitted, accepted);
|
|
1052
1139
|
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1053
1140
|
? { kind: 'staged', confirmed: current.confirmed }
|
|
1054
|
-
: applyReturned(current.confirmed, accepted.documents, accepted.revisions, accepted.removals, accepted.attachments, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1141
|
+
: applyReturned(current.confirmed, accepted.documents, accepted.revisions, accepted.changes, accepted.removals, accepted.attachments, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1055
1142
|
? accepted.roomVersion
|
|
1056
1143
|
: held, accepted.roomVersion);
|
|
1057
1144
|
if (applied.kind !== 'staged') {
|
|
@@ -1259,7 +1346,7 @@ export function composeRuntime(options) {
|
|
|
1259
1346
|
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1260
1347
|
};
|
|
1261
1348
|
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
1262
|
-
?
|
|
1349
|
+
? { kind: 'staged', confirmed: confirmedOf(declared, held.confirmed) }
|
|
1263
1350
|
: readReclassified(declared, held.confirmed);
|
|
1264
1351
|
const record = reclassified.kind === 'staged'
|
|
1265
1352
|
? declared
|
|
@@ -1267,7 +1354,9 @@ export function composeRuntime(options) {
|
|
|
1267
1354
|
...declared,
|
|
1268
1355
|
typedSelection: held.confirmed.record.typedSelection,
|
|
1269
1356
|
};
|
|
1270
|
-
const read = reclassified.kind === 'deferred'
|
|
1357
|
+
const read = reclassified.kind === 'deferred'
|
|
1358
|
+
? { kind: 'staged', confirmed: confirmedOf(record, held.confirmed) }
|
|
1359
|
+
: reclassified;
|
|
1271
1360
|
await local.write({
|
|
1272
1361
|
next: record,
|
|
1273
1362
|
previous: held.confirmed.record,
|
|
@@ -1299,7 +1388,11 @@ export function composeRuntime(options) {
|
|
|
1299
1388
|
});
|
|
1300
1389
|
}
|
|
1301
1390
|
async function scoped(result, room, local) {
|
|
1302
|
-
scopedRefusals.set(room.locator, {
|
|
1391
|
+
scopedRefusals.set(room.locator, {
|
|
1392
|
+
code: result.code,
|
|
1393
|
+
fields: result.fields,
|
|
1394
|
+
retryable: result.retryable,
|
|
1395
|
+
});
|
|
1303
1396
|
let scopedAway = room;
|
|
1304
1397
|
if (result.scope === 'model')
|
|
1305
1398
|
await retainModel(local, room, 'collection', {
|
|
@@ -1416,14 +1509,14 @@ export function composeRuntime(options) {
|
|
|
1416
1509
|
async function reload(room, local, progress) {
|
|
1417
1510
|
if (discoveryPaused || authorization === undefined)
|
|
1418
1511
|
return;
|
|
1419
|
-
const bootstrapped = await pullBootstrap(authorization, room);
|
|
1512
|
+
const bootstrapped = await pullBootstrap(authorization, room, local);
|
|
1420
1513
|
if (!continuing())
|
|
1421
1514
|
return;
|
|
1422
1515
|
if (bootstrapped.kind !== 'staged') {
|
|
1423
1516
|
await withhold(bootstrapped, room, local, progress);
|
|
1424
1517
|
return;
|
|
1425
1518
|
}
|
|
1426
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1519
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
1427
1520
|
await caughtUp(room, local, progress);
|
|
1428
1521
|
}
|
|
1429
1522
|
async function discoverRooms(local, progress, attempt) {
|
|
@@ -1458,7 +1551,11 @@ export function composeRuntime(options) {
|
|
|
1458
1551
|
}
|
|
1459
1552
|
if (result.kind === 'contract-unavailable') {
|
|
1460
1553
|
journal.record(`connection contract unavailable ${result.code}`);
|
|
1461
|
-
await refuse('missing-required-guarantee', {
|
|
1554
|
+
await refuse('missing-required-guarantee', {
|
|
1555
|
+
code: result.code,
|
|
1556
|
+
fields: result.fields,
|
|
1557
|
+
retryable: result.retryable,
|
|
1558
|
+
});
|
|
1462
1559
|
notifyReady();
|
|
1463
1560
|
return;
|
|
1464
1561
|
}
|
|
@@ -1534,6 +1631,8 @@ export function composeRuntime(options) {
|
|
|
1534
1631
|
}
|
|
1535
1632
|
const progress = { complete: true, reachable: true };
|
|
1536
1633
|
underway = attempt === 'opened' ? progress : null;
|
|
1634
|
+
if (attempt === 'opened')
|
|
1635
|
+
streamed = progress;
|
|
1537
1636
|
complete = false;
|
|
1538
1637
|
recorded = false;
|
|
1539
1638
|
moveTo(transferring(status));
|
|
@@ -1574,9 +1673,14 @@ export function composeRuntime(options) {
|
|
|
1574
1673
|
void refuse('revoked-authorization');
|
|
1575
1674
|
return;
|
|
1576
1675
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1676
|
+
complete = false;
|
|
1677
|
+
if (streamed !== null)
|
|
1678
|
+
streamed.complete = false;
|
|
1679
|
+
moveTo({
|
|
1680
|
+
...status,
|
|
1681
|
+
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
1682
|
+
providerUnreachable: status.providerUnreachable || !opened,
|
|
1683
|
+
});
|
|
1580
1684
|
}
|
|
1581
1685
|
function applyOpening(expired) {
|
|
1582
1686
|
return task(async () => {
|
|
@@ -1631,6 +1735,8 @@ export function composeRuntime(options) {
|
|
|
1631
1735
|
}
|
|
1632
1736
|
}
|
|
1633
1737
|
function submitLater(locator) {
|
|
1738
|
+
if (localSaves)
|
|
1739
|
+
return;
|
|
1634
1740
|
const running = submitting.get(locator);
|
|
1635
1741
|
if (running !== undefined) {
|
|
1636
1742
|
running.again = true;
|
|
@@ -1710,7 +1816,7 @@ export function composeRuntime(options) {
|
|
|
1710
1816
|
}
|
|
1711
1817
|
}
|
|
1712
1818
|
moveTo({
|
|
1713
|
-
state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
|
|
1819
|
+
state: pending() ? 'pending' : synced && complete ? 'synced' : 'syncing',
|
|
1714
1820
|
lastCompleted,
|
|
1715
1821
|
providerUnreachable: !progress.reachable,
|
|
1716
1822
|
...attention(),
|
|
@@ -1819,20 +1925,15 @@ export function composeRuntime(options) {
|
|
|
1819
1925
|
show();
|
|
1820
1926
|
return;
|
|
1821
1927
|
}
|
|
1822
|
-
const
|
|
1823
|
-
|
|
1824
|
-
moveTo(transferring(resting));
|
|
1928
|
+
const progress = { complete: false, reachable: !status.providerUnreachable };
|
|
1929
|
+
moveTo(transferring(status));
|
|
1825
1930
|
try {
|
|
1826
1931
|
await conversations.catchUp(progress);
|
|
1827
1932
|
}
|
|
1828
1933
|
catch {
|
|
1829
1934
|
progress.reachable = false;
|
|
1830
1935
|
}
|
|
1831
|
-
|
|
1832
|
-
if (!closed) {
|
|
1833
|
-
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1936
|
+
await rest(progress);
|
|
1836
1937
|
}
|
|
1837
1938
|
function close() {
|
|
1838
1939
|
if (closed)
|
|
@@ -1886,13 +1987,16 @@ export function composeRuntime(options) {
|
|
|
1886
1987
|
return planOf(input, roomFacts(reach), planReach(reach));
|
|
1887
1988
|
}
|
|
1888
1989
|
function watchPlan(plan, deliver) {
|
|
1990
|
+
materialize(reachedRooms(plan));
|
|
1889
1991
|
return readSide.watch(plan, identityOf(plan), deliver);
|
|
1890
1992
|
}
|
|
1891
1993
|
async function searched(input, reach) {
|
|
1892
1994
|
const plan = searchPlanOf(input);
|
|
1893
1995
|
const selected = plan.documents === undefined && reach === null
|
|
1894
1996
|
? null
|
|
1895
|
-
: (await
|
|
1997
|
+
: (await rowsQuery(filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)))).documents;
|
|
1998
|
+
if (selected === null)
|
|
1999
|
+
materialize(null);
|
|
1896
2000
|
return searchIndex.search(reach === null || reach.conversations ? plan : { ...plan, conversations: [] }, selected);
|
|
1897
2001
|
}
|
|
1898
2002
|
const watch = (topic, input, deliver) => {
|
|
@@ -1996,7 +2100,7 @@ export function composeRuntime(options) {
|
|
|
1996
2100
|
rooms() {
|
|
1997
2101
|
return listed();
|
|
1998
2102
|
},
|
|
1999
|
-
query: ((input) =>
|
|
2103
|
+
query: ((input) => rowsQuery(planned(input, null))),
|
|
2000
2104
|
search: (input) => searched(input, null),
|
|
2001
2105
|
types(locator) {
|
|
2002
2106
|
return {
|
|
@@ -2025,14 +2129,11 @@ export function composeRuntime(options) {
|
|
|
2025
2129
|
return exclusive(async () => {
|
|
2026
2130
|
const parts = readParts(input);
|
|
2027
2131
|
if (path === '' || normalize(path) !== path)
|
|
2028
|
-
throw
|
|
2132
|
+
throw runtimeError('room-draft-malformed');
|
|
2029
2133
|
const local = await openLocal();
|
|
2030
2134
|
const current = roomDrafts.get(room)?.get(path);
|
|
2031
2135
|
if ((current?.revision ?? null) !== revision) {
|
|
2032
|
-
return
|
|
2033
|
-
outcome: 'stale',
|
|
2034
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2035
|
-
};
|
|
2136
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2036
2137
|
}
|
|
2037
2138
|
const record = {
|
|
2038
2139
|
locator: room,
|
|
@@ -2052,10 +2153,7 @@ export function composeRuntime(options) {
|
|
|
2052
2153
|
const local = await openLocal();
|
|
2053
2154
|
const current = roomDrafts.get(room)?.get(path);
|
|
2054
2155
|
if (current === undefined || current.revision !== revision) {
|
|
2055
|
-
return
|
|
2056
|
-
outcome: 'stale',
|
|
2057
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2058
|
-
};
|
|
2156
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2059
2157
|
}
|
|
2060
2158
|
await local.discardRoomDraft(room, path);
|
|
2061
2159
|
forgetRoomDraft(room, path);
|
|
@@ -2123,7 +2221,7 @@ export function composeRuntime(options) {
|
|
|
2123
2221
|
md,
|
|
2124
2222
|
watch,
|
|
2125
2223
|
plan: planned,
|
|
2126
|
-
query: (plan) =>
|
|
2224
|
+
query: (plan) => rowsQuery(plan),
|
|
2127
2225
|
watchPlan,
|
|
2128
2226
|
search: searched,
|
|
2129
2227
|
};
|