@agentshouse/mdruntime 0.1.0-c3903a68dcbbf → 0.1.0-c6e4c19c13638
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 +72 -41
- package/README.md +11 -11
- package/dist/adapter.d.ts +80 -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 +34 -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 +518 -639
- 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 +78 -27
- package/dist/model.d.ts +4 -11
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +4 -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 +104 -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 +31 -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 +4 -4
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +14 -19
- 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,19 @@ 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
|
+
const submitting = new Map();
|
|
86
|
+
const answering = new Map();
|
|
87
|
+
let complete = false;
|
|
88
|
+
let recorded = false;
|
|
89
|
+
const stale = new Set();
|
|
127
90
|
let chain = Promise.resolve();
|
|
128
91
|
let mutations = Promise.resolve();
|
|
129
|
-
let inflight = false;
|
|
130
|
-
let queued = false;
|
|
131
92
|
const roomListeners = new Set();
|
|
132
93
|
const refusalListeners = new Set();
|
|
133
94
|
const storageLossListeners = new Set();
|
|
@@ -138,12 +99,13 @@ export function composeRuntime(options) {
|
|
|
138
99
|
const readSide = createReadSide();
|
|
139
100
|
let withdrawConversations;
|
|
140
101
|
let currencyQueued = false;
|
|
102
|
+
let socketOpen = false;
|
|
141
103
|
let serviceMode = 'open';
|
|
142
104
|
let haltedRefusal = false;
|
|
143
105
|
let openSignals = 0;
|
|
144
106
|
const serviceModeListeners = new Set();
|
|
145
107
|
const conversations = createConversationStore({
|
|
146
|
-
|
|
108
|
+
agents: declaredAgents,
|
|
147
109
|
documents: (locator) => readSide.query(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
148
110
|
call: (name, input) => invokeAction(name, input),
|
|
149
111
|
durably,
|
|
@@ -252,20 +214,14 @@ export function composeRuntime(options) {
|
|
|
252
214
|
function listed() {
|
|
253
215
|
return structuredClone([...rooms.values()]);
|
|
254
216
|
}
|
|
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) => {
|
|
217
|
+
function roomFacts(reach = null) {
|
|
218
|
+
const held = [...rooms.values()]
|
|
219
|
+
.filter((room) => holds(reach, room.locator))
|
|
220
|
+
.map((room) => {
|
|
221
|
+
const governed = governs(reach, room);
|
|
266
222
|
const attached = [
|
|
267
|
-
modelOf(room.locator),
|
|
268
|
-
...(
|
|
223
|
+
...(governed.collection ? [modelOf(room.locator)] : []),
|
|
224
|
+
...(governed.selection ? [modelOf(room.locator, 'typed-selection')] : []),
|
|
269
225
|
];
|
|
270
226
|
return {
|
|
271
227
|
locator: room.locator,
|
|
@@ -306,14 +262,18 @@ export function composeRuntime(options) {
|
|
|
306
262
|
for (const listener of statusListeners)
|
|
307
263
|
listener(shown);
|
|
308
264
|
}
|
|
309
|
-
function
|
|
310
|
-
|
|
265
|
+
function entryOf(batch) {
|
|
266
|
+
const state = pendings.get(batch.id);
|
|
267
|
+
if (state?.accepted !== undefined) {
|
|
268
|
+
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
269
|
+
}
|
|
270
|
+
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
311
271
|
}
|
|
312
272
|
function entriesOf(locator) {
|
|
313
273
|
const replica = replicas.get(locator);
|
|
314
274
|
if (replica === undefined)
|
|
315
275
|
return [];
|
|
316
|
-
return replica.queue.map(
|
|
276
|
+
return replica.queue.map(entryOf);
|
|
317
277
|
}
|
|
318
278
|
function emitQueue(locator) {
|
|
319
279
|
const entries = entriesOf(locator);
|
|
@@ -339,23 +299,32 @@ export function composeRuntime(options) {
|
|
|
339
299
|
for (const app of set.presentations)
|
|
340
300
|
emitPresentation(app);
|
|
341
301
|
conversations.changed(set);
|
|
342
|
-
const cards = declaredCards();
|
|
343
|
-
if (cards !== undefined && set.documents.some((change) => change.locator === cards.locator)) {
|
|
344
|
-
scheduleCurrency();
|
|
345
|
-
}
|
|
346
302
|
}
|
|
347
303
|
function stopTimer() {
|
|
348
|
-
if (
|
|
304
|
+
if (resetTimer === undefined)
|
|
349
305
|
return;
|
|
350
|
-
|
|
351
|
-
|
|
306
|
+
clearTimeout(resetTimer);
|
|
307
|
+
resetTimer = undefined;
|
|
352
308
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
309
|
+
function armTimer() {
|
|
310
|
+
stopTimer();
|
|
311
|
+
if (closed || stopping)
|
|
355
312
|
return;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
313
|
+
let earliest = null;
|
|
314
|
+
for (const { submission } of pendings.values()) {
|
|
315
|
+
if (submission.state !== 'reset')
|
|
316
|
+
continue;
|
|
317
|
+
const at = Date.parse(submission.reset);
|
|
318
|
+
if (earliest === null || at < earliest)
|
|
319
|
+
earliest = at;
|
|
320
|
+
}
|
|
321
|
+
if (earliest === null)
|
|
322
|
+
return;
|
|
323
|
+
// A Save House held back goes again only once the reset its answer named has passed.
|
|
324
|
+
resetTimer = setTimeout(() => {
|
|
325
|
+
resetTimer = undefined;
|
|
326
|
+
submitAll();
|
|
327
|
+
}, Math.max(0, earliest - Date.now()));
|
|
359
328
|
}
|
|
360
329
|
async function refuse(reason, provider = null) {
|
|
361
330
|
stopTimer();
|
|
@@ -381,7 +350,6 @@ export function composeRuntime(options) {
|
|
|
381
350
|
}
|
|
382
351
|
for (const key of keys) {
|
|
383
352
|
invalidModels.add(key);
|
|
384
|
-
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
385
353
|
models.set(key, revoked);
|
|
386
354
|
liveModels.delete(key);
|
|
387
355
|
}
|
|
@@ -396,7 +364,7 @@ export function composeRuntime(options) {
|
|
|
396
364
|
};
|
|
397
365
|
const read = readConfirmed(record, before.confirmed);
|
|
398
366
|
replicas.set(locator, read.kind === 'staged'
|
|
399
|
-
? replicaOf(read.confirmed, before.queue,
|
|
367
|
+
? replicaOf(read.confirmed, before.queue, before)
|
|
400
368
|
: { ...before, confirmed: { ...before.confirmed, record } });
|
|
401
369
|
}
|
|
402
370
|
rooms.set(locator, {
|
|
@@ -427,18 +395,16 @@ export function composeRuntime(options) {
|
|
|
427
395
|
rooms.set(room.locator, {
|
|
428
396
|
name: room.name,
|
|
429
397
|
locator: room.locator,
|
|
398
|
+
handle: room.handle,
|
|
430
399
|
operations: room.operations,
|
|
431
400
|
replica: room.replica,
|
|
432
|
-
attachedModel: room.attachedModel,
|
|
433
401
|
model: modelTypes(modelOf(room.locator)),
|
|
434
402
|
typedSelection: selection === null
|
|
435
403
|
? null
|
|
436
404
|
: {
|
|
437
405
|
roots: selection.roots,
|
|
438
|
-
attachedModel: selection.attachedModel,
|
|
439
406
|
model: modelTypes(modelOf(room.locator, 'typed-selection')),
|
|
440
407
|
},
|
|
441
|
-
untypedFolders: room.untypedFolders ?? [],
|
|
442
408
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
443
409
|
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
444
410
|
attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
|
|
@@ -453,9 +419,6 @@ export function composeRuntime(options) {
|
|
|
453
419
|
discovered.clear();
|
|
454
420
|
for (const room of listed)
|
|
455
421
|
discovered.set(room.locator, room);
|
|
456
|
-
for (const locator of wanted.keys())
|
|
457
|
-
if (!listedLocators.has(locator))
|
|
458
|
-
wanted.delete(locator);
|
|
459
422
|
await dropUnlisted(listedLocators, local);
|
|
460
423
|
for (const room of listed) {
|
|
461
424
|
const held = replicas.get(room.locator)?.confirmed.record;
|
|
@@ -477,12 +440,6 @@ export function composeRuntime(options) {
|
|
|
477
440
|
await exclusive(async () => {
|
|
478
441
|
const unlisted = unlistedHeld(listed);
|
|
479
442
|
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
443
|
const removed = unlisted.map((locator) => changeSet(locator, replicas.get(locator) ?? null, null, {
|
|
487
444
|
room: true,
|
|
488
445
|
queue: true,
|
|
@@ -495,6 +452,7 @@ export function composeRuntime(options) {
|
|
|
495
452
|
rooms.delete(locator);
|
|
496
453
|
roomDrafts.delete(locator);
|
|
497
454
|
unreadable.delete(locator);
|
|
455
|
+
stale.delete(locator);
|
|
498
456
|
scopedRefusals.delete(locator);
|
|
499
457
|
sourceRefusals.delete(locator);
|
|
500
458
|
declarationRefusals.delete(locator);
|
|
@@ -503,7 +461,6 @@ export function composeRuntime(options) {
|
|
|
503
461
|
models.delete(key);
|
|
504
462
|
liveModels.delete(key);
|
|
505
463
|
invalidModels.delete(key);
|
|
506
|
-
modelEpoch.delete(key);
|
|
507
464
|
}
|
|
508
465
|
journal.record(`room unlisted ${locator}`);
|
|
509
466
|
}
|
|
@@ -516,7 +473,6 @@ export function composeRuntime(options) {
|
|
|
516
473
|
if (prior !== undefined &&
|
|
517
474
|
prior.status !== 'unavailable' &&
|
|
518
475
|
(persisted.status === 'unavailable' || !sameModel(prior.snapshot, persisted.snapshot))) {
|
|
519
|
-
modelEpoch.set(key, (modelEpoch.get(key) ?? 0) + 1);
|
|
520
476
|
invalidModels.add(key);
|
|
521
477
|
}
|
|
522
478
|
const model = persisted.status === 'unavailable' ||
|
|
@@ -531,10 +487,9 @@ export function composeRuntime(options) {
|
|
|
531
487
|
return model;
|
|
532
488
|
}
|
|
533
489
|
async function hydrate(opened) {
|
|
534
|
-
const [records, queues,
|
|
490
|
+
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named] = await Promise.all([
|
|
535
491
|
opened.all(),
|
|
536
492
|
opened.queues(),
|
|
537
|
-
opened.outcomes(),
|
|
538
493
|
opened.roomDrafts(),
|
|
539
494
|
opened.presentation(),
|
|
540
495
|
opened.conversations(),
|
|
@@ -542,11 +497,9 @@ export function composeRuntime(options) {
|
|
|
542
497
|
opened.lastCompleted(),
|
|
543
498
|
opened.providerLimits(),
|
|
544
499
|
opened.capabilities(),
|
|
545
|
-
opened.slots(),
|
|
546
500
|
]);
|
|
547
501
|
limits = declared;
|
|
548
502
|
capabilities = named;
|
|
549
|
-
slots = listedSlots;
|
|
550
503
|
roomDrafts.clear();
|
|
551
504
|
for (const record of kept)
|
|
552
505
|
rememberRoomDraft(record);
|
|
@@ -560,12 +513,6 @@ export function composeRuntime(options) {
|
|
|
560
513
|
for (const { batch, state } of queue.pending)
|
|
561
514
|
pendings.set(batch.id, state);
|
|
562
515
|
}
|
|
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
516
|
const previousReplicas = new Map(replicas);
|
|
570
517
|
replicas.clear();
|
|
571
518
|
for (const record of records) {
|
|
@@ -580,7 +527,7 @@ export function composeRuntime(options) {
|
|
|
580
527
|
record: retyped,
|
|
581
528
|
collection: parseCollection(retyped.documents, compositionOf(retyped)),
|
|
582
529
|
};
|
|
583
|
-
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [],
|
|
530
|
+
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
584
531
|
const existing = rooms.get(record.locator);
|
|
585
532
|
if (existing === undefined)
|
|
586
533
|
list(record, false);
|
|
@@ -591,16 +538,13 @@ export function composeRuntime(options) {
|
|
|
591
538
|
attachments: record.attachments,
|
|
592
539
|
operations: record.operations,
|
|
593
540
|
replica: record.replica,
|
|
594
|
-
attachedModel: record.attachedModel,
|
|
595
541
|
model: modelTypes(model),
|
|
596
542
|
typedSelection: record.typedSelection === null
|
|
597
543
|
? null
|
|
598
544
|
: {
|
|
599
545
|
roots: record.typedSelection.roots,
|
|
600
|
-
attachedModel: record.typedSelection.attachedModel,
|
|
601
546
|
model: modelTypes(selectionModel),
|
|
602
547
|
},
|
|
603
|
-
untypedFolders: record.untypedFolders,
|
|
604
548
|
});
|
|
605
549
|
}
|
|
606
550
|
if (read.kind === 'refused') {
|
|
@@ -666,7 +610,7 @@ export function composeRuntime(options) {
|
|
|
666
610
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
667
611
|
}
|
|
668
612
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
669
|
-
const withdrawn = withdrawals(confirmed, queue, withdrawable
|
|
613
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable);
|
|
670
614
|
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
671
615
|
const returning = [
|
|
672
616
|
...withdrawn.map((batch) => ({ batch, returned: { kind: 'withdrawn' } })).reverse(),
|
|
@@ -687,69 +631,88 @@ export function composeRuntime(options) {
|
|
|
687
631
|
});
|
|
688
632
|
}
|
|
689
633
|
}
|
|
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
634
|
await local.write({
|
|
698
635
|
next: confirmed.record,
|
|
699
636
|
previous: before?.confirmed.record ?? null,
|
|
700
637
|
queue: kept,
|
|
701
638
|
previousQueue: before?.queue ?? [],
|
|
702
|
-
outcomes: settledOutcomes,
|
|
703
639
|
roomDrafts: [...written.values()],
|
|
704
640
|
});
|
|
705
|
-
for (const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
pendings.delete(id);
|
|
641
|
+
for (const batch of before?.queue ?? []) {
|
|
642
|
+
if (!kept.includes(batch))
|
|
643
|
+
pendings.delete(batch.id);
|
|
709
644
|
}
|
|
710
645
|
for (const record of written.values())
|
|
711
646
|
rememberRoomDraft(record);
|
|
712
647
|
for (const batch of withdrawn)
|
|
713
648
|
journal.record(`outcome ${locator} ${batch.id} withdrawn`);
|
|
714
|
-
replicas.set(locator, replicaOf(confirmed, kept,
|
|
649
|
+
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
715
650
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
716
651
|
room: settling.room,
|
|
717
652
|
queue: kept.length !== (before?.queue.length ?? 0),
|
|
718
653
|
drafts: [...written.keys()],
|
|
719
654
|
});
|
|
720
655
|
}
|
|
721
|
-
function
|
|
656
|
+
function adoptModels(locator, record) {
|
|
657
|
+
for (const [attached, model] of [
|
|
658
|
+
['collection', record.model],
|
|
659
|
+
['typed-selection', record.selectionModel],
|
|
660
|
+
]) {
|
|
661
|
+
const key = modelKey(locator, attached);
|
|
662
|
+
models.set(key, model);
|
|
663
|
+
invalidModels.delete(key);
|
|
664
|
+
if (model.status === 'current')
|
|
665
|
+
liveModels.set(key, JSON.stringify(model.snapshot));
|
|
666
|
+
else if (model.status === 'unavailable')
|
|
667
|
+
liveModels.delete(key);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function remodel(locator, answers, held) {
|
|
671
|
+
const taken = (answer, previous, attached) => {
|
|
672
|
+
if (answer === undefined)
|
|
673
|
+
return previous;
|
|
674
|
+
const { state, refused } = attachedModel(answer, previous);
|
|
675
|
+
if (refused !== null) {
|
|
676
|
+
const where = refused.location === undefined ? '' : ` ${refused.location}`;
|
|
677
|
+
journal.record(`model invalid ${locator} ${attached} ${refused.identity} ${refused.rule}${where}`);
|
|
678
|
+
}
|
|
679
|
+
models.set(modelKey(locator, attached), state);
|
|
680
|
+
return state;
|
|
681
|
+
};
|
|
682
|
+
return {
|
|
683
|
+
collection: taken(answers?.collection, held.collection, 'collection'),
|
|
684
|
+
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function publish(local, room, confirmed, base) {
|
|
722
688
|
return exclusive(async () => {
|
|
723
689
|
if (!continuing())
|
|
724
|
-
return;
|
|
690
|
+
return true;
|
|
725
691
|
const prior = replicas.get(room.locator);
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
throw new Error(`Cannot retype staged Room ${room.locator}`);
|
|
692
|
+
if (base !== undefined &&
|
|
693
|
+
(prior?.confirmed.record.roomVersion !== base.record.roomVersion ||
|
|
694
|
+
prior.confirmed.record.revisions !== base.record.revisions)) {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
adoptModels(room.locator, confirmed.record);
|
|
733
698
|
const queue = prior?.queue ?? [];
|
|
734
|
-
const roomVersion =
|
|
699
|
+
const roomVersion = confirmed.record.roomVersion;
|
|
735
700
|
const accepted = queue.filter((batch) => {
|
|
736
|
-
const
|
|
737
|
-
return (
|
|
738
|
-
options.adapter.reached(room.locator, roomVersion, outcome.roomVersion));
|
|
701
|
+
const accepted = pendings.get(batch.id)?.accepted;
|
|
702
|
+
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
739
703
|
});
|
|
740
|
-
const set = await settle(local, room.locator, prior,
|
|
704
|
+
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true });
|
|
741
705
|
for (const batch of accepted)
|
|
742
706
|
pendings.delete(batch.id);
|
|
743
|
-
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
}
|
|
747
|
-
unreadable.delete(room.locator);
|
|
707
|
+
stale.delete(room.locator);
|
|
708
|
+
if (unreadable.delete(room.locator))
|
|
709
|
+
scopedRefusals.delete(room.locator);
|
|
748
710
|
sourceRefusals.delete(room.locator);
|
|
749
711
|
declarationRefusals.delete(room.locator);
|
|
750
712
|
list(room, true);
|
|
751
713
|
publishChangeSet(set);
|
|
752
714
|
moveTo({ ...status, ...attention() });
|
|
715
|
+
return true;
|
|
753
716
|
});
|
|
754
717
|
}
|
|
755
718
|
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
@@ -778,7 +741,7 @@ export function composeRuntime(options) {
|
|
|
778
741
|
operations: room.operations,
|
|
779
742
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
780
743
|
providerLimits: limits,
|
|
781
|
-
|
|
744
|
+
handle: room.handle,
|
|
782
745
|
visible: replica.visible,
|
|
783
746
|
changes: parts.flatMap(({ changes }) => changes),
|
|
784
747
|
bases: parts.flatMap(({ bases }) => bases),
|
|
@@ -802,15 +765,16 @@ export function composeRuntime(options) {
|
|
|
802
765
|
return { outcome: 'refused', rule: 'durable-write' };
|
|
803
766
|
}
|
|
804
767
|
pendings.set(built.batch.id, { drafts: saved, submission: { state: 'waiting' }, lost: false });
|
|
768
|
+
recorded = false;
|
|
805
769
|
for (const path of consumed)
|
|
806
770
|
forgetRoomDraft(locator, path);
|
|
807
|
-
replicas.set(locator, replicaOf(replica.confirmed, batches,
|
|
771
|
+
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
808
772
|
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
809
773
|
queue: true,
|
|
810
774
|
drafts: consumed,
|
|
811
775
|
}));
|
|
812
776
|
moveTo({ ...saving(status), ...attention() });
|
|
813
|
-
|
|
777
|
+
submitLater(locator);
|
|
814
778
|
return { outcome: 'saved', identifier: built.batch.id };
|
|
815
779
|
}
|
|
816
780
|
async function mark(local, locator, identifier, submission, lost) {
|
|
@@ -875,10 +839,10 @@ export function composeRuntime(options) {
|
|
|
875
839
|
return { kind: 'transport-uncertainty' };
|
|
876
840
|
}
|
|
877
841
|
}
|
|
878
|
-
function
|
|
842
|
+
function declaredAgents() {
|
|
879
843
|
for (const capability of capabilities) {
|
|
880
|
-
if (capability.
|
|
881
|
-
return capability.
|
|
844
|
+
if (capability.conversationAgents !== undefined)
|
|
845
|
+
return capability.conversationAgents;
|
|
882
846
|
}
|
|
883
847
|
return undefined;
|
|
884
848
|
}
|
|
@@ -909,8 +873,6 @@ export function composeRuntime(options) {
|
|
|
909
873
|
journal.record(`transfer action ${name}`);
|
|
910
874
|
try {
|
|
911
875
|
const result = await options.adapter.action(authorization, { name, input });
|
|
912
|
-
if (result.kind === 'action' && result.at !== undefined)
|
|
913
|
-
want(result.at);
|
|
914
876
|
if (result.kind === 'scoped-refusal') {
|
|
915
877
|
const room = replicas.get(result.locator)?.confirmed.record;
|
|
916
878
|
if (room !== undefined)
|
|
@@ -942,32 +904,11 @@ export function composeRuntime(options) {
|
|
|
942
904
|
options.adapter.reached(locator, written, roomVersion) &&
|
|
943
905
|
!options.adapter.reached(locator, roomVersion, written));
|
|
944
906
|
}
|
|
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) {
|
|
907
|
+
async function pullRefresh(authorized, confirmed) {
|
|
967
908
|
if (!continuing())
|
|
968
909
|
return unconfirmed;
|
|
969
910
|
try {
|
|
970
|
-
const result = await refreshRoom(options.adapter, authorized, confirmed,
|
|
911
|
+
const result = await refreshRoom(options.adapter, authorized, confirmed, (models, held) => remodel(confirmed.record.locator, models, held), continuing);
|
|
971
912
|
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
972
913
|
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
973
914
|
return result;
|
|
@@ -984,7 +925,7 @@ export function composeRuntime(options) {
|
|
|
984
925
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
985
926
|
collection: modelOf(room.locator),
|
|
986
927
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
987
|
-
}, continuing);
|
|
928
|
+
}, (models, held) => remodel(room.locator, models, held), continuing);
|
|
988
929
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
989
930
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
990
931
|
return result;
|
|
@@ -997,20 +938,28 @@ export function composeRuntime(options) {
|
|
|
997
938
|
async function advance(room, local, authorized) {
|
|
998
939
|
const held = replicas.get(room.locator);
|
|
999
940
|
if (held !== undefined) {
|
|
1000
|
-
const refreshed = await pullRefresh(authorized, held.confirmed
|
|
941
|
+
const refreshed = await pullRefresh(authorized, held.confirmed);
|
|
1001
942
|
if (!continuing())
|
|
1002
943
|
return unconfirmed;
|
|
1003
944
|
if (refreshed.kind === 'still-current') {
|
|
945
|
+
stale.delete(room.locator);
|
|
1004
946
|
if (sourceRefusals.has(room.locator))
|
|
1005
|
-
return
|
|
947
|
+
return null;
|
|
1006
948
|
if (unreadable.delete(room.locator)) {
|
|
949
|
+
scopedRefusals.delete(room.locator);
|
|
1007
950
|
list(room, true);
|
|
1008
951
|
emitRooms();
|
|
1009
952
|
}
|
|
1010
953
|
return null;
|
|
1011
954
|
}
|
|
1012
955
|
if (refreshed.kind === 'advanced') {
|
|
1013
|
-
await publish(local, room, refreshed.confirmed)
|
|
956
|
+
if (!(await publish(local, room, refreshed.confirmed, held.confirmed))) {
|
|
957
|
+
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
958
|
+
const reached = now !== undefined &&
|
|
959
|
+
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
960
|
+
return reached ? null : advance(room, local, authorized);
|
|
961
|
+
}
|
|
962
|
+
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
1014
963
|
return null;
|
|
1015
964
|
}
|
|
1016
965
|
if (refreshed.kind !== 'beyond-horizon')
|
|
@@ -1025,7 +974,10 @@ export function composeRuntime(options) {
|
|
|
1025
974
|
return null;
|
|
1026
975
|
}
|
|
1027
976
|
async function withhold(withheld, room, local, progress) {
|
|
1028
|
-
|
|
977
|
+
if (withheld.kind === 'refused')
|
|
978
|
+
stale.delete(room.locator);
|
|
979
|
+
else
|
|
980
|
+
stale.add(room.locator);
|
|
1029
981
|
if (withheld.kind === 'scoped-refusal') {
|
|
1030
982
|
await scoped(withheld, room, local);
|
|
1031
983
|
}
|
|
@@ -1063,84 +1015,39 @@ export function composeRuntime(options) {
|
|
|
1063
1015
|
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
1064
1016
|
}
|
|
1065
1017
|
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
|
-
};
|
|
1018
|
+
if (result.kind === 'expired')
|
|
1019
|
+
return { batch, returned: { kind: 'expired' } };
|
|
1020
|
+
return { batch, returned: { kind: result.kind, code: result.code, fields: result.fields } };
|
|
1095
1021
|
}
|
|
1096
1022
|
function logAccepted(locator, submitted, accepted) {
|
|
1097
1023
|
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
1098
1024
|
(accepted.author === undefined ? '' : ` author ${accepted.author}`) +
|
|
1099
1025
|
(accepted.time === undefined ? '' : ` time ${accepted.time}`));
|
|
1100
1026
|
}
|
|
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) {
|
|
1027
|
+
async function adoptAccepted(room, local, submitted, accepted) {
|
|
1123
1028
|
if (!continuing())
|
|
1124
1029
|
return unconfirmed;
|
|
1125
1030
|
const current = replicas.get(room.locator);
|
|
1126
1031
|
if (current === undefined || !current.queue.some((batch) => batch.id === submitted.id)) {
|
|
1127
1032
|
return null;
|
|
1128
1033
|
}
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1034
|
+
const held = current.confirmed.record.roomVersion;
|
|
1035
|
+
logAccepted(room.locator, submitted, accepted);
|
|
1036
|
+
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1037
|
+
? { kind: 'staged', confirmed: current.confirmed }
|
|
1038
|
+
: applyReturned(current.confirmed, accepted.documents, accepted.revisions, accepted.removals, accepted.attachments, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1039
|
+
? accepted.roomVersion
|
|
1040
|
+
: held, accepted.roomVersion);
|
|
1041
|
+
if (applied.kind !== 'staged') {
|
|
1042
|
+
const state = pendings.get(submitted.id);
|
|
1043
|
+
await local.accept(room.locator, submitted.id, accepted.roomVersion);
|
|
1044
|
+
if (state !== undefined)
|
|
1045
|
+
pendings.set(submitted.id, { ...state, accepted: accepted.roomVersion });
|
|
1046
|
+
emitQueue(room.locator);
|
|
1139
1047
|
return applied;
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
});
|
|
1048
|
+
}
|
|
1049
|
+
const set = await settle(local, room.locator, current, applied.confirmed, current.queue.filter((batch) => batch.id !== submitted.id), { room: true });
|
|
1050
|
+
pendings.delete(submitted.id);
|
|
1144
1051
|
unreadable.delete(room.locator);
|
|
1145
1052
|
sourceRefusals.delete(room.locator);
|
|
1146
1053
|
declarationRefusals.delete(room.locator);
|
|
@@ -1149,17 +1056,8 @@ export function composeRuntime(options) {
|
|
|
1149
1056
|
moveTo({ ...status, ...attention() });
|
|
1150
1057
|
return null;
|
|
1151
1058
|
}
|
|
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));
|
|
1059
|
+
async function adopted(room, local, submitted, accepted) {
|
|
1060
|
+
const failed = await exclusive(() => adoptAccepted(room, local, submitted, accepted));
|
|
1163
1061
|
if (failed === null)
|
|
1164
1062
|
return true;
|
|
1165
1063
|
if (failed.kind === 'refused' && continuing())
|
|
@@ -1172,10 +1070,8 @@ export function composeRuntime(options) {
|
|
|
1172
1070
|
const head = replica?.queue[0];
|
|
1173
1071
|
if (replica === undefined || head === undefined)
|
|
1174
1072
|
return;
|
|
1175
|
-
if (
|
|
1176
|
-
progress.complete = false;
|
|
1073
|
+
if (pendings.get(head.id)?.accepted !== undefined)
|
|
1177
1074
|
return;
|
|
1178
|
-
}
|
|
1179
1075
|
const state = pendings.get(head.id);
|
|
1180
1076
|
const { submission, lost: earlier } = state ?? {
|
|
1181
1077
|
submission: { state: 'waiting' },
|
|
@@ -1203,6 +1099,7 @@ export function composeRuntime(options) {
|
|
|
1203
1099
|
return;
|
|
1204
1100
|
}
|
|
1205
1101
|
const opensBefore = openSignals;
|
|
1102
|
+
const answered = awaitingAnswer(room.locator);
|
|
1206
1103
|
try {
|
|
1207
1104
|
result = await options.adapter.mutate(authorized, {
|
|
1208
1105
|
locator: room.locator,
|
|
@@ -1211,11 +1108,14 @@ export function composeRuntime(options) {
|
|
|
1211
1108
|
}, (upload) => exclusive(() => keepUpload(local, room.locator, head.id, upload)));
|
|
1212
1109
|
}
|
|
1213
1110
|
catch {
|
|
1111
|
+
answered();
|
|
1214
1112
|
progress.complete = false;
|
|
1215
1113
|
if (continuing())
|
|
1216
1114
|
progress.reachable = false;
|
|
1217
1115
|
return;
|
|
1218
1116
|
}
|
|
1117
|
+
if (result.kind !== 'accepted' || !continuing())
|
|
1118
|
+
answered();
|
|
1219
1119
|
if (!continuing())
|
|
1220
1120
|
return;
|
|
1221
1121
|
if (result.kind === 'transport-uncertainty') {
|
|
@@ -1268,6 +1168,7 @@ export function composeRuntime(options) {
|
|
|
1268
1168
|
const reset = result.reset;
|
|
1269
1169
|
progress.complete = false;
|
|
1270
1170
|
await exclusive(() => mark(local, room.locator, head.id, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
1171
|
+
armTimer();
|
|
1271
1172
|
return;
|
|
1272
1173
|
}
|
|
1273
1174
|
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
@@ -1294,98 +1195,35 @@ export function composeRuntime(options) {
|
|
|
1294
1195
|
return withhold(withheld, room, local, progress);
|
|
1295
1196
|
continue;
|
|
1296
1197
|
}
|
|
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);
|
|
1198
|
+
const kept = await adopted(room, local, head, result).finally(answered);
|
|
1199
|
+
if (!kept) {
|
|
1310
1200
|
progress.complete = false;
|
|
1311
1201
|
return;
|
|
1312
1202
|
}
|
|
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
|
-
}
|
|
1341
|
-
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
|
-
return;
|
|
1354
|
-
}
|
|
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
1203
|
}
|
|
1372
|
-
|
|
1204
|
+
}
|
|
1205
|
+
function awaitingAnswer(locator) {
|
|
1206
|
+
let release = () => { };
|
|
1207
|
+
const awaited = new Promise((resolve) => {
|
|
1208
|
+
release = resolve;
|
|
1209
|
+
});
|
|
1210
|
+
answering.set(locator, awaited);
|
|
1211
|
+
return () => {
|
|
1212
|
+
if (answering.get(locator) === awaited)
|
|
1213
|
+
answering.delete(locator);
|
|
1214
|
+
release();
|
|
1215
|
+
};
|
|
1373
1216
|
}
|
|
1374
1217
|
async function retainModel(local, room, attached, next) {
|
|
1375
1218
|
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
1219
|
const previous = modelOf(room.locator, attached);
|
|
1380
1220
|
const heldBefore = replicas.get(room.locator)?.confirmed.record;
|
|
1381
1221
|
if (JSON.stringify(previous) === JSON.stringify(next) &&
|
|
1382
1222
|
(heldBefore === undefined ||
|
|
1383
1223
|
(JSON.stringify(heldBefore.operations) === JSON.stringify(room.operations) &&
|
|
1384
1224
|
JSON.stringify(heldBefore.replica) === JSON.stringify(room.replica) &&
|
|
1385
|
-
JSON.stringify(heldBefore.attachedModel) === JSON.stringify(room.attachedModel) &&
|
|
1386
1225
|
JSON.stringify(heldBefore.typedSelection) ===
|
|
1387
|
-
JSON.stringify(room.typedSelection ?? null)
|
|
1388
|
-
JSON.stringify(heldBefore.untypedFolders) === JSON.stringify(room.untypedFolders ?? []))))
|
|
1226
|
+
JSON.stringify(room.typedSelection ?? null))))
|
|
1389
1227
|
return;
|
|
1390
1228
|
if (next.status === 'unavailable') {
|
|
1391
1229
|
invalidModels.add(key);
|
|
@@ -1394,17 +1232,13 @@ export function composeRuntime(options) {
|
|
|
1394
1232
|
await exclusive(async () => {
|
|
1395
1233
|
if (!continuing())
|
|
1396
1234
|
return;
|
|
1397
|
-
if (next.status !== 'unavailable' && epoch !== (modelEpoch.get(key) ?? 0))
|
|
1398
|
-
return;
|
|
1399
1235
|
const held = replicas.get(room.locator);
|
|
1400
1236
|
if (held !== undefined) {
|
|
1401
1237
|
const declared = {
|
|
1402
1238
|
...held.confirmed.record,
|
|
1403
1239
|
operations: room.operations,
|
|
1404
1240
|
replica: room.replica,
|
|
1405
|
-
attachedModel: room.attachedModel,
|
|
1406
1241
|
typedSelection: room.typedSelection ?? null,
|
|
1407
|
-
untypedFolders: room.untypedFolders ?? [],
|
|
1408
1242
|
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1409
1243
|
};
|
|
1410
1244
|
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
@@ -1415,7 +1249,6 @@ export function composeRuntime(options) {
|
|
|
1415
1249
|
: {
|
|
1416
1250
|
...declared,
|
|
1417
1251
|
typedSelection: held.confirmed.record.typedSelection,
|
|
1418
|
-
untypedFolders: held.confirmed.record.untypedFolders,
|
|
1419
1252
|
};
|
|
1420
1253
|
const read = reclassified.kind === 'deferred' ? readConfirmed(record, held.confirmed) : reclassified;
|
|
1421
1254
|
await local.write({
|
|
@@ -1424,7 +1257,7 @@ export function composeRuntime(options) {
|
|
|
1424
1257
|
queue: held.queue,
|
|
1425
1258
|
});
|
|
1426
1259
|
if (read.kind === 'staged') {
|
|
1427
|
-
replicas.set(room.locator, replicaOf(read.confirmed, held.queue,
|
|
1260
|
+
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
|
|
1428
1261
|
if (declarationRefusals.delete(room.locator)) {
|
|
1429
1262
|
sourceRefusals.delete(room.locator);
|
|
1430
1263
|
unreadable.delete(room.locator);
|
|
@@ -1448,77 +1281,6 @@ export function composeRuntime(options) {
|
|
|
1448
1281
|
publishChangeSet(changeSet(room.locator, held, replicas.get(room.locator) ?? null, { room: true }));
|
|
1449
1282
|
});
|
|
1450
1283
|
}
|
|
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
1284
|
async function scoped(result, room, local) {
|
|
1523
1285
|
scopedRefusals.set(room.locator, { code: result.code, fields: result.fields });
|
|
1524
1286
|
let scopedAway = room;
|
|
@@ -1546,14 +1308,34 @@ export function composeRuntime(options) {
|
|
|
1546
1308
|
emitRooms();
|
|
1547
1309
|
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
1548
1310
|
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1311
|
+
function heldRooms(listed) {
|
|
1312
|
+
return listed.flatMap((room) => {
|
|
1313
|
+
const record = replicas.get(room.locator)?.confirmed.record;
|
|
1314
|
+
if (record === undefined || record.replica.selection !== room.replica.selection)
|
|
1315
|
+
return [];
|
|
1316
|
+
return [
|
|
1317
|
+
{
|
|
1318
|
+
locator: room.locator,
|
|
1319
|
+
roomVersion: record.roomVersion,
|
|
1320
|
+
documents: record.documents,
|
|
1321
|
+
revisions: record.revisions,
|
|
1322
|
+
attachments: record.attachments,
|
|
1323
|
+
},
|
|
1324
|
+
];
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
async function synchronize(listed, local, progress, attempt) {
|
|
1328
|
+
const prepared = await Promise.allSettled(listed.map((room) => prepareRoom(room, local, progress)));
|
|
1329
|
+
const ready = listed.filter((_, index) => {
|
|
1552
1330
|
const outcome = prepared[index];
|
|
1553
1331
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
1554
1332
|
});
|
|
1555
|
-
|
|
1556
|
-
|
|
1333
|
+
if (attempt === 'opened')
|
|
1334
|
+
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
1335
|
+
const loaded = await Promise.allSettled(ready
|
|
1336
|
+
.filter((room) => attempt === 'unstreamed' || !replicas.has(room.locator))
|
|
1337
|
+
.map((room) => loadRoom(room, local, progress)));
|
|
1338
|
+
const failed = [...prepared, ...loaded].find((room) => room.status === 'rejected');
|
|
1557
1339
|
if (failed !== undefined)
|
|
1558
1340
|
throw failed.reason;
|
|
1559
1341
|
}
|
|
@@ -1570,11 +1352,8 @@ export function composeRuntime(options) {
|
|
|
1570
1352
|
progress.complete = false;
|
|
1571
1353
|
return false;
|
|
1572
1354
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
acquireModel(local, room, authorization, null),
|
|
1576
|
-
...(selection === undefined ? [] : [acquireModel(local, room, authorization, selection)]),
|
|
1577
|
-
]);
|
|
1355
|
+
if (prior !== undefined)
|
|
1356
|
+
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
1578
1357
|
if (discoveryPaused || authorization === undefined)
|
|
1579
1358
|
return false;
|
|
1580
1359
|
if (room.replica.admission !== 'admitted') {
|
|
@@ -1582,24 +1361,53 @@ export function composeRuntime(options) {
|
|
|
1582
1361
|
progress.complete = false;
|
|
1583
1362
|
return false;
|
|
1584
1363
|
}
|
|
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
1364
|
return true;
|
|
1591
1365
|
}
|
|
1592
|
-
async function
|
|
1366
|
+
async function caughtUp(room, local, progress) {
|
|
1367
|
+
const held = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
1368
|
+
if (held === undefined || authorization === undefined)
|
|
1369
|
+
return;
|
|
1370
|
+
const behind = [announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later));
|
|
1371
|
+
if (!behind)
|
|
1372
|
+
return;
|
|
1373
|
+
const withheld = await advance(room, local, authorization);
|
|
1374
|
+
if (withheld !== null)
|
|
1375
|
+
await withhold(withheld, room, local, progress);
|
|
1376
|
+
}
|
|
1377
|
+
function revoked(locator) {
|
|
1378
|
+
return ['collection', 'typed-selection'].some((attached) => {
|
|
1379
|
+
const model = modelOf(locator, attached);
|
|
1380
|
+
return model.status === 'unavailable' && model.reason === 'revoked';
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
async function loadRoom(room, local, progress) {
|
|
1593
1384
|
if (discoveryPaused || authorization === undefined)
|
|
1594
1385
|
return;
|
|
1386
|
+
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
1387
|
+
await reload(room, local, progress);
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1595
1390
|
const withheld = await advance(room, local, authorization);
|
|
1596
1391
|
if (withheld !== null) {
|
|
1597
1392
|
await withhold(withheld, room, local, progress);
|
|
1598
1393
|
return;
|
|
1599
1394
|
}
|
|
1600
|
-
await
|
|
1395
|
+
await caughtUp(room, local, progress);
|
|
1601
1396
|
}
|
|
1602
|
-
async function
|
|
1397
|
+
async function reload(room, local, progress) {
|
|
1398
|
+
if (discoveryPaused || authorization === undefined)
|
|
1399
|
+
return;
|
|
1400
|
+
const bootstrapped = await pullBootstrap(authorization, room);
|
|
1401
|
+
if (!continuing())
|
|
1402
|
+
return;
|
|
1403
|
+
if (bootstrapped.kind !== 'staged') {
|
|
1404
|
+
await withhold(bootstrapped, room, local, progress);
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
await publish(local, room, bootstrapped.confirmed);
|
|
1408
|
+
await caughtUp(room, local, progress);
|
|
1409
|
+
}
|
|
1410
|
+
async function discoverRooms(local, progress, attempt) {
|
|
1603
1411
|
if (closed || authorization === undefined) {
|
|
1604
1412
|
notifyReady();
|
|
1605
1413
|
return;
|
|
@@ -1625,6 +1433,10 @@ export function composeRuntime(options) {
|
|
|
1625
1433
|
}
|
|
1626
1434
|
}
|
|
1627
1435
|
progress.reachable = false;
|
|
1436
|
+
progress.complete = false;
|
|
1437
|
+
if (attempt === 'opened') {
|
|
1438
|
+
options.adapter.roomStream?.hold(heldRooms([...discovered.values()]));
|
|
1439
|
+
}
|
|
1628
1440
|
notifyReady();
|
|
1629
1441
|
return;
|
|
1630
1442
|
}
|
|
@@ -1639,17 +1451,6 @@ export function composeRuntime(options) {
|
|
|
1639
1451
|
notifyReady();
|
|
1640
1452
|
return;
|
|
1641
1453
|
}
|
|
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
1454
|
for (const room of result.rooms) {
|
|
1654
1455
|
if (room.operations === undefined ||
|
|
1655
1456
|
!['read', 'sync', 'write', 'delete'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
@@ -1657,8 +1458,7 @@ export function composeRuntime(options) {
|
|
|
1657
1458
|
room.replica === undefined ||
|
|
1658
1459
|
typeof room.replica.selection !== 'string' ||
|
|
1659
1460
|
room.replica.selection.length === 0 ||
|
|
1660
|
-
!['admitted', 'unavailable'].includes(room.replica.admission)
|
|
1661
|
-
!declaredIdentity(room.attachedModel)) {
|
|
1461
|
+
!['admitted', 'unavailable'].includes(room.replica.admission)) {
|
|
1662
1462
|
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
1663
1463
|
await refuse('missing-required-guarantee');
|
|
1664
1464
|
notifyReady();
|
|
@@ -1670,20 +1470,6 @@ export function composeRuntime(options) {
|
|
|
1670
1470
|
notifyReady();
|
|
1671
1471
|
return;
|
|
1672
1472
|
}
|
|
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
1473
|
}
|
|
1688
1474
|
if (refusal !== null) {
|
|
1689
1475
|
refusal = null;
|
|
@@ -1692,29 +1478,13 @@ export function composeRuntime(options) {
|
|
|
1692
1478
|
const previousCapabilities = capabilities;
|
|
1693
1479
|
capabilities = result.capabilities;
|
|
1694
1480
|
notifyReady();
|
|
1695
|
-
if (declared.has('room-version-notifications'))
|
|
1696
|
-
observeRoomVersions();
|
|
1697
|
-
const discoveredSlots = result.slots ?? [];
|
|
1698
1481
|
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
1699
|
-
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)
|
|
1700
|
-
JSON.stringify(discoveredSlots) !== JSON.stringify(slots)) {
|
|
1482
|
+
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)) {
|
|
1701
1483
|
limits = result.providerLimits;
|
|
1702
|
-
|
|
1703
|
-
await local.declare(limits, capabilities, slots);
|
|
1484
|
+
await local.declare(limits, capabilities);
|
|
1704
1485
|
}
|
|
1705
1486
|
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);
|
|
1487
|
+
await synchronize(result.rooms, local, progress, attempt);
|
|
1718
1488
|
}
|
|
1719
1489
|
async function ensureAuthorization() {
|
|
1720
1490
|
if (authorization !== undefined)
|
|
@@ -1728,64 +1498,246 @@ export function composeRuntime(options) {
|
|
|
1728
1498
|
throw error;
|
|
1729
1499
|
}
|
|
1730
1500
|
}
|
|
1731
|
-
|
|
1732
|
-
if (
|
|
1733
|
-
|
|
1501
|
+
function task(work) {
|
|
1502
|
+
if (stopping)
|
|
1503
|
+
return chain;
|
|
1504
|
+
chain = chain.then(work, work).catch(() => undefined);
|
|
1505
|
+
return chain;
|
|
1506
|
+
}
|
|
1507
|
+
function sync(attempt) {
|
|
1508
|
+
return task(async () => {
|
|
1509
|
+
if (closed || stopping || discoveryPaused) {
|
|
1510
|
+
notifyReady();
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const progress = { complete: true, reachable: true };
|
|
1514
|
+
underway = attempt === 'opened' ? progress : null;
|
|
1515
|
+
complete = false;
|
|
1516
|
+
recorded = false;
|
|
1517
|
+
moveTo(transferring(status));
|
|
1518
|
+
try {
|
|
1519
|
+
const local = await openLocal();
|
|
1520
|
+
if (closed)
|
|
1521
|
+
return;
|
|
1522
|
+
await ensureAuthorization();
|
|
1523
|
+
if (closed)
|
|
1524
|
+
return;
|
|
1525
|
+
await discoverRooms(local, progress, attempt);
|
|
1526
|
+
}
|
|
1527
|
+
catch {
|
|
1528
|
+
progress.complete = false;
|
|
1529
|
+
if (attempt === 'opened') {
|
|
1530
|
+
options.adapter.roomStream?.hold(heldRooms([...discovered.values()]));
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
finally {
|
|
1534
|
+
notifyReady();
|
|
1535
|
+
}
|
|
1536
|
+
if (attempt === 'opened')
|
|
1537
|
+
return;
|
|
1538
|
+
await finishOpening(progress);
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
function closedSocket(revoked) {
|
|
1542
|
+
const opened = socketOpen;
|
|
1543
|
+
socketOpen = false;
|
|
1544
|
+
if (revoked) {
|
|
1545
|
+
void refuse('revoked-authorization');
|
|
1734
1546
|
return;
|
|
1735
1547
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
if (closed)
|
|
1548
|
+
if (opened || status.providerUnreachable)
|
|
1549
|
+
return;
|
|
1550
|
+
moveTo({ ...status, providerUnreachable: true });
|
|
1551
|
+
}
|
|
1552
|
+
function applyOpening(expired) {
|
|
1553
|
+
return task(async () => {
|
|
1554
|
+
const progress = underway;
|
|
1555
|
+
underway = null;
|
|
1556
|
+
if (closed || stopping)
|
|
1745
1557
|
return;
|
|
1746
|
-
|
|
1747
|
-
|
|
1558
|
+
if (progress === null) {
|
|
1559
|
+
await reloadExpired(expired);
|
|
1748
1560
|
return;
|
|
1749
|
-
if (progress.complete) {
|
|
1750
|
-
completedAt = new Date().toISOString();
|
|
1751
|
-
await local.complete(completedAt);
|
|
1752
1561
|
}
|
|
1562
|
+
try {
|
|
1563
|
+
const local = await openLocal();
|
|
1564
|
+
const held = [...discovered.values()].filter((room) => replicas.has(room.locator));
|
|
1565
|
+
const reloading = held.filter((room) => expired.includes(room.locator) || revoked(room.locator));
|
|
1566
|
+
const reloaded = await Promise.allSettled(reloading.map((room) => reload(room, local, progress)));
|
|
1567
|
+
if (progress.reachable) {
|
|
1568
|
+
for (const room of held)
|
|
1569
|
+
if (!reloading.includes(room))
|
|
1570
|
+
await confirmModels(room, local);
|
|
1571
|
+
}
|
|
1572
|
+
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
1573
|
+
if (failed !== undefined)
|
|
1574
|
+
throw failed.reason;
|
|
1575
|
+
}
|
|
1576
|
+
catch {
|
|
1577
|
+
progress.complete = false;
|
|
1578
|
+
progress.reachable = false;
|
|
1579
|
+
}
|
|
1580
|
+
await finishOpening(progress);
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
async function reloadExpired(expired) {
|
|
1584
|
+
const progress = { complete: true, reachable: true };
|
|
1585
|
+
try {
|
|
1586
|
+
const local = await openLocal();
|
|
1587
|
+
await Promise.allSettled([...discovered.values()]
|
|
1588
|
+
.filter((room) => replicas.has(room.locator) && expired.includes(room.locator))
|
|
1589
|
+
.map((room) => reload(room, local, progress)));
|
|
1753
1590
|
}
|
|
1754
1591
|
catch {
|
|
1755
|
-
progress.
|
|
1756
|
-
|
|
1592
|
+
progress.reachable = false;
|
|
1593
|
+
}
|
|
1594
|
+
await rest(progress);
|
|
1595
|
+
}
|
|
1596
|
+
async function confirmModels(room, local) {
|
|
1597
|
+
for (const attached of ['collection', 'typed-selection']) {
|
|
1598
|
+
const model = modelOf(room.locator, attached);
|
|
1599
|
+
if (model.status === 'cached') {
|
|
1600
|
+
await retainModel(local, room, attached, { ...model, status: 'current' });
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
function submitLater(locator) {
|
|
1605
|
+
const running = submitting.get(locator);
|
|
1606
|
+
if (running !== undefined) {
|
|
1607
|
+
running.again = true;
|
|
1757
1608
|
return;
|
|
1758
1609
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1610
|
+
const run = { again: false, done: Promise.resolve() };
|
|
1611
|
+
submitting.set(locator, run);
|
|
1612
|
+
run.done = (async () => {
|
|
1613
|
+
try {
|
|
1614
|
+
do {
|
|
1615
|
+
run.again = false;
|
|
1616
|
+
const room = discovered.get(locator);
|
|
1617
|
+
if (closed || stopping || discoveryPaused || room === undefined)
|
|
1618
|
+
return;
|
|
1619
|
+
const progress = { complete: true, reachable: true };
|
|
1620
|
+
try {
|
|
1621
|
+
await ensureAuthorization();
|
|
1622
|
+
if (authorization === undefined)
|
|
1623
|
+
return;
|
|
1624
|
+
await submitRoom(room, await openLocal(), authorization, progress);
|
|
1625
|
+
}
|
|
1626
|
+
catch {
|
|
1627
|
+
progress.reachable = false;
|
|
1628
|
+
}
|
|
1629
|
+
armTimer();
|
|
1630
|
+
await rest(progress);
|
|
1631
|
+
} while (run.again);
|
|
1632
|
+
}
|
|
1633
|
+
finally {
|
|
1634
|
+
submitting.delete(locator);
|
|
1635
|
+
}
|
|
1636
|
+
})();
|
|
1637
|
+
}
|
|
1638
|
+
function submitAll() {
|
|
1639
|
+
for (const [locator, replica] of replicas) {
|
|
1640
|
+
if (replica.queue.length > 0)
|
|
1641
|
+
submitLater(locator);
|
|
1762
1642
|
}
|
|
1763
1643
|
}
|
|
1764
|
-
async function
|
|
1644
|
+
async function finishOpening(progress) {
|
|
1765
1645
|
try {
|
|
1766
|
-
|
|
1646
|
+
if (closed || discoveryPaused) {
|
|
1647
|
+
progress.complete = false;
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
submitAll();
|
|
1651
|
+
await conversations.reconcile();
|
|
1652
|
+
if (status.providerUnreachable)
|
|
1653
|
+
conversations.reconnected();
|
|
1654
|
+
const reading = { complete: true, reachable: true };
|
|
1655
|
+
await conversations.catchUp(reading);
|
|
1656
|
+
if (!reading.reachable)
|
|
1657
|
+
progress.reachable = false;
|
|
1658
|
+
}
|
|
1659
|
+
catch {
|
|
1660
|
+
progress.complete = false;
|
|
1767
1661
|
}
|
|
1768
1662
|
finally {
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
queued = false;
|
|
1772
|
-
void enqueue();
|
|
1773
|
-
}
|
|
1663
|
+
complete = progress.complete;
|
|
1664
|
+
await rest(progress);
|
|
1774
1665
|
}
|
|
1775
1666
|
}
|
|
1776
|
-
function
|
|
1777
|
-
|
|
1778
|
-
if (path === undefined || withdrawNotifications !== undefined)
|
|
1667
|
+
async function rest(progress) {
|
|
1668
|
+
if (closed)
|
|
1779
1669
|
return;
|
|
1780
|
-
|
|
1781
|
-
|
|
1670
|
+
let lastCompleted = status.lastCompleted;
|
|
1671
|
+
let synced = complete && stale.size === 0 && !pending();
|
|
1672
|
+
if (synced && !recorded) {
|
|
1673
|
+
const completedAt = new Date().toISOString();
|
|
1674
|
+
try {
|
|
1675
|
+
await (await openLocal()).complete(completedAt);
|
|
1676
|
+
recorded = true;
|
|
1677
|
+
lastCompleted = completedAt;
|
|
1678
|
+
}
|
|
1679
|
+
catch {
|
|
1680
|
+
synced = false;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
moveTo({
|
|
1684
|
+
state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
|
|
1685
|
+
lastCompleted,
|
|
1686
|
+
providerUnreachable: !progress.reachable,
|
|
1687
|
+
...attention(),
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
function applyRoom(locator, roomVersion) {
|
|
1691
|
+
announced.set(locator, roomVersion);
|
|
1692
|
+
return task(async () => {
|
|
1693
|
+
await answering.get(locator);
|
|
1694
|
+
if (closed || stopping || discoveryPaused || authorization === undefined)
|
|
1695
|
+
return;
|
|
1696
|
+
const room = discovered.get(locator);
|
|
1697
|
+
const held = replicas.get(locator);
|
|
1698
|
+
if (room === undefined || room.replica.admission !== 'admitted')
|
|
1782
1699
|
return;
|
|
1783
|
-
if (
|
|
1784
|
-
|
|
1700
|
+
if (held !== undefined &&
|
|
1701
|
+
(held.confirmed.record.replica.selection !== room.replica.selection ||
|
|
1702
|
+
options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion))) {
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
const during = underway;
|
|
1706
|
+
const progress = during ?? { complete: true, reachable: true };
|
|
1707
|
+
if (during === null)
|
|
1708
|
+
moveTo(transferring(status));
|
|
1709
|
+
try {
|
|
1710
|
+
const local = await openLocal();
|
|
1711
|
+
const withheld = await advance(room, local, authorization);
|
|
1712
|
+
if (withheld !== null)
|
|
1713
|
+
await withhold(withheld, room, local, progress);
|
|
1714
|
+
}
|
|
1715
|
+
catch {
|
|
1716
|
+
stale.add(locator);
|
|
1717
|
+
}
|
|
1718
|
+
finally {
|
|
1719
|
+
if (during === null)
|
|
1720
|
+
await rest(progress);
|
|
1721
|
+
}
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
function observeStream() {
|
|
1725
|
+
const stream = options.adapter.roomStream;
|
|
1726
|
+
if (stream === undefined || withdrawStream !== undefined)
|
|
1727
|
+
return;
|
|
1728
|
+
withdrawStream = stream.observe((event) => {
|
|
1729
|
+
if (closed || stopping)
|
|
1785
1730
|
return;
|
|
1731
|
+
if (event.kind === 'opened') {
|
|
1732
|
+
socketOpen = true;
|
|
1733
|
+
void sync('opened');
|
|
1786
1734
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1735
|
+
else if (event.kind === 'closed')
|
|
1736
|
+
closedSocket(event.revoked);
|
|
1737
|
+
else if (event.kind === 'opening')
|
|
1738
|
+
void applyOpening(event.expired);
|
|
1739
|
+
else
|
|
1740
|
+
void applyRoom(event.locator, event.roomVersion);
|
|
1789
1741
|
});
|
|
1790
1742
|
}
|
|
1791
1743
|
function writesHeld() {
|
|
@@ -1806,14 +1758,17 @@ export function composeRuntime(options) {
|
|
|
1806
1758
|
listener(mode);
|
|
1807
1759
|
}
|
|
1808
1760
|
if (releasing)
|
|
1809
|
-
|
|
1761
|
+
submitAll();
|
|
1810
1762
|
}
|
|
1811
1763
|
function observeConversations() {
|
|
1812
1764
|
const stream = options.adapter.conversations;
|
|
1813
1765
|
if (stream === undefined || withdrawConversations !== undefined)
|
|
1814
1766
|
return;
|
|
1815
1767
|
withdrawConversations = stream.observe((event) => {
|
|
1816
|
-
conversations.receive(event)
|
|
1768
|
+
if (conversations.receive(event)) {
|
|
1769
|
+
void ready.then(scheduleCurrency);
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1817
1772
|
if (event.kind !== 'gap' || conversations.current())
|
|
1818
1773
|
return;
|
|
1819
1774
|
show();
|
|
@@ -1824,7 +1779,7 @@ export function composeRuntime(options) {
|
|
|
1824
1779
|
if (currencyQueued || closed || stopping)
|
|
1825
1780
|
return;
|
|
1826
1781
|
currencyQueued = true;
|
|
1827
|
-
|
|
1782
|
+
void task(runCurrency);
|
|
1828
1783
|
}
|
|
1829
1784
|
async function runCurrency() {
|
|
1830
1785
|
currencyQueued = false;
|
|
@@ -1850,95 +1805,12 @@ export function composeRuntime(options) {
|
|
|
1850
1805
|
}
|
|
1851
1806
|
}
|
|
1852
1807
|
}
|
|
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
1808
|
function close() {
|
|
1937
1809
|
if (closed)
|
|
1938
1810
|
return;
|
|
1939
1811
|
closed = true;
|
|
1940
|
-
|
|
1941
|
-
|
|
1812
|
+
withdrawStream?.();
|
|
1813
|
+
withdrawStream = undefined;
|
|
1942
1814
|
withdrawConversations?.();
|
|
1943
1815
|
withdrawConversations = undefined;
|
|
1944
1816
|
withdrawServiceMode?.();
|
|
@@ -1962,6 +1834,7 @@ export function composeRuntime(options) {
|
|
|
1962
1834
|
stopping = true;
|
|
1963
1835
|
stopTimer();
|
|
1964
1836
|
await chain.catch(() => undefined);
|
|
1837
|
+
await Promise.all([...submitting.values()].map((run) => run.done));
|
|
1965
1838
|
await mutations;
|
|
1966
1839
|
close();
|
|
1967
1840
|
}
|
|
@@ -1970,11 +1843,29 @@ export function composeRuntime(options) {
|
|
|
1970
1843
|
options.signal?.addEventListener('abort', close, { once: true });
|
|
1971
1844
|
if (options.signal?.aborted)
|
|
1972
1845
|
close();
|
|
1973
|
-
|
|
1846
|
+
observeStream();
|
|
1847
|
+
void openLocal().then(() => {
|
|
1848
|
+
if (capabilities.length > 0)
|
|
1849
|
+
notifyReady();
|
|
1850
|
+
}, () => {
|
|
1974
1851
|
notifyReady();
|
|
1975
1852
|
});
|
|
1976
|
-
|
|
1977
|
-
|
|
1853
|
+
if (options.adapter.roomStream === undefined)
|
|
1854
|
+
void sync('unstreamed');
|
|
1855
|
+
function planned(input, reach) {
|
|
1856
|
+
return planOf(input, roomFacts(reach), planReach(reach));
|
|
1857
|
+
}
|
|
1858
|
+
function watchPlan(plan, deliver) {
|
|
1859
|
+
return readSide.watch(plan, identityOf(plan), deliver);
|
|
1860
|
+
}
|
|
1861
|
+
async function searched(input, reach) {
|
|
1862
|
+
const plan = searchPlanOf(input);
|
|
1863
|
+
const selected = plan.documents === undefined && reach === null
|
|
1864
|
+
? null
|
|
1865
|
+
: (await readSide.query(filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)))).documents;
|
|
1866
|
+
return searchIndex.search(reach === null || reach.conversations ? plan : { ...plan, conversations: [] }, selected);
|
|
1867
|
+
}
|
|
1868
|
+
const watch = (topic, input, deliver) => {
|
|
1978
1869
|
if (topic === 'room-drafts') {
|
|
1979
1870
|
const listener = { notify: deliver, ready: false };
|
|
1980
1871
|
roomDraftListeners.add(listener);
|
|
@@ -1995,6 +1886,8 @@ export function composeRuntime(options) {
|
|
|
1995
1886
|
if (topic === 'conversation') {
|
|
1996
1887
|
return conversations.watch(input[0], input[1], deliver);
|
|
1997
1888
|
}
|
|
1889
|
+
if (topic === 'conversations')
|
|
1890
|
+
return conversations.watchList(deliver);
|
|
1998
1891
|
if (topic === 'presentation') {
|
|
1999
1892
|
const listener = {
|
|
2000
1893
|
app: input[0],
|
|
@@ -2057,9 +1950,8 @@ export function composeRuntime(options) {
|
|
|
2057
1950
|
queueSubscriptions.delete(queue);
|
|
2058
1951
|
};
|
|
2059
1952
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
}
|
|
1953
|
+
return watchPlan(planned(input[0], null), deliver);
|
|
1954
|
+
};
|
|
2063
1955
|
const md = {
|
|
2064
1956
|
loaded() {
|
|
2065
1957
|
return openLocal().then(() => undefined);
|
|
@@ -2067,18 +1959,8 @@ export function composeRuntime(options) {
|
|
|
2067
1959
|
rooms() {
|
|
2068
1960
|
return listed();
|
|
2069
1961
|
},
|
|
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
|
-
},
|
|
1962
|
+
query: ((input) => readSide.query(planned(input, null))),
|
|
1963
|
+
search: (input) => searched(input, null),
|
|
2082
1964
|
types(locator) {
|
|
2083
1965
|
return {
|
|
2084
1966
|
collection: modelTypes(modelOf(locator)),
|
|
@@ -2088,12 +1970,6 @@ export function composeRuntime(options) {
|
|
|
2088
1970
|
: modelTypes(modelOf(locator, 'typed-selection')),
|
|
2089
1971
|
};
|
|
2090
1972
|
},
|
|
2091
|
-
syncStatus() {
|
|
2092
|
-
return shown;
|
|
2093
|
-
},
|
|
2094
|
-
serviceMode() {
|
|
2095
|
-
return serviceMode;
|
|
2096
|
-
},
|
|
2097
1973
|
roomDraft: {
|
|
2098
1974
|
list() {
|
|
2099
1975
|
return exclusive(async () => {
|
|
@@ -2174,9 +2050,6 @@ export function composeRuntime(options) {
|
|
|
2174
2050
|
save(room, named, writtenBy) {
|
|
2175
2051
|
return exclusive(() => saveRoomDrafts(room, named, writtenBy));
|
|
2176
2052
|
},
|
|
2177
|
-
outcome(identifier) {
|
|
2178
|
-
return outcomes.get(identifier) ?? null;
|
|
2179
|
-
},
|
|
2180
2053
|
history(locator, path, query) {
|
|
2181
2054
|
return readHistory(locator, path, query);
|
|
2182
2055
|
},
|
|
@@ -2191,15 +2064,10 @@ export function composeRuntime(options) {
|
|
|
2191
2064
|
log() {
|
|
2192
2065
|
return journal.export();
|
|
2193
2066
|
},
|
|
2194
|
-
storageLoss() {
|
|
2195
|
-
return storageLoss;
|
|
2196
|
-
},
|
|
2197
2067
|
persistence() {
|
|
2198
2068
|
return options.shell.persistentStorage;
|
|
2199
2069
|
},
|
|
2200
|
-
observe: (
|
|
2201
|
-
deliver(structuredClone(message));
|
|
2202
|
-
}), topic === 'query')),
|
|
2070
|
+
observe: observing(watch),
|
|
2203
2071
|
async reauthorize() {
|
|
2204
2072
|
if (refusal === null || !refusal.reauthorizationOffered)
|
|
2205
2073
|
return;
|
|
@@ -2208,8 +2076,19 @@ export function composeRuntime(options) {
|
|
|
2208
2076
|
await mutations;
|
|
2209
2077
|
authorization = undefined;
|
|
2210
2078
|
discoveryPaused = false;
|
|
2211
|
-
|
|
2079
|
+
if (options.adapter.roomStream === undefined)
|
|
2080
|
+
await sync('unstreamed');
|
|
2081
|
+
else
|
|
2082
|
+
await task(ensureAuthorization);
|
|
2212
2083
|
},
|
|
2213
2084
|
};
|
|
2214
|
-
|
|
2085
|
+
const reachable = {
|
|
2086
|
+
md,
|
|
2087
|
+
watch,
|
|
2088
|
+
plan: planned,
|
|
2089
|
+
query: (plan) => readSide.query(plan),
|
|
2090
|
+
watchPlan,
|
|
2091
|
+
search: searched,
|
|
2092
|
+
};
|
|
2093
|
+
return { md, stop, watch, within: (reach) => narrowed(reachable, reach) };
|
|
2215
2094
|
}
|