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