@agentshouse/mdruntime 0.1.0-c5b50992a4193 → 0.1.0-c698c0ebd65bd
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 +151 -96
- package/README.md +13 -3
- package/dist/adapter.d.ts +47 -14
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -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 +92 -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 +33 -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 +26 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +62 -0
- package/dist/chat/markdown.d.ts +20 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +188 -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 +620 -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 +15 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +66 -16
- 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 +22 -9
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +566 -175
- package/dist/conversation.d.ts +15 -5
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +189 -64
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +156 -0
- 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/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/host.d.ts +3 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +16 -12
- 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 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +62 -20
- package/dist/model.d.ts +3 -2
- package/dist/model.d.ts.map +1 -1
- 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 +32 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +42 -1
- package/dist/overlay.d.ts +14 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +75 -37
- package/dist/query.d.ts +4 -0
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -0
- package/dist/reach.d.ts +2 -0
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +44 -20
- package/dist/react.d.ts +25 -16
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +8 -6
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +9 -2
- 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 +7 -4
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +74 -54
- 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 +5 -1
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +10 -2
- package/dist/save.d.ts +26 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +345 -37
- 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 +48 -14
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +366 -170
- package/dist/wire.d.ts +7 -3
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +15 -2
- package/package.json +26 -14
- 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,18 +3,24 @@ 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, failure, 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';
|
|
23
|
+
import { FencedMap } from './fence.js';
|
|
18
24
|
import { roomDraftRevision } from './identity.js';
|
|
19
25
|
import { beneath, normalize } from './paths.js';
|
|
20
26
|
import { openRuntimeStore, } from './store.js';
|
|
@@ -34,19 +40,29 @@ function declaredSelection(room) {
|
|
|
34
40
|
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
35
41
|
}
|
|
36
42
|
export function composeRuntime(options) {
|
|
43
|
+
const localSaves = options.adapter.localSaves === true;
|
|
37
44
|
let authorization = options.authorization;
|
|
38
45
|
let opening;
|
|
39
46
|
let store;
|
|
40
|
-
const
|
|
47
|
+
const fences = new Map();
|
|
48
|
+
const isFenced = (locator) => fences.has(locator);
|
|
49
|
+
let fencings = 0;
|
|
50
|
+
const lastFenced = new Map();
|
|
51
|
+
const fencedSince = (locator, since) => fences.has(locator) || (lastFenced.get(locator) ?? 0) > since;
|
|
52
|
+
let discoveries = 0;
|
|
53
|
+
const replicas = new FencedMap(isFenced);
|
|
54
|
+
const queuesWithoutReplica = new FencedMap(isFenced);
|
|
41
55
|
const pendings = new Map();
|
|
42
|
-
const roomDrafts = new
|
|
56
|
+
const roomDrafts = new FencedMap(isFenced);
|
|
43
57
|
const presentations = new Map();
|
|
44
58
|
let limits = [];
|
|
45
59
|
let capabilities = [];
|
|
46
|
-
const rooms = new
|
|
60
|
+
const rooms = new FencedMap(isFenced);
|
|
61
|
+
const counted = new Map();
|
|
47
62
|
const unreadable = new Set();
|
|
48
|
-
const
|
|
49
|
-
const
|
|
63
|
+
const isModelFenced = (key) => isFenced(key.slice(key.indexOf(' ') + 1));
|
|
64
|
+
const models = new FencedMap(isModelFenced);
|
|
65
|
+
const liveModels = new FencedMap(isModelFenced);
|
|
50
66
|
const invalidModels = new Set();
|
|
51
67
|
const discovered = new Map();
|
|
52
68
|
let roomsListed = false;
|
|
@@ -82,6 +98,7 @@ export function composeRuntime(options) {
|
|
|
82
98
|
let withdrawStream;
|
|
83
99
|
const announced = new Map();
|
|
84
100
|
let underway = null;
|
|
101
|
+
let streamed = null;
|
|
85
102
|
let handed = false;
|
|
86
103
|
const submitting = new Map();
|
|
87
104
|
const answering = new Map();
|
|
@@ -105,13 +122,19 @@ export function composeRuntime(options) {
|
|
|
105
122
|
let haltedRefusal = false;
|
|
106
123
|
let openSignals = 0;
|
|
107
124
|
const serviceModeListeners = new Set();
|
|
125
|
+
let member = null;
|
|
126
|
+
const memberListeners = new Set();
|
|
127
|
+
const materialized = new Set();
|
|
128
|
+
let everyRoom = false;
|
|
129
|
+
const transfers = new FencedMap(isFenced);
|
|
108
130
|
const conversations = createConversationStore({
|
|
109
131
|
agents: declaredAgents,
|
|
110
|
-
documents: (locator) =>
|
|
132
|
+
documents: (locator) => rowsQuery(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
111
133
|
call: (name, input) => invokeAction(name, input),
|
|
112
134
|
durably,
|
|
113
135
|
stream: () => options.adapter.conversations,
|
|
114
136
|
publish: publishChangeSet,
|
|
137
|
+
online: () => !status.providerUnreachable && refusal === null,
|
|
115
138
|
closed: () => closed,
|
|
116
139
|
});
|
|
117
140
|
const searchIndex = createSearchIndex({
|
|
@@ -183,7 +206,9 @@ export function composeRuntime(options) {
|
|
|
183
206
|
}
|
|
184
207
|
}
|
|
185
208
|
function pending() {
|
|
186
|
-
return
|
|
209
|
+
return (!localSaves &&
|
|
210
|
+
(queuesWithoutReplica.size > 0 ||
|
|
211
|
+
[...replicas.values()].some((replica) => replica.queue.length > 0)));
|
|
187
212
|
}
|
|
188
213
|
function attention() {
|
|
189
214
|
const attentionRequired = [];
|
|
@@ -212,8 +237,23 @@ export function composeRuntime(options) {
|
|
|
212
237
|
}
|
|
213
238
|
return { attentionRequired, reset };
|
|
214
239
|
}
|
|
240
|
+
function documentsIn(locator) {
|
|
241
|
+
const replica = replicas.get(locator);
|
|
242
|
+
if (replica === undefined)
|
|
243
|
+
return 0;
|
|
244
|
+
const roots = replica.confirmed.record.typedSelection?.roots ?? [];
|
|
245
|
+
const paths = [
|
|
246
|
+
...Object.keys(replica.visible.documents).filter(markdownPath),
|
|
247
|
+
...Object.keys(replica.visible.binaries),
|
|
248
|
+
];
|
|
249
|
+
return paths.filter((path) => !roots.some((root) => beneath(root, path))).length;
|
|
250
|
+
}
|
|
215
251
|
function listed() {
|
|
216
|
-
return structuredClone([...rooms.values()])
|
|
252
|
+
return structuredClone([...rooms.values()].map((room) => {
|
|
253
|
+
const documents = documentsIn(room.locator);
|
|
254
|
+
counted.set(room.locator, documents);
|
|
255
|
+
return { ...room, documents };
|
|
256
|
+
}));
|
|
217
257
|
}
|
|
218
258
|
function roomFacts(reach = null) {
|
|
219
259
|
const held = [...rooms.values()]
|
|
@@ -245,6 +285,10 @@ export function composeRuntime(options) {
|
|
|
245
285
|
for (const listener of storageLossListeners)
|
|
246
286
|
listener(storageLoss);
|
|
247
287
|
}
|
|
288
|
+
function emitMember() {
|
|
289
|
+
for (const listener of memberListeners)
|
|
290
|
+
listener(member);
|
|
291
|
+
}
|
|
248
292
|
function moveTo(next) {
|
|
249
293
|
if (closed)
|
|
250
294
|
return;
|
|
@@ -264,6 +308,8 @@ export function composeRuntime(options) {
|
|
|
264
308
|
listener(shown);
|
|
265
309
|
}
|
|
266
310
|
function entryOf(batch) {
|
|
311
|
+
if (localSaves)
|
|
312
|
+
return Object.freeze({ batch, state: 'local' });
|
|
267
313
|
const state = pendings.get(batch.id);
|
|
268
314
|
if (state?.accepted !== undefined) {
|
|
269
315
|
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
@@ -271,10 +317,7 @@ export function composeRuntime(options) {
|
|
|
271
317
|
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
272
318
|
}
|
|
273
319
|
function entriesOf(locator) {
|
|
274
|
-
|
|
275
|
-
if (replica === undefined)
|
|
276
|
-
return [];
|
|
277
|
-
return replica.queue.map(entryOf);
|
|
320
|
+
return (replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []).map(entryOf);
|
|
278
321
|
}
|
|
279
322
|
function emitQueue(locator) {
|
|
280
323
|
const entries = entriesOf(locator);
|
|
@@ -284,12 +327,47 @@ export function composeRuntime(options) {
|
|
|
284
327
|
}
|
|
285
328
|
}
|
|
286
329
|
function applyChangeSet(set) {
|
|
330
|
+
const held = everyRoom
|
|
331
|
+
? set
|
|
332
|
+
: { ...set, documents: set.documents.filter(({ locator }) => materialized.has(locator)) };
|
|
333
|
+
readSide.apply(held);
|
|
334
|
+
searchIndex.changed(held);
|
|
335
|
+
}
|
|
336
|
+
function materialize(locators) {
|
|
337
|
+
if (everyRoom)
|
|
338
|
+
return;
|
|
339
|
+
if (locators === null)
|
|
340
|
+
everyRoom = true;
|
|
341
|
+
const sets = [];
|
|
342
|
+
for (const locator of locators ?? [...replicas.keys()]) {
|
|
343
|
+
if (materialized.has(locator))
|
|
344
|
+
continue;
|
|
345
|
+
materialized.add(locator);
|
|
346
|
+
const replica = replicas.get(locator);
|
|
347
|
+
if (replica !== undefined)
|
|
348
|
+
sets.push(changeSet(locator, null, replica));
|
|
349
|
+
}
|
|
350
|
+
if (sets.length === 0)
|
|
351
|
+
return;
|
|
352
|
+
const set = mergeChangeSets(sets);
|
|
287
353
|
readSide.apply(set);
|
|
288
354
|
searchIndex.changed(set);
|
|
289
355
|
}
|
|
356
|
+
function reachedRooms(plan) {
|
|
357
|
+
if (plan.rooms !== null)
|
|
358
|
+
return plan.rooms;
|
|
359
|
+
if (plan.reach === null)
|
|
360
|
+
return null;
|
|
361
|
+
return [...plan.reach.rooms, ...plan.reach.documents.map(roomOfKey)];
|
|
362
|
+
}
|
|
363
|
+
function rowsQuery(plan) {
|
|
364
|
+
materialize(reachedRooms(plan));
|
|
365
|
+
return readSide.query(plan);
|
|
366
|
+
}
|
|
290
367
|
function publishChangeSet(set) {
|
|
291
368
|
applyChangeSet(set);
|
|
292
|
-
if (set.rooms.length > 0
|
|
369
|
+
if (set.rooms.length > 0 ||
|
|
370
|
+
[...new Set(set.documents.map(({ locator }) => locator))].some((locator) => rooms.has(locator) && counted.get(locator) !== documentsIn(locator)))
|
|
293
371
|
emitRooms();
|
|
294
372
|
for (const locator of set.queues)
|
|
295
373
|
emitQueue(locator);
|
|
@@ -336,10 +414,7 @@ export function composeRuntime(options) {
|
|
|
336
414
|
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
337
415
|
try {
|
|
338
416
|
const local = await openLocal();
|
|
339
|
-
await local.revokeModels([...replicas.values()].map((replica) =>
|
|
340
|
-
record: replica.confirmed.record,
|
|
341
|
-
queue: replica.queue,
|
|
342
|
-
})));
|
|
417
|
+
await local.revokeModels([...replicas.values()].map((replica) => replica.confirmed.record));
|
|
343
418
|
}
|
|
344
419
|
catch {
|
|
345
420
|
journal.record('model revocation persistence failed');
|
|
@@ -363,10 +438,7 @@ export function composeRuntime(options) {
|
|
|
363
438
|
model: revoked,
|
|
364
439
|
selectionModel: revoked,
|
|
365
440
|
};
|
|
366
|
-
|
|
367
|
-
replicas.set(locator, read.kind === 'staged'
|
|
368
|
-
? replicaOf(read.confirmed, before.queue, before)
|
|
369
|
-
: { ...before, confirmed: { ...before.confirmed, record } });
|
|
441
|
+
replicas.set(locator, replicaOf(confirmedOf(record, before.confirmed), before.queue, before));
|
|
370
442
|
}
|
|
371
443
|
rooms.set(locator, {
|
|
372
444
|
...room,
|
|
@@ -383,7 +455,8 @@ export function composeRuntime(options) {
|
|
|
383
455
|
}
|
|
384
456
|
finally {
|
|
385
457
|
refusal = {
|
|
386
|
-
reason,
|
|
458
|
+
code: reason,
|
|
459
|
+
retryable: false,
|
|
387
460
|
reauthorizationOffered: reason === 'revoked-authorization',
|
|
388
461
|
refusal: provider,
|
|
389
462
|
};
|
|
@@ -408,19 +481,28 @@ export function composeRuntime(options) {
|
|
|
408
481
|
},
|
|
409
482
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
410
483
|
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
411
|
-
attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
|
|
412
484
|
synchronizationAvailable,
|
|
413
485
|
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
414
486
|
});
|
|
415
487
|
}
|
|
416
|
-
async function applyDiscovery(
|
|
488
|
+
async function applyDiscovery(found, started) {
|
|
489
|
+
for (const room of found) {
|
|
490
|
+
const fence = fences.get(room.locator);
|
|
491
|
+
if (fence !== undefined && fence.erased && fence.at < started)
|
|
492
|
+
fences.delete(room.locator);
|
|
493
|
+
}
|
|
494
|
+
const listed = found.filter((room) => !fences.has(room.locator));
|
|
417
495
|
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
418
496
|
for (const locator of listedLocators)
|
|
419
497
|
scopedRefusals.delete(locator);
|
|
420
498
|
discovered.clear();
|
|
421
499
|
for (const room of listed)
|
|
422
500
|
discovered.set(room.locator, room);
|
|
423
|
-
|
|
501
|
+
const unlisted = unlistedHeld(new Set(found.map((room) => room.locator)));
|
|
502
|
+
if (options.adapter.exhaustiveDiscovery === true)
|
|
503
|
+
await loseRooms(unlisted);
|
|
504
|
+
else
|
|
505
|
+
unlistedKept(unlisted);
|
|
424
506
|
for (const room of listed) {
|
|
425
507
|
const held = replicas.get(room.locator)?.confirmed.record;
|
|
426
508
|
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
@@ -431,43 +513,79 @@ export function composeRuntime(options) {
|
|
|
431
513
|
}
|
|
432
514
|
roomsListed = true;
|
|
433
515
|
emitRooms();
|
|
516
|
+
return listed;
|
|
434
517
|
}
|
|
435
518
|
function unlistedHeld(listed) {
|
|
436
|
-
return [
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
pendings.delete(batch.id);
|
|
452
|
-
replicas.delete(locator);
|
|
519
|
+
return [
|
|
520
|
+
...new Set([
|
|
521
|
+
...rooms.keys(),
|
|
522
|
+
...roomDrafts.keys(),
|
|
523
|
+
...transfers.keys(),
|
|
524
|
+
...queuesWithoutReplica.keys(),
|
|
525
|
+
]),
|
|
526
|
+
].filter((locator) => !listed.has(locator));
|
|
527
|
+
}
|
|
528
|
+
function unlistedKept(unlisted) {
|
|
529
|
+
for (const locator of unlisted) {
|
|
530
|
+
const held = replicas.get(locator)?.confirmed.record;
|
|
531
|
+
if (held !== undefined)
|
|
532
|
+
list(held, false);
|
|
533
|
+
else
|
|
453
534
|
rooms.delete(locator);
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function fence(locators) {
|
|
538
|
+
const fresh = [...new Set(locators)].filter((locator) => !fences.has(locator));
|
|
539
|
+
const removed = fresh.map((locator) => changeSet(locator, replicas.get(locator) ?? null, null, {
|
|
540
|
+
room: true,
|
|
541
|
+
queue: true,
|
|
542
|
+
drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
|
|
543
|
+
}));
|
|
544
|
+
for (const locator of fresh) {
|
|
545
|
+
fences.set(locator, { at: discoveries, erased: false });
|
|
546
|
+
lastFenced.set(locator, ++fencings);
|
|
547
|
+
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
548
|
+
pendings.delete(batch.id);
|
|
549
|
+
replicas.delete(locator);
|
|
550
|
+
queuesWithoutReplica.delete(locator);
|
|
551
|
+
rooms.delete(locator);
|
|
552
|
+
roomDrafts.delete(locator);
|
|
553
|
+
transfers.delete(locator);
|
|
554
|
+
discovered.delete(locator);
|
|
555
|
+
announced.delete(locator);
|
|
556
|
+
unreadable.delete(locator);
|
|
557
|
+
stale.delete(locator);
|
|
558
|
+
scopedRefusals.delete(locator);
|
|
559
|
+
sourceRefusals.delete(locator);
|
|
560
|
+
declarationRefusals.delete(locator);
|
|
561
|
+
for (const attached of ['collection', 'typed-selection']) {
|
|
562
|
+
const key = modelKey(locator, attached);
|
|
563
|
+
models.delete(key);
|
|
564
|
+
liveModels.delete(key);
|
|
565
|
+
invalidModels.delete(key);
|
|
467
566
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
567
|
+
journal.record(`room lost ${locator}`);
|
|
568
|
+
}
|
|
569
|
+
if (fresh.length === 0)
|
|
570
|
+
return fresh;
|
|
571
|
+
publishChangeSet(mergeChangeSets(removed));
|
|
572
|
+
for (const locator of fresh)
|
|
573
|
+
materialized.delete(locator);
|
|
574
|
+
moveTo({ ...status, ...attention() });
|
|
575
|
+
return fresh;
|
|
576
|
+
}
|
|
577
|
+
async function erased(local, locators) {
|
|
578
|
+
await local.erase(locators);
|
|
579
|
+
for (const locator of locators) {
|
|
580
|
+
const held = fences.get(locator);
|
|
581
|
+
if (held !== undefined)
|
|
582
|
+
held.erased = true;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async function loseRooms(locators) {
|
|
586
|
+
const fresh = fence(locators);
|
|
587
|
+
if (fresh.length > 0)
|
|
588
|
+
await durably((local) => erased(local, fresh));
|
|
471
589
|
}
|
|
472
590
|
function restored(key, persisted) {
|
|
473
591
|
const prior = models.get(key);
|
|
@@ -488,7 +606,7 @@ export function composeRuntime(options) {
|
|
|
488
606
|
return model;
|
|
489
607
|
}
|
|
490
608
|
async function hydrate(opened) {
|
|
491
|
-
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named] = await Promise.all([
|
|
609
|
+
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known, staged,] = await Promise.all([
|
|
492
610
|
opened.all(),
|
|
493
611
|
opened.queues(),
|
|
494
612
|
opened.roomDrafts(),
|
|
@@ -498,9 +616,15 @@ export function composeRuntime(options) {
|
|
|
498
616
|
opened.lastCompleted(),
|
|
499
617
|
opened.providerLimits(),
|
|
500
618
|
opened.capabilities(),
|
|
619
|
+
opened.member(),
|
|
620
|
+
opened.transfers(),
|
|
501
621
|
]);
|
|
622
|
+
transfers.clear();
|
|
623
|
+
for (const record of staged)
|
|
624
|
+
transfers.set(record.locator, record);
|
|
502
625
|
limits = declared;
|
|
503
626
|
capabilities = named;
|
|
627
|
+
member = known;
|
|
504
628
|
roomDrafts.clear();
|
|
505
629
|
for (const record of kept)
|
|
506
630
|
rememberRoomDraft(record);
|
|
@@ -516,18 +640,17 @@ export function composeRuntime(options) {
|
|
|
516
640
|
}
|
|
517
641
|
const previousReplicas = new Map(replicas);
|
|
518
642
|
replicas.clear();
|
|
643
|
+
queuesWithoutReplica.clear();
|
|
644
|
+
const replicated = new Set(records.map((record) => record.locator));
|
|
645
|
+
for (const [locator, queue] of queuedBatches) {
|
|
646
|
+
if (!replicated.has(locator))
|
|
647
|
+
queuesWithoutReplica.set(locator, queue);
|
|
648
|
+
}
|
|
519
649
|
for (const record of records) {
|
|
520
650
|
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
521
651
|
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
522
652
|
const prior = previousReplicas.get(record.locator);
|
|
523
|
-
const
|
|
524
|
-
const read = readConfirmed(retyped, prior?.confirmed);
|
|
525
|
-
const confirmed = read.kind === 'staged'
|
|
526
|
-
? read.confirmed
|
|
527
|
-
: {
|
|
528
|
-
record: retyped,
|
|
529
|
-
collection: parseCollection(retyped.documents, compositionOf(retyped)),
|
|
530
|
-
};
|
|
653
|
+
const confirmed = confirmedOf({ ...record, model, selectionModel }, prior?.confirmed);
|
|
531
654
|
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
532
655
|
const existing = rooms.get(record.locator);
|
|
533
656
|
if (existing === undefined)
|
|
@@ -536,7 +659,6 @@ export function composeRuntime(options) {
|
|
|
536
659
|
rooms.set(record.locator, {
|
|
537
660
|
...existing,
|
|
538
661
|
roomVersion: record.roomVersion,
|
|
539
|
-
attachments: record.attachments,
|
|
540
662
|
operations: record.operations,
|
|
541
663
|
replica: record.replica,
|
|
542
664
|
model: modelTypes(model),
|
|
@@ -548,10 +670,6 @@ export function composeRuntime(options) {
|
|
|
548
670
|
},
|
|
549
671
|
});
|
|
550
672
|
}
|
|
551
|
-
if (read.kind === 'refused') {
|
|
552
|
-
refusedSource(record, read);
|
|
553
|
-
declarationRefusals.add(record.locator);
|
|
554
|
-
}
|
|
555
673
|
}
|
|
556
674
|
return {
|
|
557
675
|
lastCompleted,
|
|
@@ -564,14 +682,23 @@ export function composeRuntime(options) {
|
|
|
564
682
|
applyChangeSet(read);
|
|
565
683
|
if (replicas.size > 0)
|
|
566
684
|
emitRooms();
|
|
685
|
+
if (member !== null)
|
|
686
|
+
emitMember();
|
|
567
687
|
for (const locator of locators)
|
|
568
688
|
emitQueue(locator);
|
|
569
689
|
publishRoomDrafts(false);
|
|
570
690
|
return lastCompleted;
|
|
571
691
|
}
|
|
572
692
|
async function open() {
|
|
573
|
-
|
|
693
|
+
if (closed)
|
|
694
|
+
throw new Error('runtime closed');
|
|
695
|
+
const opened = await openRuntimeStore(isFenced);
|
|
574
696
|
store = opened;
|
|
697
|
+
const interrupted = await opened.fences();
|
|
698
|
+
for (const locator of interrupted) {
|
|
699
|
+
fences.set(locator, { at: discoveries, erased: false });
|
|
700
|
+
lastFenced.set(locator, ++fencings);
|
|
701
|
+
}
|
|
575
702
|
const kept = options.shell.generation;
|
|
576
703
|
const storedGeneration = await opened.generation();
|
|
577
704
|
const loss = detectStorageLoss(storedGeneration, kept);
|
|
@@ -585,6 +712,8 @@ export function composeRuntime(options) {
|
|
|
585
712
|
emitStorageLoss();
|
|
586
713
|
}
|
|
587
714
|
const lastCompleted = await readState(opened);
|
|
715
|
+
if (interrupted.length > 0)
|
|
716
|
+
await erased(opened, interrupted);
|
|
588
717
|
moveTo({ ...status, lastCompleted, ...attention() });
|
|
589
718
|
return opened;
|
|
590
719
|
}
|
|
@@ -611,11 +740,12 @@ export function composeRuntime(options) {
|
|
|
611
740
|
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
612
741
|
}
|
|
613
742
|
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
614
|
-
const
|
|
743
|
+
const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
744
|
+
const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
|
|
615
745
|
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
616
746
|
const unqueued = withdrawn.map((batch) => ({
|
|
617
747
|
batch,
|
|
618
|
-
returned: landed(batch, confirmed) ? null :
|
|
748
|
+
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
619
749
|
}));
|
|
620
750
|
const returning = [
|
|
621
751
|
...[...unqueued].reverse(),
|
|
@@ -636,26 +766,44 @@ export function composeRuntime(options) {
|
|
|
636
766
|
});
|
|
637
767
|
}
|
|
638
768
|
}
|
|
639
|
-
await local.
|
|
640
|
-
|
|
641
|
-
previous: before?.confirmed.record ?? null,
|
|
642
|
-
queue: kept,
|
|
643
|
-
previousQueue: before?.queue ?? [],
|
|
644
|
-
roomDrafts: [...written.values()],
|
|
645
|
-
});
|
|
646
|
-
for (const batch of before?.queue ?? []) {
|
|
769
|
+
await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
|
|
770
|
+
for (const batch of previousQueue) {
|
|
647
771
|
if (!kept.includes(batch))
|
|
648
772
|
pendings.delete(batch.id);
|
|
649
773
|
}
|
|
650
774
|
for (const record of written.values())
|
|
651
775
|
rememberRoomDraft(record);
|
|
776
|
+
try {
|
|
777
|
+
await local.write({
|
|
778
|
+
next: confirmed.record,
|
|
779
|
+
previous: before?.confirmed.record ?? null,
|
|
780
|
+
...(settling.stored === undefined ? {} : { stored: settling.stored }),
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
catch (error) {
|
|
784
|
+
const held = before === undefined ? null : replicaOf(before.confirmed, kept, before);
|
|
785
|
+
if (held !== null)
|
|
786
|
+
replicas.set(locator, held);
|
|
787
|
+
else if (kept.length > 0)
|
|
788
|
+
queuesWithoutReplica.set(locator, kept);
|
|
789
|
+
else
|
|
790
|
+
queuesWithoutReplica.delete(locator);
|
|
791
|
+
publishChangeSet(changeSet(locator, before ?? null, held, {
|
|
792
|
+
queue: kept.length !== previousQueue.length,
|
|
793
|
+
drafts: [...written.keys()],
|
|
794
|
+
}));
|
|
795
|
+
moveTo({ ...status, ...attention() });
|
|
796
|
+
throw error;
|
|
797
|
+
}
|
|
798
|
+
queuesWithoutReplica.delete(locator);
|
|
652
799
|
for (const { batch, returned } of unqueued) {
|
|
653
|
-
|
|
800
|
+
const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
|
|
801
|
+
journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
|
|
654
802
|
}
|
|
655
803
|
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
656
804
|
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
657
805
|
room: settling.room,
|
|
658
|
-
queue: kept.length !==
|
|
806
|
+
queue: kept.length !== previousQueue.length,
|
|
659
807
|
drafts: [...written.keys()],
|
|
660
808
|
});
|
|
661
809
|
}
|
|
@@ -690,7 +838,8 @@ export function composeRuntime(options) {
|
|
|
690
838
|
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
691
839
|
};
|
|
692
840
|
}
|
|
693
|
-
function publish(local, room, confirmed,
|
|
841
|
+
function publish(local, room, confirmed, landing = {}) {
|
|
842
|
+
const { base, stored } = landing;
|
|
694
843
|
return exclusive(async () => {
|
|
695
844
|
if (!continuing())
|
|
696
845
|
return true;
|
|
@@ -701,13 +850,15 @@ export function composeRuntime(options) {
|
|
|
701
850
|
return false;
|
|
702
851
|
}
|
|
703
852
|
adoptModels(room.locator, confirmed.record);
|
|
704
|
-
const queue = prior?.queue ?? [];
|
|
853
|
+
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
705
854
|
const roomVersion = confirmed.record.roomVersion;
|
|
706
855
|
const accepted = queue.filter((batch) => {
|
|
707
856
|
const accepted = pendings.get(batch.id)?.accepted;
|
|
708
857
|
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
709
858
|
});
|
|
710
|
-
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true });
|
|
859
|
+
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true, ...(stored === undefined ? {} : { stored }) });
|
|
860
|
+
if (stored !== undefined)
|
|
861
|
+
transfers.delete(room.locator);
|
|
711
862
|
for (const batch of accepted)
|
|
712
863
|
pendings.delete(batch.id);
|
|
713
864
|
stale.delete(room.locator);
|
|
@@ -726,22 +877,40 @@ export function composeRuntime(options) {
|
|
|
726
877
|
const replica = replicas.get(locator);
|
|
727
878
|
const room = rooms.get(locator);
|
|
728
879
|
if (replica === undefined || room === undefined) {
|
|
729
|
-
throw
|
|
880
|
+
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
730
881
|
}
|
|
731
882
|
const saved = [];
|
|
732
883
|
for (const { path, revision } of named) {
|
|
733
884
|
const record = roomDrafts.get(locator)?.get(path);
|
|
734
885
|
if (record === undefined || record.revision !== revision) {
|
|
735
|
-
return { outcome: 'refused',
|
|
886
|
+
return { outcome: 'refused', code: 'stale', retryable: false, file: path };
|
|
736
887
|
}
|
|
737
888
|
if (!saved.some((draft) => draft.path === path))
|
|
738
889
|
saved.push({ path, parts: record.parts });
|
|
739
890
|
}
|
|
740
891
|
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
741
892
|
if (changed.length > 0)
|
|
742
|
-
return { outcome: 'refused',
|
|
893
|
+
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
743
894
|
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
744
|
-
const
|
|
895
|
+
const changes = parts.flatMap(({ changes }) => changes);
|
|
896
|
+
const requeue = requeued(replica.queue, changes, replica.visible, withdrawable, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
897
|
+
if (requeue.kind === 'refused')
|
|
898
|
+
return { outcome: 'refused', ...requeue.refusal };
|
|
899
|
+
const foreign = [...requeue.withdrawn.keys()].some((origin) => {
|
|
900
|
+
const writer = replica.queue.find((batch) => batch.id === origin)?.writtenBy;
|
|
901
|
+
return writer?.app !== writtenBy?.app || writer?.release !== writtenBy?.release;
|
|
902
|
+
});
|
|
903
|
+
if (foreign) {
|
|
904
|
+
return {
|
|
905
|
+
outcome: 'refused',
|
|
906
|
+
code: 'unknown-revision',
|
|
907
|
+
retryable: false,
|
|
908
|
+
file: [...requeue.queued].at(-1),
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
const withdrawing = requeue.withdrawn.size > 0;
|
|
912
|
+
const input = {
|
|
913
|
+
local: localSaves,
|
|
745
914
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
746
915
|
composition: compositionFor(replica.confirmed.record),
|
|
747
916
|
operations: room.operations,
|
|
@@ -749,29 +918,88 @@ export function composeRuntime(options) {
|
|
|
749
918
|
providerLimits: limits,
|
|
750
919
|
handle: room.handle,
|
|
751
920
|
visible: replica.visible,
|
|
752
|
-
changes
|
|
921
|
+
changes,
|
|
753
922
|
bases: parts.flatMap(({ bases }) => bases),
|
|
754
923
|
writtenBy,
|
|
924
|
+
};
|
|
925
|
+
const checked = withdrawing ? buildBatch({ ...input, queued: requeue.queued }) : null;
|
|
926
|
+
if (checked?.kind === 'refused')
|
|
927
|
+
return { outcome: 'refused', ...checked.refusal };
|
|
928
|
+
const owners = [...requeue.owners].flatMap((origin) => {
|
|
929
|
+
const batch = requeue.withdrawn.get(origin);
|
|
930
|
+
return batch === undefined || batch === null ? [] : [{ origin, batch }];
|
|
755
931
|
});
|
|
756
|
-
if (
|
|
932
|
+
if (owners.length > 1) {
|
|
933
|
+
return {
|
|
934
|
+
outcome: 'refused',
|
|
935
|
+
code: 'unknown-revision',
|
|
936
|
+
retryable: false,
|
|
937
|
+
file: [...requeue.queued].at(-1),
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
const built = !withdrawing
|
|
941
|
+
? buildBatch(input)
|
|
942
|
+
: requeue.changes.length === 0
|
|
943
|
+
? null
|
|
944
|
+
: buildBatch({
|
|
945
|
+
...input,
|
|
946
|
+
visible: replicaOf(replica.confirmed, requeue.queue, replica).visible,
|
|
947
|
+
changes: requeue.changes,
|
|
948
|
+
});
|
|
949
|
+
if (built?.kind === 'refused')
|
|
757
950
|
return { outcome: 'refused', ...built.refusal };
|
|
758
|
-
const
|
|
951
|
+
const [owner] = owners;
|
|
952
|
+
const join = built === null || owner === undefined
|
|
953
|
+
? null
|
|
954
|
+
: joined(requeue.queue, owner.batch, built.batch);
|
|
955
|
+
if (join?.kind === 'refused')
|
|
956
|
+
return { outcome: 'refused', ...join.refusal };
|
|
957
|
+
const withdrawn = new Map(requeue.withdrawn);
|
|
958
|
+
if (join !== null && owner !== undefined)
|
|
959
|
+
withdrawn.set(owner.origin, join.batch);
|
|
960
|
+
const drafted = new Map();
|
|
961
|
+
for (const [origin, replaced] of withdrawn) {
|
|
962
|
+
if (replaced === null)
|
|
963
|
+
continue;
|
|
964
|
+
const carried = withdrawnDrafts(pendings.get(origin)?.drafts ?? [], changes);
|
|
965
|
+
drafted.set(replaced.id, replaced === join?.batch
|
|
966
|
+
? [
|
|
967
|
+
...carried.map((held) => joinedDraft(held, saved)),
|
|
968
|
+
...saved.filter((draft) => !carried.some((held) => held.path === draft.path)),
|
|
969
|
+
]
|
|
970
|
+
: carried);
|
|
971
|
+
}
|
|
972
|
+
if (built !== null && join === null)
|
|
973
|
+
drafted.set(built.batch.id, saved);
|
|
974
|
+
const batches = join !== null ? join.queue : built === null ? requeue.queue : [...requeue.queue, built.batch];
|
|
975
|
+
for (const replaced of withdrawn.values()) {
|
|
976
|
+
if (replaced === null || !batches.includes(replaced))
|
|
977
|
+
continue;
|
|
978
|
+
const limited = limitedIn(batches, replaced, limits, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
979
|
+
if (limited !== null)
|
|
980
|
+
return { outcome: 'refused', ...limited };
|
|
981
|
+
}
|
|
759
982
|
const consumed = saved.map(({ path }) => path);
|
|
760
983
|
try {
|
|
761
|
-
await local.
|
|
762
|
-
|
|
763
|
-
previous: replica.confirmed.record,
|
|
984
|
+
await local.keepAuthored({
|
|
985
|
+
locator,
|
|
764
986
|
queue: batches,
|
|
765
|
-
|
|
766
|
-
saved,
|
|
987
|
+
saved: drafted,
|
|
767
988
|
consumedRoomDrafts: consumed,
|
|
768
989
|
});
|
|
769
990
|
}
|
|
770
991
|
catch {
|
|
771
|
-
return { outcome: 'refused',
|
|
992
|
+
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
772
993
|
}
|
|
773
|
-
|
|
774
|
-
|
|
994
|
+
if (fences.has(locator))
|
|
995
|
+
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
996
|
+
for (const origin of withdrawn.keys())
|
|
997
|
+
pendings.delete(origin);
|
|
998
|
+
for (const [id, drafts] of drafted) {
|
|
999
|
+
pendings.set(id, { drafts, submission: { state: 'waiting' }, lost: false });
|
|
1000
|
+
}
|
|
1001
|
+
if (!localSaves)
|
|
1002
|
+
recorded = false;
|
|
775
1003
|
for (const path of consumed)
|
|
776
1004
|
forgetRoomDraft(locator, path);
|
|
777
1005
|
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
@@ -779,9 +1007,12 @@ export function composeRuntime(options) {
|
|
|
779
1007
|
queue: true,
|
|
780
1008
|
drafts: consumed,
|
|
781
1009
|
}));
|
|
782
|
-
moveTo({ ...saving(status), ...attention() });
|
|
1010
|
+
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
783
1011
|
submitLater(locator);
|
|
784
|
-
|
|
1012
|
+
const identifier = join?.batch.id ??
|
|
1013
|
+
built?.batch.id ??
|
|
1014
|
+
[...requeue.withdrawn].map(([origin, replaced]) => replaced?.id ?? origin).at(-1);
|
|
1015
|
+
return { outcome: 'saved', identifier };
|
|
785
1016
|
}
|
|
786
1017
|
async function mark(local, locator, identifier, submission, lost) {
|
|
787
1018
|
const state = pendings.get(identifier);
|
|
@@ -791,6 +1022,8 @@ export function composeRuntime(options) {
|
|
|
791
1022
|
return;
|
|
792
1023
|
}
|
|
793
1024
|
await local.submit(locator, identifier, submission, lost);
|
|
1025
|
+
if (fences.has(locator))
|
|
1026
|
+
return;
|
|
794
1027
|
pendings.set(identifier, { ...state, submission, lost });
|
|
795
1028
|
const replica = replicas.get(locator);
|
|
796
1029
|
if (replica !== undefined)
|
|
@@ -801,22 +1034,24 @@ export function composeRuntime(options) {
|
|
|
801
1034
|
if (!continuing())
|
|
802
1035
|
throw new Error('connection refused');
|
|
803
1036
|
await local.keepUpload(locator, identifier, upload);
|
|
1037
|
+
if (fences.has(locator))
|
|
1038
|
+
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
804
1039
|
pendings.set(identifier, { ...pendings.get(identifier), upload });
|
|
805
1040
|
if (!continuing())
|
|
806
1041
|
throw new Error('connection refused');
|
|
807
1042
|
}
|
|
808
1043
|
async function readHistory(locator, path, query = {}) {
|
|
809
1044
|
if (closed || refusal !== null)
|
|
810
|
-
return
|
|
1045
|
+
return UNCERTAIN;
|
|
811
1046
|
try {
|
|
812
1047
|
await ensureAuthorization();
|
|
813
1048
|
}
|
|
814
1049
|
catch {
|
|
815
|
-
return
|
|
816
|
-
}
|
|
817
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
818
|
-
return { kind: 'transport-uncertainty' };
|
|
1050
|
+
return UNCERTAIN;
|
|
819
1051
|
}
|
|
1052
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1053
|
+
return UNCERTAIN;
|
|
1054
|
+
const since = fencings;
|
|
820
1055
|
const transfer = ['transfer', 'history', locator, path];
|
|
821
1056
|
if (query.revision !== undefined)
|
|
822
1057
|
transfer.push(query.revision);
|
|
@@ -831,6 +1066,8 @@ export function composeRuntime(options) {
|
|
|
831
1066
|
...(query.pageToken === undefined ? {} : { pageToken: query.pageToken }),
|
|
832
1067
|
});
|
|
833
1068
|
journal.record(`outcome history ${result.kind}`);
|
|
1069
|
+
if (fencedSince(locator, since))
|
|
1070
|
+
return failure({ kind: 'room-not-readable', locator });
|
|
834
1071
|
if (result.kind === 'scoped-refusal') {
|
|
835
1072
|
const room = replicas.get(result.locator)?.confirmed.record;
|
|
836
1073
|
if (room !== undefined)
|
|
@@ -838,12 +1075,106 @@ export function composeRuntime(options) {
|
|
|
838
1075
|
}
|
|
839
1076
|
if (result.kind === 'revoked-authorization')
|
|
840
1077
|
await refuse('revoked-authorization');
|
|
841
|
-
return result;
|
|
1078
|
+
return historyAnswer(result);
|
|
842
1079
|
}
|
|
843
1080
|
catch {
|
|
844
1081
|
journal.record('outcome history transport-uncertainty');
|
|
845
|
-
return
|
|
1082
|
+
return UNCERTAIN;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
function queuedBlob(locator, path, localId) {
|
|
1086
|
+
for (const batch of replicas.get(locator)?.queue ?? []) {
|
|
1087
|
+
const change = batch.changes[queuedFile(batch, path, localId)];
|
|
1088
|
+
if (change?.kind === 'binary')
|
|
1089
|
+
return change.blob;
|
|
1090
|
+
}
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
function binaryRoom(request) {
|
|
1094
|
+
if (request.kind === 'path')
|
|
1095
|
+
return request.locator;
|
|
1096
|
+
const scope = options.adapter.referrerScope(request.referrer);
|
|
1097
|
+
return scope?.kind === 'room' ? scope.locator : null;
|
|
1098
|
+
}
|
|
1099
|
+
async function readBinary(request) {
|
|
1100
|
+
if (closed || refusal !== null)
|
|
1101
|
+
return UNCERTAIN;
|
|
1102
|
+
try {
|
|
1103
|
+
await ensureAuthorization();
|
|
846
1104
|
}
|
|
1105
|
+
catch {
|
|
1106
|
+
return UNCERTAIN;
|
|
1107
|
+
}
|
|
1108
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1109
|
+
return UNCERTAIN;
|
|
1110
|
+
const since = fencings;
|
|
1111
|
+
journal.record(request.kind === 'path'
|
|
1112
|
+
? `transfer binary ${request.locator} ${request.path}`
|
|
1113
|
+
: `transfer binary ${request.fileRef}`);
|
|
1114
|
+
try {
|
|
1115
|
+
const result = await options.adapter.binary(authorization, request);
|
|
1116
|
+
journal.record(`outcome binary ${result.kind}`);
|
|
1117
|
+
const locator = binaryRoom(request);
|
|
1118
|
+
if (locator !== null && fencedSince(locator, since))
|
|
1119
|
+
return failure({ kind: 'room-not-readable', locator });
|
|
1120
|
+
if (result.kind === 'scoped-refusal') {
|
|
1121
|
+
const room = replicas.get(result.locator)?.confirmed.record;
|
|
1122
|
+
if (room !== undefined)
|
|
1123
|
+
await scoped(result, room, await openLocal());
|
|
1124
|
+
}
|
|
1125
|
+
if (result.kind === 'revoked-authorization')
|
|
1126
|
+
await refuse('revoked-authorization');
|
|
1127
|
+
return binaryAnswer(result);
|
|
1128
|
+
}
|
|
1129
|
+
catch {
|
|
1130
|
+
journal.record('outcome binary transport-uncertainty');
|
|
1131
|
+
return UNCERTAIN;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
async function readBinaryPath(locator, path, revision) {
|
|
1135
|
+
await ready;
|
|
1136
|
+
if (revision !== undefined)
|
|
1137
|
+
return readBinary({ kind: 'path', locator, path, revision });
|
|
1138
|
+
const visible = replicas.get(locator)?.visible;
|
|
1139
|
+
const held = visible?.binaries[path];
|
|
1140
|
+
if (visible === undefined || held === undefined)
|
|
1141
|
+
return absentBinary(path);
|
|
1142
|
+
if (held.state === 'pending') {
|
|
1143
|
+
const blob = queuedBlob(locator, path, held.localId);
|
|
1144
|
+
return blob === null ? absentBinary(path) : { kind: 'binary', blob };
|
|
1145
|
+
}
|
|
1146
|
+
const record = binaryRecord(path, held, visible.revisions[path]);
|
|
1147
|
+
if (record.state !== 'accepted')
|
|
1148
|
+
return absentBinary(path);
|
|
1149
|
+
const replica = replicas.get(locator);
|
|
1150
|
+
const queued = (replica?.queue ?? []).flatMap((batch) => batch.changes);
|
|
1151
|
+
const holds = (at) => {
|
|
1152
|
+
const kept = replica?.confirmed.record.binaries[at];
|
|
1153
|
+
return (kept?.state === 'accepted' &&
|
|
1154
|
+
kept.fileRef === record.fileRef &&
|
|
1155
|
+
replica?.confirmed.record.revisions[at] === record.revision);
|
|
1156
|
+
};
|
|
1157
|
+
let confirmed = path;
|
|
1158
|
+
for (let index = queued.length - 1; index >= 0 && !holds(confirmed); index -= 1) {
|
|
1159
|
+
const change = queued[index];
|
|
1160
|
+
if (change.kind === 'move' && change.to === confirmed)
|
|
1161
|
+
confirmed = change.path;
|
|
1162
|
+
}
|
|
1163
|
+
return readBinary({
|
|
1164
|
+
kind: 'path',
|
|
1165
|
+
locator,
|
|
1166
|
+
path: confirmed,
|
|
1167
|
+
revision: record.revision,
|
|
1168
|
+
fileRef: record.fileRef,
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
async function readBinaryReference(reference) {
|
|
1172
|
+
await ready;
|
|
1173
|
+
return readBinary({
|
|
1174
|
+
kind: 'reference',
|
|
1175
|
+
fileRef: reference.fileRef,
|
|
1176
|
+
referrer: reference.referrer,
|
|
1177
|
+
});
|
|
847
1178
|
}
|
|
848
1179
|
function declaredAgents() {
|
|
849
1180
|
for (const capability of capabilities) {
|
|
@@ -864,18 +1195,17 @@ export function composeRuntime(options) {
|
|
|
864
1195
|
async function invokeAction(name, input) {
|
|
865
1196
|
await ready;
|
|
866
1197
|
if (closed || refusal !== null)
|
|
867
|
-
return
|
|
1198
|
+
return UNCERTAIN;
|
|
868
1199
|
if (declaredAction(name) === undefined)
|
|
869
|
-
return
|
|
1200
|
+
return undeclared(name);
|
|
870
1201
|
try {
|
|
871
1202
|
await ensureAuthorization();
|
|
872
1203
|
}
|
|
873
1204
|
catch {
|
|
874
|
-
return
|
|
875
|
-
}
|
|
876
|
-
if (closed || authorization === undefined || refusal !== null) {
|
|
877
|
-
return { kind: 'transport-uncertainty' };
|
|
1205
|
+
return UNCERTAIN;
|
|
878
1206
|
}
|
|
1207
|
+
if (closed || authorization === undefined || refusal !== null)
|
|
1208
|
+
return UNCERTAIN;
|
|
879
1209
|
journal.record(`transfer action ${name}`);
|
|
880
1210
|
try {
|
|
881
1211
|
const result = await options.adapter.action(authorization, { name, input });
|
|
@@ -889,11 +1219,11 @@ export function composeRuntime(options) {
|
|
|
889
1219
|
journal.record(`outcome action ${result.kind}`);
|
|
890
1220
|
if (result.kind === 'rejected')
|
|
891
1221
|
journal.record(`refusal rejected ${result.code}`);
|
|
892
|
-
return result;
|
|
1222
|
+
return calledResult(result);
|
|
893
1223
|
}
|
|
894
1224
|
catch {
|
|
895
1225
|
journal.record('outcome action transport-uncertainty');
|
|
896
|
-
return
|
|
1226
|
+
return UNCERTAIN;
|
|
897
1227
|
}
|
|
898
1228
|
}
|
|
899
1229
|
function continuing() {
|
|
@@ -924,14 +1254,33 @@ export function composeRuntime(options) {
|
|
|
924
1254
|
throw error;
|
|
925
1255
|
}
|
|
926
1256
|
}
|
|
927
|
-
|
|
1257
|
+
function staging(local) {
|
|
1258
|
+
return {
|
|
1259
|
+
held: (locator) => transfers.get(locator) ?? null,
|
|
1260
|
+
staged: (record) => local.staged(record),
|
|
1261
|
+
stage: (record, page) => exclusive(async () => {
|
|
1262
|
+
if (!continuing())
|
|
1263
|
+
return false;
|
|
1264
|
+
await local.stage(record, page);
|
|
1265
|
+
transfers.set(record.locator, record);
|
|
1266
|
+
return true;
|
|
1267
|
+
}),
|
|
1268
|
+
abandon: (locator) => exclusive(async () => {
|
|
1269
|
+
if (!continuing())
|
|
1270
|
+
return;
|
|
1271
|
+
await local.abandon(locator);
|
|
1272
|
+
transfers.delete(locator);
|
|
1273
|
+
}),
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
async function pullBootstrap(authorized, room, local) {
|
|
928
1277
|
if (!continuing())
|
|
929
1278
|
return unconfirmed;
|
|
930
1279
|
try {
|
|
931
1280
|
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
932
1281
|
collection: modelOf(room.locator),
|
|
933
1282
|
selection: modelOf(room.locator, 'typed-selection'),
|
|
934
|
-
}, (models, held) => remodel(room.locator, models, held), continuing);
|
|
1283
|
+
}, (models, held) => remodel(room.locator, models, held), continuing, staging(local));
|
|
935
1284
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
936
1285
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
937
1286
|
return result;
|
|
@@ -959,7 +1308,7 @@ export function composeRuntime(options) {
|
|
|
959
1308
|
return null;
|
|
960
1309
|
}
|
|
961
1310
|
if (refreshed.kind === 'advanced') {
|
|
962
|
-
if (!(await publish(local, room, refreshed.confirmed, held.confirmed))) {
|
|
1311
|
+
if (!(await publish(local, room, refreshed.confirmed, { base: held.confirmed }))) {
|
|
963
1312
|
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
964
1313
|
const reached = now !== undefined &&
|
|
965
1314
|
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
@@ -971,12 +1320,12 @@ export function composeRuntime(options) {
|
|
|
971
1320
|
if (refreshed.kind !== 'beyond-horizon')
|
|
972
1321
|
return refreshed;
|
|
973
1322
|
}
|
|
974
|
-
const bootstrapped = await pullBootstrap(authorized, room);
|
|
1323
|
+
const bootstrapped = await pullBootstrap(authorized, room, local);
|
|
975
1324
|
if (!continuing())
|
|
976
1325
|
return unconfirmed;
|
|
977
1326
|
if (bootstrapped.kind !== 'staged')
|
|
978
1327
|
return bootstrapped;
|
|
979
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1328
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
980
1329
|
return null;
|
|
981
1330
|
}
|
|
982
1331
|
async function withhold(withheld, room, local, progress) {
|
|
@@ -1008,6 +1357,7 @@ export function composeRuntime(options) {
|
|
|
1008
1357
|
function refusedSource(room, { refusal }) {
|
|
1009
1358
|
sourceRefusals.set(room.locator, {
|
|
1010
1359
|
code: refusal.rule,
|
|
1360
|
+
retryable: false,
|
|
1011
1361
|
fields: {
|
|
1012
1362
|
file: refusal.file,
|
|
1013
1363
|
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
@@ -1022,8 +1372,16 @@ export function composeRuntime(options) {
|
|
|
1022
1372
|
}
|
|
1023
1373
|
function leaving(batch, result) {
|
|
1024
1374
|
if (result.kind === 'expired')
|
|
1025
|
-
return { batch, returned:
|
|
1026
|
-
return {
|
|
1375
|
+
return { batch, returned: returnedBy('expired') };
|
|
1376
|
+
return {
|
|
1377
|
+
batch,
|
|
1378
|
+
returned: {
|
|
1379
|
+
kind: result.kind,
|
|
1380
|
+
code: result.code,
|
|
1381
|
+
fields: result.fields,
|
|
1382
|
+
retryable: result.retryable,
|
|
1383
|
+
},
|
|
1384
|
+
};
|
|
1027
1385
|
}
|
|
1028
1386
|
function logAccepted(locator, submitted, accepted) {
|
|
1029
1387
|
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
@@ -1041,13 +1399,13 @@ export function composeRuntime(options) {
|
|
|
1041
1399
|
logAccepted(room.locator, submitted, accepted);
|
|
1042
1400
|
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1043
1401
|
? { kind: 'staged', confirmed: current.confirmed }
|
|
1044
|
-
: applyReturned(current.confirmed,
|
|
1402
|
+
: applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1045
1403
|
? accepted.roomVersion
|
|
1046
1404
|
: held, accepted.roomVersion);
|
|
1047
1405
|
if (applied.kind !== 'staged') {
|
|
1048
1406
|
const state = pendings.get(submitted.id);
|
|
1049
1407
|
await local.accept(room.locator, submitted.id, accepted.roomVersion);
|
|
1050
|
-
if (state !== undefined)
|
|
1408
|
+
if (state !== undefined && !fences.has(room.locator))
|
|
1051
1409
|
pendings.set(submitted.id, { ...state, accepted: accepted.roomVersion });
|
|
1052
1410
|
emitQueue(room.locator);
|
|
1053
1411
|
return applied;
|
|
@@ -1096,7 +1454,7 @@ export function composeRuntime(options) {
|
|
|
1096
1454
|
let result;
|
|
1097
1455
|
journal.record(`submission ${room.locator} ${head.id}`);
|
|
1098
1456
|
await exclusive(() => mark(local, room.locator, head.id, { state: 'submitted' }, lost));
|
|
1099
|
-
if (discoveryPaused)
|
|
1457
|
+
if (discoveryPaused || fences.has(room.locator))
|
|
1100
1458
|
return;
|
|
1101
1459
|
if (writesHeld()) {
|
|
1102
1460
|
await exclusive(() => mark(local, room.locator, head.id, submission, earlier));
|
|
@@ -1249,7 +1607,7 @@ export function composeRuntime(options) {
|
|
|
1249
1607
|
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1250
1608
|
};
|
|
1251
1609
|
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
1252
|
-
?
|
|
1610
|
+
? { kind: 'staged', confirmed: confirmedOf(declared, held.confirmed) }
|
|
1253
1611
|
: readReclassified(declared, held.confirmed);
|
|
1254
1612
|
const record = reclassified.kind === 'staged'
|
|
1255
1613
|
? declared
|
|
@@ -1257,12 +1615,10 @@ export function composeRuntime(options) {
|
|
|
1257
1615
|
...declared,
|
|
1258
1616
|
typedSelection: held.confirmed.record.typedSelection,
|
|
1259
1617
|
};
|
|
1260
|
-
const read = reclassified.kind === 'deferred'
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
queue: held.queue,
|
|
1265
|
-
});
|
|
1618
|
+
const read = reclassified.kind === 'deferred'
|
|
1619
|
+
? { kind: 'staged', confirmed: confirmedOf(record, held.confirmed) }
|
|
1620
|
+
: reclassified;
|
|
1621
|
+
await local.write({ next: record, previous: held.confirmed.record });
|
|
1266
1622
|
if (read.kind === 'staged') {
|
|
1267
1623
|
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
|
|
1268
1624
|
if (declarationRefusals.delete(room.locator)) {
|
|
@@ -1289,7 +1645,11 @@ export function composeRuntime(options) {
|
|
|
1289
1645
|
});
|
|
1290
1646
|
}
|
|
1291
1647
|
async function scoped(result, room, local) {
|
|
1292
|
-
scopedRefusals.set(room.locator, {
|
|
1648
|
+
scopedRefusals.set(room.locator, {
|
|
1649
|
+
code: result.code,
|
|
1650
|
+
fields: result.fields,
|
|
1651
|
+
retryable: result.retryable,
|
|
1652
|
+
});
|
|
1293
1653
|
let scopedAway = room;
|
|
1294
1654
|
if (result.scope === 'model')
|
|
1295
1655
|
await retainModel(local, room, 'collection', {
|
|
@@ -1326,7 +1686,6 @@ export function composeRuntime(options) {
|
|
|
1326
1686
|
roomVersion: record.roomVersion,
|
|
1327
1687
|
documents: record.documents,
|
|
1328
1688
|
revisions: record.revisions,
|
|
1329
|
-
attachments: record.attachments,
|
|
1330
1689
|
},
|
|
1331
1690
|
];
|
|
1332
1691
|
});
|
|
@@ -1406,14 +1765,14 @@ export function composeRuntime(options) {
|
|
|
1406
1765
|
async function reload(room, local, progress) {
|
|
1407
1766
|
if (discoveryPaused || authorization === undefined)
|
|
1408
1767
|
return;
|
|
1409
|
-
const bootstrapped = await pullBootstrap(authorization, room);
|
|
1768
|
+
const bootstrapped = await pullBootstrap(authorization, room, local);
|
|
1410
1769
|
if (!continuing())
|
|
1411
1770
|
return;
|
|
1412
1771
|
if (bootstrapped.kind !== 'staged') {
|
|
1413
1772
|
await withhold(bootstrapped, room, local, progress);
|
|
1414
1773
|
return;
|
|
1415
1774
|
}
|
|
1416
|
-
await publish(local, room, bootstrapped.confirmed);
|
|
1775
|
+
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
1417
1776
|
await caughtUp(room, local, progress);
|
|
1418
1777
|
}
|
|
1419
1778
|
async function discoverRooms(local, progress, attempt) {
|
|
@@ -1422,6 +1781,8 @@ export function composeRuntime(options) {
|
|
|
1422
1781
|
return;
|
|
1423
1782
|
}
|
|
1424
1783
|
moveTo(transferring(status));
|
|
1784
|
+
discoveries += 1;
|
|
1785
|
+
const started = discoveries;
|
|
1425
1786
|
let result;
|
|
1426
1787
|
try {
|
|
1427
1788
|
result = await options.adapter.discover(authorization);
|
|
@@ -1448,7 +1809,11 @@ export function composeRuntime(options) {
|
|
|
1448
1809
|
}
|
|
1449
1810
|
if (result.kind === 'contract-unavailable') {
|
|
1450
1811
|
journal.record(`connection contract unavailable ${result.code}`);
|
|
1451
|
-
await refuse('missing-required-guarantee', {
|
|
1812
|
+
await refuse('missing-required-guarantee', {
|
|
1813
|
+
code: result.code,
|
|
1814
|
+
fields: result.fields,
|
|
1815
|
+
retryable: result.retryable,
|
|
1816
|
+
});
|
|
1452
1817
|
notifyReady();
|
|
1453
1818
|
return;
|
|
1454
1819
|
}
|
|
@@ -1489,8 +1854,13 @@ export function composeRuntime(options) {
|
|
|
1489
1854
|
limits = result.providerLimits;
|
|
1490
1855
|
await local.declare(limits, capabilities);
|
|
1491
1856
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1857
|
+
const signedIn = result.member ?? null;
|
|
1858
|
+
if (JSON.stringify(signedIn) !== JSON.stringify(member)) {
|
|
1859
|
+
await local.keepMember(signedIn);
|
|
1860
|
+
member = signedIn;
|
|
1861
|
+
emitMember();
|
|
1862
|
+
}
|
|
1863
|
+
await synchronize(await applyDiscovery(result.rooms, started), local, progress, attempt);
|
|
1494
1864
|
}
|
|
1495
1865
|
async function ensureAuthorization() {
|
|
1496
1866
|
if (authorization !== undefined)
|
|
@@ -1518,6 +1888,8 @@ export function composeRuntime(options) {
|
|
|
1518
1888
|
}
|
|
1519
1889
|
const progress = { complete: true, reachable: true };
|
|
1520
1890
|
underway = attempt === 'opened' ? progress : null;
|
|
1891
|
+
if (attempt === 'opened')
|
|
1892
|
+
streamed = progress;
|
|
1521
1893
|
complete = false;
|
|
1522
1894
|
recorded = false;
|
|
1523
1895
|
moveTo(transferring(status));
|
|
@@ -1558,9 +1930,14 @@ export function composeRuntime(options) {
|
|
|
1558
1930
|
void refuse('revoked-authorization');
|
|
1559
1931
|
return;
|
|
1560
1932
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1933
|
+
complete = false;
|
|
1934
|
+
if (streamed !== null)
|
|
1935
|
+
streamed.complete = false;
|
|
1936
|
+
moveTo({
|
|
1937
|
+
...status,
|
|
1938
|
+
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
1939
|
+
providerUnreachable: status.providerUnreachable || !opened,
|
|
1940
|
+
});
|
|
1564
1941
|
}
|
|
1565
1942
|
function applyOpening(expired) {
|
|
1566
1943
|
return task(async () => {
|
|
@@ -1615,6 +1992,8 @@ export function composeRuntime(options) {
|
|
|
1615
1992
|
}
|
|
1616
1993
|
}
|
|
1617
1994
|
function submitLater(locator) {
|
|
1995
|
+
if (localSaves)
|
|
1996
|
+
return;
|
|
1618
1997
|
const running = submitting.get(locator);
|
|
1619
1998
|
if (running !== undefined) {
|
|
1620
1999
|
running.again = true;
|
|
@@ -1666,6 +2045,8 @@ export function composeRuntime(options) {
|
|
|
1666
2045
|
conversations.reconnected();
|
|
1667
2046
|
const reading = { complete: true, reachable: true };
|
|
1668
2047
|
await conversations.catchUp(reading);
|
|
2048
|
+
if (progress.reachable && reading.reachable)
|
|
2049
|
+
await conversations.resend(reading);
|
|
1669
2050
|
if (!reading.reachable)
|
|
1670
2051
|
progress.reachable = false;
|
|
1671
2052
|
}
|
|
@@ -1694,7 +2075,7 @@ export function composeRuntime(options) {
|
|
|
1694
2075
|
}
|
|
1695
2076
|
}
|
|
1696
2077
|
moveTo({
|
|
1697
|
-
state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
|
|
2078
|
+
state: pending() ? 'pending' : synced && complete ? 'synced' : 'syncing',
|
|
1698
2079
|
lastCompleted,
|
|
1699
2080
|
providerUnreachable: !progress.reachable,
|
|
1700
2081
|
...attention(),
|
|
@@ -1803,20 +2184,15 @@ export function composeRuntime(options) {
|
|
|
1803
2184
|
show();
|
|
1804
2185
|
return;
|
|
1805
2186
|
}
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
moveTo(transferring(resting));
|
|
2187
|
+
const progress = { complete: false, reachable: !status.providerUnreachable };
|
|
2188
|
+
moveTo(transferring(status));
|
|
1809
2189
|
try {
|
|
1810
2190
|
await conversations.catchUp(progress);
|
|
1811
2191
|
}
|
|
1812
2192
|
catch {
|
|
1813
2193
|
progress.reachable = false;
|
|
1814
2194
|
}
|
|
1815
|
-
|
|
1816
|
-
if (!closed) {
|
|
1817
|
-
moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
2195
|
+
await rest(progress);
|
|
1820
2196
|
}
|
|
1821
2197
|
function close() {
|
|
1822
2198
|
if (closed)
|
|
@@ -1838,6 +2214,7 @@ export function composeRuntime(options) {
|
|
|
1838
2214
|
storageLossListeners.clear();
|
|
1839
2215
|
statusListeners.clear();
|
|
1840
2216
|
serviceModeListeners.clear();
|
|
2217
|
+
memberListeners.clear();
|
|
1841
2218
|
readSide.close();
|
|
1842
2219
|
queueSubscriptions.clear();
|
|
1843
2220
|
roomDraftListeners.clear();
|
|
@@ -1869,13 +2246,16 @@ export function composeRuntime(options) {
|
|
|
1869
2246
|
return planOf(input, roomFacts(reach), planReach(reach));
|
|
1870
2247
|
}
|
|
1871
2248
|
function watchPlan(plan, deliver) {
|
|
2249
|
+
materialize(reachedRooms(plan));
|
|
1872
2250
|
return readSide.watch(plan, identityOf(plan), deliver);
|
|
1873
2251
|
}
|
|
1874
2252
|
async function searched(input, reach) {
|
|
1875
2253
|
const plan = searchPlanOf(input);
|
|
1876
2254
|
const selected = plan.documents === undefined && reach === null
|
|
1877
2255
|
? null
|
|
1878
|
-
: (await
|
|
2256
|
+
: (await rowsQuery(filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)))).documents;
|
|
2257
|
+
if (selected === null)
|
|
2258
|
+
materialize(null);
|
|
1879
2259
|
return searchIndex.search(reach === null || reach.conversations ? plan : { ...plan, conversations: [] }, selected);
|
|
1880
2260
|
}
|
|
1881
2261
|
const watch = (topic, input, deliver) => {
|
|
@@ -1901,6 +2281,8 @@ export function composeRuntime(options) {
|
|
|
1901
2281
|
}
|
|
1902
2282
|
if (topic === 'conversations')
|
|
1903
2283
|
return conversations.watchList(deliver);
|
|
2284
|
+
if (topic === 'agents')
|
|
2285
|
+
return conversations.watchAgents(deliver);
|
|
1904
2286
|
if (topic === 'presentation') {
|
|
1905
2287
|
const listener = {
|
|
1906
2288
|
app: input[0],
|
|
@@ -1955,6 +2337,13 @@ export function composeRuntime(options) {
|
|
|
1955
2337
|
serviceModeListeners.delete(deliver);
|
|
1956
2338
|
};
|
|
1957
2339
|
}
|
|
2340
|
+
if (topic === 'member') {
|
|
2341
|
+
memberListeners.add(deliver);
|
|
2342
|
+
deliver(member);
|
|
2343
|
+
return () => {
|
|
2344
|
+
memberListeners.delete(deliver);
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
1958
2347
|
if (topic === 'queue') {
|
|
1959
2348
|
const queue = { locator: input[0], notify: deliver };
|
|
1960
2349
|
queueSubscriptions.add(queue);
|
|
@@ -1972,7 +2361,7 @@ export function composeRuntime(options) {
|
|
|
1972
2361
|
rooms() {
|
|
1973
2362
|
return listed();
|
|
1974
2363
|
},
|
|
1975
|
-
query: ((input) =>
|
|
2364
|
+
query: ((input) => rowsQuery(planned(input, null))),
|
|
1976
2365
|
search: (input) => searched(input, null),
|
|
1977
2366
|
types(locator) {
|
|
1978
2367
|
return {
|
|
@@ -2001,20 +2390,20 @@ export function composeRuntime(options) {
|
|
|
2001
2390
|
return exclusive(async () => {
|
|
2002
2391
|
const parts = readParts(input);
|
|
2003
2392
|
if (path === '' || normalize(path) !== path)
|
|
2004
|
-
throw
|
|
2393
|
+
throw runtimeError('room-draft-malformed');
|
|
2005
2394
|
const local = await openLocal();
|
|
2006
2395
|
const current = roomDrafts.get(room)?.get(path);
|
|
2007
2396
|
if ((current?.revision ?? null) !== revision) {
|
|
2008
|
-
return
|
|
2009
|
-
outcome: 'stale',
|
|
2010
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2011
|
-
};
|
|
2397
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2012
2398
|
}
|
|
2399
|
+
const file = parts.file === undefined
|
|
2400
|
+
? undefined
|
|
2401
|
+
: await heldFile(parts.file.blob, binaryBase(replicas.get(room)?.visible, path));
|
|
2013
2402
|
const record = {
|
|
2014
2403
|
locator: room,
|
|
2015
2404
|
path,
|
|
2016
2405
|
revision: roomDraftRevision(),
|
|
2017
|
-
parts: held(current?.parts, parts),
|
|
2406
|
+
parts: held(current?.parts, parts, file),
|
|
2018
2407
|
returned: current?.returned ?? null,
|
|
2019
2408
|
};
|
|
2020
2409
|
await local.keepRoomDraft(record);
|
|
@@ -2028,10 +2417,7 @@ export function composeRuntime(options) {
|
|
|
2028
2417
|
const local = await openLocal();
|
|
2029
2418
|
const current = roomDrafts.get(room)?.get(path);
|
|
2030
2419
|
if (current === undefined || current.revision !== revision) {
|
|
2031
|
-
return
|
|
2032
|
-
outcome: 'stale',
|
|
2033
|
-
current: current === undefined ? null : roomDraftOf(current),
|
|
2034
|
-
};
|
|
2420
|
+
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2035
2421
|
}
|
|
2036
2422
|
await local.discardRoomDraft(room, path);
|
|
2037
2423
|
forgetRoomDraft(room, path);
|
|
@@ -2066,6 +2452,10 @@ export function composeRuntime(options) {
|
|
|
2066
2452
|
history(locator, path, query) {
|
|
2067
2453
|
return readHistory(locator, path, query);
|
|
2068
2454
|
},
|
|
2455
|
+
binaryFile: {
|
|
2456
|
+
read: readBinaryPath,
|
|
2457
|
+
readReference: readBinaryReference,
|
|
2458
|
+
},
|
|
2069
2459
|
call(name, input) {
|
|
2070
2460
|
return invokeAction(name, input);
|
|
2071
2461
|
},
|
|
@@ -2098,10 +2488,11 @@ export function composeRuntime(options) {
|
|
|
2098
2488
|
const reachable = {
|
|
2099
2489
|
md,
|
|
2100
2490
|
watch,
|
|
2491
|
+
referrerScope: (referrer) => options.adapter.referrerScope(referrer),
|
|
2101
2492
|
plan: planned,
|
|
2102
|
-
query: (plan) =>
|
|
2493
|
+
query: (plan) => rowsQuery(plan),
|
|
2103
2494
|
watchPlan,
|
|
2104
2495
|
search: searched,
|
|
2105
2496
|
};
|
|
2106
|
-
return { md, stop, watch, within: (reach) => narrowed(reachable, reach) };
|
|
2497
|
+
return { md, stop, lose: loseRooms, watch, within: (reach) => narrowed(reachable, reach) };
|
|
2107
2498
|
}
|