@agentshouse/mdruntime 0.1.0-ca9a5a0b81b98 → 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 +135 -48
- package/README.md +20 -10
- package/dist/adapter.d.ts +49 -14
- package/dist/adapter.d.ts.map +1 -1
- 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 +16 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +65 -15
- 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 +24 -11
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +478 -145
- package/dist/conversation.d.ts +11 -3
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +47 -7
- 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 +9 -8
- 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 +4 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +61 -20
- 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 +44 -35
- package/dist/react.d.ts +26 -16
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +10 -8
- 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 +8 -5
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +86 -55
- 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 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +428 -77
- 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 +7 -3
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +15 -2
- package/package.json +17 -9
- 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,17 +3,22 @@ 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
8
|
import { bootstrapRoom } from './bootstrap.js';
|
|
8
9
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
9
10
|
import { createConversationStore, } from './conversation.js';
|
|
10
|
-
import {
|
|
11
|
+
import { queuedFile } from './change.js';
|
|
12
|
+
import { validRoot } from './collection.js';
|
|
13
|
+
import { runtimeError } from './failure.js';
|
|
11
14
|
import { createLog } from './log.js';
|
|
15
|
+
import { binaryRecord } from './binary.js';
|
|
16
|
+
import { absentBinary, binaryAnswer, calledResult, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
|
|
12
17
|
import { refreshRoom } from './refresh.js';
|
|
13
|
-
import { buildBatch } from './save.js';
|
|
14
|
-
import { applyReturned, compositionOf,
|
|
15
|
-
import { changedParts, changesOf, held, readParts, returnedInto, underneath, } from './room-draft.js';
|
|
16
|
-
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';
|
|
17
22
|
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
18
23
|
import { roomDraftRevision } from './identity.js';
|
|
19
24
|
import { beneath, normalize } from './paths.js';
|
|
@@ -34,16 +39,19 @@ function declaredSelection(room) {
|
|
|
34
39
|
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
35
40
|
}
|
|
36
41
|
export function composeRuntime(options) {
|
|
42
|
+
const localSaves = options.adapter.localSaves === true;
|
|
37
43
|
let authorization = options.authorization;
|
|
38
44
|
let opening;
|
|
39
45
|
let store;
|
|
40
46
|
const replicas = new Map();
|
|
47
|
+
const queuesWithoutReplica = new Map();
|
|
41
48
|
const pendings = new Map();
|
|
42
49
|
const roomDrafts = new Map();
|
|
43
50
|
const presentations = new Map();
|
|
44
51
|
let limits = [];
|
|
45
52
|
let capabilities = [];
|
|
46
53
|
const rooms = new Map();
|
|
54
|
+
const counted = new Map();
|
|
47
55
|
const unreadable = new Set();
|
|
48
56
|
const models = new Map();
|
|
49
57
|
const liveModels = new Map();
|
|
@@ -82,6 +90,8 @@ export function composeRuntime(options) {
|
|
|
82
90
|
let withdrawStream;
|
|
83
91
|
const announced = new Map();
|
|
84
92
|
let underway = null;
|
|
93
|
+
let streamed = null;
|
|
94
|
+
let handed = false;
|
|
85
95
|
const submitting = new Map();
|
|
86
96
|
const answering = new Map();
|
|
87
97
|
let complete = false;
|
|
@@ -104,9 +114,14 @@ export function composeRuntime(options) {
|
|
|
104
114
|
let haltedRefusal = false;
|
|
105
115
|
let openSignals = 0;
|
|
106
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();
|
|
107
122
|
const conversations = createConversationStore({
|
|
108
123
|
agents: declaredAgents,
|
|
109
|
-
documents: (locator) =>
|
|
124
|
+
documents: (locator) => rowsQuery(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
110
125
|
call: (name, input) => invokeAction(name, input),
|
|
111
126
|
durably,
|
|
112
127
|
stream: () => options.adapter.conversations,
|
|
@@ -182,7 +197,9 @@ export function composeRuntime(options) {
|
|
|
182
197
|
}
|
|
183
198
|
}
|
|
184
199
|
function pending() {
|
|
185
|
-
return
|
|
200
|
+
return (!localSaves &&
|
|
201
|
+
(queuesWithoutReplica.size > 0 ||
|
|
202
|
+
[...replicas.values()].some((replica) => replica.queue.length > 0)));
|
|
186
203
|
}
|
|
187
204
|
function attention() {
|
|
188
205
|
const attentionRequired = [];
|
|
@@ -211,8 +228,24 @@ export function composeRuntime(options) {
|
|
|
211
228
|
}
|
|
212
229
|
return { attentionRequired, reset };
|
|
213
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
|
+
}
|
|
214
243
|
function listed() {
|
|
215
|
-
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
|
+
}));
|
|
216
249
|
}
|
|
217
250
|
function roomFacts(reach = null) {
|
|
218
251
|
const held = [...rooms.values()]
|
|
@@ -244,6 +277,10 @@ export function composeRuntime(options) {
|
|
|
244
277
|
for (const listener of storageLossListeners)
|
|
245
278
|
listener(storageLoss);
|
|
246
279
|
}
|
|
280
|
+
function emitMember() {
|
|
281
|
+
for (const listener of memberListeners)
|
|
282
|
+
listener(member);
|
|
283
|
+
}
|
|
247
284
|
function moveTo(next) {
|
|
248
285
|
if (closed)
|
|
249
286
|
return;
|
|
@@ -263,6 +300,8 @@ export function composeRuntime(options) {
|
|
|
263
300
|
listener(shown);
|
|
264
301
|
}
|
|
265
302
|
function entryOf(batch) {
|
|
303
|
+
if (localSaves)
|
|
304
|
+
return Object.freeze({ batch, state: 'local' });
|
|
266
305
|
const state = pendings.get(batch.id);
|
|
267
306
|
if (state?.accepted !== undefined) {
|
|
268
307
|
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
@@ -270,10 +309,7 @@ export function composeRuntime(options) {
|
|
|
270
309
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
271
310
|
}
|
|
272
311
|
function entriesOf(locator) {
|
|
273
|
-
|
|
274
|
-
if (replica === undefined)
|
|
275
|
-
return [];
|
|
276
|
-
return replica.queue.map(entryOf);
|
|
312
|
+
return (replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []).map(entryOf);
|
|
277
313
|
}
|
|
278
314
|
function emitQueue(locator) {
|
|
279
315
|
const entries = entriesOf(locator);
|
|
@@ -283,12 +319,47 @@ export function composeRuntime(options) {
|
|
|
283
319
|
}
|
|
284
320
|
}
|
|
285
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);
|
|
286
345
|
readSide.apply(set);
|
|
287
346
|
searchIndex.changed(set);
|
|
288
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
|
+
}
|
|
289
359
|
function publishChangeSet(set) {
|
|
290
360
|
applyChangeSet(set);
|
|
291
|
-
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)))
|
|
292
363
|
emitRooms();
|
|
293
364
|
for (const locator of set.queues)
|
|
294
365
|
emitQueue(locator);
|
|
@@ -335,10 +406,7 @@ export function composeRuntime(options) {
|
|
|
335
406
|
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
336
407
|
try {
|
|
337
408
|
const local = await openLocal();
|
|
338
|
-
await local.revokeModels([...replicas.values()].map((replica) =>
|
|
339
|
-
record: replica.confirmed.record,
|
|
340
|
-
queue: replica.queue,
|
|
341
|
-
})));
|
|
409
|
+
await local.revokeModels([...replicas.values()].map((replica) => replica.confirmed.record));
|
|
342
410
|
}
|
|
343
411
|
catch {
|
|
344
412
|
journal.record('model revocation persistence failed');
|
|
@@ -362,10 +430,7 @@ export function composeRuntime(options) {
|
|
|
362
430
|
model: revoked,
|
|
363
431
|
selectionModel: revoked,
|
|
364
432
|
};
|
|
365
|
-
|
|
366
|
-
replicas.set(locator, read.kind === 'staged'
|
|
367
|
-
? replicaOf(read.confirmed, before.queue, before)
|
|
368
|
-
: { ...before, confirmed: { ...before.confirmed, record } });
|
|
433
|
+
replicas.set(locator, replicaOf(confirmedOf(record, before.confirmed), before.queue, before));
|
|
369
434
|
}
|
|
370
435
|
rooms.set(locator, {
|
|
371
436
|
...room,
|
|
@@ -382,7 +447,8 @@ export function composeRuntime(options) {
|
|
|
382
447
|
}
|
|
383
448
|
finally {
|
|
384
449
|
refusal = {
|
|
385
|
-
reason,
|
|
450
|
+
code: reason,
|
|
451
|
+
retryable: false,
|
|
386
452
|
reauthorizationOffered: reason === 'revoked-authorization',
|
|
387
453
|
refusal: provider,
|
|
388
454
|
};
|
|
@@ -395,6 +461,7 @@ export function composeRuntime(options) {
|
|
|
395
461
|
rooms.set(room.locator, {
|
|
396
462
|
name: room.name,
|
|
397
463
|
locator: room.locator,
|
|
464
|
+
handle: room.handle,
|
|
398
465
|
operations: room.operations,
|
|
399
466
|
replica: room.replica,
|
|
400
467
|
model: modelTypes(modelOf(room.locator)),
|
|
@@ -406,7 +473,6 @@ export function composeRuntime(options) {
|
|
|
406
473
|
},
|
|
407
474
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
408
475
|
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
409
|
-
attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
|
|
410
476
|
synchronizationAvailable,
|
|
411
477
|
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
412
478
|
});
|
|
@@ -431,7 +497,14 @@ export function composeRuntime(options) {
|
|
|
431
497
|
emitRooms();
|
|
432
498
|
}
|
|
433
499
|
function unlistedHeld(listed) {
|
|
434
|
-
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));
|
|
435
508
|
}
|
|
436
509
|
async function dropUnlisted(listed, local) {
|
|
437
510
|
if (unlistedHeld(listed).length === 0)
|
|
@@ -445,11 +518,13 @@ export function composeRuntime(options) {
|
|
|
445
518
|
drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
|
|
446
519
|
}));
|
|
447
520
|
for (const locator of unlisted) {
|
|
448
|
-
for (const batch of replicas.get(locator)?.queue ?? [])
|
|
521
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
449
522
|
pendings.delete(batch.id);
|
|
450
523
|
replicas.delete(locator);
|
|
524
|
+
queuesWithoutReplica.delete(locator);
|
|
451
525
|
rooms.delete(locator);
|
|
452
526
|
roomDrafts.delete(locator);
|
|
527
|
+
transfers.delete(locator);
|
|
453
528
|
unreadable.delete(locator);
|
|
454
529
|
stale.delete(locator);
|
|
455
530
|
scopedRefusals.delete(locator);
|
|
@@ -486,7 +561,7 @@ export function composeRuntime(options) {
|
|
|
486
561
|
return model;
|
|
487
562
|
}
|
|
488
563
|
async function hydrate(opened) {
|
|
489
|
-
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([
|
|
490
565
|
opened.all(),
|
|
491
566
|
opened.queues(),
|
|
492
567
|
opened.roomDrafts(),
|
|
@@ -496,9 +571,15 @@ export function composeRuntime(options) {
|
|
|
496
571
|
opened.lastCompleted(),
|
|
497
572
|
opened.providerLimits(),
|
|
498
573
|
opened.capabilities(),
|
|
574
|
+
opened.member(),
|
|
575
|
+
opened.transfers(),
|
|
499
576
|
]);
|
|
577
|
+
transfers.clear();
|
|
578
|
+
for (const record of staged)
|
|
579
|
+
transfers.set(record.locator, record);
|
|
500
580
|
limits = declared;
|
|
501
581
|
capabilities = named;
|
|
582
|
+
member = known;
|
|
502
583
|
roomDrafts.clear();
|
|
503
584
|
for (const record of kept)
|
|
504
585
|
rememberRoomDraft(record);
|
|
@@ -514,18 +595,17 @@ export function composeRuntime(options) {
|
|
|
514
595
|
}
|
|
515
596
|
const previousReplicas = new Map(replicas);
|
|
516
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
|
+
}
|
|
517
604
|
for (const record of records) {
|
|
518
605
|
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
519
606
|
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
520
607
|
const prior = previousReplicas.get(record.locator);
|
|
521
|
-
const
|
|
522
|
-
const read = readConfirmed(retyped, prior?.confirmed);
|
|
523
|
-
const confirmed = read.kind === 'staged'
|
|
524
|
-
? read.confirmed
|
|
525
|
-
: {
|
|
526
|
-
record: retyped,
|
|
527
|
-
collection: parseCollection(retyped.documents, compositionOf(retyped)),
|
|
528
|
-
};
|
|
608
|
+
const confirmed = confirmedOf({ ...record, model, selectionModel }, prior?.confirmed);
|
|
529
609
|
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
530
610
|
const existing = rooms.get(record.locator);
|
|
531
611
|
if (existing === undefined)
|
|
@@ -534,7 +614,6 @@ export function composeRuntime(options) {
|
|
|
534
614
|
rooms.set(record.locator, {
|
|
535
615
|
...existing,
|
|
536
616
|
roomVersion: record.roomVersion,
|
|
537
|
-
attachments: record.attachments,
|
|
538
617
|
operations: record.operations,
|
|
539
618
|
replica: record.replica,
|
|
540
619
|
model: modelTypes(model),
|
|
@@ -546,10 +625,6 @@ export function composeRuntime(options) {
|
|
|
546
625
|
},
|
|
547
626
|
});
|
|
548
627
|
}
|
|
549
|
-
if (read.kind === 'refused') {
|
|
550
|
-
refusedSource(record, read);
|
|
551
|
-
declarationRefusals.add(record.locator);
|
|
552
|
-
}
|
|
553
628
|
}
|
|
554
629
|
return {
|
|
555
630
|
lastCompleted,
|
|
@@ -562,6 +637,8 @@ export function composeRuntime(options) {
|
|
|
562
637
|
applyChangeSet(read);
|
|
563
638
|
if (replicas.size > 0)
|
|
564
639
|
emitRooms();
|
|
640
|
+
if (member !== null)
|
|
641
|
+
emitMember();
|
|
565
642
|
for (const locator of locators)
|
|
566
643
|
emitQueue(locator);
|
|
567
644
|
publishRoomDrafts(false);
|
|
@@ -609,10 +686,15 @@ export function composeRuntime(options) {
|
|
|
609
686
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
610
687
|
}
|
|
611
688
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
612
|
-
const
|
|
689
|
+
const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
690
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
|
|
613
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
|
+
}));
|
|
614
696
|
const returning = [
|
|
615
|
-
...
|
|
697
|
+
...[...unqueued].reverse(),
|
|
616
698
|
...(settling.leaving === undefined ? [] : [settling.leaving]),
|
|
617
699
|
];
|
|
618
700
|
const written = new Map();
|
|
@@ -630,25 +712,44 @@ export function composeRuntime(options) {
|
|
|
630
712
|
});
|
|
631
713
|
}
|
|
632
714
|
}
|
|
633
|
-
await local.
|
|
634
|
-
|
|
635
|
-
previous: before?.confirmed.record ?? null,
|
|
636
|
-
queue: kept,
|
|
637
|
-
previousQueue: before?.queue ?? [],
|
|
638
|
-
roomDrafts: [...written.values()],
|
|
639
|
-
});
|
|
640
|
-
for (const batch of before?.queue ?? []) {
|
|
715
|
+
await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
|
|
716
|
+
for (const batch of previousQueue) {
|
|
641
717
|
if (!kept.includes(batch))
|
|
642
718
|
pendings.delete(batch.id);
|
|
643
719
|
}
|
|
644
720
|
for (const record of written.values())
|
|
645
721
|
rememberRoomDraft(record);
|
|
646
|
-
|
|
647
|
-
|
|
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
|
+
}
|
|
648
749
|
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
649
750
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
650
751
|
room: settling.room,
|
|
651
|
-
queue: kept.length !==
|
|
752
|
+
queue: kept.length !== previousQueue.length,
|
|
652
753
|
drafts: [...written.keys()],
|
|
653
754
|
});
|
|
654
755
|
}
|
|
@@ -683,7 +784,8 @@ export function composeRuntime(options) {
|
|
|
683
784
|
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
684
785
|
};
|
|
685
786
|
}
|
|
686
|
-
function publish(local, room, confirmed,
|
|
787
|
+
function publish(local, room, confirmed, landing = {}) {
|
|
788
|
+
const { base, stored } = landing;
|
|
687
789
|
return exclusive(async () => {
|
|
688
790
|
if (!continuing())
|
|
689
791
|
return true;
|
|
@@ -694,13 +796,15 @@ export function composeRuntime(options) {
|
|
|
694
796
|
return false;
|
|
695
797
|
}
|
|
696
798
|
adoptModels(room.locator, confirmed.record);
|
|
697
|
-
const queue = prior?.queue ?? [];
|
|
799
|
+
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
698
800
|
const roomVersion = confirmed.record.roomVersion;
|
|
699
801
|
const accepted = queue.filter((batch) => {
|
|
700
802
|
const accepted = pendings.get(batch.id)?.accepted;
|
|
701
803
|
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
702
804
|
});
|
|
703
|
-
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);
|
|
704
808
|
for (const batch of accepted)
|
|
705
809
|
pendings.delete(batch.id);
|
|
706
810
|
stale.delete(room.locator);
|
|
@@ -719,51 +823,127 @@ export function composeRuntime(options) {
|
|
|
719
823
|
const replica = replicas.get(locator);
|
|
720
824
|
const room = rooms.get(locator);
|
|
721
825
|
if (replica === undefined || room === undefined) {
|
|
722
|
-
throw
|
|
826
|
+
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
723
827
|
}
|
|
724
828
|
const saved = [];
|
|
725
829
|
for (const { path, revision } of named) {
|
|
726
830
|
const record = roomDrafts.get(locator)?.get(path);
|
|
727
831
|
if (record === undefined || record.revision !== revision) {
|
|
728
|
-
return { outcome: 'refused',
|
|
832
|
+
return { outcome: 'refused', code: 'stale', retryable: false, file: path };
|
|
729
833
|
}
|
|
730
834
|
if (!saved.some((draft) => draft.path === path))
|
|
731
835
|
saved.push({ path, parts: record.parts });
|
|
732
836
|
}
|
|
733
837
|
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
734
838
|
if (changed.length > 0)
|
|
735
|
-
return { outcome: 'refused',
|
|
839
|
+
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
736
840
|
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
737
|
-
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,
|
|
738
860
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
739
861
|
composition: compositionFor(replica.confirmed.record),
|
|
740
862
|
operations: room.operations,
|
|
741
863
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
742
864
|
providerLimits: limits,
|
|
865
|
+
handle: room.handle,
|
|
743
866
|
visible: replica.visible,
|
|
744
|
-
changes
|
|
867
|
+
changes,
|
|
745
868
|
bases: parts.flatMap(({ bases }) => bases),
|
|
746
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 }];
|
|
747
877
|
});
|
|
748
|
-
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')
|
|
749
896
|
return { outcome: 'refused', ...built.refusal };
|
|
750
|
-
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
|
+
}
|
|
751
928
|
const consumed = saved.map(({ path }) => path);
|
|
752
929
|
try {
|
|
753
|
-
await local.
|
|
754
|
-
|
|
755
|
-
previous: replica.confirmed.record,
|
|
930
|
+
await local.keepAuthored({
|
|
931
|
+
locator,
|
|
756
932
|
queue: batches,
|
|
757
|
-
|
|
758
|
-
saved,
|
|
933
|
+
saved: drafted,
|
|
759
934
|
consumedRoomDrafts: consumed,
|
|
760
935
|
});
|
|
761
936
|
}
|
|
762
937
|
catch {
|
|
763
|
-
return { outcome: 'refused',
|
|
938
|
+
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
764
939
|
}
|
|
765
|
-
|
|
766
|
-
|
|
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 });
|
|
944
|
+
}
|
|
945
|
+
if (!localSaves)
|
|
946
|
+
recorded = false;
|
|
767
947
|
for (const path of consumed)
|
|
768
948
|
forgetRoomDraft(locator, path);
|
|
769
949
|
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
@@ -771,9 +951,12 @@ export function composeRuntime(options) {
|
|
|
771
951
|
queue: true,
|
|
772
952
|
drafts: consumed,
|
|
773
953
|
}));
|
|
774
|
-
moveTo({ ...saving(status), ...attention() });
|
|
954
|
+
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
775
955
|
submitLater(locator);
|
|
776
|
-
|
|
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 };
|
|
777
960
|
}
|
|
778
961
|
async function mark(local, locator, identifier, submission, lost) {
|
|
779
962
|
const state = pendings.get(identifier);
|
|
@@ -799,16 +982,15 @@ export function composeRuntime(options) {
|
|
|
799
982
|
}
|
|
800
983
|
async function readHistory(locator, path, query = {}) {
|
|
801
984
|
if (closed || refusal !== null)
|
|
802
|
-
return
|
|
985
|
+
return UNCERTAIN;
|
|
803
986
|
try {
|
|
804
987
|
await ensureAuthorization();
|
|
805
988
|
}
|
|
806
989
|
catch {
|
|
807
|
-
return
|
|
808
|
-
}
|
|
809
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
810
|
-
return { kind: 'transport-uncertainty' };
|
|
990
|
+
return UNCERTAIN;
|
|
811
991
|
}
|
|
992
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
993
|
+
return UNCERTAIN;
|
|
812
994
|
const transfer = ['transfer', 'history', locator, path];
|
|
813
995
|
if (query.revision !== undefined)
|
|
814
996
|
transfer.push(query.revision);
|
|
@@ -830,13 +1012,97 @@ export function composeRuntime(options) {
|
|
|
830
1012
|
}
|
|
831
1013
|
if (result.kind === 'revoked-authorization')
|
|
832
1014
|
await refuse('revoked-authorization');
|
|
833
|
-
return result;
|
|
1015
|
+
return historyAnswer(result);
|
|
834
1016
|
}
|
|
835
1017
|
catch {
|
|
836
1018
|
journal.record('outcome history transport-uncertainty');
|
|
837
|
-
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);
|
|
1055
|
+
}
|
|
1056
|
+
catch {
|
|
1057
|
+
journal.record('outcome binary transport-uncertainty');
|
|
1058
|
+
return UNCERTAIN;
|
|
838
1059
|
}
|
|
839
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
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
async function readBinaryReference(reference) {
|
|
1099
|
+
await ready;
|
|
1100
|
+
return readBinary({
|
|
1101
|
+
kind: 'reference',
|
|
1102
|
+
fileRef: reference.fileRef,
|
|
1103
|
+
referrer: reference.referrer,
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
840
1106
|
function declaredAgents() {
|
|
841
1107
|
for (const capability of capabilities) {
|
|
842
1108
|
if (capability.conversationAgents !== undefined)
|
|
@@ -856,18 +1122,17 @@ export function composeRuntime(options) {
|
|
|
856
1122
|
async function invokeAction(name, input) {
|
|
857
1123
|
await ready;
|
|
858
1124
|
if (closed || refusal !== null)
|
|
859
|
-
return
|
|
1125
|
+
return UNCERTAIN;
|
|
860
1126
|
if (declaredAction(name) === undefined)
|
|
861
|
-
return
|
|
1127
|
+
return undeclared(name);
|
|
862
1128
|
try {
|
|
863
1129
|
await ensureAuthorization();
|
|
864
1130
|
}
|
|
865
1131
|
catch {
|
|
866
|
-
return
|
|
867
|
-
}
|
|
868
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
869
|
-
return { kind: 'transport-uncertainty' };
|
|
1132
|
+
return UNCERTAIN;
|
|
870
1133
|
}
|
|
1134
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1135
|
+
return UNCERTAIN;
|
|
871
1136
|
journal.record(`transfer action ${name}`);
|
|
872
1137
|
try {
|
|
873
1138
|
const result = await options.adapter.action(authorization, { name, input });
|
|
@@ -881,11 +1146,11 @@ export function composeRuntime(options) {
|
|
|
881
1146
|
journal.record(`outcome action ${result.kind}`);
|
|
882
1147
|
if (result.kind === 'rejected')
|
|
883
1148
|
journal.record(`refusal rejected ${result.code}`);
|
|
884
|
-
return result;
|
|
1149
|
+
return calledResult(result);
|
|
885
1150
|
}
|
|
886
1151
|
catch {
|
|
887
1152
|
journal.record('outcome action transport-uncertainty');
|
|
888
|
-
return
|
|
1153
|
+
return UNCERTAIN;
|
|
889
1154
|
}
|
|
890
1155
|
}
|
|
891
1156
|
function continuing() {
|
|
@@ -916,14 +1181,33 @@ export function composeRuntime(options) {
|
|
|
916
1181
|
throw error;
|
|
917
1182
|
}
|
|
918
1183
|
}
|
|
919
|
-
|
|
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) {
|
|
920
1204
|
if (!continuing())
|
|
921
1205
|
return unconfirmed;
|
|
922
1206
|
try {
|
|
923
1207
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
924
1208
|
collection: modelOf(room.locator),
|
|
925
1209
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
926
|
-
}, (models, held) => remodel(room.locator, models, held), continuing);
|
|
1210
|
+
}, (models, held) => remodel(room.locator, models, held), continuing, staging(local));
|
|
927
1211
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
928
1212
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
929
1213
|
return result;
|
|
@@ -940,9 +1224,9 @@ export function composeRuntime(options) {
|
|
|
940
1224
|
if (!continuing())
|
|
941
1225
|
return unconfirmed;
|
|
942
1226
|
if (refreshed.kind === 'still-current') {
|
|
943
|
-
if (sourceRefusals.has(room.locator))
|
|
944
|
-
return unconfirmed;
|
|
945
1227
|
stale.delete(room.locator);
|
|
1228
|
+
if (sourceRefusals.has(room.locator))
|
|
1229
|
+
return null;
|
|
946
1230
|
if (unreadable.delete(room.locator)) {
|
|
947
1231
|
scopedRefusals.delete(room.locator);
|
|
948
1232
|
list(room, true);
|
|
@@ -951,7 +1235,7 @@ export function composeRuntime(options) {
|
|
|
951
1235
|
return null;
|
|
952
1236
|
}
|
|
953
1237
|
if (refreshed.kind === 'advanced') {
|
|
954
|
-
if (!(await publish(local, room, refreshed.confirmed, held.confirmed))) {
|
|
1238
|
+
if (!(await publish(local, room, refreshed.confirmed, { base: held.confirmed }))) {
|
|
955
1239
|
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
956
1240
|
const reached = now !== undefined &&
|
|
957
1241
|
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
@@ -963,16 +1247,19 @@ export function composeRuntime(options) {
|
|
|
963
1247
|
if (refreshed.kind !== 'beyond-horizon')
|
|
964
1248
|
return refreshed;
|
|
965
1249
|
}
|
|
966
|
-
const bootstrapped = await pullBootstrap(authorized, room);
|
|
1250
|
+
const bootstrapped = await pullBootstrap(authorized, room, local);
|
|
967
1251
|
if (!continuing())
|
|
968
1252
|
return unconfirmed;
|
|
969
1253
|
if (bootstrapped.kind !== 'staged')
|
|
970
1254
|
return bootstrapped;
|
|
971
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1255
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
972
1256
|
return null;
|
|
973
1257
|
}
|
|
974
1258
|
async function withhold(withheld, room, local, progress) {
|
|
975
|
-
|
|
1259
|
+
if (withheld.kind === 'refused')
|
|
1260
|
+
stale.delete(room.locator);
|
|
1261
|
+
else
|
|
1262
|
+
stale.add(room.locator);
|
|
976
1263
|
if (withheld.kind === 'scoped-refusal') {
|
|
977
1264
|
await scoped(withheld, room, local);
|
|
978
1265
|
}
|
|
@@ -997,6 +1284,7 @@ export function composeRuntime(options) {
|
|
|
997
1284
|
function refusedSource(room, { refusal }) {
|
|
998
1285
|
sourceRefusals.set(room.locator, {
|
|
999
1286
|
code: refusal.rule,
|
|
1287
|
+
retryable: false,
|
|
1000
1288
|
fields: {
|
|
1001
1289
|
file: refusal.file,
|
|
1002
1290
|
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
@@ -1011,8 +1299,16 @@ export function composeRuntime(options) {
|
|
|
1011
1299
|
}
|
|
1012
1300
|
function leaving(batch, result) {
|
|
1013
1301
|
if (result.kind === 'expired')
|
|
1014
|
-
return { batch, returned:
|
|
1015
|
-
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
|
+
};
|
|
1016
1312
|
}
|
|
1017
1313
|
function logAccepted(locator, submitted, accepted) {
|
|
1018
1314
|
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
@@ -1030,7 +1326,7 @@ export function composeRuntime(options) {
|
|
|
1030
1326
|
logAccepted(room.locator, submitted, accepted);
|
|
1031
1327
|
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1032
1328
|
? { kind: 'staged', confirmed: current.confirmed }
|
|
1033
|
-
: applyReturned(current.confirmed,
|
|
1329
|
+
: applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1034
1330
|
? accepted.roomVersion
|
|
1035
1331
|
: held, accepted.roomVersion);
|
|
1036
1332
|
if (applied.kind !== 'staged') {
|
|
@@ -1176,7 +1472,8 @@ export function composeRuntime(options) {
|
|
|
1176
1472
|
return;
|
|
1177
1473
|
}
|
|
1178
1474
|
const set = await settle(local, room.locator, current, current.confirmed, current.queue.filter((batch) => batch.id !== head.id), {
|
|
1179
|
-
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))
|
|
1180
1477
|
? { ...left, returned: null }
|
|
1181
1478
|
: left,
|
|
1182
1479
|
});
|
|
@@ -1237,7 +1534,7 @@ export function composeRuntime(options) {
|
|
|
1237
1534
|
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1238
1535
|
};
|
|
1239
1536
|
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
1240
|
-
?
|
|
1537
|
+
? { kind: 'staged', confirmed: confirmedOf(declared, held.confirmed) }
|
|
1241
1538
|
: readReclassified(declared, held.confirmed);
|
|
1242
1539
|
const record = reclassified.kind === 'staged'
|
|
1243
1540
|
? declared
|
|
@@ -1245,12 +1542,10 @@ export function composeRuntime(options) {
|
|
|
1245
1542
|
...declared,
|
|
1246
1543
|
typedSelection: held.confirmed.record.typedSelection,
|
|
1247
1544
|
};
|
|
1248
|
-
const read = reclassified.kind === 'deferred'
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
queue: held.queue,
|
|
1253
|
-
});
|
|
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 });
|
|
1254
1549
|
if (read.kind === 'staged') {
|
|
1255
1550
|
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
|
|
1256
1551
|
if (declarationRefusals.delete(room.locator)) {
|
|
@@ -1277,7 +1572,11 @@ export function composeRuntime(options) {
|
|
|
1277
1572
|
});
|
|
1278
1573
|
}
|
|
1279
1574
|
async function scoped(result, room, local) {
|
|
1280
|
-
scopedRefusals.set(room.locator, {
|
|
1575
|
+
scopedRefusals.set(room.locator, {
|
|
1576
|
+
code: result.code,
|
|
1577
|
+
fields: result.fields,
|
|
1578
|
+
retryable: result.retryable,
|
|
1579
|
+
});
|
|
1281
1580
|
let scopedAway = room;
|
|
1282
1581
|
if (result.scope === 'model')
|
|
1283
1582
|
await retainModel(local, room, 'collection', {
|
|
@@ -1314,7 +1613,6 @@ export function composeRuntime(options) {
|
|
|
1314
1613
|
roomVersion: record.roomVersion,
|
|
1315
1614
|
documents: record.documents,
|
|
1316
1615
|
revisions: record.revisions,
|
|
1317
|
-
attachments: record.attachments,
|
|
1318
1616
|
},
|
|
1319
1617
|
];
|
|
1320
1618
|
});
|
|
@@ -1325,8 +1623,10 @@ export function composeRuntime(options) {
|
|
|
1325
1623
|
const outcome = prepared[index];
|
|
1326
1624
|
return outcome?.status === 'fulfilled' && outcome.value;
|
|
1327
1625
|
});
|
|
1328
|
-
if (attempt === 'opened')
|
|
1626
|
+
if (attempt === 'opened') {
|
|
1329
1627
|
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
1628
|
+
handed = true;
|
|
1629
|
+
}
|
|
1330
1630
|
const loaded = await Promise.allSettled(ready
|
|
1331
1631
|
.filter((room) => attempt === 'unstreamed' || !replicas.has(room.locator))
|
|
1332
1632
|
.map((room) => loadRoom(room, local, progress)));
|
|
@@ -1392,14 +1692,14 @@ export function composeRuntime(options) {
|
|
|
1392
1692
|
async function reload(room, local, progress) {
|
|
1393
1693
|
if (discoveryPaused || authorization === undefined)
|
|
1394
1694
|
return;
|
|
1395
|
-
const bootstrapped = await pullBootstrap(authorization, room);
|
|
1695
|
+
const bootstrapped = await pullBootstrap(authorization, room, local);
|
|
1396
1696
|
if (!continuing())
|
|
1397
1697
|
return;
|
|
1398
1698
|
if (bootstrapped.kind !== 'staged') {
|
|
1399
1699
|
await withhold(bootstrapped, room, local, progress);
|
|
1400
1700
|
return;
|
|
1401
1701
|
}
|
|
1402
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1702
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
1403
1703
|
await caughtUp(room, local, progress);
|
|
1404
1704
|
}
|
|
1405
1705
|
async function discoverRooms(local, progress, attempt) {
|
|
@@ -1429,15 +1729,16 @@ export function composeRuntime(options) {
|
|
|
1429
1729
|
}
|
|
1430
1730
|
progress.reachable = false;
|
|
1431
1731
|
progress.complete = false;
|
|
1432
|
-
if (attempt === 'opened') {
|
|
1433
|
-
options.adapter.roomStream?.hold(heldRooms([...discovered.values()]));
|
|
1434
|
-
}
|
|
1435
1732
|
notifyReady();
|
|
1436
1733
|
return;
|
|
1437
1734
|
}
|
|
1438
1735
|
if (result.kind === 'contract-unavailable') {
|
|
1439
1736
|
journal.record(`connection contract unavailable ${result.code}`);
|
|
1440
|
-
await refuse('missing-required-guarantee', {
|
|
1737
|
+
await refuse('missing-required-guarantee', {
|
|
1738
|
+
code: result.code,
|
|
1739
|
+
fields: result.fields,
|
|
1740
|
+
retryable: result.retryable,
|
|
1741
|
+
});
|
|
1441
1742
|
notifyReady();
|
|
1442
1743
|
return;
|
|
1443
1744
|
}
|
|
@@ -1478,6 +1779,12 @@ export function composeRuntime(options) {
|
|
|
1478
1779
|
limits = result.providerLimits;
|
|
1479
1780
|
await local.declare(limits, capabilities);
|
|
1480
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
|
+
}
|
|
1481
1788
|
await applyDiscovery(result.rooms, local);
|
|
1482
1789
|
await synchronize(result.rooms, local, progress, attempt);
|
|
1483
1790
|
}
|
|
@@ -1507,9 +1814,12 @@ export function composeRuntime(options) {
|
|
|
1507
1814
|
}
|
|
1508
1815
|
const progress = { complete: true, reachable: true };
|
|
1509
1816
|
underway = attempt === 'opened' ? progress : null;
|
|
1817
|
+
if (attempt === 'opened')
|
|
1818
|
+
streamed = progress;
|
|
1510
1819
|
complete = false;
|
|
1511
1820
|
recorded = false;
|
|
1512
1821
|
moveTo(transferring(status));
|
|
1822
|
+
handed = false;
|
|
1513
1823
|
try {
|
|
1514
1824
|
const local = await openLocal();
|
|
1515
1825
|
if (closed)
|
|
@@ -1521,16 +1831,22 @@ export function composeRuntime(options) {
|
|
|
1521
1831
|
}
|
|
1522
1832
|
catch {
|
|
1523
1833
|
progress.complete = false;
|
|
1524
|
-
if (attempt === 'opened')
|
|
1525
|
-
|
|
1526
|
-
}
|
|
1834
|
+
if (attempt === 'opened' && !handed)
|
|
1835
|
+
progress.reachable = false;
|
|
1527
1836
|
}
|
|
1528
1837
|
finally {
|
|
1529
1838
|
notifyReady();
|
|
1530
1839
|
}
|
|
1531
|
-
if (attempt
|
|
1840
|
+
if (attempt !== 'opened') {
|
|
1841
|
+
await finishOpening(progress);
|
|
1532
1842
|
return;
|
|
1533
|
-
|
|
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();
|
|
1534
1850
|
});
|
|
1535
1851
|
}
|
|
1536
1852
|
function closedSocket(revoked) {
|
|
@@ -1540,9 +1856,14 @@ export function composeRuntime(options) {
|
|
|
1540
1856
|
void refuse('revoked-authorization');
|
|
1541
1857
|
return;
|
|
1542
1858
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
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
|
+
});
|
|
1546
1867
|
}
|
|
1547
1868
|
function applyOpening(expired) {
|
|
1548
1869
|
return task(async () => {
|
|
@@ -1597,6 +1918,8 @@ export function composeRuntime(options) {
|
|
|
1597
1918
|
}
|
|
1598
1919
|
}
|
|
1599
1920
|
function submitLater(locator) {
|
|
1921
|
+
if (localSaves)
|
|
1922
|
+
return;
|
|
1600
1923
|
const running = submitting.get(locator);
|
|
1601
1924
|
if (running !== undefined) {
|
|
1602
1925
|
running.again = true;
|
|
@@ -1676,7 +1999,7 @@ export function composeRuntime(options) {
|
|
|
1676
1999
|
}
|
|
1677
2000
|
}
|
|
1678
2001
|
moveTo({
|
|
1679
|
-
state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
|
|
2002
|
+
state: pending() ? 'pending' : synced && complete ? 'synced' : 'syncing',
|
|
1680
2003
|
lastCompleted,
|
|
1681
2004
|
providerUnreachable: !progress.reachable,
|
|
1682
2005
|
...attention(),
|
|
@@ -1785,20 +2108,15 @@ export function composeRuntime(options) {
|
|
|
1785
2108
|
show();
|
|
1786
2109
|
return;
|
|
1787
2110
|
}
|
|
1788
|
-
const
|
|
1789
|
-
|
|
1790
|
-
moveTo(transferring(resting));
|
|
2111
|
+
const progress = { complete: false, reachable: !status.providerUnreachable };
|
|
2112
|
+
moveTo(transferring(status));
|
|
1791
2113
|
try {
|
|
1792
2114
|
await conversations.catchUp(progress);
|
|
1793
2115
|
}
|
|
1794
2116
|
catch {
|
|
1795
2117
|
progress.reachable = false;
|
|
1796
2118
|
}
|
|
1797
|
-
|
|
1798
|
-
if (!closed) {
|
|
1799
|
-
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
2119
|
+
await rest(progress);
|
|
1802
2120
|
}
|
|
1803
2121
|
function close() {
|
|
1804
2122
|
if (closed)
|
|
@@ -1820,6 +2138,7 @@ export function composeRuntime(options) {
|
|
|
1820
2138
|
storageLossListeners.clear();
|
|
1821
2139
|
statusListeners.clear();
|
|
1822
2140
|
serviceModeListeners.clear();
|
|
2141
|
+
memberListeners.clear();
|
|
1823
2142
|
readSide.close();
|
|
1824
2143
|
queueSubscriptions.clear();
|
|
1825
2144
|
roomDraftListeners.clear();
|
|
@@ -1851,13 +2170,16 @@ export function composeRuntime(options) {
|
|
|
1851
2170
|
return planOf(input, roomFacts(reach), planReach(reach));
|
|
1852
2171
|
}
|
|
1853
2172
|
function watchPlan(plan, deliver) {
|
|
2173
|
+
materialize(reachedRooms(plan));
|
|
1854
2174
|
return readSide.watch(plan, identityOf(plan), deliver);
|
|
1855
2175
|
}
|
|
1856
2176
|
async function searched(input, reach) {
|
|
1857
2177
|
const plan = searchPlanOf(input);
|
|
1858
2178
|
const selected = plan.documents === undefined && reach === null
|
|
1859
2179
|
? null
|
|
1860
|
-
: (await
|
|
2180
|
+
: (await rowsQuery(filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)))).documents;
|
|
2181
|
+
if (selected === null)
|
|
2182
|
+
materialize(null);
|
|
1861
2183
|
return searchIndex.search(reach === null || reach.conversations ? plan : { ...plan, conversations: [] }, selected);
|
|
1862
2184
|
}
|
|
1863
2185
|
const watch = (topic, input, deliver) => {
|
|
@@ -1883,6 +2205,8 @@ export function composeRuntime(options) {
|
|
|
1883
2205
|
}
|
|
1884
2206
|
if (topic === 'conversations')
|
|
1885
2207
|
return conversations.watchList(deliver);
|
|
2208
|
+
if (topic === 'agents')
|
|
2209
|
+
return conversations.watchAgents(deliver);
|
|
1886
2210
|
if (topic === 'presentation') {
|
|
1887
2211
|
const listener = {
|
|
1888
2212
|
app: input[0],
|
|
@@ -1937,6 +2261,13 @@ export function composeRuntime(options) {
|
|
|
1937
2261
|
serviceModeListeners.delete(deliver);
|
|
1938
2262
|
};
|
|
1939
2263
|
}
|
|
2264
|
+
if (topic === 'member') {
|
|
2265
|
+
memberListeners.add(deliver);
|
|
2266
|
+
deliver(member);
|
|
2267
|
+
return () => {
|
|
2268
|
+
memberListeners.delete(deliver);
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
1940
2271
|
if (topic === 'queue') {
|
|
1941
2272
|
const queue = { locator: input[0], notify: deliver };
|
|
1942
2273
|
queueSubscriptions.add(queue);
|
|
@@ -1954,7 +2285,7 @@ export function composeRuntime(options) {
|
|
|
1954
2285
|
rooms() {
|
|
1955
2286
|
return listed();
|
|
1956
2287
|
},
|
|
1957
|
-
query: ((input) =>
|
|
2288
|
+
query: ((input) => rowsQuery(planned(input, null))),
|
|
1958
2289
|
search: (input) => searched(input, null),
|
|
1959
2290
|
types(locator) {
|
|
1960
2291
|
return {
|
|
@@ -1983,20 +2314,20 @@ export function composeRuntime(options) {
|
|
|
1983
2314
|
return exclusive(async () => {
|
|
1984
2315
|
const parts = readParts(input);
|
|
1985
2316
|
if (path === '' || normalize(path) !== path)
|
|
1986
|
-
throw
|
|
2317
|
+
throw runtimeError('room-draft-malformed');
|
|
1987
2318
|
const local = await openLocal();
|
|
1988
2319
|
const current = roomDrafts.get(room)?.get(path);
|
|
1989
2320
|
if ((current?.revision ?? null) !== revision) {
|
|
1990
|
-
return
|
|
1991
|
-
outcome: 'stale',
|
|
1992
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
1993
|
-
};
|
|
2321
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
1994
2322
|
}
|
|
2323
|
+
const file = parts.file === undefined
|
|
2324
|
+
? undefined
|
|
2325
|
+
: await heldFile(parts.file.blob, binaryBase(replicas.get(room)?.visible, path));
|
|
1995
2326
|
const record = {
|
|
1996
2327
|
locator: room,
|
|
1997
2328
|
path,
|
|
1998
2329
|
revision: roomDraftRevision(),
|
|
1999
|
-
parts: held(current?.parts, parts),
|
|
2330
|
+
parts: held(current?.parts, parts, file),
|
|
2000
2331
|
returned: current?.returned ?? null,
|
|
2001
2332
|
};
|
|
2002
2333
|
await local.keepRoomDraft(record);
|
|
@@ -2010,10 +2341,7 @@ export function composeRuntime(options) {
|
|
|
2010
2341
|
const local = await openLocal();
|
|
2011
2342
|
const current = roomDrafts.get(room)?.get(path);
|
|
2012
2343
|
if (current === undefined || current.revision !== revision) {
|
|
2013
|
-
return
|
|
2014
|
-
outcome: 'stale',
|
|
2015
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2016
|
-
};
|
|
2344
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2017
2345
|
}
|
|
2018
2346
|
await local.discardRoomDraft(room, path);
|
|
2019
2347
|
forgetRoomDraft(room, path);
|
|
@@ -2048,6 +2376,10 @@ export function composeRuntime(options) {
|
|
|
2048
2376
|
history(locator, path, query) {
|
|
2049
2377
|
return readHistory(locator, path, query);
|
|
2050
2378
|
},
|
|
2379
|
+
binaryFile: {
|
|
2380
|
+
read: readBinaryPath,
|
|
2381
|
+
readReference: readBinaryReference,
|
|
2382
|
+
},
|
|
2051
2383
|
call(name, input) {
|
|
2052
2384
|
return invokeAction(name, input);
|
|
2053
2385
|
},
|
|
@@ -2080,8 +2412,9 @@ export function composeRuntime(options) {
|
|
|
2080
2412
|
const reachable = {
|
|
2081
2413
|
md,
|
|
2082
2414
|
watch,
|
|
2415
|
+
referrerScope: (referrer) => options.adapter.referrerScope(referrer),
|
|
2083
2416
|
plan: planned,
|
|
2084
|
-
query: (plan) =>
|
|
2417
|
+
query: (plan) => rowsQuery(plan),
|
|
2085
2418
|
watchPlan,
|
|
2086
2419
|
search: searched,
|
|
2087
2420
|
};
|