@agentshouse/mdruntime 0.1.0-ca7c26c6bc27e → 0.1.0-ca9afacd5aebd
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 +144 -41
- package/README.md +21 -11
- package/dist/adapter.d.ts +71 -20
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -9
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +15 -2
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +57 -0
- package/dist/binary.d.ts +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +17 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +89 -38
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +19 -12
- package/dist/change.d.ts +15 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +26 -1
- package/dist/chat/acts.d.ts +34 -0
- package/dist/chat/acts.d.ts.map +1 -0
- package/dist/chat/acts.js +36 -0
- package/dist/chat/context.d.ts +3 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +10 -0
- package/dist/chat/input.d.ts +9 -0
- package/dist/chat/input.d.ts.map +1 -0
- package/dist/chat/input.js +99 -0
- package/dist/chat/kit.d.ts +60 -0
- package/dist/chat/kit.d.ts.map +1 -0
- package/dist/chat/kit.js +123 -0
- package/dist/chat/lasting.d.ts +6 -0
- package/dist/chat/lasting.d.ts.map +1 -0
- package/dist/chat/lasting.js +19 -0
- package/dist/chat/list.d.ts +20 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +59 -0
- package/dist/chat/markdown.d.ts +19 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +187 -0
- package/dist/chat/message.d.ts +6 -0
- package/dist/chat/message.d.ts.map +1 -0
- package/dist/chat/message.js +192 -0
- package/dist/chat/messages.d.ts +21 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +151 -0
- package/dist/chat/name.d.ts +6 -0
- package/dist/chat/name.d.ts.map +1 -0
- package/dist/chat/name.js +37 -0
- package/dist/chat/opened.d.ts +11 -0
- package/dist/chat/opened.d.ts.map +1 -0
- package/dist/chat/opened.js +17 -0
- package/dist/chat/overlay.d.ts +31 -0
- package/dist/chat/overlay.d.ts.map +1 -0
- package/dist/chat/overlay.js +97 -0
- package/dist/chat/parts.d.ts +57 -0
- package/dist/chat/parts.d.ts.map +1 -0
- package/dist/chat/parts.js +119 -0
- package/dist/chat/process.d.ts +20 -0
- package/dist/chat/process.d.ts.map +1 -0
- package/dist/chat/process.js +32 -0
- package/dist/chat/provider.d.ts +63 -0
- package/dist/chat/provider.d.ts.map +1 -0
- package/dist/chat/provider.js +645 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +80 -0
- package/dist/chat/questions.d.ts +31 -0
- package/dist/chat/questions.d.ts.map +1 -0
- package/dist/chat/questions.js +111 -0
- package/dist/chat/read.d.ts +5 -0
- package/dist/chat/read.d.ts.map +1 -0
- package/dist/chat/read.js +13 -0
- package/dist/chat/ready.d.ts +33 -0
- package/dist/chat/ready.d.ts.map +1 -0
- package/dist/chat/ready.js +91 -0
- package/dist/chat/style.d.ts +4 -0
- package/dist/chat/style.d.ts.map +1 -0
- package/dist/chat/style.js +302 -0
- package/dist/chat.d.ts +14 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +8 -0
- package/dist/client.d.ts +17 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +68 -25
- package/dist/collection.d.ts +6 -11
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +56 -19
- package/dist/connection.d.ts +25 -12
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +492 -185
- package/dist/conversation.d.ts +27 -8
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +210 -81
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/host.d.ts +1 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +12 -19
- package/dist/identity.d.ts +3 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +13 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/memory.d.ts +5 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +73 -27
- package/dist/model.d.ts +13 -8
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +6 -3
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/outcome.d.ts +28 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +41 -1
- package/dist/overlay.d.ts +14 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +75 -37
- 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 +5 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +74 -33
- package/dist/reach.d.ts +2 -0
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +51 -34
- package/dist/react.d.ts +41 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +64 -0
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +111 -48
- package/dist/refresh.d.ts +10 -0
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +42 -38
- package/dist/replica.d.ts +9 -7
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +90 -64
- package/dist/room-draft.d.ts +26 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +132 -23
- package/dist/rows.d.ts +7 -2
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +85 -40
- package/dist/save.d.ts +28 -3
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +429 -80
- package/dist/search.d.ts +3 -2
- package/dist/search.d.ts.map +1 -1
- package/dist/status.d.ts +2 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +34 -9
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +273 -164
- package/dist/wire.d.ts +8 -15
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +16 -17
- package/package.json +23 -3
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
package/dist/connection.js
CHANGED
|
@@ -3,25 +3,28 @@ import { cached, modelTypes, NO_MODEL, sameModel, } from './model.js';
|
|
|
3
3
|
import { observing } from './observation.js';
|
|
4
4
|
import { filterOf, identityOf, planOf, } from './query.js';
|
|
5
5
|
import { createReadSide } from './read-side.js';
|
|
6
|
+
import { markdownPath, roomOfKey } from './rows.js';
|
|
6
7
|
import { createSearchIndex, searchPlanOf, } from './search.js';
|
|
7
|
-
import { ROOM_CONTRACT_VERSION, VERSION_1_CAPABILITIES, } from './adapter.js';
|
|
8
8
|
import { bootstrapRoom } from './bootstrap.js';
|
|
9
9
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
10
10
|
import { createConversationStore, } from './conversation.js';
|
|
11
|
-
import {
|
|
11
|
+
import { queuedFile } from './change.js';
|
|
12
|
+
import { validRoot } from './collection.js';
|
|
13
|
+
import { runtimeError } from './failure.js';
|
|
12
14
|
import { createLog } from './log.js';
|
|
15
|
+
import { binaryRecord } from './binary.js';
|
|
16
|
+
import { absentBinary, binaryAnswer, calledResult, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
|
|
13
17
|
import { refreshRoom } from './refresh.js';
|
|
14
|
-
import { buildBatch } from './save.js';
|
|
15
|
-
import { applyReturned, compositionOf,
|
|
16
|
-
import { changedParts, changesOf, held, readParts, returnedInto, underneath, } from './room-draft.js';
|
|
17
|
-
import {
|
|
18
|
+
import { buildBatch, joined, limitedIn, requeued } from './save.js';
|
|
19
|
+
import { applyReturned, compositionOf, confirmedOf, landed, readReclassified, replicaOf, sameMembership, withdrawals, } from './replica.js';
|
|
20
|
+
import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
|
|
21
|
+
import { same, saving, STARTING, transferring, } from './status.js';
|
|
18
22
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
19
23
|
import { roomDraftRevision } from './identity.js';
|
|
20
24
|
import { beneath, normalize } from './paths.js';
|
|
21
25
|
import { openRuntimeStore, } from './store.js';
|
|
22
26
|
import { unconfirmed } from './transfer.js';
|
|
23
27
|
import { governs, holds, narrowed, planReach, } from './reach.js';
|
|
24
|
-
const knownCapabilities = new Set(VERSION_1_CAPABILITIES);
|
|
25
28
|
function within(root, path) {
|
|
26
29
|
return root === '' || beneath(root, path);
|
|
27
30
|
}
|
|
@@ -36,21 +39,19 @@ function declaredSelection(room) {
|
|
|
36
39
|
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
37
40
|
}
|
|
38
41
|
export function composeRuntime(options) {
|
|
42
|
+
const localSaves = options.adapter.localSaves === true;
|
|
39
43
|
let authorization = options.authorization;
|
|
40
44
|
let opening;
|
|
41
45
|
let store;
|
|
42
46
|
const replicas = new Map();
|
|
47
|
+
const queuesWithoutReplica = new Map();
|
|
43
48
|
const pendings = new Map();
|
|
44
49
|
const roomDrafts = new Map();
|
|
45
50
|
const presentations = new Map();
|
|
46
51
|
let limits = [];
|
|
47
52
|
let capabilities = [];
|
|
48
|
-
function rewriting() {
|
|
49
|
-
return capabilities.some((capability) => capability.name === 'move-reference-rewrites')
|
|
50
|
-
? 'provider'
|
|
51
|
-
: 'runtime';
|
|
52
|
-
}
|
|
53
53
|
const rooms = new Map();
|
|
54
|
+
const counted = new Map();
|
|
54
55
|
const unreadable = new Set();
|
|
55
56
|
const models = new Map();
|
|
56
57
|
const liveModels = new Map();
|
|
@@ -89,6 +90,8 @@ export function composeRuntime(options) {
|
|
|
89
90
|
let withdrawStream;
|
|
90
91
|
const announced = new Map();
|
|
91
92
|
let underway = null;
|
|
93
|
+
let streamed = null;
|
|
94
|
+
let handed = false;
|
|
92
95
|
const submitting = new Map();
|
|
93
96
|
const answering = new Map();
|
|
94
97
|
let complete = false;
|
|
@@ -111,9 +114,14 @@ export function composeRuntime(options) {
|
|
|
111
114
|
let haltedRefusal = false;
|
|
112
115
|
let openSignals = 0;
|
|
113
116
|
const serviceModeListeners = new Set();
|
|
117
|
+
let member = null;
|
|
118
|
+
const memberListeners = new Set();
|
|
119
|
+
const materialized = new Set();
|
|
120
|
+
let everyRoom = false;
|
|
121
|
+
const transfers = new Map();
|
|
114
122
|
const conversations = createConversationStore({
|
|
115
|
-
|
|
116
|
-
documents: (locator) =>
|
|
123
|
+
agents: declaredAgents,
|
|
124
|
+
documents: (locator) => rowsQuery(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
117
125
|
call: (name, input) => invokeAction(name, input),
|
|
118
126
|
durably,
|
|
119
127
|
stream: () => options.adapter.conversations,
|
|
@@ -189,7 +197,9 @@ export function composeRuntime(options) {
|
|
|
189
197
|
}
|
|
190
198
|
}
|
|
191
199
|
function pending() {
|
|
192
|
-
return
|
|
200
|
+
return (!localSaves &&
|
|
201
|
+
(queuesWithoutReplica.size > 0 ||
|
|
202
|
+
[...replicas.values()].some((replica) => replica.queue.length > 0)));
|
|
193
203
|
}
|
|
194
204
|
function attention() {
|
|
195
205
|
const attentionRequired = [];
|
|
@@ -218,8 +228,24 @@ export function composeRuntime(options) {
|
|
|
218
228
|
}
|
|
219
229
|
return { attentionRequired, reset };
|
|
220
230
|
}
|
|
231
|
+
function documentsIn(locator) {
|
|
232
|
+
const replica = replicas.get(locator);
|
|
233
|
+
if (replica === undefined)
|
|
234
|
+
return 0;
|
|
235
|
+
const roots = replica.confirmed.record.typedSelection?.roots ?? [];
|
|
236
|
+
let count = 0;
|
|
237
|
+
for (const path of Object.keys(replica.visible.documents)) {
|
|
238
|
+
if (markdownPath(path) && !roots.some((root) => beneath(root, path)))
|
|
239
|
+
count += 1;
|
|
240
|
+
}
|
|
241
|
+
return count;
|
|
242
|
+
}
|
|
221
243
|
function listed() {
|
|
222
|
-
return structuredClone([...rooms.values()])
|
|
244
|
+
return structuredClone([...rooms.values()].map((room) => {
|
|
245
|
+
const documents = documentsIn(room.locator);
|
|
246
|
+
counted.set(room.locator, documents);
|
|
247
|
+
return { ...room, documents };
|
|
248
|
+
}));
|
|
223
249
|
}
|
|
224
250
|
function roomFacts(reach = null) {
|
|
225
251
|
const held = [...rooms.values()]
|
|
@@ -251,6 +277,10 @@ export function composeRuntime(options) {
|
|
|
251
277
|
for (const listener of storageLossListeners)
|
|
252
278
|
listener(storageLoss);
|
|
253
279
|
}
|
|
280
|
+
function emitMember() {
|
|
281
|
+
for (const listener of memberListeners)
|
|
282
|
+
listener(member);
|
|
283
|
+
}
|
|
254
284
|
function moveTo(next) {
|
|
255
285
|
if (closed)
|
|
256
286
|
return;
|
|
@@ -270,6 +300,8 @@ export function composeRuntime(options) {
|
|
|
270
300
|
listener(shown);
|
|
271
301
|
}
|
|
272
302
|
function entryOf(batch) {
|
|
303
|
+
if (localSaves)
|
|
304
|
+
return Object.freeze({ batch, state: 'local' });
|
|
273
305
|
const state = pendings.get(batch.id);
|
|
274
306
|
if (state?.accepted !== undefined) {
|
|
275
307
|
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
@@ -277,10 +309,7 @@ export function composeRuntime(options) {
|
|
|
277
309
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
278
310
|
}
|
|
279
311
|
function entriesOf(locator) {
|
|
280
|
-
|
|
281
|
-
if (replica === undefined)
|
|
282
|
-
return [];
|
|
283
|
-
return replica.queue.map(entryOf);
|
|
312
|
+
return (replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []).map(entryOf);
|
|
284
313
|
}
|
|
285
314
|
function emitQueue(locator) {
|
|
286
315
|
const entries = entriesOf(locator);
|
|
@@ -290,12 +319,47 @@ export function composeRuntime(options) {
|
|
|
290
319
|
}
|
|
291
320
|
}
|
|
292
321
|
function applyChangeSet(set) {
|
|
322
|
+
const held = everyRoom
|
|
323
|
+
? set
|
|
324
|
+
: { ...set, documents: set.documents.filter(({ locator }) => materialized.has(locator)) };
|
|
325
|
+
readSide.apply(held);
|
|
326
|
+
searchIndex.changed(held);
|
|
327
|
+
}
|
|
328
|
+
function materialize(locators) {
|
|
329
|
+
if (everyRoom)
|
|
330
|
+
return;
|
|
331
|
+
if (locators === null)
|
|
332
|
+
everyRoom = true;
|
|
333
|
+
const sets = [];
|
|
334
|
+
for (const locator of locators ?? [...replicas.keys()]) {
|
|
335
|
+
if (materialized.has(locator))
|
|
336
|
+
continue;
|
|
337
|
+
materialized.add(locator);
|
|
338
|
+
const replica = replicas.get(locator);
|
|
339
|
+
if (replica !== undefined)
|
|
340
|
+
sets.push(changeSet(locator, null, replica));
|
|
341
|
+
}
|
|
342
|
+
if (sets.length === 0)
|
|
343
|
+
return;
|
|
344
|
+
const set = mergeChangeSets(sets);
|
|
293
345
|
readSide.apply(set);
|
|
294
346
|
searchIndex.changed(set);
|
|
295
347
|
}
|
|
348
|
+
function reachedRooms(plan) {
|
|
349
|
+
if (plan.rooms !== null)
|
|
350
|
+
return plan.rooms;
|
|
351
|
+
if (plan.reach === null)
|
|
352
|
+
return null;
|
|
353
|
+
return [...plan.reach.rooms, ...plan.reach.documents.map(roomOfKey)];
|
|
354
|
+
}
|
|
355
|
+
function rowsQuery(plan) {
|
|
356
|
+
materialize(reachedRooms(plan));
|
|
357
|
+
return readSide.query(plan);
|
|
358
|
+
}
|
|
296
359
|
function publishChangeSet(set) {
|
|
297
360
|
applyChangeSet(set);
|
|
298
|
-
if (set.rooms.length > 0
|
|
361
|
+
if (set.rooms.length > 0 ||
|
|
362
|
+
[...new Set(set.documents.map(({ locator }) => locator))].some((locator) => rooms.has(locator) && counted.get(locator) !== documentsIn(locator)))
|
|
299
363
|
emitRooms();
|
|
300
364
|
for (const locator of set.queues)
|
|
301
365
|
emitQueue(locator);
|
|
@@ -306,10 +370,6 @@ export function composeRuntime(options) {
|
|
|
306
370
|
for (const app of set.presentations)
|
|
307
371
|
emitPresentation(app);
|
|
308
372
|
conversations.changed(set);
|
|
309
|
-
const cards = declaredCards();
|
|
310
|
-
if (cards !== undefined && set.documents.some((change) => change.locator === cards.locator)) {
|
|
311
|
-
scheduleCurrency();
|
|
312
|
-
}
|
|
313
373
|
}
|
|
314
374
|
function stopTimer() {
|
|
315
375
|
if (resetTimer === undefined)
|
|
@@ -346,10 +406,7 @@ export function composeRuntime(options) {
|
|
|
346
406
|
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
347
407
|
try {
|
|
348
408
|
const local = await openLocal();
|
|
349
|
-
await local.revokeModels([...replicas.values()].map((replica) =>
|
|
350
|
-
record: replica.confirmed.record,
|
|
351
|
-
queue: replica.queue,
|
|
352
|
-
})));
|
|
409
|
+
await local.revokeModels([...replicas.values()].map((replica) => replica.confirmed.record));
|
|
353
410
|
}
|
|
354
411
|
catch {
|
|
355
412
|
journal.record('model revocation persistence failed');
|
|
@@ -373,10 +430,7 @@ export function composeRuntime(options) {
|
|
|
373
430
|
model: revoked,
|
|
374
431
|
selectionModel: revoked,
|
|
375
432
|
};
|
|
376
|
-
|
|
377
|
-
replicas.set(locator, read.kind === 'staged'
|
|
378
|
-
? replicaOf(read.confirmed, before.queue, rewriting(), before)
|
|
379
|
-
: { ...before, confirmed: { ...before.confirmed, record } });
|
|
433
|
+
replicas.set(locator, replicaOf(confirmedOf(record, before.confirmed), before.queue, before));
|
|
380
434
|
}
|
|
381
435
|
rooms.set(locator, {
|
|
382
436
|
...room,
|
|
@@ -393,7 +447,8 @@ export function composeRuntime(options) {
|
|
|
393
447
|
}
|
|
394
448
|
finally {
|
|
395
449
|
refusal = {
|
|
396
|
-
reason,
|
|
450
|
+
code: reason,
|
|
451
|
+
retryable: false,
|
|
397
452
|
reauthorizationOffered: reason === 'revoked-authorization',
|
|
398
453
|
refusal: provider,
|
|
399
454
|
};
|
|
@@ -406,6 +461,7 @@ export function composeRuntime(options) {
|
|
|
406
461
|
rooms.set(room.locator, {
|
|
407
462
|
name: room.name,
|
|
408
463
|
locator: room.locator,
|
|
464
|
+
handle: room.handle,
|
|
409
465
|
operations: room.operations,
|
|
410
466
|
replica: room.replica,
|
|
411
467
|
model: modelTypes(modelOf(room.locator)),
|
|
@@ -417,7 +473,6 @@ export function composeRuntime(options) {
|
|
|
417
473
|
},
|
|
418
474
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
419
475
|
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
420
|
-
attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
|
|
421
476
|
synchronizationAvailable,
|
|
422
477
|
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
423
478
|
});
|
|
@@ -442,7 +497,14 @@ export function composeRuntime(options) {
|
|
|
442
497
|
emitRooms();
|
|
443
498
|
}
|
|
444
499
|
function unlistedHeld(listed) {
|
|
445
|
-
return [
|
|
500
|
+
return [
|
|
501
|
+
...new Set([
|
|
502
|
+
...rooms.keys(),
|
|
503
|
+
...roomDrafts.keys(),
|
|
504
|
+
...transfers.keys(),
|
|
505
|
+
...queuesWithoutReplica.keys(),
|
|
506
|
+
]),
|
|
507
|
+
].filter((locator) => !listed.has(locator));
|
|
446
508
|
}
|
|
447
509
|
async function dropUnlisted(listed, local) {
|
|
448
510
|
if (unlistedHeld(listed).length === 0)
|
|
@@ -456,11 +518,13 @@ export function composeRuntime(options) {
|
|
|
456
518
|
drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
|
|
457
519
|
}));
|
|
458
520
|
for (const locator of unlisted) {
|
|
459
|
-
for (const batch of replicas.get(locator)?.queue ?? [])
|
|
521
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
460
522
|
pendings.delete(batch.id);
|
|
461
523
|
replicas.delete(locator);
|
|
524
|
+
queuesWithoutReplica.delete(locator);
|
|
462
525
|
rooms.delete(locator);
|
|
463
526
|
roomDrafts.delete(locator);
|
|
527
|
+
transfers.delete(locator);
|
|
464
528
|
unreadable.delete(locator);
|
|
465
529
|
stale.delete(locator);
|
|
466
530
|
scopedRefusals.delete(locator);
|
|
@@ -497,7 +561,7 @@ export function composeRuntime(options) {
|
|
|
497
561
|
return model;
|
|
498
562
|
}
|
|
499
563
|
async function hydrate(opened) {
|
|
500
|
-
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named] = await Promise.all([
|
|
564
|
+
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known, staged,] = await Promise.all([
|
|
501
565
|
opened.all(),
|
|
502
566
|
opened.queues(),
|
|
503
567
|
opened.roomDrafts(),
|
|
@@ -507,9 +571,15 @@ export function composeRuntime(options) {
|
|
|
507
571
|
opened.lastCompleted(),
|
|
508
572
|
opened.providerLimits(),
|
|
509
573
|
opened.capabilities(),
|
|
574
|
+
opened.member(),
|
|
575
|
+
opened.transfers(),
|
|
510
576
|
]);
|
|
577
|
+
transfers.clear();
|
|
578
|
+
for (const record of staged)
|
|
579
|
+
transfers.set(record.locator, record);
|
|
511
580
|
limits = declared;
|
|
512
581
|
capabilities = named;
|
|
582
|
+
member = known;
|
|
513
583
|
roomDrafts.clear();
|
|
514
584
|
for (const record of kept)
|
|
515
585
|
rememberRoomDraft(record);
|
|
@@ -525,19 +595,18 @@ export function composeRuntime(options) {
|
|
|
525
595
|
}
|
|
526
596
|
const previousReplicas = new Map(replicas);
|
|
527
597
|
replicas.clear();
|
|
598
|
+
queuesWithoutReplica.clear();
|
|
599
|
+
const replicated = new Set(records.map((record) => record.locator));
|
|
600
|
+
for (const [locator, queue] of queuedBatches) {
|
|
601
|
+
if (!replicated.has(locator))
|
|
602
|
+
queuesWithoutReplica.set(locator, queue);
|
|
603
|
+
}
|
|
528
604
|
for (const record of records) {
|
|
529
605
|
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
530
606
|
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
531
607
|
const prior = previousReplicas.get(record.locator);
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
const confirmed = read.kind === 'staged'
|
|
535
|
-
? read.confirmed
|
|
536
|
-
: {
|
|
537
|
-
record: retyped,
|
|
538
|
-
collection: parseCollection(retyped.documents, compositionOf(retyped)),
|
|
539
|
-
};
|
|
540
|
-
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], rewriting(), prior));
|
|
608
|
+
const confirmed = confirmedOf({ ...record, model, selectionModel }, prior?.confirmed);
|
|
609
|
+
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
541
610
|
const existing = rooms.get(record.locator);
|
|
542
611
|
if (existing === undefined)
|
|
543
612
|
list(record, false);
|
|
@@ -545,7 +614,6 @@ export function composeRuntime(options) {
|
|
|
545
614
|
rooms.set(record.locator, {
|
|
546
615
|
...existing,
|
|
547
616
|
roomVersion: record.roomVersion,
|
|
548
|
-
attachments: record.attachments,
|
|
549
617
|
operations: record.operations,
|
|
550
618
|
replica: record.replica,
|
|
551
619
|
model: modelTypes(model),
|
|
@@ -557,10 +625,6 @@ export function composeRuntime(options) {
|
|
|
557
625
|
},
|
|
558
626
|
});
|
|
559
627
|
}
|
|
560
|
-
if (read.kind === 'refused') {
|
|
561
|
-
refusedSource(record, read);
|
|
562
|
-
declarationRefusals.add(record.locator);
|
|
563
|
-
}
|
|
564
628
|
}
|
|
565
629
|
return {
|
|
566
630
|
lastCompleted,
|
|
@@ -573,6 +637,8 @@ export function composeRuntime(options) {
|
|
|
573
637
|
applyChangeSet(read);
|
|
574
638
|
if (replicas.size > 0)
|
|
575
639
|
emitRooms();
|
|
640
|
+
if (member !== null)
|
|
641
|
+
emitMember();
|
|
576
642
|
for (const locator of locators)
|
|
577
643
|
emitQueue(locator);
|
|
578
644
|
publishRoomDrafts(false);
|
|
@@ -620,10 +686,15 @@ export function composeRuntime(options) {
|
|
|
620
686
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
621
687
|
}
|
|
622
688
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
623
|
-
const
|
|
689
|
+
const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
690
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
|
|
624
691
|
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
692
|
+
const unqueued = withdrawn.map((batch) => ({
|
|
693
|
+
batch,
|
|
694
|
+
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
695
|
+
}));
|
|
625
696
|
const returning = [
|
|
626
|
-
...
|
|
697
|
+
...[...unqueued].reverse(),
|
|
627
698
|
...(settling.leaving === undefined ? [] : [settling.leaving]),
|
|
628
699
|
];
|
|
629
700
|
const written = new Map();
|
|
@@ -641,25 +712,44 @@ export function composeRuntime(options) {
|
|
|
641
712
|
});
|
|
642
713
|
}
|
|
643
714
|
}
|
|
644
|
-
await local.
|
|
645
|
-
|
|
646
|
-
previous: before?.confirmed.record ?? null,
|
|
647
|
-
queue: kept,
|
|
648
|
-
previousQueue: before?.queue ?? [],
|
|
649
|
-
roomDrafts: [...written.values()],
|
|
650
|
-
});
|
|
651
|
-
for (const batch of before?.queue ?? []) {
|
|
715
|
+
await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
|
|
716
|
+
for (const batch of previousQueue) {
|
|
652
717
|
if (!kept.includes(batch))
|
|
653
718
|
pendings.delete(batch.id);
|
|
654
719
|
}
|
|
655
720
|
for (const record of written.values())
|
|
656
721
|
rememberRoomDraft(record);
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
722
|
+
try {
|
|
723
|
+
await local.write({
|
|
724
|
+
next: confirmed.record,
|
|
725
|
+
previous: before?.confirmed.record ?? null,
|
|
726
|
+
...(settling.stored === undefined ? {} : { stored: settling.stored }),
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
const held = before === undefined ? null : replicaOf(before.confirmed, kept, before);
|
|
731
|
+
if (held !== null)
|
|
732
|
+
replicas.set(locator, held);
|
|
733
|
+
else if (kept.length > 0)
|
|
734
|
+
queuesWithoutReplica.set(locator, kept);
|
|
735
|
+
else
|
|
736
|
+
queuesWithoutReplica.delete(locator);
|
|
737
|
+
publishChangeSet(changeSet(locator, before ?? null, held, {
|
|
738
|
+
queue: kept.length !== previousQueue.length,
|
|
739
|
+
drafts: [...written.keys()],
|
|
740
|
+
}));
|
|
741
|
+
moveTo({ ...status, ...attention() });
|
|
742
|
+
throw error;
|
|
743
|
+
}
|
|
744
|
+
queuesWithoutReplica.delete(locator);
|
|
745
|
+
for (const { batch, returned } of unqueued) {
|
|
746
|
+
const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
|
|
747
|
+
journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
|
|
748
|
+
}
|
|
749
|
+
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
660
750
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
661
751
|
room: settling.room,
|
|
662
|
-
queue: kept.length !==
|
|
752
|
+
queue: kept.length !== previousQueue.length,
|
|
663
753
|
drafts: [...written.keys()],
|
|
664
754
|
});
|
|
665
755
|
}
|
|
@@ -694,7 +784,8 @@ export function composeRuntime(options) {
|
|
|
694
784
|
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
695
785
|
};
|
|
696
786
|
}
|
|
697
|
-
function publish(local, room, confirmed,
|
|
787
|
+
function publish(local, room, confirmed, landing = {}) {
|
|
788
|
+
const { base, stored } = landing;
|
|
698
789
|
return exclusive(async () => {
|
|
699
790
|
if (!continuing())
|
|
700
791
|
return true;
|
|
@@ -705,13 +796,15 @@ export function composeRuntime(options) {
|
|
|
705
796
|
return false;
|
|
706
797
|
}
|
|
707
798
|
adoptModels(room.locator, confirmed.record);
|
|
708
|
-
const queue = prior?.queue ?? [];
|
|
799
|
+
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
709
800
|
const roomVersion = confirmed.record.roomVersion;
|
|
710
801
|
const accepted = queue.filter((batch) => {
|
|
711
802
|
const accepted = pendings.get(batch.id)?.accepted;
|
|
712
803
|
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
713
804
|
});
|
|
714
|
-
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true });
|
|
805
|
+
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true, ...(stored === undefined ? {} : { stored }) });
|
|
806
|
+
if (stored !== undefined)
|
|
807
|
+
transfers.delete(room.locator);
|
|
715
808
|
for (const batch of accepted)
|
|
716
809
|
pendings.delete(batch.id);
|
|
717
810
|
stale.delete(room.locator);
|
|
@@ -730,62 +823,140 @@ export function composeRuntime(options) {
|
|
|
730
823
|
const replica = replicas.get(locator);
|
|
731
824
|
const room = rooms.get(locator);
|
|
732
825
|
if (replica === undefined || room === undefined) {
|
|
733
|
-
throw
|
|
826
|
+
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
734
827
|
}
|
|
735
828
|
const saved = [];
|
|
736
829
|
for (const { path, revision } of named) {
|
|
737
830
|
const record = roomDrafts.get(locator)?.get(path);
|
|
738
831
|
if (record === undefined || record.revision !== revision) {
|
|
739
|
-
return { outcome: 'refused',
|
|
832
|
+
return { outcome: 'refused', code: 'stale', retryable: false, file: path };
|
|
740
833
|
}
|
|
741
834
|
if (!saved.some((draft) => draft.path === path))
|
|
742
835
|
saved.push({ path, parts: record.parts });
|
|
743
836
|
}
|
|
744
837
|
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
745
838
|
if (changed.length > 0)
|
|
746
|
-
return { outcome: 'refused',
|
|
839
|
+
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
747
840
|
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
748
|
-
const
|
|
841
|
+
const changes = parts.flatMap(({ changes }) => changes);
|
|
842
|
+
const requeue = requeued(replica.queue, changes, replica.visible, withdrawable, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
843
|
+
if (requeue.kind === 'refused')
|
|
844
|
+
return { outcome: 'refused', ...requeue.refusal };
|
|
845
|
+
const foreign = [...requeue.withdrawn.keys()].some((origin) => {
|
|
846
|
+
const writer = replica.queue.find((batch) => batch.id === origin)?.writtenBy;
|
|
847
|
+
return writer?.app !== writtenBy?.app || writer?.release !== writtenBy?.release;
|
|
848
|
+
});
|
|
849
|
+
if (foreign) {
|
|
850
|
+
return {
|
|
851
|
+
outcome: 'refused',
|
|
852
|
+
code: 'unknown-revision',
|
|
853
|
+
retryable: false,
|
|
854
|
+
file: [...requeue.queued].at(-1),
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
const withdrawing = requeue.withdrawn.size > 0;
|
|
858
|
+
const input = {
|
|
859
|
+
local: localSaves,
|
|
749
860
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
750
861
|
composition: compositionFor(replica.confirmed.record),
|
|
751
862
|
operations: room.operations,
|
|
752
863
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
753
864
|
providerLimits: limits,
|
|
754
|
-
|
|
865
|
+
handle: room.handle,
|
|
755
866
|
visible: replica.visible,
|
|
756
|
-
changes
|
|
867
|
+
changes,
|
|
757
868
|
bases: parts.flatMap(({ bases }) => bases),
|
|
758
869
|
writtenBy,
|
|
870
|
+
};
|
|
871
|
+
const checked = withdrawing ? buildBatch({ ...input, queued: requeue.queued }) : null;
|
|
872
|
+
if (checked?.kind === 'refused')
|
|
873
|
+
return { outcome: 'refused', ...checked.refusal };
|
|
874
|
+
const owners = [...requeue.owners].flatMap((origin) => {
|
|
875
|
+
const batch = requeue.withdrawn.get(origin);
|
|
876
|
+
return batch === undefined || batch === null ? [] : [{ origin, batch }];
|
|
759
877
|
});
|
|
760
|
-
if (
|
|
878
|
+
if (owners.length > 1) {
|
|
879
|
+
return {
|
|
880
|
+
outcome: 'refused',
|
|
881
|
+
code: 'unknown-revision',
|
|
882
|
+
retryable: false,
|
|
883
|
+
file: [...requeue.queued].at(-1),
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
const built = !withdrawing
|
|
887
|
+
? buildBatch(input)
|
|
888
|
+
: requeue.changes.length === 0
|
|
889
|
+
? null
|
|
890
|
+
: buildBatch({
|
|
891
|
+
...input,
|
|
892
|
+
visible: replicaOf(replica.confirmed, requeue.queue, replica).visible,
|
|
893
|
+
changes: requeue.changes,
|
|
894
|
+
});
|
|
895
|
+
if (built?.kind === 'refused')
|
|
761
896
|
return { outcome: 'refused', ...built.refusal };
|
|
762
|
-
const
|
|
897
|
+
const [owner] = owners;
|
|
898
|
+
const join = built === null || owner === undefined
|
|
899
|
+
? null
|
|
900
|
+
: joined(requeue.queue, owner.batch, built.batch);
|
|
901
|
+
if (join?.kind === 'refused')
|
|
902
|
+
return { outcome: 'refused', ...join.refusal };
|
|
903
|
+
const withdrawn = new Map(requeue.withdrawn);
|
|
904
|
+
if (join !== null && owner !== undefined)
|
|
905
|
+
withdrawn.set(owner.origin, join.batch);
|
|
906
|
+
const drafted = new Map();
|
|
907
|
+
for (const [origin, replaced] of withdrawn) {
|
|
908
|
+
if (replaced === null)
|
|
909
|
+
continue;
|
|
910
|
+
const carried = withdrawnDrafts(pendings.get(origin)?.drafts ?? [], changes);
|
|
911
|
+
drafted.set(replaced.id, replaced === join?.batch
|
|
912
|
+
? [
|
|
913
|
+
...carried.map((held) => joinedDraft(held, saved)),
|
|
914
|
+
...saved.filter((draft) => !carried.some((held) => held.path === draft.path)),
|
|
915
|
+
]
|
|
916
|
+
: carried);
|
|
917
|
+
}
|
|
918
|
+
if (built !== null && join === null)
|
|
919
|
+
drafted.set(built.batch.id, saved);
|
|
920
|
+
const batches = join !== null ? join.queue : built === null ? requeue.queue : [...requeue.queue, built.batch];
|
|
921
|
+
for (const replaced of withdrawn.values()) {
|
|
922
|
+
if (replaced === null || !batches.includes(replaced))
|
|
923
|
+
continue;
|
|
924
|
+
const limited = limitedIn(batches, replaced, limits, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
925
|
+
if (limited !== null)
|
|
926
|
+
return { outcome: 'refused', ...limited };
|
|
927
|
+
}
|
|
763
928
|
const consumed = saved.map(({ path }) => path);
|
|
764
929
|
try {
|
|
765
|
-
await local.
|
|
766
|
-
|
|
767
|
-
previous: replica.confirmed.record,
|
|
930
|
+
await local.keepAuthored({
|
|
931
|
+
locator,
|
|
768
932
|
queue: batches,
|
|
769
|
-
|
|
770
|
-
saved,
|
|
933
|
+
saved: drafted,
|
|
771
934
|
consumedRoomDrafts: consumed,
|
|
772
935
|
});
|
|
773
936
|
}
|
|
774
937
|
catch {
|
|
775
|
-
return { outcome: 'refused',
|
|
938
|
+
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
939
|
+
}
|
|
940
|
+
for (const origin of withdrawn.keys())
|
|
941
|
+
pendings.delete(origin);
|
|
942
|
+
for (const [id, drafts] of drafted) {
|
|
943
|
+
pendings.set(id, { drafts, submission: { state: 'waiting' }, lost: false });
|
|
776
944
|
}
|
|
777
|
-
|
|
778
|
-
|
|
945
|
+
if (!localSaves)
|
|
946
|
+
recorded = false;
|
|
779
947
|
for (const path of consumed)
|
|
780
948
|
forgetRoomDraft(locator, path);
|
|
781
|
-
replicas.set(locator, replicaOf(replica.confirmed, batches,
|
|
949
|
+
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
782
950
|
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
783
951
|
queue: true,
|
|
784
952
|
drafts: consumed,
|
|
785
953
|
}));
|
|
786
|
-
moveTo({ ...saving(status), ...attention() });
|
|
954
|
+
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
787
955
|
submitLater(locator);
|
|
788
|
-
|
|
956
|
+
const identifier = join?.batch.id ??
|
|
957
|
+
built?.batch.id ??
|
|
958
|
+
[...requeue.withdrawn].map(([origin, replaced]) => replaced?.id ?? origin).at(-1);
|
|
959
|
+
return { outcome: 'saved', identifier };
|
|
789
960
|
}
|
|
790
961
|
async function mark(local, locator, identifier, submission, lost) {
|
|
791
962
|
const state = pendings.get(identifier);
|
|
@@ -811,16 +982,15 @@ export function composeRuntime(options) {
|
|
|
811
982
|
}
|
|
812
983
|
async function readHistory(locator, path, query = {}) {
|
|
813
984
|
if (closed || refusal !== null)
|
|
814
|
-
return
|
|
985
|
+
return UNCERTAIN;
|
|
815
986
|
try {
|
|
816
987
|
await ensureAuthorization();
|
|
817
988
|
}
|
|
818
989
|
catch {
|
|
819
|
-
return
|
|
820
|
-
}
|
|
821
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
822
|
-
return { kind: 'transport-uncertainty' };
|
|
990
|
+
return UNCERTAIN;
|
|
823
991
|
}
|
|
992
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
993
|
+
return UNCERTAIN;
|
|
824
994
|
const transfer = ['transfer', 'history', locator, path];
|
|
825
995
|
if (query.revision !== undefined)
|
|
826
996
|
transfer.push(query.revision);
|
|
@@ -842,17 +1012,101 @@ export function composeRuntime(options) {
|
|
|
842
1012
|
}
|
|
843
1013
|
if (result.kind === 'revoked-authorization')
|
|
844
1014
|
await refuse('revoked-authorization');
|
|
845
|
-
return result;
|
|
1015
|
+
return historyAnswer(result);
|
|
846
1016
|
}
|
|
847
1017
|
catch {
|
|
848
1018
|
journal.record('outcome history transport-uncertainty');
|
|
849
|
-
return
|
|
1019
|
+
return UNCERTAIN;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function queuedBlob(locator, path, localId) {
|
|
1023
|
+
for (const batch of replicas.get(locator)?.queue ?? []) {
|
|
1024
|
+
const change = batch.changes[queuedFile(batch, path, localId)];
|
|
1025
|
+
if (change?.kind === 'binary')
|
|
1026
|
+
return change.blob;
|
|
1027
|
+
}
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
async function readBinary(request) {
|
|
1031
|
+
if (closed || refusal !== null)
|
|
1032
|
+
return UNCERTAIN;
|
|
1033
|
+
try {
|
|
1034
|
+
await ensureAuthorization();
|
|
1035
|
+
}
|
|
1036
|
+
catch {
|
|
1037
|
+
return UNCERTAIN;
|
|
1038
|
+
}
|
|
1039
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1040
|
+
return UNCERTAIN;
|
|
1041
|
+
journal.record(request.kind === 'path'
|
|
1042
|
+
? `transfer binary ${request.locator} ${request.path}`
|
|
1043
|
+
: `transfer binary ${request.fileRef}`);
|
|
1044
|
+
try {
|
|
1045
|
+
const result = await options.adapter.binary(authorization, request);
|
|
1046
|
+
journal.record(`outcome binary ${result.kind}`);
|
|
1047
|
+
if (result.kind === 'scoped-refusal') {
|
|
1048
|
+
const room = replicas.get(result.locator)?.confirmed.record;
|
|
1049
|
+
if (room !== undefined)
|
|
1050
|
+
await scoped(result, room, await openLocal());
|
|
1051
|
+
}
|
|
1052
|
+
if (result.kind === 'revoked-authorization')
|
|
1053
|
+
await refuse('revoked-authorization');
|
|
1054
|
+
return binaryAnswer(result);
|
|
850
1055
|
}
|
|
1056
|
+
catch {
|
|
1057
|
+
journal.record('outcome binary transport-uncertainty');
|
|
1058
|
+
return UNCERTAIN;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
async function readBinaryPath(locator, path, revision) {
|
|
1062
|
+
await ready;
|
|
1063
|
+
if (revision !== undefined)
|
|
1064
|
+
return readBinary({ kind: 'path', locator, path, revision });
|
|
1065
|
+
const visible = replicas.get(locator)?.visible;
|
|
1066
|
+
const held = visible?.binaries[path];
|
|
1067
|
+
if (visible === undefined || held === undefined)
|
|
1068
|
+
return absentBinary(path);
|
|
1069
|
+
if (held.state === 'pending') {
|
|
1070
|
+
const blob = queuedBlob(locator, path, held.localId);
|
|
1071
|
+
return blob === null ? absentBinary(path) : { kind: 'binary', blob };
|
|
1072
|
+
}
|
|
1073
|
+
const record = binaryRecord(path, held, visible.revisions[path]);
|
|
1074
|
+
if (record.state !== 'accepted')
|
|
1075
|
+
return absentBinary(path);
|
|
1076
|
+
const replica = replicas.get(locator);
|
|
1077
|
+
const queued = (replica?.queue ?? []).flatMap((batch) => batch.changes);
|
|
1078
|
+
const holds = (at) => {
|
|
1079
|
+
const kept = replica?.confirmed.record.binaries[at];
|
|
1080
|
+
return (kept?.state === 'accepted' &&
|
|
1081
|
+
kept.fileRef === record.fileRef &&
|
|
1082
|
+
replica?.confirmed.record.revisions[at] === record.revision);
|
|
1083
|
+
};
|
|
1084
|
+
let confirmed = path;
|
|
1085
|
+
for (let index = queued.length - 1; index >= 0 && !holds(confirmed); index -= 1) {
|
|
1086
|
+
const change = queued[index];
|
|
1087
|
+
if (change.kind === 'move' && change.to === confirmed)
|
|
1088
|
+
confirmed = change.path;
|
|
1089
|
+
}
|
|
1090
|
+
return readBinary({
|
|
1091
|
+
kind: 'path',
|
|
1092
|
+
locator,
|
|
1093
|
+
path: confirmed,
|
|
1094
|
+
revision: record.revision,
|
|
1095
|
+
fileRef: record.fileRef,
|
|
1096
|
+
});
|
|
851
1097
|
}
|
|
852
|
-
function
|
|
1098
|
+
async function readBinaryReference(reference) {
|
|
1099
|
+
await ready;
|
|
1100
|
+
return readBinary({
|
|
1101
|
+
kind: 'reference',
|
|
1102
|
+
fileRef: reference.fileRef,
|
|
1103
|
+
referrer: reference.referrer,
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
function declaredAgents() {
|
|
853
1107
|
for (const capability of capabilities) {
|
|
854
|
-
if (capability.
|
|
855
|
-
return capability.
|
|
1108
|
+
if (capability.conversationAgents !== undefined)
|
|
1109
|
+
return capability.conversationAgents;
|
|
856
1110
|
}
|
|
857
1111
|
return undefined;
|
|
858
1112
|
}
|
|
@@ -868,18 +1122,17 @@ export function composeRuntime(options) {
|
|
|
868
1122
|
async function invokeAction(name, input) {
|
|
869
1123
|
await ready;
|
|
870
1124
|
if (closed || refusal !== null)
|
|
871
|
-
return
|
|
1125
|
+
return UNCERTAIN;
|
|
872
1126
|
if (declaredAction(name) === undefined)
|
|
873
|
-
return
|
|
1127
|
+
return undeclared(name);
|
|
874
1128
|
try {
|
|
875
1129
|
await ensureAuthorization();
|
|
876
1130
|
}
|
|
877
1131
|
catch {
|
|
878
|
-
return
|
|
879
|
-
}
|
|
880
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
881
|
-
return { kind: 'transport-uncertainty' };
|
|
1132
|
+
return UNCERTAIN;
|
|
882
1133
|
}
|
|
1134
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1135
|
+
return UNCERTAIN;
|
|
883
1136
|
journal.record(`transfer action ${name}`);
|
|
884
1137
|
try {
|
|
885
1138
|
const result = await options.adapter.action(authorization, { name, input });
|
|
@@ -893,11 +1146,11 @@ export function composeRuntime(options) {
|
|
|
893
1146
|
journal.record(`outcome action ${result.kind}`);
|
|
894
1147
|
if (result.kind === 'rejected')
|
|
895
1148
|
journal.record(`refusal rejected ${result.code}`);
|
|
896
|
-
return result;
|
|
1149
|
+
return calledResult(result);
|
|
897
1150
|
}
|
|
898
1151
|
catch {
|
|
899
1152
|
journal.record('outcome action transport-uncertainty');
|
|
900
|
-
return
|
|
1153
|
+
return UNCERTAIN;
|
|
901
1154
|
}
|
|
902
1155
|
}
|
|
903
1156
|
function continuing() {
|
|
@@ -928,14 +1181,33 @@ export function composeRuntime(options) {
|
|
|
928
1181
|
throw error;
|
|
929
1182
|
}
|
|
930
1183
|
}
|
|
931
|
-
|
|
1184
|
+
function staging(local) {
|
|
1185
|
+
return {
|
|
1186
|
+
held: (locator) => transfers.get(locator) ?? null,
|
|
1187
|
+
staged: (record) => local.staged(record),
|
|
1188
|
+
stage: (record, page) => exclusive(async () => {
|
|
1189
|
+
if (!continuing())
|
|
1190
|
+
return false;
|
|
1191
|
+
await local.stage(record, page);
|
|
1192
|
+
transfers.set(record.locator, record);
|
|
1193
|
+
return true;
|
|
1194
|
+
}),
|
|
1195
|
+
abandon: (locator) => exclusive(async () => {
|
|
1196
|
+
if (!continuing())
|
|
1197
|
+
return;
|
|
1198
|
+
await local.abandon(locator);
|
|
1199
|
+
transfers.delete(locator);
|
|
1200
|
+
}),
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
async function pullBootstrap(authorized, room, local) {
|
|
932
1204
|
if (!continuing())
|
|
933
1205
|
return unconfirmed;
|
|
934
1206
|
try {
|
|
935
1207
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
936
1208
|
collection: modelOf(room.locator),
|
|
937
1209
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
938
|
-
}, (models, held) => remodel(room.locator, models, held), continuing);
|
|
1210
|
+
}, (models, held) => remodel(room.locator, models, held), continuing, staging(local));
|
|
939
1211
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
940
1212
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
941
1213
|
return result;
|
|
@@ -952,9 +1224,9 @@ export function composeRuntime(options) {
|
|
|
952
1224
|
if (!continuing())
|
|
953
1225
|
return unconfirmed;
|
|
954
1226
|
if (refreshed.kind === 'still-current') {
|
|
955
|
-
if (sourceRefusals.has(room.locator))
|
|
956
|
-
return unconfirmed;
|
|
957
1227
|
stale.delete(room.locator);
|
|
1228
|
+
if (sourceRefusals.has(room.locator))
|
|
1229
|
+
return null;
|
|
958
1230
|
if (unreadable.delete(room.locator)) {
|
|
959
1231
|
scopedRefusals.delete(room.locator);
|
|
960
1232
|
list(room, true);
|
|
@@ -963,7 +1235,7 @@ export function composeRuntime(options) {
|
|
|
963
1235
|
return null;
|
|
964
1236
|
}
|
|
965
1237
|
if (refreshed.kind === 'advanced') {
|
|
966
|
-
if (!(await publish(local, room, refreshed.confirmed, held.confirmed))) {
|
|
1238
|
+
if (!(await publish(local, room, refreshed.confirmed, { base: held.confirmed }))) {
|
|
967
1239
|
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
968
1240
|
const reached = now !== undefined &&
|
|
969
1241
|
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
@@ -975,16 +1247,19 @@ export function composeRuntime(options) {
|
|
|
975
1247
|
if (refreshed.kind !== 'beyond-horizon')
|
|
976
1248
|
return refreshed;
|
|
977
1249
|
}
|
|
978
|
-
const bootstrapped = await pullBootstrap(authorized, room);
|
|
1250
|
+
const bootstrapped = await pullBootstrap(authorized, room, local);
|
|
979
1251
|
if (!continuing())
|
|
980
1252
|
return unconfirmed;
|
|
981
1253
|
if (bootstrapped.kind !== 'staged')
|
|
982
1254
|
return bootstrapped;
|
|
983
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1255
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
984
1256
|
return null;
|
|
985
1257
|
}
|
|
986
1258
|
async function withhold(withheld, room, local, progress) {
|
|
987
|
-
|
|
1259
|
+
if (withheld.kind === 'refused')
|
|
1260
|
+
stale.delete(room.locator);
|
|
1261
|
+
else
|
|
1262
|
+
stale.add(room.locator);
|
|
988
1263
|
if (withheld.kind === 'scoped-refusal') {
|
|
989
1264
|
await scoped(withheld, room, local);
|
|
990
1265
|
}
|
|
@@ -1009,6 +1284,7 @@ export function composeRuntime(options) {
|
|
|
1009
1284
|
function refusedSource(room, { refusal }) {
|
|
1010
1285
|
sourceRefusals.set(room.locator, {
|
|
1011
1286
|
code: refusal.rule,
|
|
1287
|
+
retryable: false,
|
|
1012
1288
|
fields: {
|
|
1013
1289
|
file: refusal.file,
|
|
1014
1290
|
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
@@ -1023,8 +1299,16 @@ export function composeRuntime(options) {
|
|
|
1023
1299
|
}
|
|
1024
1300
|
function leaving(batch, result) {
|
|
1025
1301
|
if (result.kind === 'expired')
|
|
1026
|
-
return { batch, returned:
|
|
1027
|
-
return {
|
|
1302
|
+
return { batch, returned: returnedBy('expired') };
|
|
1303
|
+
return {
|
|
1304
|
+
batch,
|
|
1305
|
+
returned: {
|
|
1306
|
+
kind: result.kind,
|
|
1307
|
+
code: result.code,
|
|
1308
|
+
fields: result.fields,
|
|
1309
|
+
retryable: result.retryable,
|
|
1310
|
+
},
|
|
1311
|
+
};
|
|
1028
1312
|
}
|
|
1029
1313
|
function logAccepted(locator, submitted, accepted) {
|
|
1030
1314
|
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
@@ -1042,7 +1326,7 @@ export function composeRuntime(options) {
|
|
|
1042
1326
|
logAccepted(room.locator, submitted, accepted);
|
|
1043
1327
|
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1044
1328
|
? { kind: 'staged', confirmed: current.confirmed }
|
|
1045
|
-
: applyReturned(current.confirmed,
|
|
1329
|
+
: applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1046
1330
|
? accepted.roomVersion
|
|
1047
1331
|
: held, accepted.roomVersion);
|
|
1048
1332
|
if (applied.kind !== 'staged') {
|
|
@@ -1188,7 +1472,8 @@ export function composeRuntime(options) {
|
|
|
1188
1472
|
return;
|
|
1189
1473
|
}
|
|
1190
1474
|
const set = await settle(local, room.locator, current, current.confirmed, current.queue.filter((batch) => batch.id !== head.id), {
|
|
1191
|
-
leaving: result.kind === 'expired' && landed(head, current.confirmed)
|
|
1475
|
+
leaving: (result.kind === 'expired' && landed(head, current.confirmed)) ||
|
|
1476
|
+
(result.kind === 'conflicted' && landed(head, current.confirmed, result.affected))
|
|
1192
1477
|
? { ...left, returned: null }
|
|
1193
1478
|
: left,
|
|
1194
1479
|
});
|
|
@@ -1249,7 +1534,7 @@ export function composeRuntime(options) {
|
|
|
1249
1534
|
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1250
1535
|
};
|
|
1251
1536
|
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
1252
|
-
?
|
|
1537
|
+
? { kind: 'staged', confirmed: confirmedOf(declared, held.confirmed) }
|
|
1253
1538
|
: readReclassified(declared, held.confirmed);
|
|
1254
1539
|
const record = reclassified.kind === 'staged'
|
|
1255
1540
|
? declared
|
|
@@ -1257,14 +1542,12 @@ export function composeRuntime(options) {
|
|
|
1257
1542
|
...declared,
|
|
1258
1543
|
typedSelection: held.confirmed.record.typedSelection,
|
|
1259
1544
|
};
|
|
1260
|
-
const read = reclassified.kind === 'deferred'
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
queue: held.queue,
|
|
1265
|
-
});
|
|
1545
|
+
const read = reclassified.kind === 'deferred'
|
|
1546
|
+
? { kind: 'staged', confirmed: confirmedOf(record, held.confirmed) }
|
|
1547
|
+
: reclassified;
|
|
1548
|
+
await local.write({ next: record, previous: held.confirmed.record });
|
|
1266
1549
|
if (read.kind === 'staged') {
|
|
1267
|
-
replicas.set(room.locator, replicaOf(read.confirmed, held.queue,
|
|
1550
|
+
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
|
|
1268
1551
|
if (declarationRefusals.delete(room.locator)) {
|
|
1269
1552
|
sourceRefusals.delete(room.locator);
|
|
1270
1553
|
unreadable.delete(room.locator);
|
|
@@ -1289,7 +1572,11 @@ export function composeRuntime(options) {
|
|
|
1289
1572
|
});
|
|
1290
1573
|
}
|
|
1291
1574
|
async function scoped(result, room, local) {
|
|
1292
|
-
scopedRefusals.set(room.locator, {
|
|
1575
|
+
scopedRefusals.set(room.locator, {
|
|
1576
|
+
code: result.code,
|
|
1577
|
+
fields: result.fields,
|
|
1578
|
+
retryable: result.retryable,
|
|
1579
|
+
});
|
|
1293
1580
|
let scopedAway = room;
|
|
1294
1581
|
if (result.scope === 'model')
|
|
1295
1582
|
await retainModel(local, room, 'collection', {
|
|
@@ -1326,7 +1613,6 @@ export function composeRuntime(options) {
|
|
|
1326
1613
|
roomVersion: record.roomVersion,
|
|
1327
1614
|
documents: record.documents,
|
|
1328
1615
|
revisions: record.revisions,
|
|
1329
|
-
attachments: record.attachments,
|
|
1330
1616
|
},
|
|
1331
1617
|
];
|
|
1332
1618
|
});
|
|
@@ -1337,8 +1623,10 @@ export function composeRuntime(options) {
|
|
|
1337
1623
|
const outcome = prepared[index];
|
|
1338
1624
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
1339
1625
|
});
|
|
1340
|
-
if (attempt === 'opened')
|
|
1626
|
+
if (attempt === 'opened') {
|
|
1341
1627
|
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
1628
|
+
handed = true;
|
|
1629
|
+
}
|
|
1342
1630
|
const loaded = await Promise.allSettled(ready
|
|
1343
1631
|
.filter((room) => attempt === 'unstreamed' || !replicas.has(room.locator))
|
|
1344
1632
|
.map((room) => loadRoom(room, local, progress)));
|
|
@@ -1404,14 +1692,14 @@ export function composeRuntime(options) {
|
|
|
1404
1692
|
async function reload(room, local, progress) {
|
|
1405
1693
|
if (discoveryPaused || authorization === undefined)
|
|
1406
1694
|
return;
|
|
1407
|
-
const bootstrapped = await pullBootstrap(authorization, room);
|
|
1695
|
+
const bootstrapped = await pullBootstrap(authorization, room, local);
|
|
1408
1696
|
if (!continuing())
|
|
1409
1697
|
return;
|
|
1410
1698
|
if (bootstrapped.kind !== 'staged') {
|
|
1411
1699
|
await withhold(bootstrapped, room, local, progress);
|
|
1412
1700
|
return;
|
|
1413
1701
|
}
|
|
1414
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1702
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
1415
1703
|
await caughtUp(room, local, progress);
|
|
1416
1704
|
}
|
|
1417
1705
|
async function discoverRooms(local, progress, attempt) {
|
|
@@ -1441,15 +1729,16 @@ export function composeRuntime(options) {
|
|
|
1441
1729
|
}
|
|
1442
1730
|
progress.reachable = false;
|
|
1443
1731
|
progress.complete = false;
|
|
1444
|
-
if (attempt === 'opened') {
|
|
1445
|
-
options.adapter.roomStream?.hold(heldRooms([...discovered.values()]));
|
|
1446
|
-
}
|
|
1447
1732
|
notifyReady();
|
|
1448
1733
|
return;
|
|
1449
1734
|
}
|
|
1450
1735
|
if (result.kind === 'contract-unavailable') {
|
|
1451
1736
|
journal.record(`connection contract unavailable ${result.code}`);
|
|
1452
|
-
await refuse('missing-required-guarantee', {
|
|
1737
|
+
await refuse('missing-required-guarantee', {
|
|
1738
|
+
code: result.code,
|
|
1739
|
+
fields: result.fields,
|
|
1740
|
+
retryable: result.retryable,
|
|
1741
|
+
});
|
|
1453
1742
|
notifyReady();
|
|
1454
1743
|
return;
|
|
1455
1744
|
}
|
|
@@ -1458,17 +1747,6 @@ export function composeRuntime(options) {
|
|
|
1458
1747
|
notifyReady();
|
|
1459
1748
|
return;
|
|
1460
1749
|
}
|
|
1461
|
-
if (!result.supportedVersions.includes(ROOM_CONTRACT_VERSION)) {
|
|
1462
|
-
await refuse('no-matching-version');
|
|
1463
|
-
notifyReady();
|
|
1464
|
-
return;
|
|
1465
|
-
}
|
|
1466
|
-
if (result.mdModel.identity !== MD_MODEL.identity ||
|
|
1467
|
-
result.mdModel.version !== MD_MODEL.version) {
|
|
1468
|
-
await refuse('md-model-version-mismatch');
|
|
1469
|
-
notifyReady();
|
|
1470
|
-
return;
|
|
1471
|
-
}
|
|
1472
1750
|
for (const room of result.rooms) {
|
|
1473
1751
|
if (room.operations === undefined ||
|
|
1474
1752
|
!['read', 'sync', 'write', 'delete'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
@@ -1489,14 +1767,6 @@ export function composeRuntime(options) {
|
|
|
1489
1767
|
return;
|
|
1490
1768
|
}
|
|
1491
1769
|
}
|
|
1492
|
-
const declared = new Set(result.capabilities.map((capability) => capability.name));
|
|
1493
|
-
for (const required of options.requiredGuarantees) {
|
|
1494
|
-
if (!knownCapabilities.has(required) || !declared.has(required)) {
|
|
1495
|
-
await refuse('missing-required-guarantee');
|
|
1496
|
-
notifyReady();
|
|
1497
|
-
return;
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
1770
|
if (refusal !== null) {
|
|
1501
1771
|
refusal = null;
|
|
1502
1772
|
emitRefusal();
|
|
@@ -1509,6 +1779,12 @@ export function composeRuntime(options) {
|
|
|
1509
1779
|
limits = result.providerLimits;
|
|
1510
1780
|
await local.declare(limits, capabilities);
|
|
1511
1781
|
}
|
|
1782
|
+
const signedIn = result.member ?? null;
|
|
1783
|
+
if (JSON.stringify(signedIn) !== JSON.stringify(member)) {
|
|
1784
|
+
await local.keepMember(signedIn);
|
|
1785
|
+
member = signedIn;
|
|
1786
|
+
emitMember();
|
|
1787
|
+
}
|
|
1512
1788
|
await applyDiscovery(result.rooms, local);
|
|
1513
1789
|
await synchronize(result.rooms, local, progress, attempt);
|
|
1514
1790
|
}
|
|
@@ -1538,9 +1814,12 @@ export function composeRuntime(options) {
|
|
|
1538
1814
|
}
|
|
1539
1815
|
const progress = { complete: true, reachable: true };
|
|
1540
1816
|
underway = attempt === 'opened' ? progress : null;
|
|
1817
|
+
if (attempt === 'opened')
|
|
1818
|
+
streamed = progress;
|
|
1541
1819
|
complete = false;
|
|
1542
1820
|
recorded = false;
|
|
1543
1821
|
moveTo(transferring(status));
|
|
1822
|
+
handed = false;
|
|
1544
1823
|
try {
|
|
1545
1824
|
const local = await openLocal();
|
|
1546
1825
|
if (closed)
|
|
@@ -1552,16 +1831,22 @@ export function composeRuntime(options) {
|
|
|
1552
1831
|
}
|
|
1553
1832
|
catch {
|
|
1554
1833
|
progress.complete = false;
|
|
1555
|
-
if (attempt === 'opened')
|
|
1556
|
-
|
|
1557
|
-
}
|
|
1834
|
+
if (attempt === 'opened' && !handed)
|
|
1835
|
+
progress.reachable = false;
|
|
1558
1836
|
}
|
|
1559
1837
|
finally {
|
|
1560
1838
|
notifyReady();
|
|
1561
1839
|
}
|
|
1562
|
-
if (attempt
|
|
1840
|
+
if (attempt !== 'opened') {
|
|
1841
|
+
await finishOpening(progress);
|
|
1563
1842
|
return;
|
|
1564
|
-
|
|
1843
|
+
}
|
|
1844
|
+
if (handed || closed || progress.reachable)
|
|
1845
|
+
return;
|
|
1846
|
+
underway = null;
|
|
1847
|
+
journal.record('connection reopen');
|
|
1848
|
+
await rest(progress);
|
|
1849
|
+
options.adapter.roomStream?.reopen();
|
|
1565
1850
|
});
|
|
1566
1851
|
}
|
|
1567
1852
|
function closedSocket(revoked) {
|
|
@@ -1571,9 +1856,14 @@ export function composeRuntime(options) {
|
|
|
1571
1856
|
void refuse('revoked-authorization');
|
|
1572
1857
|
return;
|
|
1573
1858
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1859
|
+
complete = false;
|
|
1860
|
+
if (streamed !== null)
|
|
1861
|
+
streamed.complete = false;
|
|
1862
|
+
moveTo({
|
|
1863
|
+
...status,
|
|
1864
|
+
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
1865
|
+
providerUnreachable: status.providerUnreachable || !opened,
|
|
1866
|
+
});
|
|
1577
1867
|
}
|
|
1578
1868
|
function applyOpening(expired) {
|
|
1579
1869
|
return task(async () => {
|
|
@@ -1628,6 +1918,8 @@ export function composeRuntime(options) {
|
|
|
1628
1918
|
}
|
|
1629
1919
|
}
|
|
1630
1920
|
function submitLater(locator) {
|
|
1921
|
+
if (localSaves)
|
|
1922
|
+
return;
|
|
1631
1923
|
const running = submitting.get(locator);
|
|
1632
1924
|
if (running !== undefined) {
|
|
1633
1925
|
running.again = true;
|
|
@@ -1707,7 +1999,7 @@ export function composeRuntime(options) {
|
|
|
1707
1999
|
}
|
|
1708
2000
|
}
|
|
1709
2001
|
moveTo({
|
|
1710
|
-
state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
|
|
2002
|
+
state: pending() ? 'pending' : synced && complete ? 'synced' : 'syncing',
|
|
1711
2003
|
lastCompleted,
|
|
1712
2004
|
providerUnreachable: !progress.reachable,
|
|
1713
2005
|
...attention(),
|
|
@@ -1791,7 +2083,10 @@ export function composeRuntime(options) {
|
|
|
1791
2083
|
if (stream === undefined || withdrawConversations !== undefined)
|
|
1792
2084
|
return;
|
|
1793
2085
|
withdrawConversations = stream.observe((event) => {
|
|
1794
|
-
conversations.receive(event)
|
|
2086
|
+
if (conversations.receive(event)) {
|
|
2087
|
+
void ready.then(scheduleCurrency);
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
1795
2090
|
if (event.kind !== 'gap' || conversations.current())
|
|
1796
2091
|
return;
|
|
1797
2092
|
show();
|
|
@@ -1813,20 +2108,15 @@ export function composeRuntime(options) {
|
|
|
1813
2108
|
show();
|
|
1814
2109
|
return;
|
|
1815
2110
|
}
|
|
1816
|
-
const
|
|
1817
|
-
|
|
1818
|
-
moveTo(transferring(resting));
|
|
2111
|
+
const progress = { complete: false, reachable: !status.providerUnreachable };
|
|
2112
|
+
moveTo(transferring(status));
|
|
1819
2113
|
try {
|
|
1820
2114
|
await conversations.catchUp(progress);
|
|
1821
2115
|
}
|
|
1822
2116
|
catch {
|
|
1823
2117
|
progress.reachable = false;
|
|
1824
2118
|
}
|
|
1825
|
-
|
|
1826
|
-
if (!closed) {
|
|
1827
|
-
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
2119
|
+
await rest(progress);
|
|
1830
2120
|
}
|
|
1831
2121
|
function close() {
|
|
1832
2122
|
if (closed)
|
|
@@ -1848,6 +2138,7 @@ export function composeRuntime(options) {
|
|
|
1848
2138
|
storageLossListeners.clear();
|
|
1849
2139
|
statusListeners.clear();
|
|
1850
2140
|
serviceModeListeners.clear();
|
|
2141
|
+
memberListeners.clear();
|
|
1851
2142
|
readSide.close();
|
|
1852
2143
|
queueSubscriptions.clear();
|
|
1853
2144
|
roomDraftListeners.clear();
|
|
@@ -1879,13 +2170,16 @@ export function composeRuntime(options) {
|
|
|
1879
2170
|
return planOf(input, roomFacts(reach), planReach(reach));
|
|
1880
2171
|
}
|
|
1881
2172
|
function watchPlan(plan, deliver) {
|
|
2173
|
+
materialize(reachedRooms(plan));
|
|
1882
2174
|
return readSide.watch(plan, identityOf(plan), deliver);
|
|
1883
2175
|
}
|
|
1884
2176
|
async function searched(input, reach) {
|
|
1885
2177
|
const plan = searchPlanOf(input);
|
|
1886
2178
|
const selected = plan.documents === undefined && reach === null
|
|
1887
2179
|
? null
|
|
1888
|
-
: (await
|
|
2180
|
+
: (await rowsQuery(filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)))).documents;
|
|
2181
|
+
if (selected === null)
|
|
2182
|
+
materialize(null);
|
|
1889
2183
|
return searchIndex.search(reach === null || reach.conversations ? plan : { ...plan, conversations: [] }, selected);
|
|
1890
2184
|
}
|
|
1891
2185
|
const watch = (topic, input, deliver) => {
|
|
@@ -1909,6 +2203,10 @@ export function composeRuntime(options) {
|
|
|
1909
2203
|
if (topic === 'conversation') {
|
|
1910
2204
|
return conversations.watch(input[0], input[1], deliver);
|
|
1911
2205
|
}
|
|
2206
|
+
if (topic === 'conversations')
|
|
2207
|
+
return conversations.watchList(deliver);
|
|
2208
|
+
if (topic === 'agents')
|
|
2209
|
+
return conversations.watchAgents(deliver);
|
|
1912
2210
|
if (topic === 'presentation') {
|
|
1913
2211
|
const listener = {
|
|
1914
2212
|
app: input[0],
|
|
@@ -1963,6 +2261,13 @@ export function composeRuntime(options) {
|
|
|
1963
2261
|
serviceModeListeners.delete(deliver);
|
|
1964
2262
|
};
|
|
1965
2263
|
}
|
|
2264
|
+
if (topic === 'member') {
|
|
2265
|
+
memberListeners.add(deliver);
|
|
2266
|
+
deliver(member);
|
|
2267
|
+
return () => {
|
|
2268
|
+
memberListeners.delete(deliver);
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
1966
2271
|
if (topic === 'queue') {
|
|
1967
2272
|
const queue = { locator: input[0], notify: deliver };
|
|
1968
2273
|
queueSubscriptions.add(queue);
|
|
@@ -1980,7 +2285,7 @@ export function composeRuntime(options) {
|
|
|
1980
2285
|
rooms() {
|
|
1981
2286
|
return listed();
|
|
1982
2287
|
},
|
|
1983
|
-
query: ((input) =>
|
|
2288
|
+
query: ((input) => rowsQuery(planned(input, null))),
|
|
1984
2289
|
search: (input) => searched(input, null),
|
|
1985
2290
|
types(locator) {
|
|
1986
2291
|
return {
|
|
@@ -2009,20 +2314,20 @@ export function composeRuntime(options) {
|
|
|
2009
2314
|
return exclusive(async () => {
|
|
2010
2315
|
const parts = readParts(input);
|
|
2011
2316
|
if (path === '' || normalize(path) !== path)
|
|
2012
|
-
throw
|
|
2317
|
+
throw runtimeError('room-draft-malformed');
|
|
2013
2318
|
const local = await openLocal();
|
|
2014
2319
|
const current = roomDrafts.get(room)?.get(path);
|
|
2015
2320
|
if ((current?.revision ?? null) !== revision) {
|
|
2016
|
-
return
|
|
2017
|
-
outcome: 'stale',
|
|
2018
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2019
|
-
};
|
|
2321
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2020
2322
|
}
|
|
2323
|
+
const file = parts.file === undefined
|
|
2324
|
+
? undefined
|
|
2325
|
+
: await heldFile(parts.file.blob, binaryBase(replicas.get(room)?.visible, path));
|
|
2021
2326
|
const record = {
|
|
2022
2327
|
locator: room,
|
|
2023
2328
|
path,
|
|
2024
2329
|
revision: roomDraftRevision(),
|
|
2025
|
-
parts: held(current?.parts, parts),
|
|
2330
|
+
parts: held(current?.parts, parts, file),
|
|
2026
2331
|
returned: current?.returned ?? null,
|
|
2027
2332
|
};
|
|
2028
2333
|
await local.keepRoomDraft(record);
|
|
@@ -2036,10 +2341,7 @@ export function composeRuntime(options) {
|
|
|
2036
2341
|
const local = await openLocal();
|
|
2037
2342
|
const current = roomDrafts.get(room)?.get(path);
|
|
2038
2343
|
if (current === undefined || current.revision !== revision) {
|
|
2039
|
-
return
|
|
2040
|
-
outcome: 'stale',
|
|
2041
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2042
|
-
};
|
|
2344
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2043
2345
|
}
|
|
2044
2346
|
await local.discardRoomDraft(room, path);
|
|
2045
2347
|
forgetRoomDraft(room, path);
|
|
@@ -2074,6 +2376,10 @@ export function composeRuntime(options) {
|
|
|
2074
2376
|
history(locator, path, query) {
|
|
2075
2377
|
return readHistory(locator, path, query);
|
|
2076
2378
|
},
|
|
2379
|
+
binaryFile: {
|
|
2380
|
+
read: readBinaryPath,
|
|
2381
|
+
readReference: readBinaryReference,
|
|
2382
|
+
},
|
|
2077
2383
|
call(name, input) {
|
|
2078
2384
|
return invokeAction(name, input);
|
|
2079
2385
|
},
|
|
@@ -2106,8 +2412,9 @@ export function composeRuntime(options) {
|
|
|
2106
2412
|
const reachable = {
|
|
2107
2413
|
md,
|
|
2108
2414
|
watch,
|
|
2415
|
+
referrerScope: (referrer) => options.adapter.referrerScope(referrer),
|
|
2109
2416
|
plan: planned,
|
|
2110
|
-
query: (plan) =>
|
|
2417
|
+
query: (plan) => rowsQuery(plan),
|
|
2111
2418
|
watchPlan,
|
|
2112
2419
|
search: searched,
|
|
2113
2420
|
};
|