@agentshouse/mdruntime 0.1.0-c3903a68dcbbf → 0.1.0-c5b50992a4193
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 +75 -41
- package/README.md +11 -11
- package/dist/adapter.d.ts +81 -51
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -9
- package/dist/attached-model.d.ts +11 -0
- package/dist/attached-model.d.ts.map +1 -0
- package/dist/attached-model.js +47 -0
- package/dist/bootstrap.d.ts +5 -3
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +3 -3
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +2 -6
- package/dist/client.d.ts +4 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -16
- package/dist/collection.d.ts +0 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +2 -12
- package/dist/connection.d.ts +11 -28
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +535 -643
- package/dist/conversation.d.ts +22 -11
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +250 -105
- package/dist/host.d.ts +7 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +37 -57
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/memory.d.ts +11 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +84 -27
- package/dist/model.d.ts +11 -11
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +6 -3
- package/dist/observation.d.ts +2 -0
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +24 -14
- package/dist/outcome.d.ts +5 -21
- package/dist/outcome.d.ts.map +1 -1
- package/dist/paths.d.ts +0 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +0 -16
- package/dist/query.d.ts +8 -3
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +75 -36
- package/dist/reach.d.ts +40 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +270 -0
- package/dist/react.d.ts +32 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +62 -0
- package/dist/read-side.d.ts +4 -0
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +116 -54
- 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 +5 -5
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +16 -21
- 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/rows.d.ts +2 -1
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +75 -38
- package/dist/save.d.ts +1 -2
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +92 -51
- package/dist/store.d.ts +6 -16
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +10 -32
- package/dist/wire.d.ts +51 -16
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +50 -65
- package/package.json +14 -2
package/dist/connection.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
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';
|
|
7
|
-
import { ROOM_CONTRACT_VERSION, VERSION_1_CAPABILITIES, } from './adapter.js';
|
|
8
7
|
import { bootstrapRoom } from './bootstrap.js';
|
|
9
8
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
10
9
|
import { createConversationStore, } from './conversation.js';
|
|
11
|
-
import {
|
|
10
|
+
import { parseCollection, validRoot } from './collection.js';
|
|
12
11
|
import { createLog } from './log.js';
|
|
13
12
|
import { refreshRoom } from './refresh.js';
|
|
14
13
|
import { buildBatch } from './save.js';
|
|
@@ -20,52 +19,21 @@ import { roomDraftRevision } from './identity.js';
|
|
|
20
19
|
import { beneath, normalize } from './paths.js';
|
|
21
20
|
import { openRuntimeStore, } from './store.js';
|
|
22
21
|
import { unconfirmed } from './transfer.js';
|
|
23
|
-
|
|
24
|
-
const DEFAULT_DISCOVERY_INTERVAL_MS = 60_000;
|
|
25
|
-
const EMPTY_SCHEMA = { verdict: 'accept', model: { types: {} } };
|
|
26
|
-
const knownCapabilities = new Set(VERSION_1_CAPABILITIES);
|
|
22
|
+
import { governs, holds, narrowed, planReach, } from './reach.js';
|
|
27
23
|
function within(root, path) {
|
|
28
24
|
return root === '' || beneath(root, path);
|
|
29
25
|
}
|
|
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
26
|
function declaredSelection(room) {
|
|
41
27
|
const selection = room.typedSelection;
|
|
42
28
|
if (selection === undefined)
|
|
43
29
|
return true;
|
|
44
30
|
if (!Array.isArray(selection.roots) || selection.roots.length === 0)
|
|
45
31
|
return false;
|
|
46
|
-
if (!declaredIdentity(selection.attachedModel))
|
|
47
|
-
return false;
|
|
48
32
|
return selection.roots.every((root, index) => validRoot(root) &&
|
|
49
33
|
root !== '' &&
|
|
50
34
|
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
51
35
|
}
|
|
52
|
-
function declaredUntypedFolders(room) {
|
|
53
|
-
const roots = room.untypedFolders;
|
|
54
|
-
if (roots === undefined)
|
|
55
|
-
return true;
|
|
56
|
-
if (!Array.isArray(roots) || roots.length === 0)
|
|
57
|
-
return false;
|
|
58
|
-
const selected = room.typedSelection?.roots ?? [];
|
|
59
|
-
return roots.every((root, index) => validRoot(root) &&
|
|
60
|
-
root !== '' &&
|
|
61
|
-
roots.every((other, at) => at === index || !within(other, root)) &&
|
|
62
|
-
selected.every((other) => !within(other, root) && !within(root, other)));
|
|
63
|
-
}
|
|
64
|
-
export function createMdRuntime(options) {
|
|
65
|
-
return composeRuntime(options).md;
|
|
66
|
-
}
|
|
67
36
|
export function composeRuntime(options) {
|
|
68
|
-
const period = options.discoveryIntervalMs ?? DEFAULT_DISCOVERY_INTERVAL_MS;
|
|
69
37
|
let authorization = options.authorization;
|
|
70
38
|
let opening;
|
|
71
39
|
let store;
|
|
@@ -73,23 +41,13 @@ export function composeRuntime(options) {
|
|
|
73
41
|
const pendings = new Map();
|
|
74
42
|
const roomDrafts = new Map();
|
|
75
43
|
const presentations = new Map();
|
|
76
|
-
const outcomes = new Map();
|
|
77
|
-
const outcomeRooms = new Map();
|
|
78
|
-
const wanted = new Map();
|
|
79
44
|
let limits = [];
|
|
80
|
-
let slots = [];
|
|
81
45
|
let capabilities = [];
|
|
82
|
-
function rewriting() {
|
|
83
|
-
return capabilities.some((capability) => capability.name === 'move-reference-rewrites')
|
|
84
|
-
? 'provider'
|
|
85
|
-
: 'runtime';
|
|
86
|
-
}
|
|
87
46
|
const rooms = new Map();
|
|
88
47
|
const unreadable = new Set();
|
|
89
48
|
const models = new Map();
|
|
90
49
|
const liveModels = new Map();
|
|
91
50
|
const invalidModels = new Set();
|
|
92
|
-
const modelEpoch = new Map();
|
|
93
51
|
const discovered = new Map();
|
|
94
52
|
let roomsListed = false;
|
|
95
53
|
const scopedRefusals = new Map();
|
|
@@ -118,16 +76,20 @@ export function composeRuntime(options) {
|
|
|
118
76
|
let status = STARTING;
|
|
119
77
|
let shown = STARTING;
|
|
120
78
|
let discoveryPaused = false;
|
|
121
|
-
let
|
|
79
|
+
let resetTimer;
|
|
122
80
|
let closed = false;
|
|
123
81
|
let stopping = false;
|
|
124
|
-
let
|
|
125
|
-
|
|
126
|
-
|
|
82
|
+
let withdrawStream;
|
|
83
|
+
const announced = new Map();
|
|
84
|
+
let underway = null;
|
|
85
|
+
let handed = false;
|
|
86
|
+
const submitting = new Map();
|
|
87
|
+
const answering = new Map();
|
|
88
|
+
let complete = false;
|
|
89
|
+
let recorded = false;
|
|
90
|
+
const stale = new Set();
|
|
127
91
|
let chain = Promise.resolve();
|
|
128
92
|
let mutations = Promise.resolve();
|
|
129
|
-
let inflight = false;
|
|
130
|
-
let queued = false;
|
|
131
93
|
const roomListeners = new Set();
|
|
132
94
|
const refusalListeners = new Set();
|
|
133
95
|
const storageLossListeners = new Set();
|
|
@@ -138,12 +100,13 @@ export function composeRuntime(options) {
|
|
|
138
100
|
const readSide = createReadSide();
|
|
139
101
|
let withdrawConversations;
|
|
140
102
|
let currencyQueued = false;
|
|
103
|
+
let socketOpen = false;
|
|
141
104
|
let serviceMode = 'open';
|
|
142
105
|
let haltedRefusal = false;
|
|
143
106
|
let openSignals = 0;
|
|
144
107
|
const serviceModeListeners = new Set();
|
|
145
108
|
const conversations = createConversationStore({
|
|
146
|
-
|
|
109
|
+
agents: declaredAgents,
|
|
147
110
|
documents: (locator) => readSide.query(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
148
111
|
call: (name, input) => invokeAction(name, input),
|
|
149
112
|
durably,
|
|
@@ -252,20 +215,14 @@ export function composeRuntime(options) {
|
|
|
252
215
|
function listed() {
|
|
253
216
|
return structuredClone([...rooms.values()]);
|
|
254
217
|
}
|
|
255
|
-
function
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return room === undefined ? [] : [{ room, segment }];
|
|
261
|
-
}),
|
|
262
|
-
})));
|
|
263
|
-
}
|
|
264
|
-
function roomFacts() {
|
|
265
|
-
const held = [...rooms.values()].map((room) => {
|
|
218
|
+
function roomFacts(reach = null) {
|
|
219
|
+
const held = [...rooms.values()]
|
|
220
|
+
.filter((room) => holds(reach, room.locator))
|
|
221
|
+
.map((room) => {
|
|
222
|
+
const governed = governs(reach, room);
|
|
266
223
|
const attached = [
|
|
267
|
-
modelOf(room.locator),
|
|
268
|
-
...(
|
|
224
|
+
...(governed.collection ? [modelOf(room.locator)] : []),
|
|
225
|
+
...(governed.selection ? [modelOf(room.locator, 'typed-selection')] : []),
|
|
269
226
|
];
|
|
270
227
|
return {
|
|
271
228
|
locator: room.locator,
|
|
@@ -306,14 +263,18 @@ export function composeRuntime(options) {
|
|
|
306
263
|
for (const listener of statusListeners)
|
|
307
264
|
listener(shown);
|
|
308
265
|
}
|
|
309
|
-
function
|
|
310
|
-
|
|
266
|
+
function entryOf(batch) {
|
|
267
|
+
const state = pendings.get(batch.id);
|
|
268
|
+
if (state?.accepted !== undefined) {
|
|
269
|
+
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
270
|
+
}
|
|
271
|
+
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
311
272
|
}
|
|
312
273
|
function entriesOf(locator) {
|
|
313
274
|
const replica = replicas.get(locator);
|
|
314
275
|
if (replica === undefined)
|
|
315
276
|
return [];
|
|
316
|
-
return replica.queue.map(
|
|
277
|
+
return replica.queue.map(entryOf);
|
|
317
278
|
}
|
|
318
279
|
function emitQueue(locator) {
|
|
319
280
|
const entries = entriesOf(locator);
|
|
@@ -339,23 +300,32 @@ export function composeRuntime(options) {
|
|
|
339
300
|
for (const app of set.presentations)
|
|
340
301
|
emitPresentation(app);
|
|
341
302
|
conversations.changed(set);
|
|
342
|
-
const cards = declaredCards();
|
|
343
|
-
if (cards !== undefined && set.documents.some((change) => change.locator === cards.locator)) {
|
|
344
|
-
scheduleCurrency();
|
|
345
|
-
}
|
|
346
303
|
}
|
|
347
304
|
function stopTimer() {
|
|
348
|
-
if (
|
|
305
|
+
if (resetTimer === undefined)
|
|
349
306
|
return;
|
|
350
|
-
|
|
351
|
-
|
|
307
|
+
clearTimeout(resetTimer);
|
|
308
|
+
resetTimer = undefined;
|
|
352
309
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
310
|
+
function armTimer() {
|
|
311
|
+
stopTimer();
|
|
312
|
+
if (closed || stopping)
|
|
355
313
|
return;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
314
|
+
let earliest = null;
|
|
315
|
+
for (const { submission } of pendings.values()) {
|
|
316
|
+
if (submission.state !== 'reset')
|
|
317
|
+
continue;
|
|
318
|
+
const at = Date.parse(submission.reset);
|
|
319
|
+
if (earliest === null || at < earliest)
|
|
320
|
+
earliest = at;
|
|
321
|
+
}
|
|
322
|
+
if (earliest === null)
|
|
323
|
+
return;
|
|
324
|
+
// A Save House held back goes again only once the reset its answer named has passed.
|
|
325
|
+
resetTimer = setTimeout(() => {
|
|
326
|
+
resetTimer = undefined;
|
|
327
|
+
submitAll();
|
|
328
|
+
}, Math.max(0, earliest - Date.now()));
|
|
359
329
|
}
|
|
360
330
|
async function refuse(reason, provider = null) {
|
|
361
331
|
stopTimer();
|
|
@@ -381,7 +351,6 @@ export function composeRuntime(options) {
|
|
|
381
351
|
}
|
|
382
352
|
for (const key of keys) {
|
|
383
353
|
invalidModels.add(key);
|
|
384
|
-
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
385
354
|
models.set(key, revoked);
|
|
386
355
|
liveModels.delete(key);
|
|
387
356
|
}
|
|
@@ -396,7 +365,7 @@ export function composeRuntime(options) {
|
|
|
396
365
|
};
|
|
397
366
|
const read = readConfirmed(record, before.confirmed);
|
|
398
367
|
replicas.set(locator, read.kind === 'staged'
|
|
399
|
-
? replicaOf(read.confirmed, before.queue,
|
|
368
|
+
? replicaOf(read.confirmed, before.queue, before)
|
|
400
369
|
: { ...before, confirmed: { ...before.confirmed, record } });
|
|
401
370
|
}
|
|
402
371
|
rooms.set(locator, {
|
|
@@ -427,18 +396,16 @@ export function composeRuntime(options) {
|
|
|
427
396
|
rooms.set(room.locator, {
|
|
428
397
|
name: room.name,
|
|
429
398
|
locator: room.locator,
|
|
399
|
+
handle: room.handle,
|
|
430
400
|
operations: room.operations,
|
|
431
401
|
replica: room.replica,
|
|
432
|
-
attachedModel: room.attachedModel,
|
|
433
402
|
model: modelTypes(modelOf(room.locator)),
|
|
434
403
|
typedSelection: selection === null
|
|
435
404
|
? null
|
|
436
405
|
: {
|
|
437
406
|
roots: selection.roots,
|
|
438
|
-
attachedModel: selection.attachedModel,
|
|
439
407
|
model: modelTypes(modelOf(room.locator, 'typed-selection')),
|
|
440
408
|
},
|
|
441
|
-
untypedFolders: room.untypedFolders ?? [],
|
|
442
409
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
443
410
|
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
444
411
|
attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
|
|
@@ -453,9 +420,6 @@ export function composeRuntime(options) {
|
|
|
453
420
|
discovered.clear();
|
|
454
421
|
for (const room of listed)
|
|
455
422
|
discovered.set(room.locator, room);
|
|
456
|
-
for (const locator of wanted.keys())
|
|
457
|
-
if (!listedLocators.has(locator))
|
|
458
|
-
wanted.delete(locator);
|
|
459
423
|
await dropUnlisted(listedLocators, local);
|
|
460
424
|
for (const room of listed) {
|
|
461
425
|
const held = replicas.get(room.locator)?.confirmed.record;
|
|
@@ -477,12 +441,6 @@ export function composeRuntime(options) {
|
|
|
477
441
|
await exclusive(async () => {
|
|
478
442
|
const unlisted = unlistedHeld(listed);
|
|
479
443
|
await local.dropRooms(unlisted);
|
|
480
|
-
for (const [batch, locator] of outcomeRooms) {
|
|
481
|
-
if (listed.has(locator))
|
|
482
|
-
continue;
|
|
483
|
-
outcomeRooms.delete(batch);
|
|
484
|
-
outcomes.delete(batch);
|
|
485
|
-
}
|
|
486
444
|
const removed = unlisted.map((locator) => changeSet(locator, replicas.get(locator) ?? null, null, {
|
|
487
445
|
room: true,
|
|
488
446
|
queue: true,
|
|
@@ -495,6 +453,7 @@ export function composeRuntime(options) {
|
|
|
495
453
|
rooms.delete(locator);
|
|
496
454
|
roomDrafts.delete(locator);
|
|
497
455
|
unreadable.delete(locator);
|
|
456
|
+
stale.delete(locator);
|
|
498
457
|
scopedRefusals.delete(locator);
|
|
499
458
|
sourceRefusals.delete(locator);
|
|
500
459
|
declarationRefusals.delete(locator);
|
|
@@ -503,7 +462,6 @@ export function composeRuntime(options) {
|
|
|
503
462
|
models.delete(key);
|
|
504
463
|
liveModels.delete(key);
|
|
505
464
|
invalidModels.delete(key);
|
|
506
|
-
modelEpoch.delete(key);
|
|
507
465
|
}
|
|
508
466
|
journal.record(`room unlisted ${locator}`);
|
|
509
467
|
}
|
|
@@ -516,7 +474,6 @@ export function composeRuntime(options) {
|
|
|
516
474
|
if (prior !== undefined &&
|
|
517
475
|
prior.status !== 'unavailable' &&
|
|
518
476
|
(persisted.status === 'unavailable' || !sameModel(prior.snapshot, persisted.snapshot))) {
|
|
519
|
-
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
520
477
|
invalidModels.add(key);
|
|
521
478
|
}
|
|
522
479
|
const model = persisted.status === 'unavailable' ||
|
|
@@ -531,10 +488,9 @@ export function composeRuntime(options) {
|
|
|
531
488
|
return model;
|
|
532
489
|
}
|
|
533
490
|
async function hydrate(opened) {
|
|
534
|
-
const [records, queues,
|
|
491
|
+
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named] = await Promise.all([
|
|
535
492
|
opened.all(),
|
|
536
493
|
opened.queues(),
|
|
537
|
-
opened.outcomes(),
|
|
538
494
|
opened.roomDrafts(),
|
|
539
495
|
opened.presentation(),
|
|
540
496
|
opened.conversations(),
|
|
@@ -542,11 +498,9 @@ export function composeRuntime(options) {
|
|
|
542
498
|
opened.lastCompleted(),
|
|
543
499
|
opened.providerLimits(),
|
|
544
500
|
opened.capabilities(),
|
|
545
|
-
opened.slots(),
|
|
546
501
|
]);
|
|
547
502
|
limits = declared;
|
|
548
503
|
capabilities = named;
|
|
549
|
-
slots = listedSlots;
|
|
550
504
|
roomDrafts.clear();
|
|
551
505
|
for (const record of kept)
|
|
552
506
|
rememberRoomDraft(record);
|
|
@@ -560,12 +514,6 @@ export function composeRuntime(options) {
|
|
|
560
514
|
for (const { batch, state } of queue.pending)
|
|
561
515
|
pendings.set(batch.id, state);
|
|
562
516
|
}
|
|
563
|
-
outcomes.clear();
|
|
564
|
-
outcomeRooms.clear();
|
|
565
|
-
for (const record of recordedOutcomes) {
|
|
566
|
-
outcomes.set(record.id, record.outcome);
|
|
567
|
-
outcomeRooms.set(record.id, record.locator);
|
|
568
|
-
}
|
|
569
517
|
const previousReplicas = new Map(replicas);
|
|
570
518
|
replicas.clear();
|
|
571
519
|
for (const record of records) {
|
|
@@ -580,7 +528,7 @@ export function composeRuntime(options) {
|
|
|
580
528
|
record: retyped,
|
|
581
529
|
collection: parseCollection(retyped.documents, compositionOf(retyped)),
|
|
582
530
|
};
|
|
583
|
-
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [],
|
|
531
|
+
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
584
532
|
const existing = rooms.get(record.locator);
|
|
585
533
|
if (existing === undefined)
|
|
586
534
|
list(record, false);
|
|
@@ -591,16 +539,13 @@ export function composeRuntime(options) {
|
|
|
591
539
|
attachments: record.attachments,
|
|
592
540
|
operations: record.operations,
|
|
593
541
|
replica: record.replica,
|
|
594
|
-
attachedModel: record.attachedModel,
|
|
595
542
|
model: modelTypes(model),
|
|
596
543
|
typedSelection: record.typedSelection === null
|
|
597
544
|
? null
|
|
598
545
|
: {
|
|
599
546
|
roots: record.typedSelection.roots,
|
|
600
|
-
attachedModel: record.typedSelection.attachedModel,
|
|
601
547
|
model: modelTypes(selectionModel),
|
|
602
548
|
},
|
|
603
|
-
untypedFolders: record.untypedFolders,
|
|
604
549
|
});
|
|
605
550
|
}
|
|
606
551
|
if (read.kind === 'refused') {
|
|
@@ -666,10 +611,14 @@ export function composeRuntime(options) {
|
|
|
666
611
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
667
612
|
}
|
|
668
613
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
669
|
-
const withdrawn = withdrawals(confirmed, queue, withdrawable
|
|
614
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable);
|
|
670
615
|
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
616
|
+
const unqueued = withdrawn.map((batch) => ({
|
|
617
|
+
batch,
|
|
618
|
+
returned: landed(batch, confirmed) ? null : { kind: 'withdrawn' },
|
|
619
|
+
}));
|
|
671
620
|
const returning = [
|
|
672
|
-
...
|
|
621
|
+
...[...unqueued].reverse(),
|
|
673
622
|
...(settling.leaving === undefined ? [] : [settling.leaving]),
|
|
674
623
|
];
|
|
675
624
|
const written = new Map();
|
|
@@ -687,69 +636,89 @@ export function composeRuntime(options) {
|
|
|
687
636
|
});
|
|
688
637
|
}
|
|
689
638
|
}
|
|
690
|
-
const settledOutcomes = [
|
|
691
|
-
...(settling.accepted === undefined ? [] : [settling.accepted]),
|
|
692
|
-
...(settling.leaving === undefined
|
|
693
|
-
? []
|
|
694
|
-
: [{ id: settling.leaving.batch.id, outcome: settling.leaving.outcome }]),
|
|
695
|
-
...withdrawn.map((batch) => ({ id: batch.id, outcome: { kind: 'withdrawn' } })),
|
|
696
|
-
];
|
|
697
639
|
await local.write({
|
|
698
640
|
next: confirmed.record,
|
|
699
641
|
previous: before?.confirmed.record ?? null,
|
|
700
642
|
queue: kept,
|
|
701
643
|
previousQueue: before?.queue ?? [],
|
|
702
|
-
outcomes: settledOutcomes,
|
|
703
644
|
roomDrafts: [...written.values()],
|
|
704
645
|
});
|
|
705
|
-
for (const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
pendings.delete(id);
|
|
646
|
+
for (const batch of before?.queue ?? []) {
|
|
647
|
+
if (!kept.includes(batch))
|
|
648
|
+
pendings.delete(batch.id);
|
|
709
649
|
}
|
|
710
650
|
for (const record of written.values())
|
|
711
651
|
rememberRoomDraft(record);
|
|
712
|
-
for (const batch of
|
|
713
|
-
journal.record(`outcome ${locator} ${batch.id} withdrawn`);
|
|
714
|
-
|
|
652
|
+
for (const { batch, returned } of unqueued) {
|
|
653
|
+
journal.record(`outcome ${locator} ${batch.id} ${returned === null ? 'landed' : 'withdrawn'}`);
|
|
654
|
+
}
|
|
655
|
+
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
715
656
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
716
657
|
room: settling.room,
|
|
717
658
|
queue: kept.length !== (before?.queue.length ?? 0),
|
|
718
659
|
drafts: [...written.keys()],
|
|
719
660
|
});
|
|
720
661
|
}
|
|
721
|
-
function
|
|
662
|
+
function adoptModels(locator, record) {
|
|
663
|
+
for (const [attached, model] of [
|
|
664
|
+
['collection', record.model],
|
|
665
|
+
['typed-selection', record.selectionModel],
|
|
666
|
+
]) {
|
|
667
|
+
const key = modelKey(locator, attached);
|
|
668
|
+
models.set(key, model);
|
|
669
|
+
invalidModels.delete(key);
|
|
670
|
+
if (model.status === 'current')
|
|
671
|
+
liveModels.set(key, JSON.stringify(model.snapshot));
|
|
672
|
+
else if (model.status === 'unavailable')
|
|
673
|
+
liveModels.delete(key);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function remodel(locator, answers, held) {
|
|
677
|
+
const taken = (answer, previous, attached) => {
|
|
678
|
+
if (answer === undefined)
|
|
679
|
+
return previous;
|
|
680
|
+
const { state, refused } = attachedModel(answer, previous);
|
|
681
|
+
if (refused !== null) {
|
|
682
|
+
const where = refused.location === undefined ? '' : ` ${refused.location}`;
|
|
683
|
+
journal.record(`model invalid ${locator} ${attached} ${refused.identity} ${refused.rule}${where}`);
|
|
684
|
+
}
|
|
685
|
+
models.set(modelKey(locator, attached), state);
|
|
686
|
+
return state;
|
|
687
|
+
};
|
|
688
|
+
return {
|
|
689
|
+
collection: taken(answers?.collection, held.collection, 'collection'),
|
|
690
|
+
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function publish(local, room, confirmed, base) {
|
|
722
694
|
return exclusive(async () => {
|
|
723
695
|
if (!continuing())
|
|
724
|
-
return;
|
|
696
|
+
return true;
|
|
725
697
|
const prior = replicas.get(room.locator);
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
throw new Error(`Cannot retype staged Room ${room.locator}`);
|
|
698
|
+
if (base !== undefined &&
|
|
699
|
+
(prior?.confirmed.record.roomVersion !== base.record.roomVersion ||
|
|
700
|
+
prior.confirmed.record.revisions !== base.record.revisions)) {
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
adoptModels(room.locator, confirmed.record);
|
|
733
704
|
const queue = prior?.queue ?? [];
|
|
734
|
-
const roomVersion =
|
|
705
|
+
const roomVersion = confirmed.record.roomVersion;
|
|
735
706
|
const accepted = queue.filter((batch) => {
|
|
736
|
-
const
|
|
737
|
-
return (
|
|
738
|
-
options.adapter.reached(room.locator, roomVersion, outcome.roomVersion));
|
|
707
|
+
const accepted = pendings.get(batch.id)?.accepted;
|
|
708
|
+
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
739
709
|
});
|
|
740
|
-
const set = await settle(local, room.locator, prior,
|
|
710
|
+
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true });
|
|
741
711
|
for (const batch of accepted)
|
|
742
712
|
pendings.delete(batch.id);
|
|
743
|
-
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
}
|
|
747
|
-
unreadable.delete(room.locator);
|
|
713
|
+
stale.delete(room.locator);
|
|
714
|
+
if (unreadable.delete(room.locator))
|
|
715
|
+
scopedRefusals.delete(room.locator);
|
|
748
716
|
sourceRefusals.delete(room.locator);
|
|
749
717
|
declarationRefusals.delete(room.locator);
|
|
750
718
|
list(room, true);
|
|
751
719
|
publishChangeSet(set);
|
|
752
720
|
moveTo({ ...status, ...attention() });
|
|
721
|
+
return true;
|
|
753
722
|
});
|
|
754
723
|
}
|
|
755
724
|
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
@@ -778,7 +747,7 @@ export function composeRuntime(options) {
|
|
|
778
747
|
operations: room.operations,
|
|
779
748
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
780
749
|
providerLimits: limits,
|
|
781
|
-
|
|
750
|
+
handle: room.handle,
|
|
782
751
|
visible: replica.visible,
|
|
783
752
|
changes: parts.flatMap(({ changes }) => changes),
|
|
784
753
|
bases: parts.flatMap(({ bases }) => bases),
|
|
@@ -802,15 +771,16 @@ export function composeRuntime(options) {
|
|
|
802
771
|
return { outcome: 'refused', rule: 'durable-write' };
|
|
803
772
|
}
|
|
804
773
|
pendings.set(built.batch.id, { drafts: saved, submission: { state: 'waiting' }, lost: false });
|
|
774
|
+
recorded = false;
|
|
805
775
|
for (const path of consumed)
|
|
806
776
|
forgetRoomDraft(locator, path);
|
|
807
|
-
replicas.set(locator, replicaOf(replica.confirmed, batches,
|
|
777
|
+
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
808
778
|
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
809
779
|
queue: true,
|
|
810
780
|
drafts: consumed,
|
|
811
781
|
}));
|
|
812
782
|
moveTo({ ...saving(status), ...attention() });
|
|
813
|
-
|
|
783
|
+
submitLater(locator);
|
|
814
784
|
return { outcome: 'saved', identifier: built.batch.id };
|
|
815
785
|
}
|
|
816
786
|
async function mark(local, locator, identifier, submission, lost) {
|
|
@@ -875,10 +845,10 @@ export function composeRuntime(options) {
|
|
|
875
845
|
return { kind: 'transport-uncertainty' };
|
|
876
846
|
}
|
|
877
847
|
}
|
|
878
|
-
function
|
|
848
|
+
function declaredAgents() {
|
|
879
849
|
for (const capability of capabilities) {
|
|
880
|
-
if (capability.
|
|
881
|
-
return capability.
|
|
850
|
+
if (capability.conversationAgents !== undefined)
|
|
851
|
+
return capability.conversationAgents;
|
|
882
852
|
}
|
|
883
853
|
return undefined;
|
|
884
854
|
}
|
|
@@ -909,8 +879,6 @@ export function composeRuntime(options) {
|
|
|
909
879
|
journal.record(`transfer action ${name}`);
|
|
910
880
|
try {
|
|
911
881
|
const result = await options.adapter.action(authorization, { name, input });
|
|
912
|
-
if (result.kind === 'action' && result.at !== undefined)
|
|
913
|
-
want(result.at);
|
|
914
882
|
if (result.kind === 'scoped-refusal') {
|
|
915
883
|
const room = replicas.get(result.locator)?.confirmed.record;
|
|
916
884
|
if (room !== undefined)
|
|
@@ -942,32 +910,11 @@ export function composeRuntime(options) {
|
|
|
942
910
|
options.adapter.reached(locator, written, roomVersion) &&
|
|
943
911
|
!options.adapter.reached(locator, roomVersion, written));
|
|
944
912
|
}
|
|
945
|
-
function
|
|
946
|
-
if (reachedBy(locator, roomVersion))
|
|
947
|
-
return;
|
|
948
|
-
const held = wanted.get(locator);
|
|
949
|
-
if (held === undefined || options.adapter.reached(locator, roomVersion, held)) {
|
|
950
|
-
wanted.set(locator, roomVersion);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
function reachOf(locator) {
|
|
954
|
-
let target = wanted.get(locator);
|
|
955
|
-
const versions = (replicas.get(locator)?.queue ?? []).flatMap((batch) => {
|
|
956
|
-
const outcome = outcomes.get(batch.id);
|
|
957
|
-
return outcome?.kind === 'accepted' ? [outcome.roomVersion] : [];
|
|
958
|
-
});
|
|
959
|
-
for (const version of versions) {
|
|
960
|
-
if (target === undefined || options.adapter.reached(locator, version, target)) {
|
|
961
|
-
target = version;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
return target === undefined || reachedBy(locator, target) ? undefined : target;
|
|
965
|
-
}
|
|
966
|
-
async function pullRefresh(authorized, confirmed, minPosition) {
|
|
913
|
+
async function pullRefresh(authorized, confirmed) {
|
|
967
914
|
if (!continuing())
|
|
968
915
|
return unconfirmed;
|
|
969
916
|
try {
|
|
970
|
-
const result = await refreshRoom(options.adapter, authorized, confirmed,
|
|
917
|
+
const result = await refreshRoom(options.adapter, authorized, confirmed, (models, held) => remodel(confirmed.record.locator, models, held), continuing);
|
|
971
918
|
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
972
919
|
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
973
920
|
return result;
|
|
@@ -984,7 +931,7 @@ export function composeRuntime(options) {
|
|
|
984
931
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
985
932
|
collection: modelOf(room.locator),
|
|
986
933
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
987
|
-
}, continuing);
|
|
934
|
+
}, (models, held) => remodel(room.locator, models, held), continuing);
|
|
988
935
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
989
936
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
990
937
|
return result;
|
|
@@ -997,20 +944,28 @@ export function composeRuntime(options) {
|
|
|
997
944
|
async function advance(room, local, authorized) {
|
|
998
945
|
const held = replicas.get(room.locator);
|
|
999
946
|
if (held !== undefined) {
|
|
1000
|
-
const refreshed = await pullRefresh(authorized, held.confirmed
|
|
947
|
+
const refreshed = await pullRefresh(authorized, held.confirmed);
|
|
1001
948
|
if (!continuing())
|
|
1002
949
|
return unconfirmed;
|
|
1003
950
|
if (refreshed.kind === 'still-current') {
|
|
951
|
+
stale.delete(room.locator);
|
|
1004
952
|
if (sourceRefusals.has(room.locator))
|
|
1005
|
-
return
|
|
953
|
+
return null;
|
|
1006
954
|
if (unreadable.delete(room.locator)) {
|
|
955
|
+
scopedRefusals.delete(room.locator);
|
|
1007
956
|
list(room, true);
|
|
1008
957
|
emitRooms();
|
|
1009
958
|
}
|
|
1010
959
|
return null;
|
|
1011
960
|
}
|
|
1012
961
|
if (refreshed.kind === 'advanced') {
|
|
1013
|
-
await publish(local, room, refreshed.confirmed)
|
|
962
|
+
if (!(await publish(local, room, refreshed.confirmed, held.confirmed))) {
|
|
963
|
+
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
964
|
+
const reached = now !== undefined &&
|
|
965
|
+
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
966
|
+
return reached ? null : advance(room, local, authorized);
|
|
967
|
+
}
|
|
968
|
+
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
1014
969
|
return null;
|
|
1015
970
|
}
|
|
1016
971
|
if (refreshed.kind !== 'beyond-horizon')
|
|
@@ -1025,7 +980,10 @@ export function composeRuntime(options) {
|
|
|
1025
980
|
return null;
|
|
1026
981
|
}
|
|
1027
982
|
async function withhold(withheld, room, local, progress) {
|
|
1028
|
-
|
|
983
|
+
if (withheld.kind === 'refused')
|
|
984
|
+
stale.delete(room.locator);
|
|
985
|
+
else
|
|
986
|
+
stale.add(room.locator);
|
|
1029
987
|
if (withheld.kind === 'scoped-refusal') {
|
|
1030
988
|
await scoped(withheld, room, local);
|
|
1031
989
|
}
|
|
@@ -1063,84 +1021,39 @@ export function composeRuntime(options) {
|
|
|
1063
1021
|
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
1064
1022
|
}
|
|
1065
1023
|
function leaving(batch, result) {
|
|
1066
|
-
if (result.kind === 'expired')
|
|
1067
|
-
return { batch,
|
|
1068
|
-
}
|
|
1069
|
-
const refusal = { code: result.code, fields: result.fields };
|
|
1070
|
-
if (result.kind === 'conflicted') {
|
|
1071
|
-
return {
|
|
1072
|
-
batch,
|
|
1073
|
-
outcome: {
|
|
1074
|
-
kind: 'conflicted',
|
|
1075
|
-
...(result.roomVersion === undefined ? {} : { roomVersion: result.roomVersion }),
|
|
1076
|
-
affected: result.affected,
|
|
1077
|
-
...refusal,
|
|
1078
|
-
},
|
|
1079
|
-
returned: { kind: 'conflicted', ...refusal },
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
|
-
return {
|
|
1083
|
-
batch,
|
|
1084
|
-
outcome: { kind: 'rejected', affected: result.affected, ...refusal },
|
|
1085
|
-
returned: { kind: 'rejected', ...refusal },
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
function acceptedOf(accepted) {
|
|
1089
|
-
return {
|
|
1090
|
-
kind: 'accepted',
|
|
1091
|
-
roomVersion: accepted.roomVersion,
|
|
1092
|
-
...(accepted.author === undefined ? {} : { author: accepted.author }),
|
|
1093
|
-
...(accepted.time === undefined ? {} : { time: accepted.time }),
|
|
1094
|
-
};
|
|
1024
|
+
if (result.kind === 'expired')
|
|
1025
|
+
return { batch, returned: { kind: 'expired' } };
|
|
1026
|
+
return { batch, returned: { kind: result.kind, code: result.code, fields: result.fields } };
|
|
1095
1027
|
}
|
|
1096
1028
|
function logAccepted(locator, submitted, accepted) {
|
|
1097
1029
|
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
1098
1030
|
(accepted.author === undefined ? '' : ` author ${accepted.author}`) +
|
|
1099
1031
|
(accepted.time === undefined ? '' : ` time ${accepted.time}`));
|
|
1100
1032
|
}
|
|
1101
|
-
async function
|
|
1102
|
-
await exclusive(async () => {
|
|
1103
|
-
if (!continuing())
|
|
1104
|
-
return;
|
|
1105
|
-
const current = replicas.get(room.locator);
|
|
1106
|
-
if (current === undefined || !current.queue.some((batch) => batch.id === submitted.id)) {
|
|
1107
|
-
return;
|
|
1108
|
-
}
|
|
1109
|
-
const outcome = acceptedOf(accepted);
|
|
1110
|
-
await local.write({
|
|
1111
|
-
next: current.confirmed.record,
|
|
1112
|
-
previous: current.confirmed.record,
|
|
1113
|
-
queue: current.queue,
|
|
1114
|
-
previousQueue: current.queue,
|
|
1115
|
-
outcomes: [{ id: submitted.id, outcome }],
|
|
1116
|
-
});
|
|
1117
|
-
outcomes.set(submitted.id, outcome);
|
|
1118
|
-
outcomeRooms.set(submitted.id, room.locator);
|
|
1119
|
-
logAccepted(room.locator, submitted, accepted);
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
1122
|
-
async function commitAccept(room, local, submitted, accepted, base, overlay) {
|
|
1033
|
+
async function adoptAccepted(room, local, submitted, accepted) {
|
|
1123
1034
|
if (!continuing())
|
|
1124
1035
|
return unconfirmed;
|
|
1125
1036
|
const current = replicas.get(room.locator);
|
|
1126
1037
|
if (current === undefined || !current.queue.some((batch) => batch.id === submitted.id)) {
|
|
1127
1038
|
return null;
|
|
1128
1039
|
}
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1040
|
+
const held = current.confirmed.record.roomVersion;
|
|
1041
|
+
logAccepted(room.locator, submitted, accepted);
|
|
1042
|
+
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1043
|
+
? { kind: 'staged', confirmed: current.confirmed }
|
|
1044
|
+
: applyReturned(current.confirmed, accepted.documents, accepted.revisions, accepted.removals, accepted.attachments, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1045
|
+
? accepted.roomVersion
|
|
1046
|
+
: held, accepted.roomVersion);
|
|
1047
|
+
if (applied.kind !== 'staged') {
|
|
1048
|
+
const state = pendings.get(submitted.id);
|
|
1049
|
+
await local.accept(room.locator, submitted.id, accepted.roomVersion);
|
|
1050
|
+
if (state !== undefined)
|
|
1051
|
+
pendings.set(submitted.id, { ...state, accepted: accepted.roomVersion });
|
|
1052
|
+
emitQueue(room.locator);
|
|
1139
1053
|
return applied;
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
});
|
|
1054
|
+
}
|
|
1055
|
+
const set = await settle(local, room.locator, current, applied.confirmed, current.queue.filter((batch) => batch.id !== submitted.id), { room: true });
|
|
1056
|
+
pendings.delete(submitted.id);
|
|
1144
1057
|
unreadable.delete(room.locator);
|
|
1145
1058
|
sourceRefusals.delete(room.locator);
|
|
1146
1059
|
declarationRefusals.delete(room.locator);
|
|
@@ -1149,17 +1062,8 @@ export function composeRuntime(options) {
|
|
|
1149
1062
|
moveTo({ ...status, ...attention() });
|
|
1150
1063
|
return null;
|
|
1151
1064
|
}
|
|
1152
|
-
async function
|
|
1153
|
-
const
|
|
1154
|
-
if (options.adapter.reached(room.locator, roomVersion, accepted.roomVersion)) {
|
|
1155
|
-
return committed(room, local, submitted, accepted, pulled, roomVersion === accepted.roomVersion);
|
|
1156
|
-
}
|
|
1157
|
-
if (fresh && continuing())
|
|
1158
|
-
await publish(local, room, pulled);
|
|
1159
|
-
return false;
|
|
1160
|
-
}
|
|
1161
|
-
async function committed(room, local, submitted, accepted, base, overlay) {
|
|
1162
|
-
const failed = await exclusive(() => commitAccept(room, local, submitted, accepted, base, overlay));
|
|
1065
|
+
async function adopted(room, local, submitted, accepted) {
|
|
1066
|
+
const failed = await exclusive(() => adoptAccepted(room, local, submitted, accepted));
|
|
1163
1067
|
if (failed === null)
|
|
1164
1068
|
return true;
|
|
1165
1069
|
if (failed.kind === 'refused' && continuing())
|
|
@@ -1172,10 +1076,8 @@ export function composeRuntime(options) {
|
|
|
1172
1076
|
const head = replica?.queue[0];
|
|
1173
1077
|
if (replica === undefined || head === undefined)
|
|
1174
1078
|
return;
|
|
1175
|
-
if (
|
|
1176
|
-
progress.complete = false;
|
|
1079
|
+
if (pendings.get(head.id)?.accepted !== undefined)
|
|
1177
1080
|
return;
|
|
1178
|
-
}
|
|
1179
1081
|
const state = pendings.get(head.id);
|
|
1180
1082
|
const { submission, lost: earlier } = state ?? {
|
|
1181
1083
|
submission: { state: 'waiting' },
|
|
@@ -1203,6 +1105,7 @@ export function composeRuntime(options) {
|
|
|
1203
1105
|
return;
|
|
1204
1106
|
}
|
|
1205
1107
|
const opensBefore = openSignals;
|
|
1108
|
+
const answered = awaitingAnswer(room.locator);
|
|
1206
1109
|
try {
|
|
1207
1110
|
result = await options.adapter.mutate(authorized, {
|
|
1208
1111
|
locator: room.locator,
|
|
@@ -1211,11 +1114,14 @@ export function composeRuntime(options) {
|
|
|
1211
1114
|
}, (upload) => exclusive(() => keepUpload(local, room.locator, head.id, upload)));
|
|
1212
1115
|
}
|
|
1213
1116
|
catch {
|
|
1117
|
+
answered();
|
|
1214
1118
|
progress.complete = false;
|
|
1215
1119
|
if (continuing())
|
|
1216
1120
|
progress.reachable = false;
|
|
1217
1121
|
return;
|
|
1218
1122
|
}
|
|
1123
|
+
if (result.kind !== 'accepted' || !continuing())
|
|
1124
|
+
answered();
|
|
1219
1125
|
if (!continuing())
|
|
1220
1126
|
return;
|
|
1221
1127
|
if (result.kind === 'transport-uncertainty') {
|
|
@@ -1268,6 +1174,7 @@ export function composeRuntime(options) {
|
|
|
1268
1174
|
const reset = result.reset;
|
|
1269
1175
|
progress.complete = false;
|
|
1270
1176
|
await exclusive(() => mark(local, room.locator, head.id, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
1177
|
+
armTimer();
|
|
1271
1178
|
return;
|
|
1272
1179
|
}
|
|
1273
1180
|
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
@@ -1280,7 +1187,8 @@ export function composeRuntime(options) {
|
|
|
1280
1187
|
return;
|
|
1281
1188
|
}
|
|
1282
1189
|
const set = await settle(local, room.locator, current, current.confirmed, current.queue.filter((batch) => batch.id !== head.id), {
|
|
1283
|
-
leaving: result.kind === 'expired' && landed(head, current.confirmed)
|
|
1190
|
+
leaving: (result.kind === 'expired' && landed(head, current.confirmed)) ||
|
|
1191
|
+
(result.kind === 'conflicted' && landed(head, current.confirmed, result.affected))
|
|
1284
1192
|
? { ...left, returned: null }
|
|
1285
1193
|
: left,
|
|
1286
1194
|
});
|
|
@@ -1294,98 +1202,35 @@ export function composeRuntime(options) {
|
|
|
1294
1202
|
return withhold(withheld, room, local, progress);
|
|
1295
1203
|
continue;
|
|
1296
1204
|
}
|
|
1297
|
-
await
|
|
1298
|
-
|
|
1299
|
-
if (from === undefined)
|
|
1300
|
-
return;
|
|
1301
|
-
const pulled = await pullRefresh(authorized, from, reachOf(room.locator));
|
|
1302
|
-
if (!continuing())
|
|
1303
|
-
return;
|
|
1304
|
-
if (pulled.kind === 'revoked-authorization') {
|
|
1305
|
-
await refuse('revoked-authorization');
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
if (pulled.kind === 'scoped-refusal') {
|
|
1309
|
-
await scoped(pulled, room, local);
|
|
1310
|
-
progress.complete = false;
|
|
1311
|
-
return;
|
|
1312
|
-
}
|
|
1313
|
-
if (pulled.kind === 'still-current' || pulled.kind === 'advanced') {
|
|
1314
|
-
const fresh = pulled.kind === 'advanced';
|
|
1315
|
-
if (!(await accepting(room, local, head, result, fresh ? pulled.confirmed : from, fresh))) {
|
|
1316
|
-
progress.complete = false;
|
|
1317
|
-
return;
|
|
1318
|
-
}
|
|
1319
|
-
continue;
|
|
1320
|
-
}
|
|
1321
|
-
if (pulled.kind === 'beyond-horizon') {
|
|
1322
|
-
const bootstrapped = await pullBootstrap(authorized, room);
|
|
1323
|
-
if (!continuing())
|
|
1324
|
-
return;
|
|
1325
|
-
if (bootstrapped.kind === 'revoked-authorization') {
|
|
1326
|
-
await refuse('revoked-authorization');
|
|
1327
|
-
return;
|
|
1328
|
-
}
|
|
1329
|
-
if (bootstrapped.kind === 'scoped-refusal') {
|
|
1330
|
-
await scoped(bootstrapped, room, local);
|
|
1331
|
-
progress.complete = false;
|
|
1332
|
-
return;
|
|
1333
|
-
}
|
|
1334
|
-
if (bootstrapped.kind === 'staged') {
|
|
1335
|
-
if (!(await accepting(room, local, head, result, bootstrapped.confirmed, true))) {
|
|
1336
|
-
progress.complete = false;
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1205
|
+
const kept = await adopted(room, local, head, result).finally(answered);
|
|
1206
|
+
if (!kept) {
|
|
1341
1207
|
progress.complete = false;
|
|
1342
|
-
if (bootstrapped.kind === 'transport-uncertainty') {
|
|
1343
|
-
progress.reachable = false;
|
|
1344
|
-
}
|
|
1345
|
-
else if (bootstrapped.kind === 'room-not-readable') {
|
|
1346
|
-
unreadable.add(room.locator);
|
|
1347
|
-
list(room, false);
|
|
1348
|
-
emitRooms();
|
|
1349
|
-
}
|
|
1350
|
-
else if (bootstrapped.kind === 'refused') {
|
|
1351
|
-
refusedSource(room, bootstrapped);
|
|
1352
|
-
}
|
|
1353
1208
|
return;
|
|
1354
1209
|
}
|
|
1355
|
-
progress.complete = false;
|
|
1356
|
-
if (pulled.kind === 'transport-uncertainty') {
|
|
1357
|
-
progress.reachable = false;
|
|
1358
|
-
}
|
|
1359
|
-
else if (pulled.kind === 'room-not-readable') {
|
|
1360
|
-
unreadable.add(room.locator);
|
|
1361
|
-
list(room, false);
|
|
1362
|
-
emitRooms();
|
|
1363
|
-
}
|
|
1364
|
-
else if (pulled.kind === 'retryable-refusal') {
|
|
1365
|
-
journal.record(`refusal ${pulled.kind} ${pulled.code}`);
|
|
1366
|
-
}
|
|
1367
|
-
else if (pulled.kind === 'refused') {
|
|
1368
|
-
refusedSource(room, pulled);
|
|
1369
|
-
}
|
|
1370
|
-
return;
|
|
1371
1210
|
}
|
|
1372
|
-
|
|
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
|
+
};
|
|
1373
1223
|
}
|
|
1374
1224
|
async function retainModel(local, room, attached, next) {
|
|
1375
1225
|
const key = modelKey(room.locator, attached);
|
|
1376
|
-
if (next.status === 'unavailable')
|
|
1377
|
-
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
1378
|
-
const epoch = modelEpoch.get(key) ?? 0;
|
|
1379
1226
|
const previous = modelOf(room.locator, attached);
|
|
1380
1227
|
const heldBefore = replicas.get(room.locator)?.confirmed.record;
|
|
1381
1228
|
if (JSON.stringify(previous) === JSON.stringify(next) &&
|
|
1382
1229
|
(heldBefore === undefined ||
|
|
1383
1230
|
(JSON.stringify(heldBefore.operations) === JSON.stringify(room.operations) &&
|
|
1384
1231
|
JSON.stringify(heldBefore.replica) === JSON.stringify(room.replica) &&
|
|
1385
|
-
JSON.stringify(heldBefore.attachedModel) === JSON.stringify(room.attachedModel) &&
|
|
1386
1232
|
JSON.stringify(heldBefore.typedSelection) ===
|
|
1387
|
-
JSON.stringify(room.typedSelection ?? null)
|
|
1388
|
-
JSON.stringify(heldBefore.untypedFolders) === JSON.stringify(room.untypedFolders ?? []))))
|
|
1233
|
+
JSON.stringify(room.typedSelection ?? null))))
|
|
1389
1234
|
return;
|
|
1390
1235
|
if (next.status === 'unavailable') {
|
|
1391
1236
|
invalidModels.add(key);
|
|
@@ -1394,17 +1239,13 @@ export function composeRuntime(options) {
|
|
|
1394
1239
|
await exclusive(async () => {
|
|
1395
1240
|
if (!continuing())
|
|
1396
1241
|
return;
|
|
1397
|
-
if (next.status !== 'unavailable' && epoch !== (modelEpoch.get(key) ?? 0))
|
|
1398
|
-
return;
|
|
1399
1242
|
const held = replicas.get(room.locator);
|
|
1400
1243
|
if (held !== undefined) {
|
|
1401
1244
|
const declared = {
|
|
1402
1245
|
...held.confirmed.record,
|
|
1403
1246
|
operations: room.operations,
|
|
1404
1247
|
replica: room.replica,
|
|
1405
|
-
attachedModel: room.attachedModel,
|
|
1406
1248
|
typedSelection: room.typedSelection ?? null,
|
|
1407
|
-
untypedFolders: room.untypedFolders ?? [],
|
|
1408
1249
|
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1409
1250
|
};
|
|
1410
1251
|
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
@@ -1415,7 +1256,6 @@ export function composeRuntime(options) {
|
|
|
1415
1256
|
: {
|
|
1416
1257
|
...declared,
|
|
1417
1258
|
typedSelection: held.confirmed.record.typedSelection,
|
|
1418
|
-
untypedFolders: held.confirmed.record.untypedFolders,
|
|
1419
1259
|
};
|
|
1420
1260
|
const read = reclassified.kind === 'deferred' ? readConfirmed(record, held.confirmed) : reclassified;
|
|
1421
1261
|
await local.write({
|
|
@@ -1424,7 +1264,7 @@ export function composeRuntime(options) {
|
|
|
1424
1264
|
queue: held.queue,
|
|
1425
1265
|
});
|
|
1426
1266
|
if (read.kind === 'staged') {
|
|
1427
|
-
replicas.set(room.locator, replicaOf(read.confirmed, held.queue,
|
|
1267
|
+
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
|
|
1428
1268
|
if (declarationRefusals.delete(room.locator)) {
|
|
1429
1269
|
sourceRefusals.delete(room.locator);
|
|
1430
1270
|
unreadable.delete(room.locator);
|
|
@@ -1448,77 +1288,6 @@ export function composeRuntime(options) {
|
|
|
1448
1288
|
publishChangeSet(changeSet(room.locator, held, replicas.get(room.locator) ?? null, { room: true }));
|
|
1449
1289
|
});
|
|
1450
1290
|
}
|
|
1451
|
-
async function acquireModel(local, room, authorized, selection) {
|
|
1452
|
-
const attached = selection === null ? 'collection' : 'typed-selection';
|
|
1453
|
-
const key = modelKey(room.locator, attached);
|
|
1454
|
-
const declared = selection === null ? room.attachedModel : selection.attachedModel;
|
|
1455
|
-
let previous = modelOf(room.locator, attached);
|
|
1456
|
-
if (previous.status !== 'unavailable' &&
|
|
1457
|
-
(declared === null || !sameModel(previous.snapshot, declared))) {
|
|
1458
|
-
previous = { status: 'unavailable', reason: 'version-changed' };
|
|
1459
|
-
await retainModel(local, room, attached, previous);
|
|
1460
|
-
if (discoveryPaused)
|
|
1461
|
-
return;
|
|
1462
|
-
}
|
|
1463
|
-
const epoch = modelEpoch.get(key) ?? 0;
|
|
1464
|
-
let answer;
|
|
1465
|
-
try {
|
|
1466
|
-
answer = await options.adapter.model(authorized, selection === null
|
|
1467
|
-
? { locator: room.locator, attached: 'collection', model: declared }
|
|
1468
|
-
: {
|
|
1469
|
-
locator: room.locator,
|
|
1470
|
-
attached: 'typed-selection',
|
|
1471
|
-
roots: selection.roots,
|
|
1472
|
-
model: declared,
|
|
1473
|
-
});
|
|
1474
|
-
}
|
|
1475
|
-
catch {
|
|
1476
|
-
answer = { kind: 'transport-uncertainty' };
|
|
1477
|
-
}
|
|
1478
|
-
if (discoveryPaused || epoch !== (modelEpoch.get(key) ?? 0))
|
|
1479
|
-
return;
|
|
1480
|
-
if (answer.kind === 'revoked-authorization') {
|
|
1481
|
-
await refuse('revoked-authorization');
|
|
1482
|
-
return;
|
|
1483
|
-
}
|
|
1484
|
-
if (answer.kind === 'transport-uncertainty') {
|
|
1485
|
-
await retainModel(local, room, attached, cached(previous));
|
|
1486
|
-
return;
|
|
1487
|
-
}
|
|
1488
|
-
if (answer.kind === 'model-unavailable') {
|
|
1489
|
-
await retainModel(local, room, attached, {
|
|
1490
|
-
status: 'unavailable',
|
|
1491
|
-
reason: answer.reason,
|
|
1492
|
-
});
|
|
1493
|
-
return;
|
|
1494
|
-
}
|
|
1495
|
-
if (declared === null || !sameModel(answer.snapshot, declared)) {
|
|
1496
|
-
await retainModel(local, room, attached, {
|
|
1497
|
-
status: 'unavailable',
|
|
1498
|
-
reason: 'version-changed',
|
|
1499
|
-
});
|
|
1500
|
-
return;
|
|
1501
|
-
}
|
|
1502
|
-
if (previous.status !== 'unavailable' &&
|
|
1503
|
-
sameModel(previous.snapshot, answer.snapshot) &&
|
|
1504
|
-
previous.snapshot.schema !== answer.snapshot.schema) {
|
|
1505
|
-
await retainModel(local, room, attached, { status: 'unavailable', reason: 'invalid' });
|
|
1506
|
-
return;
|
|
1507
|
-
}
|
|
1508
|
-
const verified = answer.snapshot.schema === null ? EMPTY_SCHEMA : verify(answer.snapshot.schema);
|
|
1509
|
-
if (verified.verdict === 'refuse') {
|
|
1510
|
-
const where = verified.location === undefined ? '' : ` ${verified.location}`;
|
|
1511
|
-
journal.record(`model invalid ${room.locator} ${attached} ${answer.snapshot.identity} ${verified.rule}${where}`);
|
|
1512
|
-
}
|
|
1513
|
-
await retainModel(local, room, attached, verified.verdict === 'accept'
|
|
1514
|
-
? {
|
|
1515
|
-
status: 'current',
|
|
1516
|
-
snapshot: structuredClone(answer.snapshot),
|
|
1517
|
-
verifiedWith: MD_MODEL,
|
|
1518
|
-
types: verified.model.types,
|
|
1519
|
-
}
|
|
1520
|
-
: { status: 'unavailable', reason: 'invalid' });
|
|
1521
|
-
}
|
|
1522
1291
|
async function scoped(result, room, local) {
|
|
1523
1292
|
scopedRefusals.set(room.locator, { code: result.code, fields: result.fields });
|
|
1524
1293
|
let scopedAway = room;
|
|
@@ -1546,14 +1315,36 @@ export function composeRuntime(options) {
|
|
|
1546
1315
|
emitRooms();
|
|
1547
1316
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
1548
1317
|
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
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) => {
|
|
1552
1337
|
const outcome = prepared[index];
|
|
1553
1338
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
1554
1339
|
});
|
|
1555
|
-
|
|
1556
|
-
|
|
1340
|
+
if (attempt === 'opened') {
|
|
1341
|
+
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
1342
|
+
handed = true;
|
|
1343
|
+
}
|
|
1344
|
+
const loaded = await Promise.allSettled(ready
|
|
1345
|
+
.filter((room) => attempt === 'unstreamed' || !replicas.has(room.locator))
|
|
1346
|
+
.map((room) => loadRoom(room, local, progress)));
|
|
1347
|
+
const failed = [...prepared, ...loaded].find((room) => room.status === 'rejected');
|
|
1557
1348
|
if (failed !== undefined)
|
|
1558
1349
|
throw failed.reason;
|
|
1559
1350
|
}
|
|
@@ -1570,11 +1361,8 @@ export function composeRuntime(options) {
|
|
|
1570
1361
|
progress.complete = false;
|
|
1571
1362
|
return false;
|
|
1572
1363
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
acquireModel(local, room, authorization, null),
|
|
1576
|
-
...(selection === undefined ? [] : [acquireModel(local, room, authorization, selection)]),
|
|
1577
|
-
]);
|
|
1364
|
+
if (prior !== undefined)
|
|
1365
|
+
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
1578
1366
|
if (discoveryPaused || authorization === undefined)
|
|
1579
1367
|
return false;
|
|
1580
1368
|
if (room.replica.admission !== 'admitted') {
|
|
@@ -1582,24 +1370,53 @@ export function composeRuntime(options) {
|
|
|
1582
1370
|
progress.complete = false;
|
|
1583
1371
|
return false;
|
|
1584
1372
|
}
|
|
1585
|
-
const held = replicas.get(room.locator);
|
|
1586
|
-
if (held !== undefined && held.confirmed.record.replica.selection !== room.replica.selection) {
|
|
1587
|
-
progress.complete = false;
|
|
1588
|
-
return false;
|
|
1589
|
-
}
|
|
1590
1373
|
return true;
|
|
1591
1374
|
}
|
|
1592
|
-
async function
|
|
1375
|
+
async function caughtUp(room, local, progress) {
|
|
1376
|
+
const held = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
1377
|
+
if (held === undefined || authorization === undefined)
|
|
1378
|
+
return;
|
|
1379
|
+
const behind = [announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later));
|
|
1380
|
+
if (!behind)
|
|
1381
|
+
return;
|
|
1382
|
+
const withheld = await advance(room, local, authorization);
|
|
1383
|
+
if (withheld !== null)
|
|
1384
|
+
await withhold(withheld, room, local, progress);
|
|
1385
|
+
}
|
|
1386
|
+
function revoked(locator) {
|
|
1387
|
+
return ['collection', 'typed-selection'].some((attached) => {
|
|
1388
|
+
const model = modelOf(locator, attached);
|
|
1389
|
+
return model.status === 'unavailable' && model.reason === 'revoked';
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
async function loadRoom(room, local, progress) {
|
|
1593
1393
|
if (discoveryPaused || authorization === undefined)
|
|
1594
1394
|
return;
|
|
1395
|
+
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
1396
|
+
await reload(room, local, progress);
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1595
1399
|
const withheld = await advance(room, local, authorization);
|
|
1596
1400
|
if (withheld !== null) {
|
|
1597
1401
|
await withhold(withheld, room, local, progress);
|
|
1598
1402
|
return;
|
|
1599
1403
|
}
|
|
1600
|
-
await
|
|
1404
|
+
await caughtUp(room, local, progress);
|
|
1405
|
+
}
|
|
1406
|
+
async function reload(room, local, progress) {
|
|
1407
|
+
if (discoveryPaused || authorization === undefined)
|
|
1408
|
+
return;
|
|
1409
|
+
const bootstrapped = await pullBootstrap(authorization, room);
|
|
1410
|
+
if (!continuing())
|
|
1411
|
+
return;
|
|
1412
|
+
if (bootstrapped.kind !== 'staged') {
|
|
1413
|
+
await withhold(bootstrapped, room, local, progress);
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
await publish(local, room, bootstrapped.confirmed);
|
|
1417
|
+
await caughtUp(room, local, progress);
|
|
1601
1418
|
}
|
|
1602
|
-
async function
|
|
1419
|
+
async function discoverRooms(local, progress, attempt) {
|
|
1603
1420
|
if (closed || authorization === undefined) {
|
|
1604
1421
|
notifyReady();
|
|
1605
1422
|
return;
|
|
@@ -1625,6 +1442,7 @@ export function composeRuntime(options) {
|
|
|
1625
1442
|
}
|
|
1626
1443
|
}
|
|
1627
1444
|
progress.reachable = false;
|
|
1445
|
+
progress.complete = false;
|
|
1628
1446
|
notifyReady();
|
|
1629
1447
|
return;
|
|
1630
1448
|
}
|
|
@@ -1639,17 +1457,6 @@ export function composeRuntime(options) {
|
|
|
1639
1457
|
notifyReady();
|
|
1640
1458
|
return;
|
|
1641
1459
|
}
|
|
1642
|
-
if (!result.supportedVersions.includes(ROOM_CONTRACT_VERSION)) {
|
|
1643
|
-
await refuse('no-matching-version');
|
|
1644
|
-
notifyReady();
|
|
1645
|
-
return;
|
|
1646
|
-
}
|
|
1647
|
-
if (result.mdModel.identity !== MD_MODEL.identity ||
|
|
1648
|
-
result.mdModel.version !== MD_MODEL.version) {
|
|
1649
|
-
await refuse('md-model-version-mismatch');
|
|
1650
|
-
notifyReady();
|
|
1651
|
-
return;
|
|
1652
|
-
}
|
|
1653
1460
|
for (const room of result.rooms) {
|
|
1654
1461
|
if (room.operations === undefined ||
|
|
1655
1462
|
!['read', 'sync', 'write', 'delete'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
@@ -1657,8 +1464,7 @@ export function composeRuntime(options) {
|
|
|
1657
1464
|
room.replica === undefined ||
|
|
1658
1465
|
typeof room.replica.selection !== 'string' ||
|
|
1659
1466
|
room.replica.selection.length === 0 ||
|
|
1660
|
-
!['admitted', 'unavailable'].includes(room.replica.admission)
|
|
1661
|
-
!declaredIdentity(room.attachedModel)) {
|
|
1467
|
+
!['admitted', 'unavailable'].includes(room.replica.admission)) {
|
|
1662
1468
|
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
1663
1469
|
await refuse('missing-required-guarantee');
|
|
1664
1470
|
notifyReady();
|
|
@@ -1670,20 +1476,6 @@ export function composeRuntime(options) {
|
|
|
1670
1476
|
notifyReady();
|
|
1671
1477
|
return;
|
|
1672
1478
|
}
|
|
1673
|
-
if (!declaredUntypedFolders(room)) {
|
|
1674
|
-
journal.record(`invalid untyped folder declaration ${room.locator}`);
|
|
1675
|
-
await refuse('missing-required-guarantee');
|
|
1676
|
-
notifyReady();
|
|
1677
|
-
return;
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
const declared = new Set(result.capabilities.map((capability) => capability.name));
|
|
1681
|
-
for (const required of options.requiredGuarantees) {
|
|
1682
|
-
if (!knownCapabilities.has(required) || !declared.has(required)) {
|
|
1683
|
-
await refuse('missing-required-guarantee');
|
|
1684
|
-
notifyReady();
|
|
1685
|
-
return;
|
|
1686
|
-
}
|
|
1687
1479
|
}
|
|
1688
1480
|
if (refusal !== null) {
|
|
1689
1481
|
refusal = null;
|
|
@@ -1692,29 +1484,13 @@ export function composeRuntime(options) {
|
|
|
1692
1484
|
const previousCapabilities = capabilities;
|
|
1693
1485
|
capabilities = result.capabilities;
|
|
1694
1486
|
notifyReady();
|
|
1695
|
-
if (declared.has('room-version-notifications'))
|
|
1696
|
-
observeRoomVersions();
|
|
1697
|
-
const discoveredSlots = result.slots ?? [];
|
|
1698
1487
|
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
1699
|
-
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)
|
|
1700
|
-
JSON.stringify(discoveredSlots) !== JSON.stringify(slots)) {
|
|
1488
|
+
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)) {
|
|
1701
1489
|
limits = result.providerLimits;
|
|
1702
|
-
|
|
1703
|
-
await local.declare(limits, capabilities, slots);
|
|
1490
|
+
await local.declare(limits, capabilities);
|
|
1704
1491
|
}
|
|
1705
1492
|
await applyDiscovery(result.rooms, local);
|
|
1706
|
-
|
|
1707
|
-
.filter((room) => room.replica.admission === 'admitted')
|
|
1708
|
-
.map((room) => room.locator)
|
|
1709
|
-
.sort());
|
|
1710
|
-
progress.complete = true;
|
|
1711
|
-
await synchronize(result.rooms, local, progress);
|
|
1712
|
-
if (closed || discoveryPaused)
|
|
1713
|
-
return;
|
|
1714
|
-
await conversations.reconcile();
|
|
1715
|
-
if (status.providerUnreachable)
|
|
1716
|
-
conversations.reconnected();
|
|
1717
|
-
await conversations.catchUp(progress);
|
|
1493
|
+
await synchronize(result.rooms, local, progress, attempt);
|
|
1718
1494
|
}
|
|
1719
1495
|
async function ensureAuthorization() {
|
|
1720
1496
|
if (authorization !== undefined)
|
|
@@ -1728,64 +1504,253 @@ export function composeRuntime(options) {
|
|
|
1728
1504
|
throw error;
|
|
1729
1505
|
}
|
|
1730
1506
|
}
|
|
1731
|
-
|
|
1732
|
-
if (
|
|
1733
|
-
|
|
1507
|
+
function task(work) {
|
|
1508
|
+
if (stopping)
|
|
1509
|
+
return chain;
|
|
1510
|
+
chain = chain.then(work, work).catch(() => undefined);
|
|
1511
|
+
return chain;
|
|
1512
|
+
}
|
|
1513
|
+
function sync(attempt) {
|
|
1514
|
+
return task(async () => {
|
|
1515
|
+
if (closed || stopping || discoveryPaused) {
|
|
1516
|
+
notifyReady();
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
const progress = { complete: true, reachable: true };
|
|
1520
|
+
underway = attempt === 'opened' ? progress : null;
|
|
1521
|
+
complete = false;
|
|
1522
|
+
recorded = false;
|
|
1523
|
+
moveTo(transferring(status));
|
|
1524
|
+
handed = false;
|
|
1525
|
+
try {
|
|
1526
|
+
const local = await openLocal();
|
|
1527
|
+
if (closed)
|
|
1528
|
+
return;
|
|
1529
|
+
await ensureAuthorization();
|
|
1530
|
+
if (closed)
|
|
1531
|
+
return;
|
|
1532
|
+
await discoverRooms(local, progress, attempt);
|
|
1533
|
+
}
|
|
1534
|
+
catch {
|
|
1535
|
+
progress.complete = false;
|
|
1536
|
+
if (attempt === 'opened' && !handed)
|
|
1537
|
+
progress.reachable = false;
|
|
1538
|
+
}
|
|
1539
|
+
finally {
|
|
1540
|
+
notifyReady();
|
|
1541
|
+
}
|
|
1542
|
+
if (attempt !== 'opened') {
|
|
1543
|
+
await finishOpening(progress);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
if (handed || closed || progress.reachable)
|
|
1547
|
+
return;
|
|
1548
|
+
underway = null;
|
|
1549
|
+
journal.record('connection reopen');
|
|
1550
|
+
await rest(progress);
|
|
1551
|
+
options.adapter.roomStream?.reopen();
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
function closedSocket(revoked) {
|
|
1555
|
+
const opened = socketOpen;
|
|
1556
|
+
socketOpen = false;
|
|
1557
|
+
if (revoked) {
|
|
1558
|
+
void refuse('revoked-authorization');
|
|
1734
1559
|
return;
|
|
1735
1560
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
if (closed)
|
|
1561
|
+
if (opened || status.providerUnreachable)
|
|
1562
|
+
return;
|
|
1563
|
+
moveTo({ ...status, providerUnreachable: true });
|
|
1564
|
+
}
|
|
1565
|
+
function applyOpening(expired) {
|
|
1566
|
+
return task(async () => {
|
|
1567
|
+
const progress = underway;
|
|
1568
|
+
underway = null;
|
|
1569
|
+
if (closed || stopping)
|
|
1745
1570
|
return;
|
|
1746
|
-
|
|
1747
|
-
|
|
1571
|
+
if (progress === null) {
|
|
1572
|
+
await reloadExpired(expired);
|
|
1748
1573
|
return;
|
|
1749
|
-
if (progress.complete) {
|
|
1750
|
-
completedAt = new Date().toISOString();
|
|
1751
|
-
await local.complete(completedAt);
|
|
1752
1574
|
}
|
|
1575
|
+
try {
|
|
1576
|
+
const local = await openLocal();
|
|
1577
|
+
const held = [...discovered.values()].filter((room) => replicas.has(room.locator));
|
|
1578
|
+
const reloading = held.filter((room) => expired.includes(room.locator) || revoked(room.locator));
|
|
1579
|
+
const reloaded = await Promise.allSettled(reloading.map((room) => reload(room, local, progress)));
|
|
1580
|
+
if (progress.reachable) {
|
|
1581
|
+
for (const room of held)
|
|
1582
|
+
if (!reloading.includes(room))
|
|
1583
|
+
await confirmModels(room, local);
|
|
1584
|
+
}
|
|
1585
|
+
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
1586
|
+
if (failed !== undefined)
|
|
1587
|
+
throw failed.reason;
|
|
1588
|
+
}
|
|
1589
|
+
catch {
|
|
1590
|
+
progress.complete = false;
|
|
1591
|
+
progress.reachable = false;
|
|
1592
|
+
}
|
|
1593
|
+
await finishOpening(progress);
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
async function reloadExpired(expired) {
|
|
1597
|
+
const progress = { complete: true, reachable: true };
|
|
1598
|
+
try {
|
|
1599
|
+
const local = await openLocal();
|
|
1600
|
+
await Promise.allSettled([...discovered.values()]
|
|
1601
|
+
.filter((room) => replicas.has(room.locator) && expired.includes(room.locator))
|
|
1602
|
+
.map((room) => reload(room, local, progress)));
|
|
1753
1603
|
}
|
|
1754
1604
|
catch {
|
|
1755
|
-
progress.
|
|
1756
|
-
|
|
1605
|
+
progress.reachable = false;
|
|
1606
|
+
}
|
|
1607
|
+
await rest(progress);
|
|
1608
|
+
}
|
|
1609
|
+
async function confirmModels(room, local) {
|
|
1610
|
+
for (const attached of ['collection', 'typed-selection']) {
|
|
1611
|
+
const model = modelOf(room.locator, attached);
|
|
1612
|
+
if (model.status === 'cached') {
|
|
1613
|
+
await retainModel(local, room, attached, { ...model, status: 'current' });
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
function submitLater(locator) {
|
|
1618
|
+
const running = submitting.get(locator);
|
|
1619
|
+
if (running !== undefined) {
|
|
1620
|
+
running.again = true;
|
|
1757
1621
|
return;
|
|
1758
1622
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1623
|
+
const run = { again: false, done: Promise.resolve() };
|
|
1624
|
+
submitting.set(locator, run);
|
|
1625
|
+
run.done = (async () => {
|
|
1626
|
+
try {
|
|
1627
|
+
do {
|
|
1628
|
+
run.again = false;
|
|
1629
|
+
const room = discovered.get(locator);
|
|
1630
|
+
if (closed || stopping || discoveryPaused || room === undefined)
|
|
1631
|
+
return;
|
|
1632
|
+
const progress = { complete: true, reachable: true };
|
|
1633
|
+
try {
|
|
1634
|
+
await ensureAuthorization();
|
|
1635
|
+
if (authorization === undefined)
|
|
1636
|
+
return;
|
|
1637
|
+
await submitRoom(room, await openLocal(), authorization, progress);
|
|
1638
|
+
}
|
|
1639
|
+
catch {
|
|
1640
|
+
progress.reachable = false;
|
|
1641
|
+
}
|
|
1642
|
+
armTimer();
|
|
1643
|
+
await rest(progress);
|
|
1644
|
+
} while (run.again);
|
|
1645
|
+
}
|
|
1646
|
+
finally {
|
|
1647
|
+
submitting.delete(locator);
|
|
1648
|
+
}
|
|
1649
|
+
})();
|
|
1650
|
+
}
|
|
1651
|
+
function submitAll() {
|
|
1652
|
+
for (const [locator, replica] of replicas) {
|
|
1653
|
+
if (replica.queue.length > 0)
|
|
1654
|
+
submitLater(locator);
|
|
1762
1655
|
}
|
|
1763
1656
|
}
|
|
1764
|
-
async function
|
|
1657
|
+
async function finishOpening(progress) {
|
|
1765
1658
|
try {
|
|
1766
|
-
|
|
1659
|
+
if (closed || discoveryPaused) {
|
|
1660
|
+
progress.complete = false;
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
submitAll();
|
|
1664
|
+
await conversations.reconcile();
|
|
1665
|
+
if (status.providerUnreachable)
|
|
1666
|
+
conversations.reconnected();
|
|
1667
|
+
const reading = { complete: true, reachable: true };
|
|
1668
|
+
await conversations.catchUp(reading);
|
|
1669
|
+
if (!reading.reachable)
|
|
1670
|
+
progress.reachable = false;
|
|
1671
|
+
}
|
|
1672
|
+
catch {
|
|
1673
|
+
progress.complete = false;
|
|
1767
1674
|
}
|
|
1768
1675
|
finally {
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
queued = false;
|
|
1772
|
-
void enqueue();
|
|
1773
|
-
}
|
|
1676
|
+
complete = progress.complete;
|
|
1677
|
+
await rest(progress);
|
|
1774
1678
|
}
|
|
1775
1679
|
}
|
|
1776
|
-
function
|
|
1777
|
-
|
|
1778
|
-
if (path === undefined || withdrawNotifications !== undefined)
|
|
1680
|
+
async function rest(progress) {
|
|
1681
|
+
if (closed)
|
|
1779
1682
|
return;
|
|
1780
|
-
|
|
1781
|
-
|
|
1683
|
+
let lastCompleted = status.lastCompleted;
|
|
1684
|
+
let synced = complete && stale.size === 0 && !pending();
|
|
1685
|
+
if (synced && !recorded) {
|
|
1686
|
+
const completedAt = new Date().toISOString();
|
|
1687
|
+
try {
|
|
1688
|
+
await (await openLocal()).complete(completedAt);
|
|
1689
|
+
recorded = true;
|
|
1690
|
+
lastCompleted = completedAt;
|
|
1691
|
+
}
|
|
1692
|
+
catch {
|
|
1693
|
+
synced = false;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
moveTo({
|
|
1697
|
+
state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
|
|
1698
|
+
lastCompleted,
|
|
1699
|
+
providerUnreachable: !progress.reachable,
|
|
1700
|
+
...attention(),
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
function applyRoom(locator, roomVersion) {
|
|
1704
|
+
announced.set(locator, roomVersion);
|
|
1705
|
+
return task(async () => {
|
|
1706
|
+
await answering.get(locator);
|
|
1707
|
+
if (closed || stopping || discoveryPaused || authorization === undefined)
|
|
1782
1708
|
return;
|
|
1783
|
-
|
|
1784
|
-
|
|
1709
|
+
const room = discovered.get(locator);
|
|
1710
|
+
const held = replicas.get(locator);
|
|
1711
|
+
if (room === undefined || room.replica.admission !== 'admitted')
|
|
1712
|
+
return;
|
|
1713
|
+
if (held !== undefined &&
|
|
1714
|
+
(held.confirmed.record.replica.selection !== room.replica.selection ||
|
|
1715
|
+
options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion))) {
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
const during = underway;
|
|
1719
|
+
const progress = during ?? { complete: true, reachable: true };
|
|
1720
|
+
if (during === null)
|
|
1721
|
+
moveTo(transferring(status));
|
|
1722
|
+
try {
|
|
1723
|
+
const local = await openLocal();
|
|
1724
|
+
const withheld = await advance(room, local, authorization);
|
|
1725
|
+
if (withheld !== null)
|
|
1726
|
+
await withhold(withheld, room, local, progress);
|
|
1727
|
+
}
|
|
1728
|
+
catch {
|
|
1729
|
+
stale.add(locator);
|
|
1730
|
+
}
|
|
1731
|
+
finally {
|
|
1732
|
+
if (during === null)
|
|
1733
|
+
await rest(progress);
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
function observeStream() {
|
|
1738
|
+
const stream = options.adapter.roomStream;
|
|
1739
|
+
if (stream === undefined || withdrawStream !== undefined)
|
|
1740
|
+
return;
|
|
1741
|
+
withdrawStream = stream.observe((event) => {
|
|
1742
|
+
if (closed || stopping)
|
|
1785
1743
|
return;
|
|
1744
|
+
if (event.kind === 'opened') {
|
|
1745
|
+
socketOpen = true;
|
|
1746
|
+
void sync('opened');
|
|
1786
1747
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1748
|
+
else if (event.kind === 'closed')
|
|
1749
|
+
closedSocket(event.revoked);
|
|
1750
|
+
else if (event.kind === 'opening')
|
|
1751
|
+
void applyOpening(event.expired);
|
|
1752
|
+
else
|
|
1753
|
+
void applyRoom(event.locator, event.roomVersion);
|
|
1789
1754
|
});
|
|
1790
1755
|
}
|
|
1791
1756
|
function writesHeld() {
|
|
@@ -1806,14 +1771,17 @@ export function composeRuntime(options) {
|
|
|
1806
1771
|
listener(mode);
|
|
1807
1772
|
}
|
|
1808
1773
|
if (releasing)
|
|
1809
|
-
|
|
1774
|
+
submitAll();
|
|
1810
1775
|
}
|
|
1811
1776
|
function observeConversations() {
|
|
1812
1777
|
const stream = options.adapter.conversations;
|
|
1813
1778
|
if (stream === undefined || withdrawConversations !== undefined)
|
|
1814
1779
|
return;
|
|
1815
1780
|
withdrawConversations = stream.observe((event) => {
|
|
1816
|
-
conversations.receive(event)
|
|
1781
|
+
if (conversations.receive(event)) {
|
|
1782
|
+
void ready.then(scheduleCurrency);
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1817
1785
|
if (event.kind !== 'gap' || conversations.current())
|
|
1818
1786
|
return;
|
|
1819
1787
|
show();
|
|
@@ -1824,7 +1792,7 @@ export function composeRuntime(options) {
|
|
|
1824
1792
|
if (currencyQueued || closed || stopping)
|
|
1825
1793
|
return;
|
|
1826
1794
|
currencyQueued = true;
|
|
1827
|
-
|
|
1795
|
+
void task(runCurrency);
|
|
1828
1796
|
}
|
|
1829
1797
|
async function runCurrency() {
|
|
1830
1798
|
currencyQueued = false;
|
|
@@ -1850,95 +1818,12 @@ export function composeRuntime(options) {
|
|
|
1850
1818
|
}
|
|
1851
1819
|
}
|
|
1852
1820
|
}
|
|
1853
|
-
function handSelected(locators) {
|
|
1854
|
-
const path = options.adapter.roomVersions;
|
|
1855
|
-
if (path === undefined || withdrawNotifications === undefined)
|
|
1856
|
-
return;
|
|
1857
|
-
if (handed !== undefined && JSON.stringify(locators) === JSON.stringify(handed))
|
|
1858
|
-
return;
|
|
1859
|
-
handed = locators;
|
|
1860
|
-
path.select(locators);
|
|
1861
|
-
}
|
|
1862
|
-
async function runNotified() {
|
|
1863
|
-
const announced = [...notices];
|
|
1864
|
-
notices.clear();
|
|
1865
|
-
if (announced.length === 0 || closed || stopping || discoveryPaused)
|
|
1866
|
-
return;
|
|
1867
|
-
const resting = status;
|
|
1868
|
-
const progress = { complete: false, reachable: !resting.providerUnreachable };
|
|
1869
|
-
moveTo(transferring(resting));
|
|
1870
|
-
try {
|
|
1871
|
-
const local = await openLocal();
|
|
1872
|
-
for (const [locator, roomVersion] of announced) {
|
|
1873
|
-
if (closed || discoveryPaused || authorization === undefined)
|
|
1874
|
-
return;
|
|
1875
|
-
if (handed?.includes(locator) !== true)
|
|
1876
|
-
continue;
|
|
1877
|
-
const room = discovered.get(locator);
|
|
1878
|
-
const held = replicas.get(locator);
|
|
1879
|
-
if (room === undefined)
|
|
1880
|
-
continue;
|
|
1881
|
-
if (sourceRefusals.has(locator)) {
|
|
1882
|
-
void enqueue();
|
|
1883
|
-
continue;
|
|
1884
|
-
}
|
|
1885
|
-
if (held === undefined)
|
|
1886
|
-
continue;
|
|
1887
|
-
if (held.confirmed.record.roomVersion === roomVersion)
|
|
1888
|
-
continue;
|
|
1889
|
-
const withheld = await advance(room, local, authorization);
|
|
1890
|
-
if (withheld === null)
|
|
1891
|
-
continue;
|
|
1892
|
-
if (withheld.kind === 'refused') {
|
|
1893
|
-
void enqueue();
|
|
1894
|
-
continue;
|
|
1895
|
-
}
|
|
1896
|
-
if (withheld.kind === 'scoped-refusal') {
|
|
1897
|
-
await scoped(withheld, room, local);
|
|
1898
|
-
continue;
|
|
1899
|
-
}
|
|
1900
|
-
if (withheld.kind === 'transport-uncertainty') {
|
|
1901
|
-
progress.reachable = false;
|
|
1902
|
-
continue;
|
|
1903
|
-
}
|
|
1904
|
-
if (withheld.kind === 'room-not-readable') {
|
|
1905
|
-
unreadable.add(locator);
|
|
1906
|
-
list(room, false);
|
|
1907
|
-
emitRooms();
|
|
1908
|
-
continue;
|
|
1909
|
-
}
|
|
1910
|
-
if (withheld.kind === 'revoked-authorization') {
|
|
1911
|
-
await refuse('revoked-authorization');
|
|
1912
|
-
return;
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
catch {
|
|
1917
|
-
progress.reachable = false;
|
|
1918
|
-
}
|
|
1919
|
-
finally {
|
|
1920
|
-
if (!closed) {
|
|
1921
|
-
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
function enqueue() {
|
|
1926
|
-
if (stopping)
|
|
1927
|
-
return chain;
|
|
1928
|
-
if (inflight) {
|
|
1929
|
-
queued = true;
|
|
1930
|
-
return chain;
|
|
1931
|
-
}
|
|
1932
|
-
inflight = true;
|
|
1933
|
-
chain = chain.then(run, run);
|
|
1934
|
-
return chain;
|
|
1935
|
-
}
|
|
1936
1821
|
function close() {
|
|
1937
1822
|
if (closed)
|
|
1938
1823
|
return;
|
|
1939
1824
|
closed = true;
|
|
1940
|
-
|
|
1941
|
-
|
|
1825
|
+
withdrawStream?.();
|
|
1826
|
+
withdrawStream = undefined;
|
|
1942
1827
|
withdrawConversations?.();
|
|
1943
1828
|
withdrawConversations = undefined;
|
|
1944
1829
|
withdrawServiceMode?.();
|
|
@@ -1962,6 +1847,7 @@ export function composeRuntime(options) {
|
|
|
1962
1847
|
stopping = true;
|
|
1963
1848
|
stopTimer();
|
|
1964
1849
|
await chain.catch(() => undefined);
|
|
1850
|
+
await Promise.all([...submitting.values()].map((run) => run.done));
|
|
1965
1851
|
await mutations;
|
|
1966
1852
|
close();
|
|
1967
1853
|
}
|
|
@@ -1970,11 +1856,29 @@ export function composeRuntime(options) {
|
|
|
1970
1856
|
options.signal?.addEventListener('abort', close, { once: true });
|
|
1971
1857
|
if (options.signal?.aborted)
|
|
1972
1858
|
close();
|
|
1973
|
-
|
|
1859
|
+
observeStream();
|
|
1860
|
+
void openLocal().then(() => {
|
|
1861
|
+
if (capabilities.length > 0)
|
|
1862
|
+
notifyReady();
|
|
1863
|
+
}, () => {
|
|
1974
1864
|
notifyReady();
|
|
1975
1865
|
});
|
|
1976
|
-
|
|
1977
|
-
|
|
1866
|
+
if (options.adapter.roomStream === undefined)
|
|
1867
|
+
void sync('unstreamed');
|
|
1868
|
+
function planned(input, reach) {
|
|
1869
|
+
return planOf(input, roomFacts(reach), planReach(reach));
|
|
1870
|
+
}
|
|
1871
|
+
function watchPlan(plan, deliver) {
|
|
1872
|
+
return readSide.watch(plan, identityOf(plan), deliver);
|
|
1873
|
+
}
|
|
1874
|
+
async function searched(input, reach) {
|
|
1875
|
+
const plan = searchPlanOf(input);
|
|
1876
|
+
const selected = plan.documents === undefined && reach === null
|
|
1877
|
+
? null
|
|
1878
|
+
: (await readSide.query(filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)))).documents;
|
|
1879
|
+
return searchIndex.search(reach === null || reach.conversations ? plan : { ...plan, conversations: [] }, selected);
|
|
1880
|
+
}
|
|
1881
|
+
const watch = (topic, input, deliver) => {
|
|
1978
1882
|
if (topic === 'room-drafts') {
|
|
1979
1883
|
const listener = { notify: deliver, ready: false };
|
|
1980
1884
|
roomDraftListeners.add(listener);
|
|
@@ -1995,6 +1899,8 @@ export function composeRuntime(options) {
|
|
|
1995
1899
|
if (topic === 'conversation') {
|
|
1996
1900
|
return conversations.watch(input[0], input[1], deliver);
|
|
1997
1901
|
}
|
|
1902
|
+
if (topic === 'conversations')
|
|
1903
|
+
return conversations.watchList(deliver);
|
|
1998
1904
|
if (topic === 'presentation') {
|
|
1999
1905
|
const listener = {
|
|
2000
1906
|
app: input[0],
|
|
@@ -2057,9 +1963,8 @@ export function composeRuntime(options) {
|
|
|
2057
1963
|
queueSubscriptions.delete(queue);
|
|
2058
1964
|
};
|
|
2059
1965
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
}
|
|
1966
|
+
return watchPlan(planned(input[0], null), deliver);
|
|
1967
|
+
};
|
|
2063
1968
|
const md = {
|
|
2064
1969
|
loaded() {
|
|
2065
1970
|
return openLocal().then(() => undefined);
|
|
@@ -2067,18 +1972,8 @@ export function composeRuntime(options) {
|
|
|
2067
1972
|
rooms() {
|
|
2068
1973
|
return listed();
|
|
2069
1974
|
},
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
},
|
|
2073
|
-
query: ((input) => readSide.query(planOf(input, roomFacts()))),
|
|
2074
|
-
async search(input) {
|
|
2075
|
-
const plan = searchPlanOf(input);
|
|
2076
|
-
const selected = plan.documents === undefined
|
|
2077
|
-
? null
|
|
2078
|
-
: (await readSide.query(filterOf(plan.documents, roomFacts())))
|
|
2079
|
-
.documents;
|
|
2080
|
-
return searchIndex.search(plan, selected);
|
|
2081
|
-
},
|
|
1975
|
+
query: ((input) => readSide.query(planned(input, null))),
|
|
1976
|
+
search: (input) => searched(input, null),
|
|
2082
1977
|
types(locator) {
|
|
2083
1978
|
return {
|
|
2084
1979
|
collection: modelTypes(modelOf(locator)),
|
|
@@ -2088,12 +1983,6 @@ export function composeRuntime(options) {
|
|
|
2088
1983
|
: modelTypes(modelOf(locator, 'typed-selection')),
|
|
2089
1984
|
};
|
|
2090
1985
|
},
|
|
2091
|
-
syncStatus() {
|
|
2092
|
-
return shown;
|
|
2093
|
-
},
|
|
2094
|
-
serviceMode() {
|
|
2095
|
-
return serviceMode;
|
|
2096
|
-
},
|
|
2097
1986
|
roomDraft: {
|
|
2098
1987
|
list() {
|
|
2099
1988
|
return exclusive(async () => {
|
|
@@ -2174,9 +2063,6 @@ export function composeRuntime(options) {
|
|
|
2174
2063
|
save(room, named, writtenBy) {
|
|
2175
2064
|
return exclusive(() => saveRoomDrafts(room, named, writtenBy));
|
|
2176
2065
|
},
|
|
2177
|
-
outcome(identifier) {
|
|
2178
|
-
return outcomes.get(identifier) ?? null;
|
|
2179
|
-
},
|
|
2180
2066
|
history(locator, path, query) {
|
|
2181
2067
|
return readHistory(locator, path, query);
|
|
2182
2068
|
},
|
|
@@ -2191,15 +2077,10 @@ export function composeRuntime(options) {
|
|
|
2191
2077
|
log() {
|
|
2192
2078
|
return journal.export();
|
|
2193
2079
|
},
|
|
2194
|
-
storageLoss() {
|
|
2195
|
-
return storageLoss;
|
|
2196
|
-
},
|
|
2197
2080
|
persistence() {
|
|
2198
2081
|
return options.shell.persistentStorage;
|
|
2199
2082
|
},
|
|
2200
|
-
observe: (
|
|
2201
|
-
deliver(structuredClone(message));
|
|
2202
|
-
}), topic === 'query')),
|
|
2083
|
+
observe: observing(watch),
|
|
2203
2084
|
async reauthorize() {
|
|
2204
2085
|
if (refusal === null || !refusal.reauthorizationOffered)
|
|
2205
2086
|
return;
|
|
@@ -2208,8 +2089,19 @@ export function composeRuntime(options) {
|
|
|
2208
2089
|
await mutations;
|
|
2209
2090
|
authorization = undefined;
|
|
2210
2091
|
discoveryPaused = false;
|
|
2211
|
-
|
|
2092
|
+
if (options.adapter.roomStream === undefined)
|
|
2093
|
+
await sync('unstreamed');
|
|
2094
|
+
else
|
|
2095
|
+
await task(ensureAuthorization);
|
|
2212
2096
|
},
|
|
2213
2097
|
};
|
|
2214
|
-
|
|
2098
|
+
const reachable = {
|
|
2099
|
+
md,
|
|
2100
|
+
watch,
|
|
2101
|
+
plan: planned,
|
|
2102
|
+
query: (plan) => readSide.query(plan),
|
|
2103
|
+
watchPlan,
|
|
2104
|
+
search: searched,
|
|
2105
|
+
};
|
|
2106
|
+
return { md, stop, watch, within: (reach) => narrowed(reachable, reach) };
|
|
2215
2107
|
}
|