@agentshouse/mdruntime 0.1.0-c8c4b020a3ec7 → 0.1.0-ca7c26c6bc27e
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 +13 -6
- package/dist/adapter.d.ts +56 -37
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +11 -0
- package/dist/attached-model.d.ts.map +1 -0
- package/dist/attached-model.js +34 -0
- package/dist/bootstrap.d.ts +5 -3
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +2 -2
- package/dist/connection.d.ts +3 -6
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +478 -468
- package/dist/conversation.d.ts +6 -6
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +93 -37
- package/dist/host.d.ts +7 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +32 -21
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/memory.d.ts +10 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +66 -20
- package/dist/model.d.ts +1 -5
- package/dist/model.d.ts.map +1 -1
- package/dist/observation.d.ts +2 -0
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +24 -14
- package/dist/query.d.ts +7 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +4 -3
- package/dist/reach.d.ts +40 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +277 -0
- package/dist/read-side.d.ts +4 -0
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +16 -10
- package/dist/refresh.d.ts +2 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +7 -3
- package/dist/replica.d.ts +2 -1
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +7 -3
- package/dist/room-draft.d.ts +3 -0
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +6 -4
- package/dist/store.d.ts +0 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/wire.d.ts +50 -4
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +50 -45
- package/package.json +2 -2
package/dist/connection.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { attachedModel } from './attached-model.js';
|
|
2
2
|
import { cached, modelTypes, NO_MODEL, sameModel, } from './model.js';
|
|
3
|
-
import {
|
|
3
|
+
import { observing } from './observation.js';
|
|
4
4
|
import { filterOf, identityOf, planOf, } from './query.js';
|
|
5
5
|
import { createReadSide } from './read-side.js';
|
|
6
6
|
import { createSearchIndex, searchPlanOf, } from './search.js';
|
|
@@ -20,37 +20,22 @@ import { roomDraftRevision } from './identity.js';
|
|
|
20
20
|
import { beneath, normalize } from './paths.js';
|
|
21
21
|
import { openRuntimeStore, } from './store.js';
|
|
22
22
|
import { unconfirmed } from './transfer.js';
|
|
23
|
-
|
|
24
|
-
const DEFAULT_DISCOVERY_INTERVAL_MS = 60_000;
|
|
25
|
-
const EMPTY_SCHEMA = { verdict: 'accept', model: { types: {} } };
|
|
23
|
+
import { governs, holds, narrowed, planReach, } from './reach.js';
|
|
26
24
|
const knownCapabilities = new Set(VERSION_1_CAPABILITIES);
|
|
27
25
|
function within(root, path) {
|
|
28
26
|
return root === '' || beneath(root, path);
|
|
29
27
|
}
|
|
30
|
-
function declaredIdentity(model) {
|
|
31
|
-
if (model === undefined)
|
|
32
|
-
return false;
|
|
33
|
-
if (model === null)
|
|
34
|
-
return true;
|
|
35
|
-
return (typeof model.identity === 'string' &&
|
|
36
|
-
model.identity.length > 0 &&
|
|
37
|
-
typeof model.version === 'string' &&
|
|
38
|
-
model.version.length > 0);
|
|
39
|
-
}
|
|
40
28
|
function declaredSelection(room) {
|
|
41
29
|
const selection = room.typedSelection;
|
|
42
30
|
if (selection === undefined)
|
|
43
31
|
return true;
|
|
44
32
|
if (!Array.isArray(selection.roots) || selection.roots.length === 0)
|
|
45
33
|
return false;
|
|
46
|
-
if (!declaredIdentity(selection.attachedModel))
|
|
47
|
-
return false;
|
|
48
34
|
return selection.roots.every((root, index) => validRoot(root) &&
|
|
49
35
|
root !== '' &&
|
|
50
36
|
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
51
37
|
}
|
|
52
38
|
export function composeRuntime(options) {
|
|
53
|
-
const period = options.discoveryIntervalMs ?? DEFAULT_DISCOVERY_INTERVAL_MS;
|
|
54
39
|
let authorization = options.authorization;
|
|
55
40
|
let opening;
|
|
56
41
|
let store;
|
|
@@ -58,7 +43,6 @@ export function composeRuntime(options) {
|
|
|
58
43
|
const pendings = new Map();
|
|
59
44
|
const roomDrafts = new Map();
|
|
60
45
|
const presentations = new Map();
|
|
61
|
-
const wanted = new Map();
|
|
62
46
|
let limits = [];
|
|
63
47
|
let capabilities = [];
|
|
64
48
|
function rewriting() {
|
|
@@ -71,7 +55,6 @@ export function composeRuntime(options) {
|
|
|
71
55
|
const models = new Map();
|
|
72
56
|
const liveModels = new Map();
|
|
73
57
|
const invalidModels = new Set();
|
|
74
|
-
const modelEpoch = new Map();
|
|
75
58
|
const discovered = new Map();
|
|
76
59
|
let roomsListed = false;
|
|
77
60
|
const scopedRefusals = new Map();
|
|
@@ -100,16 +83,19 @@ export function composeRuntime(options) {
|
|
|
100
83
|
let status = STARTING;
|
|
101
84
|
let shown = STARTING;
|
|
102
85
|
let discoveryPaused = false;
|
|
103
|
-
let
|
|
86
|
+
let resetTimer;
|
|
104
87
|
let closed = false;
|
|
105
88
|
let stopping = false;
|
|
106
|
-
let
|
|
107
|
-
|
|
108
|
-
|
|
89
|
+
let withdrawStream;
|
|
90
|
+
const announced = new Map();
|
|
91
|
+
let underway = null;
|
|
92
|
+
const submitting = new Map();
|
|
93
|
+
const answering = new Map();
|
|
94
|
+
let complete = false;
|
|
95
|
+
let recorded = false;
|
|
96
|
+
const stale = new Set();
|
|
109
97
|
let chain = Promise.resolve();
|
|
110
98
|
let mutations = Promise.resolve();
|
|
111
|
-
let inflight = false;
|
|
112
|
-
let queued = false;
|
|
113
99
|
const roomListeners = new Set();
|
|
114
100
|
const refusalListeners = new Set();
|
|
115
101
|
const storageLossListeners = new Set();
|
|
@@ -120,6 +106,7 @@ export function composeRuntime(options) {
|
|
|
120
106
|
const readSide = createReadSide();
|
|
121
107
|
let withdrawConversations;
|
|
122
108
|
let currencyQueued = false;
|
|
109
|
+
let socketOpen = false;
|
|
123
110
|
let serviceMode = 'open';
|
|
124
111
|
let haltedRefusal = false;
|
|
125
112
|
let openSignals = 0;
|
|
@@ -234,11 +221,14 @@ export function composeRuntime(options) {
|
|
|
234
221
|
function listed() {
|
|
235
222
|
return structuredClone([...rooms.values()]);
|
|
236
223
|
}
|
|
237
|
-
function roomFacts() {
|
|
238
|
-
const held = [...rooms.values()]
|
|
224
|
+
function roomFacts(reach = null) {
|
|
225
|
+
const held = [...rooms.values()]
|
|
226
|
+
.filter((room) => holds(reach, room.locator))
|
|
227
|
+
.map((room) => {
|
|
228
|
+
const governed = governs(reach, room);
|
|
239
229
|
const attached = [
|
|
240
|
-
modelOf(room.locator),
|
|
241
|
-
...(
|
|
230
|
+
...(governed.collection ? [modelOf(room.locator)] : []),
|
|
231
|
+
...(governed.selection ? [modelOf(room.locator, 'typed-selection')] : []),
|
|
242
232
|
];
|
|
243
233
|
return {
|
|
244
234
|
locator: room.locator,
|
|
@@ -322,17 +312,30 @@ export function composeRuntime(options) {
|
|
|
322
312
|
}
|
|
323
313
|
}
|
|
324
314
|
function stopTimer() {
|
|
325
|
-
if (
|
|
315
|
+
if (resetTimer === undefined)
|
|
326
316
|
return;
|
|
327
|
-
|
|
328
|
-
|
|
317
|
+
clearTimeout(resetTimer);
|
|
318
|
+
resetTimer = undefined;
|
|
329
319
|
}
|
|
330
|
-
function
|
|
331
|
-
|
|
320
|
+
function armTimer() {
|
|
321
|
+
stopTimer();
|
|
322
|
+
if (closed || stopping)
|
|
323
|
+
return;
|
|
324
|
+
let earliest = null;
|
|
325
|
+
for (const { submission } of pendings.values()) {
|
|
326
|
+
if (submission.state !== 'reset')
|
|
327
|
+
continue;
|
|
328
|
+
const at = Date.parse(submission.reset);
|
|
329
|
+
if (earliest === null || at < earliest)
|
|
330
|
+
earliest = at;
|
|
331
|
+
}
|
|
332
|
+
if (earliest === null)
|
|
332
333
|
return;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
// A Save House held back goes again only once the reset its answer named has passed.
|
|
335
|
+
resetTimer = setTimeout(() => {
|
|
336
|
+
resetTimer = undefined;
|
|
337
|
+
submitAll();
|
|
338
|
+
}, Math.max(0, earliest - Date.now()));
|
|
336
339
|
}
|
|
337
340
|
async function refuse(reason, provider = null) {
|
|
338
341
|
stopTimer();
|
|
@@ -358,7 +361,6 @@ export function composeRuntime(options) {
|
|
|
358
361
|
}
|
|
359
362
|
for (const key of keys) {
|
|
360
363
|
invalidModels.add(key);
|
|
361
|
-
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
362
364
|
models.set(key, revoked);
|
|
363
365
|
liveModels.delete(key);
|
|
364
366
|
}
|
|
@@ -406,13 +408,11 @@ export function composeRuntime(options) {
|
|
|
406
408
|
locator: room.locator,
|
|
407
409
|
operations: room.operations,
|
|
408
410
|
replica: room.replica,
|
|
409
|
-
attachedModel: room.attachedModel,
|
|
410
411
|
model: modelTypes(modelOf(room.locator)),
|
|
411
412
|
typedSelection: selection === null
|
|
412
413
|
? null
|
|
413
414
|
: {
|
|
414
415
|
roots: selection.roots,
|
|
415
|
-
attachedModel: selection.attachedModel,
|
|
416
416
|
model: modelTypes(modelOf(room.locator, 'typed-selection')),
|
|
417
417
|
},
|
|
418
418
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
@@ -429,9 +429,6 @@ export function composeRuntime(options) {
|
|
|
429
429
|
discovered.clear();
|
|
430
430
|
for (const room of listed)
|
|
431
431
|
discovered.set(room.locator, room);
|
|
432
|
-
for (const locator of wanted.keys())
|
|
433
|
-
if (!listedLocators.has(locator))
|
|
434
|
-
wanted.delete(locator);
|
|
435
432
|
await dropUnlisted(listedLocators, local);
|
|
436
433
|
for (const room of listed) {
|
|
437
434
|
const held = replicas.get(room.locator)?.confirmed.record;
|
|
@@ -465,6 +462,7 @@ export function composeRuntime(options) {
|
|
|
465
462
|
rooms.delete(locator);
|
|
466
463
|
roomDrafts.delete(locator);
|
|
467
464
|
unreadable.delete(locator);
|
|
465
|
+
stale.delete(locator);
|
|
468
466
|
scopedRefusals.delete(locator);
|
|
469
467
|
sourceRefusals.delete(locator);
|
|
470
468
|
declarationRefusals.delete(locator);
|
|
@@ -473,7 +471,6 @@ export function composeRuntime(options) {
|
|
|
473
471
|
models.delete(key);
|
|
474
472
|
liveModels.delete(key);
|
|
475
473
|
invalidModels.delete(key);
|
|
476
|
-
modelEpoch.delete(key);
|
|
477
474
|
}
|
|
478
475
|
journal.record(`room unlisted ${locator}`);
|
|
479
476
|
}
|
|
@@ -486,7 +483,6 @@ export function composeRuntime(options) {
|
|
|
486
483
|
if (prior !== undefined &&
|
|
487
484
|
prior.status !== 'unavailable' &&
|
|
488
485
|
(persisted.status === 'unavailable' || !sameModel(prior.snapshot, persisted.snapshot))) {
|
|
489
|
-
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
490
486
|
invalidModels.add(key);
|
|
491
487
|
}
|
|
492
488
|
const model = persisted.status === 'unavailable' ||
|
|
@@ -552,13 +548,11 @@ export function composeRuntime(options) {
|
|
|
552
548
|
attachments: record.attachments,
|
|
553
549
|
operations: record.operations,
|
|
554
550
|
replica: record.replica,
|
|
555
|
-
attachedModel: record.attachedModel,
|
|
556
551
|
model: modelTypes(model),
|
|
557
552
|
typedSelection: record.typedSelection === null
|
|
558
553
|
? null
|
|
559
554
|
: {
|
|
560
555
|
roots: record.typedSelection.roots,
|
|
561
|
-
attachedModel: record.typedSelection.attachedModel,
|
|
562
556
|
model: modelTypes(selectionModel),
|
|
563
557
|
},
|
|
564
558
|
});
|
|
@@ -669,35 +663,66 @@ export function composeRuntime(options) {
|
|
|
669
663
|
drafts: [...written.keys()],
|
|
670
664
|
});
|
|
671
665
|
}
|
|
672
|
-
function
|
|
666
|
+
function adoptModels(locator, record) {
|
|
667
|
+
for (const [attached, model] of [
|
|
668
|
+
['collection', record.model],
|
|
669
|
+
['typed-selection', record.selectionModel],
|
|
670
|
+
]) {
|
|
671
|
+
const key = modelKey(locator, attached);
|
|
672
|
+
models.set(key, model);
|
|
673
|
+
invalidModels.delete(key);
|
|
674
|
+
if (model.status === 'current')
|
|
675
|
+
liveModels.set(key, JSON.stringify(model.snapshot));
|
|
676
|
+
else if (model.status === 'unavailable')
|
|
677
|
+
liveModels.delete(key);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function remodel(locator, answers, held) {
|
|
681
|
+
const taken = (answer, previous, attached) => {
|
|
682
|
+
if (answer === undefined)
|
|
683
|
+
return previous;
|
|
684
|
+
const { state, refused } = attachedModel(answer, previous);
|
|
685
|
+
if (refused !== null) {
|
|
686
|
+
const where = refused.location === undefined ? '' : ` ${refused.location}`;
|
|
687
|
+
journal.record(`model invalid ${locator} ${attached} ${refused.identity} ${refused.rule}${where}`);
|
|
688
|
+
}
|
|
689
|
+
models.set(modelKey(locator, attached), state);
|
|
690
|
+
return state;
|
|
691
|
+
};
|
|
692
|
+
return {
|
|
693
|
+
collection: taken(answers?.collection, held.collection, 'collection'),
|
|
694
|
+
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function publish(local, room, confirmed, base) {
|
|
673
698
|
return exclusive(async () => {
|
|
674
699
|
if (!continuing())
|
|
675
|
-
return;
|
|
700
|
+
return true;
|
|
676
701
|
const prior = replicas.get(room.locator);
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
throw new Error(`Cannot retype staged Room ${room.locator}`);
|
|
702
|
+
if (base !== undefined &&
|
|
703
|
+
(prior?.confirmed.record.roomVersion !== base.record.roomVersion ||
|
|
704
|
+
prior.confirmed.record.revisions !== base.record.revisions)) {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
adoptModels(room.locator, confirmed.record);
|
|
684
708
|
const queue = prior?.queue ?? [];
|
|
685
|
-
const roomVersion =
|
|
709
|
+
const roomVersion = confirmed.record.roomVersion;
|
|
686
710
|
const accepted = queue.filter((batch) => {
|
|
687
711
|
const accepted = pendings.get(batch.id)?.accepted;
|
|
688
712
|
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
689
713
|
});
|
|
690
|
-
const set = await settle(local, room.locator, prior,
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
714
|
+
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true });
|
|
715
|
+
for (const batch of accepted)
|
|
716
|
+
pendings.delete(batch.id);
|
|
717
|
+
stale.delete(room.locator);
|
|
718
|
+
if (unreadable.delete(room.locator))
|
|
719
|
+
scopedRefusals.delete(room.locator);
|
|
696
720
|
sourceRefusals.delete(room.locator);
|
|
697
721
|
declarationRefusals.delete(room.locator);
|
|
698
722
|
list(room, true);
|
|
699
723
|
publishChangeSet(set);
|
|
700
724
|
moveTo({ ...status, ...attention() });
|
|
725
|
+
return true;
|
|
701
726
|
});
|
|
702
727
|
}
|
|
703
728
|
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
@@ -750,6 +775,7 @@ export function composeRuntime(options) {
|
|
|
750
775
|
return { outcome: 'refused', rule: 'durable-write' };
|
|
751
776
|
}
|
|
752
777
|
pendings.set(built.batch.id, { drafts: saved, submission: { state: 'waiting' }, lost: false });
|
|
778
|
+
recorded = false;
|
|
753
779
|
for (const path of consumed)
|
|
754
780
|
forgetRoomDraft(locator, path);
|
|
755
781
|
replicas.set(locator, replicaOf(replica.confirmed, batches, rewriting(), replica));
|
|
@@ -758,7 +784,7 @@ export function composeRuntime(options) {
|
|
|
758
784
|
drafts: consumed,
|
|
759
785
|
}));
|
|
760
786
|
moveTo({ ...saving(status), ...attention() });
|
|
761
|
-
|
|
787
|
+
submitLater(locator);
|
|
762
788
|
return { outcome: 'saved', identifier: built.batch.id };
|
|
763
789
|
}
|
|
764
790
|
async function mark(local, locator, identifier, submission, lost) {
|
|
@@ -857,8 +883,6 @@ export function composeRuntime(options) {
|
|
|
857
883
|
journal.record(`transfer action ${name}`);
|
|
858
884
|
try {
|
|
859
885
|
const result = await options.adapter.action(authorization, { name, input });
|
|
860
|
-
if (result.kind === 'action' && result.at !== undefined)
|
|
861
|
-
want(result.at);
|
|
862
886
|
if (result.kind === 'scoped-refusal') {
|
|
863
887
|
const room = replicas.get(result.locator)?.confirmed.record;
|
|
864
888
|
if (room !== undefined)
|
|
@@ -890,32 +914,11 @@ export function composeRuntime(options) {
|
|
|
890
914
|
options.adapter.reached(locator, written, roomVersion) &&
|
|
891
915
|
!options.adapter.reached(locator, roomVersion, written));
|
|
892
916
|
}
|
|
893
|
-
function
|
|
894
|
-
if (reachedBy(locator, roomVersion))
|
|
895
|
-
return;
|
|
896
|
-
const held = wanted.get(locator);
|
|
897
|
-
if (held === undefined || options.adapter.reached(locator, roomVersion, held)) {
|
|
898
|
-
wanted.set(locator, roomVersion);
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
function reachOf(locator) {
|
|
902
|
-
let target = wanted.get(locator);
|
|
903
|
-
const versions = (replicas.get(locator)?.queue ?? []).flatMap((batch) => {
|
|
904
|
-
const accepted = pendings.get(batch.id)?.accepted;
|
|
905
|
-
return accepted === undefined ? [] : [accepted];
|
|
906
|
-
});
|
|
907
|
-
for (const version of versions) {
|
|
908
|
-
if (target === undefined || options.adapter.reached(locator, version, target)) {
|
|
909
|
-
target = version;
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
return target === undefined || reachedBy(locator, target) ? undefined : target;
|
|
913
|
-
}
|
|
914
|
-
async function pullRefresh(authorized, confirmed, minPosition) {
|
|
917
|
+
async function pullRefresh(authorized, confirmed) {
|
|
915
918
|
if (!continuing())
|
|
916
919
|
return unconfirmed;
|
|
917
920
|
try {
|
|
918
|
-
const result = await refreshRoom(options.adapter, authorized, confirmed,
|
|
921
|
+
const result = await refreshRoom(options.adapter, authorized, confirmed, (models, held) => remodel(confirmed.record.locator, models, held), continuing);
|
|
919
922
|
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
920
923
|
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
921
924
|
return result;
|
|
@@ -932,7 +935,7 @@ export function composeRuntime(options) {
|
|
|
932
935
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
933
936
|
collection: modelOf(room.locator),
|
|
934
937
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
935
|
-
}, continuing);
|
|
938
|
+
}, (models, held) => remodel(room.locator, models, held), continuing);
|
|
936
939
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
937
940
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
938
941
|
return result;
|
|
@@ -945,20 +948,28 @@ export function composeRuntime(options) {
|
|
|
945
948
|
async function advance(room, local, authorized) {
|
|
946
949
|
const held = replicas.get(room.locator);
|
|
947
950
|
if (held !== undefined) {
|
|
948
|
-
const refreshed = await pullRefresh(authorized, held.confirmed
|
|
951
|
+
const refreshed = await pullRefresh(authorized, held.confirmed);
|
|
949
952
|
if (!continuing())
|
|
950
953
|
return unconfirmed;
|
|
951
954
|
if (refreshed.kind === 'still-current') {
|
|
952
955
|
if (sourceRefusals.has(room.locator))
|
|
953
956
|
return unconfirmed;
|
|
957
|
+
stale.delete(room.locator);
|
|
954
958
|
if (unreadable.delete(room.locator)) {
|
|
959
|
+
scopedRefusals.delete(room.locator);
|
|
955
960
|
list(room, true);
|
|
956
961
|
emitRooms();
|
|
957
962
|
}
|
|
958
963
|
return null;
|
|
959
964
|
}
|
|
960
965
|
if (refreshed.kind === 'advanced') {
|
|
961
|
-
await publish(local, room, refreshed.confirmed)
|
|
966
|
+
if (!(await publish(local, room, refreshed.confirmed, held.confirmed))) {
|
|
967
|
+
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
968
|
+
const reached = now !== undefined &&
|
|
969
|
+
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
970
|
+
return reached ? null : advance(room, local, authorized);
|
|
971
|
+
}
|
|
972
|
+
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
962
973
|
return null;
|
|
963
974
|
}
|
|
964
975
|
if (refreshed.kind !== 'beyond-horizon')
|
|
@@ -973,7 +984,7 @@ export function composeRuntime(options) {
|
|
|
973
984
|
return null;
|
|
974
985
|
}
|
|
975
986
|
async function withhold(withheld, room, local, progress) {
|
|
976
|
-
|
|
987
|
+
stale.add(room.locator);
|
|
977
988
|
if (withheld.kind === 'scoped-refusal') {
|
|
978
989
|
await scoped(withheld, room, local);
|
|
979
990
|
}
|
|
@@ -1020,44 +1031,30 @@ export function composeRuntime(options) {
|
|
|
1020
1031
|
(accepted.author === undefined ? '' : ` author ${accepted.author}`) +
|
|
1021
1032
|
(accepted.time === undefined ? '' : ` time ${accepted.time}`));
|
|
1022
1033
|
}
|
|
1023
|
-
async function
|
|
1024
|
-
await exclusive(async () => {
|
|
1025
|
-
if (!continuing())
|
|
1026
|
-
return;
|
|
1027
|
-
const current = replicas.get(room.locator);
|
|
1028
|
-
if (current === undefined || !current.queue.some((batch) => batch.id === submitted.id)) {
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
const state = pendings.get(submitted.id);
|
|
1032
|
-
if (state === undefined)
|
|
1033
|
-
return;
|
|
1034
|
-
await local.accept(room.locator, submitted.id, accepted.roomVersion);
|
|
1035
|
-
pendings.set(submitted.id, { ...state, accepted: accepted.roomVersion });
|
|
1036
|
-
logAccepted(room.locator, submitted, accepted);
|
|
1037
|
-
emitQueue(room.locator);
|
|
1038
|
-
});
|
|
1039
|
-
}
|
|
1040
|
-
async function commitAccept(room, local, submitted, accepted, base, overlay) {
|
|
1034
|
+
async function adoptAccepted(room, local, submitted, accepted) {
|
|
1041
1035
|
if (!continuing())
|
|
1042
1036
|
return unconfirmed;
|
|
1043
1037
|
const current = replicas.get(room.locator);
|
|
1044
1038
|
if (current === undefined || !current.queue.some((batch) => batch.id === submitted.id)) {
|
|
1045
1039
|
return null;
|
|
1046
1040
|
}
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1041
|
+
const held = current.confirmed.record.roomVersion;
|
|
1042
|
+
logAccepted(room.locator, submitted, accepted);
|
|
1043
|
+
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1044
|
+
? { kind: 'staged', confirmed: current.confirmed }
|
|
1045
|
+
: applyReturned(current.confirmed, accepted.documents, accepted.revisions, accepted.removals, accepted.attachments, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1046
|
+
? accepted.roomVersion
|
|
1047
|
+
: held, accepted.roomVersion);
|
|
1048
|
+
if (applied.kind !== 'staged') {
|
|
1049
|
+
const state = pendings.get(submitted.id);
|
|
1050
|
+
await local.accept(room.locator, submitted.id, accepted.roomVersion);
|
|
1051
|
+
if (state !== undefined)
|
|
1052
|
+
pendings.set(submitted.id, { ...state, accepted: accepted.roomVersion });
|
|
1053
|
+
emitQueue(room.locator);
|
|
1057
1054
|
return applied;
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1055
|
+
}
|
|
1056
|
+
const set = await settle(local, room.locator, current, applied.confirmed, current.queue.filter((batch) => batch.id !== submitted.id), { room: true });
|
|
1057
|
+
pendings.delete(submitted.id);
|
|
1061
1058
|
unreadable.delete(room.locator);
|
|
1062
1059
|
sourceRefusals.delete(room.locator);
|
|
1063
1060
|
declarationRefusals.delete(room.locator);
|
|
@@ -1066,17 +1063,8 @@ export function composeRuntime(options) {
|
|
|
1066
1063
|
moveTo({ ...status, ...attention() });
|
|
1067
1064
|
return null;
|
|
1068
1065
|
}
|
|
1069
|
-
async function
|
|
1070
|
-
const
|
|
1071
|
-
if (options.adapter.reached(room.locator, roomVersion, accepted.roomVersion)) {
|
|
1072
|
-
return committed(room, local, submitted, accepted, pulled, roomVersion === accepted.roomVersion);
|
|
1073
|
-
}
|
|
1074
|
-
if (fresh && continuing())
|
|
1075
|
-
await publish(local, room, pulled);
|
|
1076
|
-
return false;
|
|
1077
|
-
}
|
|
1078
|
-
async function committed(room, local, submitted, accepted, base, overlay) {
|
|
1079
|
-
const failed = await exclusive(() => commitAccept(room, local, submitted, accepted, base, overlay));
|
|
1066
|
+
async function adopted(room, local, submitted, accepted) {
|
|
1067
|
+
const failed = await exclusive(() => adoptAccepted(room, local, submitted, accepted));
|
|
1080
1068
|
if (failed === null)
|
|
1081
1069
|
return true;
|
|
1082
1070
|
if (failed.kind === 'refused' && continuing())
|
|
@@ -1089,10 +1077,8 @@ export function composeRuntime(options) {
|
|
|
1089
1077
|
const head = replica?.queue[0];
|
|
1090
1078
|
if (replica === undefined || head === undefined)
|
|
1091
1079
|
return;
|
|
1092
|
-
if (pendings.get(head.id)?.accepted !== undefined)
|
|
1093
|
-
progress.complete = false;
|
|
1080
|
+
if (pendings.get(head.id)?.accepted !== undefined)
|
|
1094
1081
|
return;
|
|
1095
|
-
}
|
|
1096
1082
|
const state = pendings.get(head.id);
|
|
1097
1083
|
const { submission, lost: earlier } = state ?? {
|
|
1098
1084
|
submission: { state: 'waiting' },
|
|
@@ -1120,6 +1106,7 @@ export function composeRuntime(options) {
|
|
|
1120
1106
|
return;
|
|
1121
1107
|
}
|
|
1122
1108
|
const opensBefore = openSignals;
|
|
1109
|
+
const answered = awaitingAnswer(room.locator);
|
|
1123
1110
|
try {
|
|
1124
1111
|
result = await options.adapter.mutate(authorized, {
|
|
1125
1112
|
locator: room.locator,
|
|
@@ -1128,11 +1115,14 @@ export function composeRuntime(options) {
|
|
|
1128
1115
|
}, (upload) => exclusive(() => keepUpload(local, room.locator, head.id, upload)));
|
|
1129
1116
|
}
|
|
1130
1117
|
catch {
|
|
1118
|
+
answered();
|
|
1131
1119
|
progress.complete = false;
|
|
1132
1120
|
if (continuing())
|
|
1133
1121
|
progress.reachable = false;
|
|
1134
1122
|
return;
|
|
1135
1123
|
}
|
|
1124
|
+
if (result.kind !== 'accepted' || !continuing())
|
|
1125
|
+
answered();
|
|
1136
1126
|
if (!continuing())
|
|
1137
1127
|
return;
|
|
1138
1128
|
if (result.kind === 'transport-uncertainty') {
|
|
@@ -1185,6 +1175,7 @@ export function composeRuntime(options) {
|
|
|
1185
1175
|
const reset = result.reset;
|
|
1186
1176
|
progress.complete = false;
|
|
1187
1177
|
await exclusive(() => mark(local, room.locator, head.id, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
1178
|
+
armTimer();
|
|
1188
1179
|
return;
|
|
1189
1180
|
}
|
|
1190
1181
|
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
@@ -1211,95 +1202,33 @@ export function composeRuntime(options) {
|
|
|
1211
1202
|
return withhold(withheld, room, local, progress);
|
|
1212
1203
|
continue;
|
|
1213
1204
|
}
|
|
1214
|
-
await
|
|
1215
|
-
|
|
1216
|
-
if (from === undefined)
|
|
1217
|
-
return;
|
|
1218
|
-
const pulled = await pullRefresh(authorized, from, reachOf(room.locator));
|
|
1219
|
-
if (!continuing())
|
|
1220
|
-
return;
|
|
1221
|
-
if (pulled.kind === 'revoked-authorization') {
|
|
1222
|
-
await refuse('revoked-authorization');
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
if (pulled.kind === 'scoped-refusal') {
|
|
1226
|
-
await scoped(pulled, room, local);
|
|
1205
|
+
const kept = await adopted(room, local, head, result).finally(answered);
|
|
1206
|
+
if (!kept) {
|
|
1227
1207
|
progress.complete = false;
|
|
1228
1208
|
return;
|
|
1229
1209
|
}
|
|
1230
|
-
if (pulled.kind === 'still-current' || pulled.kind === 'advanced') {
|
|
1231
|
-
const fresh = pulled.kind === 'advanced';
|
|
1232
|
-
if (!(await accepting(room, local, head, result, fresh ? pulled.confirmed : from, fresh))) {
|
|
1233
|
-
progress.complete = false;
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1236
|
-
continue;
|
|
1237
|
-
}
|
|
1238
|
-
if (pulled.kind === 'beyond-horizon') {
|
|
1239
|
-
const bootstrapped = await pullBootstrap(authorized, room);
|
|
1240
|
-
if (!continuing())
|
|
1241
|
-
return;
|
|
1242
|
-
if (bootstrapped.kind === 'revoked-authorization') {
|
|
1243
|
-
await refuse('revoked-authorization');
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
if (bootstrapped.kind === 'scoped-refusal') {
|
|
1247
|
-
await scoped(bootstrapped, room, local);
|
|
1248
|
-
progress.complete = false;
|
|
1249
|
-
return;
|
|
1250
|
-
}
|
|
1251
|
-
if (bootstrapped.kind === 'staged') {
|
|
1252
|
-
if (!(await accepting(room, local, head, result, bootstrapped.confirmed, true))) {
|
|
1253
|
-
progress.complete = false;
|
|
1254
|
-
return;
|
|
1255
|
-
}
|
|
1256
|
-
continue;
|
|
1257
|
-
}
|
|
1258
|
-
progress.complete = false;
|
|
1259
|
-
if (bootstrapped.kind === 'transport-uncertainty') {
|
|
1260
|
-
progress.reachable = false;
|
|
1261
|
-
}
|
|
1262
|
-
else if (bootstrapped.kind === 'room-not-readable') {
|
|
1263
|
-
unreadable.add(room.locator);
|
|
1264
|
-
list(room, false);
|
|
1265
|
-
emitRooms();
|
|
1266
|
-
}
|
|
1267
|
-
else if (bootstrapped.kind === 'refused') {
|
|
1268
|
-
refusedSource(room, bootstrapped);
|
|
1269
|
-
}
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
progress.complete = false;
|
|
1273
|
-
if (pulled.kind === 'transport-uncertainty') {
|
|
1274
|
-
progress.reachable = false;
|
|
1275
|
-
}
|
|
1276
|
-
else if (pulled.kind === 'room-not-readable') {
|
|
1277
|
-
unreadable.add(room.locator);
|
|
1278
|
-
list(room, false);
|
|
1279
|
-
emitRooms();
|
|
1280
|
-
}
|
|
1281
|
-
else if (pulled.kind === 'retryable-refusal') {
|
|
1282
|
-
journal.record(`refusal ${pulled.kind} ${pulled.code}`);
|
|
1283
|
-
}
|
|
1284
|
-
else if (pulled.kind === 'refused') {
|
|
1285
|
-
refusedSource(room, pulled);
|
|
1286
|
-
}
|
|
1287
|
-
return;
|
|
1288
1210
|
}
|
|
1289
|
-
|
|
1211
|
+
}
|
|
1212
|
+
function awaitingAnswer(locator) {
|
|
1213
|
+
let release = () => { };
|
|
1214
|
+
const awaited = new Promise((resolve) => {
|
|
1215
|
+
release = resolve;
|
|
1216
|
+
});
|
|
1217
|
+
answering.set(locator, awaited);
|
|
1218
|
+
return () => {
|
|
1219
|
+
if (answering.get(locator) === awaited)
|
|
1220
|
+
answering.delete(locator);
|
|
1221
|
+
release();
|
|
1222
|
+
};
|
|
1290
1223
|
}
|
|
1291
1224
|
async function retainModel(local, room, attached, next) {
|
|
1292
1225
|
const key = modelKey(room.locator, attached);
|
|
1293
|
-
if (next.status === 'unavailable')
|
|
1294
|
-
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
1295
|
-
const epoch = modelEpoch.get(key) ?? 0;
|
|
1296
1226
|
const previous = modelOf(room.locator, attached);
|
|
1297
1227
|
const heldBefore = replicas.get(room.locator)?.confirmed.record;
|
|
1298
1228
|
if (JSON.stringify(previous) === JSON.stringify(next) &&
|
|
1299
1229
|
(heldBefore === undefined ||
|
|
1300
1230
|
(JSON.stringify(heldBefore.operations) === JSON.stringify(room.operations) &&
|
|
1301
1231
|
JSON.stringify(heldBefore.replica) === JSON.stringify(room.replica) &&
|
|
1302
|
-
JSON.stringify(heldBefore.attachedModel) === JSON.stringify(room.attachedModel) &&
|
|
1303
1232
|
JSON.stringify(heldBefore.typedSelection) ===
|
|
1304
1233
|
JSON.stringify(room.typedSelection ?? null))))
|
|
1305
1234
|
return;
|
|
@@ -1310,15 +1239,12 @@ export function composeRuntime(options) {
|
|
|
1310
1239
|
await exclusive(async () => {
|
|
1311
1240
|
if (!continuing())
|
|
1312
1241
|
return;
|
|
1313
|
-
if (next.status !== 'unavailable' && epoch !== (modelEpoch.get(key) ?? 0))
|
|
1314
|
-
return;
|
|
1315
1242
|
const held = replicas.get(room.locator);
|
|
1316
1243
|
if (held !== undefined) {
|
|
1317
1244
|
const declared = {
|
|
1318
1245
|
...held.confirmed.record,
|
|
1319
1246
|
operations: room.operations,
|
|
1320
1247
|
replica: room.replica,
|
|
1321
|
-
attachedModel: room.attachedModel,
|
|
1322
1248
|
typedSelection: room.typedSelection ?? null,
|
|
1323
1249
|
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1324
1250
|
};
|
|
@@ -1362,77 +1288,6 @@ export function composeRuntime(options) {
|
|
|
1362
1288
|
publishChangeSet(changeSet(room.locator, held, replicas.get(room.locator) ?? null, { room: true }));
|
|
1363
1289
|
});
|
|
1364
1290
|
}
|
|
1365
|
-
async function acquireModel(local, room, authorized, selection) {
|
|
1366
|
-
const attached = selection === null ? 'collection' : 'typed-selection';
|
|
1367
|
-
const key = modelKey(room.locator, attached);
|
|
1368
|
-
const declared = selection === null ? room.attachedModel : selection.attachedModel;
|
|
1369
|
-
let previous = modelOf(room.locator, attached);
|
|
1370
|
-
if (previous.status !== 'unavailable' &&
|
|
1371
|
-
(declared === null || !sameModel(previous.snapshot, declared))) {
|
|
1372
|
-
previous = { status: 'unavailable', reason: 'version-changed' };
|
|
1373
|
-
await retainModel(local, room, attached, previous);
|
|
1374
|
-
if (discoveryPaused)
|
|
1375
|
-
return;
|
|
1376
|
-
}
|
|
1377
|
-
const epoch = modelEpoch.get(key) ?? 0;
|
|
1378
|
-
let answer;
|
|
1379
|
-
try {
|
|
1380
|
-
answer = await options.adapter.model(authorized, selection === null
|
|
1381
|
-
? { locator: room.locator, attached: 'collection', model: declared }
|
|
1382
|
-
: {
|
|
1383
|
-
locator: room.locator,
|
|
1384
|
-
attached: 'typed-selection',
|
|
1385
|
-
roots: selection.roots,
|
|
1386
|
-
model: declared,
|
|
1387
|
-
});
|
|
1388
|
-
}
|
|
1389
|
-
catch {
|
|
1390
|
-
answer = { kind: 'transport-uncertainty' };
|
|
1391
|
-
}
|
|
1392
|
-
if (discoveryPaused || epoch !== (modelEpoch.get(key) ?? 0))
|
|
1393
|
-
return;
|
|
1394
|
-
if (answer.kind === 'revoked-authorization') {
|
|
1395
|
-
await refuse('revoked-authorization');
|
|
1396
|
-
return;
|
|
1397
|
-
}
|
|
1398
|
-
if (answer.kind === 'transport-uncertainty') {
|
|
1399
|
-
await retainModel(local, room, attached, cached(previous));
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
if (answer.kind === 'model-unavailable') {
|
|
1403
|
-
await retainModel(local, room, attached, {
|
|
1404
|
-
status: 'unavailable',
|
|
1405
|
-
reason: answer.reason,
|
|
1406
|
-
});
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
|
-
if (declared === null || !sameModel(answer.snapshot, declared)) {
|
|
1410
|
-
await retainModel(local, room, attached, {
|
|
1411
|
-
status: 'unavailable',
|
|
1412
|
-
reason: 'version-changed',
|
|
1413
|
-
});
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
|
-
if (previous.status !== 'unavailable' &&
|
|
1417
|
-
sameModel(previous.snapshot, answer.snapshot) &&
|
|
1418
|
-
previous.snapshot.schema !== answer.snapshot.schema) {
|
|
1419
|
-
await retainModel(local, room, attached, { status: 'unavailable', reason: 'invalid' });
|
|
1420
|
-
return;
|
|
1421
|
-
}
|
|
1422
|
-
const verified = answer.snapshot.schema === null ? EMPTY_SCHEMA : verify(answer.snapshot.schema);
|
|
1423
|
-
if (verified.verdict === 'refuse') {
|
|
1424
|
-
const where = verified.location === undefined ? '' : ` ${verified.location}`;
|
|
1425
|
-
journal.record(`model invalid ${room.locator} ${attached} ${answer.snapshot.identity} ${verified.rule}${where}`);
|
|
1426
|
-
}
|
|
1427
|
-
await retainModel(local, room, attached, verified.verdict === 'accept'
|
|
1428
|
-
? {
|
|
1429
|
-
status: 'current',
|
|
1430
|
-
snapshot: structuredClone(answer.snapshot),
|
|
1431
|
-
verifiedWith: MD_MODEL,
|
|
1432
|
-
types: verified.model.types,
|
|
1433
|
-
}
|
|
1434
|
-
: { status: 'unavailable', reason: 'invalid' });
|
|
1435
|
-
}
|
|
1436
1291
|
async function scoped(result, room, local) {
|
|
1437
1292
|
scopedRefusals.set(room.locator, { code: result.code, fields: result.fields });
|
|
1438
1293
|
let scopedAway = room;
|
|
@@ -1460,14 +1315,34 @@ export function composeRuntime(options) {
|
|
|
1460
1315
|
emitRooms();
|
|
1461
1316
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
1462
1317
|
}
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1318
|
+
function heldRooms(listed) {
|
|
1319
|
+
return listed.flatMap((room) => {
|
|
1320
|
+
const record = replicas.get(room.locator)?.confirmed.record;
|
|
1321
|
+
if (record === undefined || record.replica.selection !== room.replica.selection)
|
|
1322
|
+
return [];
|
|
1323
|
+
return [
|
|
1324
|
+
{
|
|
1325
|
+
locator: room.locator,
|
|
1326
|
+
roomVersion: record.roomVersion,
|
|
1327
|
+
documents: record.documents,
|
|
1328
|
+
revisions: record.revisions,
|
|
1329
|
+
attachments: record.attachments,
|
|
1330
|
+
},
|
|
1331
|
+
];
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
async function synchronize(listed, local, progress, attempt) {
|
|
1335
|
+
const prepared = await Promise.allSettled(listed.map((room) => prepareRoom(room, local, progress)));
|
|
1336
|
+
const ready = listed.filter((_, index) => {
|
|
1466
1337
|
const outcome = prepared[index];
|
|
1467
1338
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
1468
1339
|
});
|
|
1469
|
-
|
|
1470
|
-
|
|
1340
|
+
if (attempt === 'opened')
|
|
1341
|
+
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
1342
|
+
const loaded = await Promise.allSettled(ready
|
|
1343
|
+
.filter((room) => attempt === 'unstreamed' || !replicas.has(room.locator))
|
|
1344
|
+
.map((room) => loadRoom(room, local, progress)));
|
|
1345
|
+
const failed = [...prepared, ...loaded].find((room) => room.status === 'rejected');
|
|
1471
1346
|
if (failed !== undefined)
|
|
1472
1347
|
throw failed.reason;
|
|
1473
1348
|
}
|
|
@@ -1484,11 +1359,8 @@ export function composeRuntime(options) {
|
|
|
1484
1359
|
progress.complete = false;
|
|
1485
1360
|
return false;
|
|
1486
1361
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
acquireModel(local, room, authorization, null),
|
|
1490
|
-
...(selection === undefined ? [] : [acquireModel(local, room, authorization, selection)]),
|
|
1491
|
-
]);
|
|
1362
|
+
if (prior !== undefined)
|
|
1363
|
+
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
1492
1364
|
if (discoveryPaused || authorization === undefined)
|
|
1493
1365
|
return false;
|
|
1494
1366
|
if (room.replica.admission !== 'admitted') {
|
|
@@ -1496,24 +1368,53 @@ export function composeRuntime(options) {
|
|
|
1496
1368
|
progress.complete = false;
|
|
1497
1369
|
return false;
|
|
1498
1370
|
}
|
|
1499
|
-
const held = replicas.get(room.locator);
|
|
1500
|
-
if (held !== undefined && held.confirmed.record.replica.selection !== room.replica.selection) {
|
|
1501
|
-
progress.complete = false;
|
|
1502
|
-
return false;
|
|
1503
|
-
}
|
|
1504
1371
|
return true;
|
|
1505
1372
|
}
|
|
1506
|
-
async function
|
|
1373
|
+
async function caughtUp(room, local, progress) {
|
|
1374
|
+
const held = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
1375
|
+
if (held === undefined || authorization === undefined)
|
|
1376
|
+
return;
|
|
1377
|
+
const behind = [announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later));
|
|
1378
|
+
if (!behind)
|
|
1379
|
+
return;
|
|
1380
|
+
const withheld = await advance(room, local, authorization);
|
|
1381
|
+
if (withheld !== null)
|
|
1382
|
+
await withhold(withheld, room, local, progress);
|
|
1383
|
+
}
|
|
1384
|
+
function revoked(locator) {
|
|
1385
|
+
return ['collection', 'typed-selection'].some((attached) => {
|
|
1386
|
+
const model = modelOf(locator, attached);
|
|
1387
|
+
return model.status === 'unavailable' && model.reason === 'revoked';
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
async function loadRoom(room, local, progress) {
|
|
1507
1391
|
if (discoveryPaused || authorization === undefined)
|
|
1508
1392
|
return;
|
|
1393
|
+
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
1394
|
+
await reload(room, local, progress);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1509
1397
|
const withheld = await advance(room, local, authorization);
|
|
1510
1398
|
if (withheld !== null) {
|
|
1511
1399
|
await withhold(withheld, room, local, progress);
|
|
1512
1400
|
return;
|
|
1513
1401
|
}
|
|
1514
|
-
await
|
|
1402
|
+
await caughtUp(room, local, progress);
|
|
1515
1403
|
}
|
|
1516
|
-
async function
|
|
1404
|
+
async function reload(room, local, progress) {
|
|
1405
|
+
if (discoveryPaused || authorization === undefined)
|
|
1406
|
+
return;
|
|
1407
|
+
const bootstrapped = await pullBootstrap(authorization, room);
|
|
1408
|
+
if (!continuing())
|
|
1409
|
+
return;
|
|
1410
|
+
if (bootstrapped.kind !== 'staged') {
|
|
1411
|
+
await withhold(bootstrapped, room, local, progress);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
await publish(local, room, bootstrapped.confirmed);
|
|
1415
|
+
await caughtUp(room, local, progress);
|
|
1416
|
+
}
|
|
1417
|
+
async function discoverRooms(local, progress, attempt) {
|
|
1517
1418
|
if (closed || authorization === undefined) {
|
|
1518
1419
|
notifyReady();
|
|
1519
1420
|
return;
|
|
@@ -1539,6 +1440,10 @@ export function composeRuntime(options) {
|
|
|
1539
1440
|
}
|
|
1540
1441
|
}
|
|
1541
1442
|
progress.reachable = false;
|
|
1443
|
+
progress.complete = false;
|
|
1444
|
+
if (attempt === 'opened') {
|
|
1445
|
+
options.adapter.roomStream?.hold(heldRooms([...discovered.values()]));
|
|
1446
|
+
}
|
|
1542
1447
|
notifyReady();
|
|
1543
1448
|
return;
|
|
1544
1449
|
}
|
|
@@ -1571,8 +1476,7 @@ export function composeRuntime(options) {
|
|
|
1571
1476
|
room.replica === undefined ||
|
|
1572
1477
|
typeof room.replica.selection !== 'string' ||
|
|
1573
1478
|
room.replica.selection.length === 0 ||
|
|
1574
|
-
!['admitted', 'unavailable'].includes(room.replica.admission)
|
|
1575
|
-
!declaredIdentity(room.attachedModel)) {
|
|
1479
|
+
!['admitted', 'unavailable'].includes(room.replica.admission)) {
|
|
1576
1480
|
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
1577
1481
|
await refuse('missing-required-guarantee');
|
|
1578
1482
|
notifyReady();
|
|
@@ -1600,26 +1504,13 @@ export function composeRuntime(options) {
|
|
|
1600
1504
|
const previousCapabilities = capabilities;
|
|
1601
1505
|
capabilities = result.capabilities;
|
|
1602
1506
|
notifyReady();
|
|
1603
|
-
if (declared.has('room-version-notifications'))
|
|
1604
|
-
observeRoomVersions();
|
|
1605
1507
|
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
1606
1508
|
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)) {
|
|
1607
1509
|
limits = result.providerLimits;
|
|
1608
1510
|
await local.declare(limits, capabilities);
|
|
1609
1511
|
}
|
|
1610
1512
|
await applyDiscovery(result.rooms, local);
|
|
1611
|
-
|
|
1612
|
-
.filter((room) => room.replica.admission === 'admitted')
|
|
1613
|
-
.map((room) => room.locator)
|
|
1614
|
-
.sort());
|
|
1615
|
-
progress.complete = true;
|
|
1616
|
-
await synchronize(result.rooms, local, progress);
|
|
1617
|
-
if (closed || discoveryPaused)
|
|
1618
|
-
return;
|
|
1619
|
-
await conversations.reconcile();
|
|
1620
|
-
if (status.providerUnreachable)
|
|
1621
|
-
conversations.reconnected();
|
|
1622
|
-
await conversations.catchUp(progress);
|
|
1513
|
+
await synchronize(result.rooms, local, progress, attempt);
|
|
1623
1514
|
}
|
|
1624
1515
|
async function ensureAuthorization() {
|
|
1625
1516
|
if (authorization !== undefined)
|
|
@@ -1633,64 +1524,246 @@ export function composeRuntime(options) {
|
|
|
1633
1524
|
throw error;
|
|
1634
1525
|
}
|
|
1635
1526
|
}
|
|
1636
|
-
|
|
1637
|
-
if (
|
|
1638
|
-
|
|
1527
|
+
function task(work) {
|
|
1528
|
+
if (stopping)
|
|
1529
|
+
return chain;
|
|
1530
|
+
chain = chain.then(work, work).catch(() => undefined);
|
|
1531
|
+
return chain;
|
|
1532
|
+
}
|
|
1533
|
+
function sync(attempt) {
|
|
1534
|
+
return task(async () => {
|
|
1535
|
+
if (closed || stopping || discoveryPaused) {
|
|
1536
|
+
notifyReady();
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
const progress = { complete: true, reachable: true };
|
|
1540
|
+
underway = attempt === 'opened' ? progress : null;
|
|
1541
|
+
complete = false;
|
|
1542
|
+
recorded = false;
|
|
1543
|
+
moveTo(transferring(status));
|
|
1544
|
+
try {
|
|
1545
|
+
const local = await openLocal();
|
|
1546
|
+
if (closed)
|
|
1547
|
+
return;
|
|
1548
|
+
await ensureAuthorization();
|
|
1549
|
+
if (closed)
|
|
1550
|
+
return;
|
|
1551
|
+
await discoverRooms(local, progress, attempt);
|
|
1552
|
+
}
|
|
1553
|
+
catch {
|
|
1554
|
+
progress.complete = false;
|
|
1555
|
+
if (attempt === 'opened') {
|
|
1556
|
+
options.adapter.roomStream?.hold(heldRooms([...discovered.values()]));
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
finally {
|
|
1560
|
+
notifyReady();
|
|
1561
|
+
}
|
|
1562
|
+
if (attempt === 'opened')
|
|
1563
|
+
return;
|
|
1564
|
+
await finishOpening(progress);
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
function closedSocket(revoked) {
|
|
1568
|
+
const opened = socketOpen;
|
|
1569
|
+
socketOpen = false;
|
|
1570
|
+
if (revoked) {
|
|
1571
|
+
void refuse('revoked-authorization');
|
|
1639
1572
|
return;
|
|
1640
1573
|
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
if (closed)
|
|
1574
|
+
if (opened || status.providerUnreachable)
|
|
1575
|
+
return;
|
|
1576
|
+
moveTo({ ...status, providerUnreachable: true });
|
|
1577
|
+
}
|
|
1578
|
+
function applyOpening(expired) {
|
|
1579
|
+
return task(async () => {
|
|
1580
|
+
const progress = underway;
|
|
1581
|
+
underway = null;
|
|
1582
|
+
if (closed || stopping)
|
|
1650
1583
|
return;
|
|
1651
|
-
|
|
1652
|
-
|
|
1584
|
+
if (progress === null) {
|
|
1585
|
+
await reloadExpired(expired);
|
|
1653
1586
|
return;
|
|
1654
|
-
if (progress.complete) {
|
|
1655
|
-
completedAt = new Date().toISOString();
|
|
1656
|
-
await local.complete(completedAt);
|
|
1657
1587
|
}
|
|
1588
|
+
try {
|
|
1589
|
+
const local = await openLocal();
|
|
1590
|
+
const held = [...discovered.values()].filter((room) => replicas.has(room.locator));
|
|
1591
|
+
const reloading = held.filter((room) => expired.includes(room.locator) || revoked(room.locator));
|
|
1592
|
+
const reloaded = await Promise.allSettled(reloading.map((room) => reload(room, local, progress)));
|
|
1593
|
+
if (progress.reachable) {
|
|
1594
|
+
for (const room of held)
|
|
1595
|
+
if (!reloading.includes(room))
|
|
1596
|
+
await confirmModels(room, local);
|
|
1597
|
+
}
|
|
1598
|
+
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
1599
|
+
if (failed !== undefined)
|
|
1600
|
+
throw failed.reason;
|
|
1601
|
+
}
|
|
1602
|
+
catch {
|
|
1603
|
+
progress.complete = false;
|
|
1604
|
+
progress.reachable = false;
|
|
1605
|
+
}
|
|
1606
|
+
await finishOpening(progress);
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
async function reloadExpired(expired) {
|
|
1610
|
+
const progress = { complete: true, reachable: true };
|
|
1611
|
+
try {
|
|
1612
|
+
const local = await openLocal();
|
|
1613
|
+
await Promise.allSettled([...discovered.values()]
|
|
1614
|
+
.filter((room) => replicas.has(room.locator) && expired.includes(room.locator))
|
|
1615
|
+
.map((room) => reload(room, local, progress)));
|
|
1658
1616
|
}
|
|
1659
1617
|
catch {
|
|
1660
|
-
progress.
|
|
1661
|
-
|
|
1618
|
+
progress.reachable = false;
|
|
1619
|
+
}
|
|
1620
|
+
await rest(progress);
|
|
1621
|
+
}
|
|
1622
|
+
async function confirmModels(room, local) {
|
|
1623
|
+
for (const attached of ['collection', 'typed-selection']) {
|
|
1624
|
+
const model = modelOf(room.locator, attached);
|
|
1625
|
+
if (model.status === 'cached') {
|
|
1626
|
+
await retainModel(local, room, attached, { ...model, status: 'current' });
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
function submitLater(locator) {
|
|
1631
|
+
const running = submitting.get(locator);
|
|
1632
|
+
if (running !== undefined) {
|
|
1633
|
+
running.again = true;
|
|
1662
1634
|
return;
|
|
1663
1635
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1636
|
+
const run = { again: false, done: Promise.resolve() };
|
|
1637
|
+
submitting.set(locator, run);
|
|
1638
|
+
run.done = (async () => {
|
|
1639
|
+
try {
|
|
1640
|
+
do {
|
|
1641
|
+
run.again = false;
|
|
1642
|
+
const room = discovered.get(locator);
|
|
1643
|
+
if (closed || stopping || discoveryPaused || room === undefined)
|
|
1644
|
+
return;
|
|
1645
|
+
const progress = { complete: true, reachable: true };
|
|
1646
|
+
try {
|
|
1647
|
+
await ensureAuthorization();
|
|
1648
|
+
if (authorization === undefined)
|
|
1649
|
+
return;
|
|
1650
|
+
await submitRoom(room, await openLocal(), authorization, progress);
|
|
1651
|
+
}
|
|
1652
|
+
catch {
|
|
1653
|
+
progress.reachable = false;
|
|
1654
|
+
}
|
|
1655
|
+
armTimer();
|
|
1656
|
+
await rest(progress);
|
|
1657
|
+
} while (run.again);
|
|
1658
|
+
}
|
|
1659
|
+
finally {
|
|
1660
|
+
submitting.delete(locator);
|
|
1661
|
+
}
|
|
1662
|
+
})();
|
|
1663
|
+
}
|
|
1664
|
+
function submitAll() {
|
|
1665
|
+
for (const [locator, replica] of replicas) {
|
|
1666
|
+
if (replica.queue.length > 0)
|
|
1667
|
+
submitLater(locator);
|
|
1667
1668
|
}
|
|
1668
1669
|
}
|
|
1669
|
-
async function
|
|
1670
|
+
async function finishOpening(progress) {
|
|
1670
1671
|
try {
|
|
1671
|
-
|
|
1672
|
+
if (closed || discoveryPaused) {
|
|
1673
|
+
progress.complete = false;
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
submitAll();
|
|
1677
|
+
await conversations.reconcile();
|
|
1678
|
+
if (status.providerUnreachable)
|
|
1679
|
+
conversations.reconnected();
|
|
1680
|
+
const reading = { complete: true, reachable: true };
|
|
1681
|
+
await conversations.catchUp(reading);
|
|
1682
|
+
if (!reading.reachable)
|
|
1683
|
+
progress.reachable = false;
|
|
1684
|
+
}
|
|
1685
|
+
catch {
|
|
1686
|
+
progress.complete = false;
|
|
1672
1687
|
}
|
|
1673
1688
|
finally {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
queued = false;
|
|
1677
|
-
void enqueue();
|
|
1678
|
-
}
|
|
1689
|
+
complete = progress.complete;
|
|
1690
|
+
await rest(progress);
|
|
1679
1691
|
}
|
|
1680
1692
|
}
|
|
1681
|
-
function
|
|
1682
|
-
|
|
1683
|
-
if (path === undefined || withdrawNotifications !== undefined)
|
|
1693
|
+
async function rest(progress) {
|
|
1694
|
+
if (closed)
|
|
1684
1695
|
return;
|
|
1685
|
-
|
|
1686
|
-
|
|
1696
|
+
let lastCompleted = status.lastCompleted;
|
|
1697
|
+
let synced = complete && stale.size === 0 && !pending();
|
|
1698
|
+
if (synced && !recorded) {
|
|
1699
|
+
const completedAt = new Date().toISOString();
|
|
1700
|
+
try {
|
|
1701
|
+
await (await openLocal()).complete(completedAt);
|
|
1702
|
+
recorded = true;
|
|
1703
|
+
lastCompleted = completedAt;
|
|
1704
|
+
}
|
|
1705
|
+
catch {
|
|
1706
|
+
synced = false;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
moveTo({
|
|
1710
|
+
state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
|
|
1711
|
+
lastCompleted,
|
|
1712
|
+
providerUnreachable: !progress.reachable,
|
|
1713
|
+
...attention(),
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
function applyRoom(locator, roomVersion) {
|
|
1717
|
+
announced.set(locator, roomVersion);
|
|
1718
|
+
return task(async () => {
|
|
1719
|
+
await answering.get(locator);
|
|
1720
|
+
if (closed || stopping || discoveryPaused || authorization === undefined)
|
|
1721
|
+
return;
|
|
1722
|
+
const room = discovered.get(locator);
|
|
1723
|
+
const held = replicas.get(locator);
|
|
1724
|
+
if (room === undefined || room.replica.admission !== 'admitted')
|
|
1687
1725
|
return;
|
|
1688
|
-
if (
|
|
1689
|
-
|
|
1726
|
+
if (held !== undefined &&
|
|
1727
|
+
(held.confirmed.record.replica.selection !== room.replica.selection ||
|
|
1728
|
+
options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion))) {
|
|
1690
1729
|
return;
|
|
1691
1730
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1731
|
+
const during = underway;
|
|
1732
|
+
const progress = during ?? { complete: true, reachable: true };
|
|
1733
|
+
if (during === null)
|
|
1734
|
+
moveTo(transferring(status));
|
|
1735
|
+
try {
|
|
1736
|
+
const local = await openLocal();
|
|
1737
|
+
const withheld = await advance(room, local, authorization);
|
|
1738
|
+
if (withheld !== null)
|
|
1739
|
+
await withhold(withheld, room, local, progress);
|
|
1740
|
+
}
|
|
1741
|
+
catch {
|
|
1742
|
+
stale.add(locator);
|
|
1743
|
+
}
|
|
1744
|
+
finally {
|
|
1745
|
+
if (during === null)
|
|
1746
|
+
await rest(progress);
|
|
1747
|
+
}
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
function observeStream() {
|
|
1751
|
+
const stream = options.adapter.roomStream;
|
|
1752
|
+
if (stream === undefined || withdrawStream !== undefined)
|
|
1753
|
+
return;
|
|
1754
|
+
withdrawStream = stream.observe((event) => {
|
|
1755
|
+
if (closed || stopping)
|
|
1756
|
+
return;
|
|
1757
|
+
if (event.kind === 'opened') {
|
|
1758
|
+
socketOpen = true;
|
|
1759
|
+
void sync('opened');
|
|
1760
|
+
}
|
|
1761
|
+
else if (event.kind === 'closed')
|
|
1762
|
+
closedSocket(event.revoked);
|
|
1763
|
+
else if (event.kind === 'opening')
|
|
1764
|
+
void applyOpening(event.expired);
|
|
1765
|
+
else
|
|
1766
|
+
void applyRoom(event.locator, event.roomVersion);
|
|
1694
1767
|
});
|
|
1695
1768
|
}
|
|
1696
1769
|
function writesHeld() {
|
|
@@ -1711,7 +1784,7 @@ export function composeRuntime(options) {
|
|
|
1711
1784
|
listener(mode);
|
|
1712
1785
|
}
|
|
1713
1786
|
if (releasing)
|
|
1714
|
-
|
|
1787
|
+
submitAll();
|
|
1715
1788
|
}
|
|
1716
1789
|
function observeConversations() {
|
|
1717
1790
|
const stream = options.adapter.conversations;
|
|
@@ -1729,7 +1802,7 @@ export function composeRuntime(options) {
|
|
|
1729
1802
|
if (currencyQueued || closed || stopping)
|
|
1730
1803
|
return;
|
|
1731
1804
|
currencyQueued = true;
|
|
1732
|
-
|
|
1805
|
+
void task(runCurrency);
|
|
1733
1806
|
}
|
|
1734
1807
|
async function runCurrency() {
|
|
1735
1808
|
currencyQueued = false;
|
|
@@ -1755,95 +1828,12 @@ export function composeRuntime(options) {
|
|
|
1755
1828
|
}
|
|
1756
1829
|
}
|
|
1757
1830
|
}
|
|
1758
|
-
function handSelected(locators) {
|
|
1759
|
-
const path = options.adapter.roomVersions;
|
|
1760
|
-
if (path === undefined || withdrawNotifications === undefined)
|
|
1761
|
-
return;
|
|
1762
|
-
if (handed !== undefined && JSON.stringify(locators) === JSON.stringify(handed))
|
|
1763
|
-
return;
|
|
1764
|
-
handed = locators;
|
|
1765
|
-
path.select(locators);
|
|
1766
|
-
}
|
|
1767
|
-
async function runNotified() {
|
|
1768
|
-
const announced = [...notices];
|
|
1769
|
-
notices.clear();
|
|
1770
|
-
if (announced.length === 0 || closed || stopping || discoveryPaused)
|
|
1771
|
-
return;
|
|
1772
|
-
const resting = status;
|
|
1773
|
-
const progress = { complete: false, reachable: !resting.providerUnreachable };
|
|
1774
|
-
moveTo(transferring(resting));
|
|
1775
|
-
try {
|
|
1776
|
-
const local = await openLocal();
|
|
1777
|
-
for (const [locator, roomVersion] of announced) {
|
|
1778
|
-
if (closed || discoveryPaused || authorization === undefined)
|
|
1779
|
-
return;
|
|
1780
|
-
if (handed?.includes(locator) !== true)
|
|
1781
|
-
continue;
|
|
1782
|
-
const room = discovered.get(locator);
|
|
1783
|
-
const held = replicas.get(locator);
|
|
1784
|
-
if (room === undefined)
|
|
1785
|
-
continue;
|
|
1786
|
-
if (sourceRefusals.has(locator)) {
|
|
1787
|
-
void enqueue();
|
|
1788
|
-
continue;
|
|
1789
|
-
}
|
|
1790
|
-
if (held === undefined)
|
|
1791
|
-
continue;
|
|
1792
|
-
if (held.confirmed.record.roomVersion === roomVersion)
|
|
1793
|
-
continue;
|
|
1794
|
-
const withheld = await advance(room, local, authorization);
|
|
1795
|
-
if (withheld === null)
|
|
1796
|
-
continue;
|
|
1797
|
-
if (withheld.kind === 'refused') {
|
|
1798
|
-
void enqueue();
|
|
1799
|
-
continue;
|
|
1800
|
-
}
|
|
1801
|
-
if (withheld.kind === 'scoped-refusal') {
|
|
1802
|
-
await scoped(withheld, room, local);
|
|
1803
|
-
continue;
|
|
1804
|
-
}
|
|
1805
|
-
if (withheld.kind === 'transport-uncertainty') {
|
|
1806
|
-
progress.reachable = false;
|
|
1807
|
-
continue;
|
|
1808
|
-
}
|
|
1809
|
-
if (withheld.kind === 'room-not-readable') {
|
|
1810
|
-
unreadable.add(locator);
|
|
1811
|
-
list(room, false);
|
|
1812
|
-
emitRooms();
|
|
1813
|
-
continue;
|
|
1814
|
-
}
|
|
1815
|
-
if (withheld.kind === 'revoked-authorization') {
|
|
1816
|
-
await refuse('revoked-authorization');
|
|
1817
|
-
return;
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
}
|
|
1821
|
-
catch {
|
|
1822
|
-
progress.reachable = false;
|
|
1823
|
-
}
|
|
1824
|
-
finally {
|
|
1825
|
-
if (!closed) {
|
|
1826
|
-
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
function enqueue() {
|
|
1831
|
-
if (stopping)
|
|
1832
|
-
return chain;
|
|
1833
|
-
if (inflight) {
|
|
1834
|
-
queued = true;
|
|
1835
|
-
return chain;
|
|
1836
|
-
}
|
|
1837
|
-
inflight = true;
|
|
1838
|
-
chain = chain.then(run, run);
|
|
1839
|
-
return chain;
|
|
1840
|
-
}
|
|
1841
1831
|
function close() {
|
|
1842
1832
|
if (closed)
|
|
1843
1833
|
return;
|
|
1844
1834
|
closed = true;
|
|
1845
|
-
|
|
1846
|
-
|
|
1835
|
+
withdrawStream?.();
|
|
1836
|
+
withdrawStream = undefined;
|
|
1847
1837
|
withdrawConversations?.();
|
|
1848
1838
|
withdrawConversations = undefined;
|
|
1849
1839
|
withdrawServiceMode?.();
|
|
@@ -1867,6 +1857,7 @@ export function composeRuntime(options) {
|
|
|
1867
1857
|
stopping = true;
|
|
1868
1858
|
stopTimer();
|
|
1869
1859
|
await chain.catch(() => undefined);
|
|
1860
|
+
await Promise.all([...submitting.values()].map((run) => run.done));
|
|
1870
1861
|
await mutations;
|
|
1871
1862
|
close();
|
|
1872
1863
|
}
|
|
@@ -1875,11 +1866,29 @@ export function composeRuntime(options) {
|
|
|
1875
1866
|
options.signal?.addEventListener('abort', close, { once: true });
|
|
1876
1867
|
if (options.signal?.aborted)
|
|
1877
1868
|
close();
|
|
1878
|
-
|
|
1869
|
+
observeStream();
|
|
1870
|
+
void openLocal().then(() => {
|
|
1871
|
+
if (capabilities.length > 0)
|
|
1872
|
+
notifyReady();
|
|
1873
|
+
}, () => {
|
|
1879
1874
|
notifyReady();
|
|
1880
1875
|
});
|
|
1881
|
-
|
|
1882
|
-
|
|
1876
|
+
if (options.adapter.roomStream === undefined)
|
|
1877
|
+
void sync('unstreamed');
|
|
1878
|
+
function planned(input, reach) {
|
|
1879
|
+
return planOf(input, roomFacts(reach), planReach(reach));
|
|
1880
|
+
}
|
|
1881
|
+
function watchPlan(plan, deliver) {
|
|
1882
|
+
return readSide.watch(plan, identityOf(plan), deliver);
|
|
1883
|
+
}
|
|
1884
|
+
async function searched(input, reach) {
|
|
1885
|
+
const plan = searchPlanOf(input);
|
|
1886
|
+
const selected = plan.documents === undefined && reach === null
|
|
1887
|
+
? null
|
|
1888
|
+
: (await readSide.query(filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)))).documents;
|
|
1889
|
+
return searchIndex.search(reach === null || reach.conversations ? plan : { ...plan, conversations: [] }, selected);
|
|
1890
|
+
}
|
|
1891
|
+
const watch = (topic, input, deliver) => {
|
|
1883
1892
|
if (topic === 'room-drafts') {
|
|
1884
1893
|
const listener = { notify: deliver, ready: false };
|
|
1885
1894
|
roomDraftListeners.add(listener);
|
|
@@ -1962,9 +1971,8 @@ export function composeRuntime(options) {
|
|
|
1962
1971
|
queueSubscriptions.delete(queue);
|
|
1963
1972
|
};
|
|
1964
1973
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
}
|
|
1974
|
+
return watchPlan(planned(input[0], null), deliver);
|
|
1975
|
+
};
|
|
1968
1976
|
const md = {
|
|
1969
1977
|
loaded() {
|
|
1970
1978
|
return openLocal().then(() => undefined);
|
|
@@ -1972,15 +1980,8 @@ export function composeRuntime(options) {
|
|
|
1972
1980
|
rooms() {
|
|
1973
1981
|
return listed();
|
|
1974
1982
|
},
|
|
1975
|
-
query: ((input) => readSide.query(
|
|
1976
|
-
|
|
1977
|
-
const plan = searchPlanOf(input);
|
|
1978
|
-
const selected = plan.documents === undefined
|
|
1979
|
-
? null
|
|
1980
|
-
: (await readSide.query(filterOf(plan.documents, roomFacts())))
|
|
1981
|
-
.documents;
|
|
1982
|
-
return searchIndex.search(plan, selected);
|
|
1983
|
-
},
|
|
1983
|
+
query: ((input) => readSide.query(planned(input, null))),
|
|
1984
|
+
search: (input) => searched(input, null),
|
|
1984
1985
|
types(locator) {
|
|
1985
1986
|
return {
|
|
1986
1987
|
collection: modelTypes(modelOf(locator)),
|
|
@@ -2087,9 +2088,7 @@ export function composeRuntime(options) {
|
|
|
2087
2088
|
persistence() {
|
|
2088
2089
|
return options.shell.persistentStorage;
|
|
2089
2090
|
},
|
|
2090
|
-
observe: (
|
|
2091
|
-
deliver(structuredClone(message));
|
|
2092
|
-
}), topic === 'query')),
|
|
2091
|
+
observe: observing(watch),
|
|
2093
2092
|
async reauthorize() {
|
|
2094
2093
|
if (refusal === null || !refusal.reauthorizationOffered)
|
|
2095
2094
|
return;
|
|
@@ -2098,8 +2097,19 @@ export function composeRuntime(options) {
|
|
|
2098
2097
|
await mutations;
|
|
2099
2098
|
authorization = undefined;
|
|
2100
2099
|
discoveryPaused = false;
|
|
2101
|
-
|
|
2100
|
+
if (options.adapter.roomStream === undefined)
|
|
2101
|
+
await sync('unstreamed');
|
|
2102
|
+
else
|
|
2103
|
+
await task(ensureAuthorization);
|
|
2102
2104
|
},
|
|
2103
2105
|
};
|
|
2104
|
-
|
|
2106
|
+
const reachable = {
|
|
2107
|
+
md,
|
|
2108
|
+
watch,
|
|
2109
|
+
plan: planned,
|
|
2110
|
+
query: (plan) => readSide.query(plan),
|
|
2111
|
+
watchPlan,
|
|
2112
|
+
search: searched,
|
|
2113
|
+
};
|
|
2114
|
+
return { md, stop, watch, within: (reach) => narrowed(reachable, reach) };
|
|
2105
2115
|
}
|