@agentshouse/mdruntime 0.1.0-c03e7b79830dd → 0.1.0-c0bed82db8d73
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 +65 -65
- package/README.md +8 -7
- package/dist/adapter.d.ts +10 -14
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +1 -1
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +2 -2
- package/dist/binary.d.ts +0 -5
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +1 -1
- package/dist/bootstrap.d.ts +13 -2
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +94 -57
- package/dist/change-set.d.ts +1 -7
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +1 -4
- package/dist/client.d.ts +2 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -2
- package/dist/compile.d.ts +6 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +12 -3
- package/dist/connection.d.ts +4 -5
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +481 -448
- package/dist/conversation.d.ts +2 -0
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +40 -19
- package/dist/durability.d.ts +2 -5
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/fulltext.d.ts +27 -0
- package/dist/fulltext.d.ts.map +1 -0
- package/dist/fulltext.js +368 -0
- package/dist/host.d.ts +4 -4
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +35 -15
- package/dist/identity.d.ts +0 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -3
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +0 -11
- package/dist/messages.d.ts +28 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +90 -0
- package/dist/outcome.d.ts +0 -1
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +0 -3
- package/dist/overlay.js +1 -1
- package/dist/query.d.ts +4 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +12 -1
- package/dist/reach.d.ts +0 -2
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +17 -28
- package/dist/readiness.d.ts +20 -9
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +6 -6
- package/dist/reads.d.ts.map +1 -1
- package/dist/reads.js +3 -1
- package/dist/refresh.d.ts +6 -2
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +60 -22
- package/dist/replica.d.ts +3 -1
- package/dist/replica.d.ts.map +1 -1
- package/dist/room-draft.d.ts +1 -0
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +33 -0
- package/dist/save.js +2 -2
- package/dist/search.d.ts +11 -43
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +0 -296
- package/dist/store.d.ts +17 -36
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +28 -227
- package/dist/stored.d.ts +9 -7
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +46 -40
- package/dist/tables.d.ts +46 -12
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +94 -7
- package/dist/wire.d.ts +0 -4
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +0 -1
- package/dist/words.d.ts +19 -0
- package/dist/words.d.ts.map +1 -0
- package/dist/words.js +150 -0
- package/package.json +11 -18
- package/dist/chat/acts.d.ts +0 -33
- package/dist/chat/acts.d.ts.map +0 -1
- package/dist/chat/acts.js +0 -36
- package/dist/chat/context.d.ts +0 -3
- package/dist/chat/context.d.ts.map +0 -1
- package/dist/chat/context.js +0 -10
- package/dist/chat/earlier.d.ts +0 -10
- package/dist/chat/earlier.d.ts.map +0 -1
- package/dist/chat/earlier.js +0 -54
- package/dist/chat/house.d.ts +0 -25
- package/dist/chat/house.d.ts.map +0 -1
- package/dist/chat/house.js +0 -38
- package/dist/chat/input.d.ts +0 -11
- package/dist/chat/input.d.ts.map +0 -1
- package/dist/chat/input.js +0 -92
- package/dist/chat/kit.d.ts +0 -61
- package/dist/chat/kit.d.ts.map +0 -1
- package/dist/chat/kit.js +0 -123
- package/dist/chat/lasting.d.ts +0 -6
- package/dist/chat/lasting.d.ts.map +0 -1
- package/dist/chat/lasting.js +0 -19
- package/dist/chat/list.d.ts +0 -28
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -103
- package/dist/chat/markdown.d.ts +0 -21
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -246
- package/dist/chat/message.d.ts +0 -7
- package/dist/chat/message.d.ts.map +0 -1
- package/dist/chat/message.js +0 -244
- package/dist/chat/messages.d.ts +0 -30
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -198
- package/dist/chat/name.d.ts +0 -7
- package/dist/chat/name.d.ts.map +0 -1
- package/dist/chat/name.js +0 -37
- package/dist/chat/opened.d.ts +0 -12
- package/dist/chat/opened.d.ts.map +0 -1
- package/dist/chat/opened.js +0 -19
- package/dist/chat/overlay.d.ts +0 -32
- package/dist/chat/overlay.d.ts.map +0 -1
- package/dist/chat/overlay.js +0 -103
- package/dist/chat/parts.d.ts +0 -58
- package/dist/chat/parts.d.ts.map +0 -1
- package/dist/chat/parts.js +0 -120
- package/dist/chat/pieces.d.ts +0 -62
- package/dist/chat/pieces.d.ts.map +0 -1
- package/dist/chat/pieces.js +0 -1110
- package/dist/chat/process.d.ts +0 -20
- package/dist/chat/process.d.ts.map +0 -1
- package/dist/chat/process.js +0 -32
- package/dist/chat/provider.d.ts +0 -70
- package/dist/chat/provider.d.ts.map +0 -1
- package/dist/chat/provider.js +0 -635
- package/dist/chat/question.d.ts +0 -9
- package/dist/chat/question.d.ts.map +0 -1
- package/dist/chat/question.js +0 -69
- package/dist/chat/questions.d.ts +0 -31
- package/dist/chat/questions.d.ts.map +0 -1
- package/dist/chat/questions.js +0 -111
- package/dist/chat/read.d.ts +0 -5
- package/dist/chat/read.d.ts.map +0 -1
- package/dist/chat/read.js +0 -13
- package/dist/chat/ready.d.ts +0 -37
- package/dist/chat/ready.d.ts.map +0 -1
- package/dist/chat/ready.js +0 -93
- package/dist/chat/room-file.d.ts +0 -8
- package/dist/chat/room-file.d.ts.map +0 -1
- package/dist/chat/room-file.js +0 -72
- package/dist/chat/steady.d.ts +0 -3
- package/dist/chat/steady.d.ts.map +0 -1
- package/dist/chat/steady.js +0 -26
- package/dist/chat/style.d.ts +0 -4
- package/dist/chat/style.d.ts.map +0 -1
- package/dist/chat/style.js +0 -306
- package/dist/chat.d.ts +0 -15
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -8
package/dist/connection.js
CHANGED
|
@@ -6,7 +6,7 @@ import { filterOf, identityOf, planOf, plain, queryKey, } from './query.js';
|
|
|
6
6
|
import { createLiveQueries } from './live.js';
|
|
7
7
|
import { seededResult } from './seed.js';
|
|
8
8
|
import { documentKey, roomOfKey } from './rows.js';
|
|
9
|
-
import {
|
|
9
|
+
import { searchPlanOf } from './search.js';
|
|
10
10
|
import { capped, MAXIMUM_WINDOW } from './windows.js';
|
|
11
11
|
import { bootstrapRoom } from './bootstrap.js';
|
|
12
12
|
import { changeSet, mergeChangeSets } from './change-set.js';
|
|
@@ -17,18 +17,17 @@ import { runtimeError } from './failure.js';
|
|
|
17
17
|
import { createLog } from './log.js';
|
|
18
18
|
import { binaryRecord } from './binary.js';
|
|
19
19
|
import { openedFiles } from './opened.js';
|
|
20
|
-
import { ABSENT_TARGET, absentBinary,
|
|
20
|
+
import { ABSENT_TARGET, absentBinary, binaryAnswer, binaryLinkAnswer, calledResult, failure, historyAnswer, LOCKED_UNREADABLE, lockedRoom, undeclared, UNCERTAIN, UNREADABLE, } from './outcome.js';
|
|
21
21
|
import { createRecovery } from './recovery.js';
|
|
22
22
|
import { doorReads } from './reads.js';
|
|
23
23
|
import { advancement } from './refresh.js';
|
|
24
24
|
import { buildBatch, buildMove, joined, limiting, queuedDocuments, requeuing, } from './save.js';
|
|
25
25
|
import { compositionOf, entryAt, landed, movedBy, overlayOf, replaying, returning, returnedPaths, sameMembership, slicing, slicingWith, standing, storedRecord, touchedBy, } from './replica.js';
|
|
26
|
-
import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, moveTarget, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
|
|
26
|
+
import { binaryBase, changedParts, changesOf, gatheredParts, held, heldFile, joinedDraft, moveTarget, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
|
|
27
27
|
import { same, saving, STARTING, transferring, } from './status.js';
|
|
28
|
-
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
29
|
-
import { readinessOf,
|
|
28
|
+
import { detectStorageLoss, nextGeneration, refusedForSpace, } from './durability.js';
|
|
29
|
+
import { readinessOf, } from './readiness.js';
|
|
30
30
|
import { FencedMap } from './fence.js';
|
|
31
|
-
import { fingerprint } from './stored.js';
|
|
32
31
|
import { sameBinary } from './overlay.js';
|
|
33
32
|
import { paced, ROW_WEIGHT, stepped, yielded } from './turn.js';
|
|
34
33
|
import { identityTime, roomDraftRevision } from './identity.js';
|
|
@@ -37,15 +36,6 @@ import { openRuntimeStore, } from './store.js';
|
|
|
37
36
|
import { changedCleanly, sameComposition, validatedCollection, } from './validation.js';
|
|
38
37
|
import { unconfirmed } from './transfer.js';
|
|
39
38
|
import { governs, holds, narrowed, planReach, } from './reach.js';
|
|
40
|
-
function roomsAlone(plan) {
|
|
41
|
-
return (plan.reach === null &&
|
|
42
|
-
plan.root.types === null &&
|
|
43
|
-
plan.root.where === null &&
|
|
44
|
-
plan.root.pathPrefix === null &&
|
|
45
|
-
plan.paths === null &&
|
|
46
|
-
plan.hop === null &&
|
|
47
|
-
plan.backlinks === null);
|
|
48
|
-
}
|
|
49
39
|
function within(root, path) {
|
|
50
40
|
return root === '' || beneath(root, path);
|
|
51
41
|
}
|
|
@@ -63,12 +53,23 @@ const OPENING_LANE = 'opening';
|
|
|
63
53
|
const CONVERSATION_LANE = 'conversations';
|
|
64
54
|
const ROOM_READS = 256;
|
|
65
55
|
const ROOM_ATTEMPTS = 16;
|
|
66
|
-
const SEARCH_BATCH = 256;
|
|
67
56
|
const ERASE_BATCH = 512;
|
|
68
57
|
const REDERIVE_BATCH = 256;
|
|
69
58
|
const WALK_BYTES = 1 << 20;
|
|
70
59
|
const QUERY_BYTES = 1 << 20;
|
|
71
60
|
const STAGE_BYTES = 1 << 20;
|
|
61
|
+
const STAGE_FLOOR = 1 << 16;
|
|
62
|
+
// A batch aims at this much database work, so a Save or a query waits behind at most one short write.
|
|
63
|
+
const STAGE_AIM_MS = 50;
|
|
64
|
+
const PARTS_HELD = 4;
|
|
65
|
+
// Bytes still arriving are reported at most this often, well inside the 2 s progress bound, so readiness is not rebuilt for every chunk the network hands over.
|
|
66
|
+
const ARRIVAL_REPORT_MS = 250;
|
|
67
|
+
const INSTALL_STEPS = [
|
|
68
|
+
'writing',
|
|
69
|
+
'downloading',
|
|
70
|
+
'replaying',
|
|
71
|
+
'listing',
|
|
72
|
+
];
|
|
72
73
|
const CLEAN_CHANGES = 2_000;
|
|
73
74
|
// A Room that locks offline stays readable this long after the connection drops, so a laptop waking or changing network keeps working while a disconnected device locks soon after.
|
|
74
75
|
const LOCK_GRACE_MS = 120_000;
|
|
@@ -92,10 +93,9 @@ function pendingKey(locator, id) {
|
|
|
92
93
|
}
|
|
93
94
|
export function composeRuntime(options) {
|
|
94
95
|
const localSaves = options.adapter.localSaves === true;
|
|
95
|
-
const roomTypes = roomTypesOf(options.adapter.roomTypes);
|
|
96
|
+
const roomTypes = roomTypesOf(options.adapter.roomTypes, options.adapter.nameableRoomTypes);
|
|
96
97
|
let authorization = options.authorization;
|
|
97
98
|
let opening;
|
|
98
|
-
let store;
|
|
99
99
|
const fences = new Map();
|
|
100
100
|
const isFenced = (locator) => fences.has(locator);
|
|
101
101
|
let fencings = 0;
|
|
@@ -172,6 +172,7 @@ export function composeRuntime(options) {
|
|
|
172
172
|
let roomReads = { generation, free: ROOM_READS, waiting: [] };
|
|
173
173
|
const working = new Set();
|
|
174
174
|
let mutations = Promise.resolve();
|
|
175
|
+
let gathering = null;
|
|
175
176
|
const roomListeners = new Set();
|
|
176
177
|
const refusalListeners = new Set();
|
|
177
178
|
const storageLossListeners = new Set();
|
|
@@ -192,19 +193,39 @@ export function composeRuntime(options) {
|
|
|
192
193
|
const serviceModeListeners = new Set();
|
|
193
194
|
let member = null;
|
|
194
195
|
const memberListeners = new Set();
|
|
195
|
-
const prepared = new Set();
|
|
196
196
|
const unpreparedDemand = new Set();
|
|
197
|
-
const
|
|
197
|
+
const unpreparable = new Set();
|
|
198
198
|
let preparing = false;
|
|
199
199
|
let targets = {};
|
|
200
200
|
let persistence = options.shell.persistence;
|
|
201
|
-
let
|
|
202
|
-
|
|
203
|
-
let
|
|
201
|
+
let full = false;
|
|
202
|
+
let probing = null;
|
|
203
|
+
let testing = null;
|
|
204
|
+
const database = {
|
|
205
|
+
async ask(request) {
|
|
206
|
+
try {
|
|
207
|
+
return await options.database.ask(request);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
if (refusedForSpace(error))
|
|
211
|
+
filled();
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
};
|
|
204
216
|
const readinessWatches = new Set();
|
|
205
217
|
let readinessQueued = false;
|
|
206
218
|
const catching = new Set();
|
|
207
219
|
const transfers = new FencedMap(isFenced);
|
|
220
|
+
const installs = new Map();
|
|
221
|
+
const arrivals = new Map();
|
|
222
|
+
let arrivalReported = 0;
|
|
223
|
+
let onScreen = options.shell.onScreen;
|
|
224
|
+
const shownWaiters = [];
|
|
225
|
+
let partsFree = PARTS_HELD;
|
|
226
|
+
const partWaiters = [];
|
|
227
|
+
let stageBytes = STAGE_FLOOR * 4;
|
|
228
|
+
let installWrites = Promise.resolve();
|
|
208
229
|
const conversations = createConversationStore({
|
|
209
230
|
agents: declaredAgents,
|
|
210
231
|
documents: (declaration, after) => rowsQuery(planOf({
|
|
@@ -225,36 +246,13 @@ export function composeRuntime(options) {
|
|
|
225
246
|
stream: () => options.adapter.conversations,
|
|
226
247
|
publish: publishChangeSet,
|
|
227
248
|
online: () => connectionOf() !== 'unreachable' && refusal === null,
|
|
249
|
+
full: () => full,
|
|
228
250
|
closed: () => closed,
|
|
229
251
|
unanswered: resendLater,
|
|
230
252
|
expired: (operation) => pastHorizon(operation),
|
|
231
253
|
});
|
|
232
|
-
const searchIndex = createSearchIndex({
|
|
233
|
-
async *room(room) {
|
|
234
|
-
const local = await openLocal();
|
|
235
|
-
for (let after = null;;) {
|
|
236
|
-
const key = replicas.get(room)?.record.key;
|
|
237
|
-
if (key === undefined)
|
|
238
|
-
return;
|
|
239
|
-
const page = await local.searched(key, room, after, SEARCH_BATCH, null);
|
|
240
|
-
yield page.documents;
|
|
241
|
-
if (page.next === null)
|
|
242
|
-
return;
|
|
243
|
-
after = page.next;
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
async documents(room, paths) {
|
|
247
|
-
const key = replicas.get(room)?.record.key;
|
|
248
|
-
if (key === undefined || paths.length === 0)
|
|
249
|
-
return [];
|
|
250
|
-
return (await (await openLocal()).searched(key, room, null, paths.length, paths)).documents;
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
const roomsToIndex = new Set();
|
|
254
|
-
const conversationsToIndex = new Set();
|
|
255
|
-
let indexing = false;
|
|
256
254
|
const journal = createLog();
|
|
257
|
-
const keptFiles = openedFiles(
|
|
255
|
+
const keptFiles = openedFiles(database, isFenced);
|
|
258
256
|
let notifyReady = () => { };
|
|
259
257
|
const ready = new Promise((resolve) => {
|
|
260
258
|
notifyReady = resolve;
|
|
@@ -334,14 +332,38 @@ export function composeRuntime(options) {
|
|
|
334
332
|
const value = presentations.get(app);
|
|
335
333
|
return value === undefined ? null : structuredClone(value);
|
|
336
334
|
}
|
|
337
|
-
function
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
335
|
+
async function writtenRoomDraft(room, path, revision, input, next) {
|
|
336
|
+
const parts = readParts(input);
|
|
337
|
+
if (path === '' || normalize(path) !== path)
|
|
338
|
+
throw runtimeError('room-draft-malformed');
|
|
339
|
+
const local = await openLocal();
|
|
340
|
+
const current = roomDrafts.get(room)?.get(path);
|
|
341
|
+
if ((current?.revision ?? null) !== revision) {
|
|
342
|
+
return staleWrite(current === undefined ? null : ((await roomDraftsOf([current]))[0] ?? null));
|
|
343
|
+
}
|
|
344
|
+
const file = parts.file === undefined
|
|
345
|
+
? undefined
|
|
346
|
+
: await heldFile(parts.file.blob, binaryBase(await visibleOf(room, [path]), path));
|
|
347
|
+
const record = {
|
|
348
|
+
locator: room,
|
|
349
|
+
path,
|
|
350
|
+
revision: next,
|
|
351
|
+
parts: held(current?.parts, parts, file),
|
|
352
|
+
returned: current?.returned ?? null,
|
|
353
|
+
};
|
|
354
|
+
await local.keepRoomDraft(record, parts.file === undefined || file === undefined
|
|
355
|
+
? []
|
|
356
|
+
: [{ key: file.localId, blob: parts.file.blob }]);
|
|
357
|
+
rememberRoomDraft(record);
|
|
358
|
+
publishChangeSet(changeSet(room, { drafts: [path] }));
|
|
359
|
+
return { outcome: 'written', roomDraft: (await roomDraftsOf([record]))[0] ?? null };
|
|
360
|
+
}
|
|
361
|
+
async function keptPresentation(app, next) {
|
|
362
|
+
const local = await openLocal();
|
|
363
|
+
const stored = next(presentations.get(app));
|
|
364
|
+
await local.present({ app, value: stored });
|
|
365
|
+
presentations.set(app, stored);
|
|
366
|
+
publishChangeSet(changeSet('', { presentation: app }));
|
|
345
367
|
}
|
|
346
368
|
function emitPresentation(app) {
|
|
347
369
|
if (closed)
|
|
@@ -532,23 +554,26 @@ export function composeRuntime(options) {
|
|
|
532
554
|
}
|
|
533
555
|
function applyChangeSet(set) {
|
|
534
556
|
liveQueries.touched(set);
|
|
535
|
-
searchIndex.changed(set);
|
|
536
557
|
}
|
|
537
558
|
function unpublished(error) {
|
|
538
559
|
journal.record(`publication failed ${error instanceof Error ? error.name : 'unknown'}`);
|
|
539
560
|
}
|
|
561
|
+
function indexed(locator) {
|
|
562
|
+
return replicas.get(locator)?.indexed === true;
|
|
563
|
+
}
|
|
540
564
|
async function prepareReplica(locator) {
|
|
541
565
|
for (;;) {
|
|
542
566
|
const held = replicas.get(locator);
|
|
543
|
-
if (held === undefined || closed ||
|
|
567
|
+
if (held === undefined || closed || held.indexed)
|
|
544
568
|
return;
|
|
545
569
|
const key = held.record.key;
|
|
546
570
|
const done = await exclusive(async () => (await openLocal()).rederive(key, REDERIVE_BATCH));
|
|
547
|
-
|
|
571
|
+
const now = replicas.get(locator);
|
|
572
|
+
if (now?.record.key !== key)
|
|
548
573
|
continue;
|
|
549
574
|
if (!done)
|
|
550
575
|
continue;
|
|
551
|
-
|
|
576
|
+
replicas.set(locator, { ...now, indexed: true });
|
|
552
577
|
publishChangeSet(changeSet(locator, { documents: null }));
|
|
553
578
|
liveQueries.recover();
|
|
554
579
|
queueReadiness();
|
|
@@ -556,11 +581,11 @@ export function composeRuntime(options) {
|
|
|
556
581
|
}
|
|
557
582
|
}
|
|
558
583
|
function unprepared() {
|
|
559
|
-
return [...unpreparedDemand, ...replicas.keys()].find((locator) => !
|
|
584
|
+
return [...unpreparedDemand, ...replicas.keys()].find((locator) => !indexed(locator) && !unpreparable.has(locator));
|
|
560
585
|
}
|
|
561
|
-
function
|
|
562
|
-
|
|
563
|
-
journal.record(`
|
|
586
|
+
function unpreparedBy(locator, error) {
|
|
587
|
+
unpreparable.add(locator);
|
|
588
|
+
journal.record(`preparation failed ${locator} ${error instanceof Error ? error.name : 'unknown'}`);
|
|
564
589
|
queueReadiness();
|
|
565
590
|
}
|
|
566
591
|
function prepare(locators = []) {
|
|
@@ -576,7 +601,7 @@ export function composeRuntime(options) {
|
|
|
576
601
|
if (closed)
|
|
577
602
|
return;
|
|
578
603
|
unpreparedDemand.delete(waiting);
|
|
579
|
-
await prepareReplica(waiting).catch((error) =>
|
|
604
|
+
await prepareReplica(waiting).catch((error) => unpreparedBy(waiting, error));
|
|
580
605
|
}
|
|
581
606
|
}
|
|
582
607
|
finally {
|
|
@@ -617,7 +642,7 @@ export function composeRuntime(options) {
|
|
|
617
642
|
pending.push({ room: locator, awaits: 'acquisition' });
|
|
618
643
|
continue;
|
|
619
644
|
}
|
|
620
|
-
if (!
|
|
645
|
+
if (!replica.indexed) {
|
|
621
646
|
pending.push({ room: locator, awaits: 'preparation' });
|
|
622
647
|
continue;
|
|
623
648
|
}
|
|
@@ -650,7 +675,7 @@ export function composeRuntime(options) {
|
|
|
650
675
|
return { listed: true, rooms: named.filter((room) => rooms.has(room)) };
|
|
651
676
|
return { listed: named.every((room) => replicas.has(room)), rooms: named };
|
|
652
677
|
}
|
|
653
|
-
function roomState(locator
|
|
678
|
+
function roomState(locator) {
|
|
654
679
|
if (sourceRefusals.has(locator))
|
|
655
680
|
return 'refused';
|
|
656
681
|
const listedRoom = discovered.get(locator);
|
|
@@ -661,27 +686,17 @@ export function composeRuntime(options) {
|
|
|
661
686
|
return 'unadmitted';
|
|
662
687
|
if (!replicas.has(locator))
|
|
663
688
|
return 'acquiring';
|
|
664
|
-
if (!
|
|
689
|
+
if (!indexed(locator))
|
|
665
690
|
return 'preparing';
|
|
666
|
-
if (search && !searchIndex.rooms().has(locator))
|
|
667
|
-
return 'indexing';
|
|
668
691
|
return 'complete';
|
|
669
692
|
}
|
|
670
|
-
function conversationCoverage(reach
|
|
693
|
+
function conversationCoverage(reach) {
|
|
671
694
|
const declared = reach === null || reach.conversations;
|
|
672
695
|
if (!declared || options.adapter.conversations === undefined || declaredAgents() === undefined)
|
|
673
696
|
return 'undeclared';
|
|
674
697
|
if (!conversations.known() || !conversations.current())
|
|
675
698
|
return 'acquiring';
|
|
676
|
-
|
|
677
|
-
return 'complete';
|
|
678
|
-
const unindexed = conversations
|
|
679
|
-
.searchable()
|
|
680
|
-
.filter((one) => conversations.acquired(one) && !searchIndex.conversations().has(one));
|
|
681
|
-
if (unindexed.length === 0)
|
|
682
|
-
return 'complete';
|
|
683
|
-
index([], unindexed);
|
|
684
|
-
return 'indexing';
|
|
699
|
+
return 'complete';
|
|
685
700
|
}
|
|
686
701
|
function connectionOf() {
|
|
687
702
|
if (socketOpen)
|
|
@@ -701,6 +716,7 @@ export function composeRuntime(options) {
|
|
|
701
716
|
return [
|
|
702
717
|
{
|
|
703
718
|
room,
|
|
719
|
+
name: discovered.get(room)?.name ?? room,
|
|
704
720
|
file: typeof file === 'string' ? file : '',
|
|
705
721
|
rule: held.code,
|
|
706
722
|
location: typeof location === 'string' ? location : null,
|
|
@@ -708,34 +724,73 @@ export function composeRuntime(options) {
|
|
|
708
724
|
];
|
|
709
725
|
});
|
|
710
726
|
}
|
|
727
|
+
function installationOf(coverage) {
|
|
728
|
+
const rooms = { done: 0, total: 0 };
|
|
729
|
+
const parts = { done: 0, total: 0 };
|
|
730
|
+
const bytes = { done: 0, total: 0 };
|
|
731
|
+
const received = { done: 0, total: 0 };
|
|
732
|
+
const steps = new Set();
|
|
733
|
+
let waiting = false;
|
|
734
|
+
for (const { room, state } of coverage) {
|
|
735
|
+
if (state === 'unadmitted')
|
|
736
|
+
continue;
|
|
737
|
+
rooms.total += 1;
|
|
738
|
+
if (state === 'complete')
|
|
739
|
+
rooms.done += 1;
|
|
740
|
+
const transfer = transfers.get(room);
|
|
741
|
+
const held = installs.get(room);
|
|
742
|
+
const inventory = transfer?.inventory ?? replicas.get(room)?.record.inventory ?? null;
|
|
743
|
+
if (inventory !== null) {
|
|
744
|
+
const done = transfer?.done ?? inventory;
|
|
745
|
+
const share = held?.share ?? null;
|
|
746
|
+
parts.total += inventory.parts;
|
|
747
|
+
bytes.total += inventory.bytes;
|
|
748
|
+
parts.done += done.parts;
|
|
749
|
+
bytes.done += done.bytes;
|
|
750
|
+
if (share !== null && share.entries > 0) {
|
|
751
|
+
bytes.done += Math.floor((share.bytes * share.written) / share.entries);
|
|
752
|
+
}
|
|
753
|
+
received.total += inventory.bytes;
|
|
754
|
+
received.done += done.bytes + (share?.bytes ?? 0);
|
|
755
|
+
for (const arrived of arrivals.get(room)?.values() ?? [])
|
|
756
|
+
received.done += arrived;
|
|
757
|
+
}
|
|
758
|
+
const installing = held !== undefined && state !== 'complete';
|
|
759
|
+
if (installing)
|
|
760
|
+
steps.add(held.step);
|
|
761
|
+
waiting ||= installing || state === 'acquiring';
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
step: INSTALL_STEPS.find((step) => steps.has(step)) ?? null,
|
|
765
|
+
paused: waiting && !onScreen ? 'hidden' : null,
|
|
766
|
+
rooms,
|
|
767
|
+
parts,
|
|
768
|
+
bytes,
|
|
769
|
+
received,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
711
772
|
function readinessFor(port, reach) {
|
|
712
773
|
const required = requiredRooms(reach);
|
|
713
774
|
const search = port.search === 'required';
|
|
714
|
-
const coverage = required.rooms.map((room) => ({ room, state: roomState(room
|
|
715
|
-
const
|
|
716
|
-
.filter((room) => room.state ===
|
|
775
|
+
const coverage = required.rooms.map((room) => ({ room, state: roomState(room) }));
|
|
776
|
+
const preparing = coverage
|
|
777
|
+
.filter((room) => room.state === 'preparing' && !unpreparable.has(room.room))
|
|
717
778
|
.map(({ room }) => room);
|
|
718
|
-
const preparing = building('preparing');
|
|
719
779
|
if (preparing.length > 0)
|
|
720
780
|
prepare(preparing);
|
|
721
|
-
const indexing = building('indexing');
|
|
722
|
-
if (indexing.length > 0)
|
|
723
|
-
index(indexing, []);
|
|
724
781
|
return readinessOf({
|
|
725
782
|
code: port.code,
|
|
726
783
|
coverage: {
|
|
727
784
|
listed: required.listed,
|
|
728
785
|
rooms: coverage,
|
|
729
|
-
conversations: conversationCoverage(reach
|
|
786
|
+
conversations: conversationCoverage(reach),
|
|
730
787
|
search: search ? 'required' : 'unused',
|
|
731
788
|
},
|
|
732
789
|
persistence,
|
|
733
790
|
connection: connectionOf(),
|
|
734
791
|
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
? capacity
|
|
738
|
-
: port.capacity,
|
|
792
|
+
space: full ? 'full' : port.space,
|
|
793
|
+
installation: installationOf(coverage),
|
|
739
794
|
refused: refusedIn(required.rooms),
|
|
740
795
|
lost: storageLoss !== null,
|
|
741
796
|
established: targets[port.target]?.established === true,
|
|
@@ -744,6 +799,7 @@ export function composeRuntime(options) {
|
|
|
744
799
|
function establish(name) {
|
|
745
800
|
targets = { ...targets, [name]: { established: true } };
|
|
746
801
|
queueReadiness();
|
|
802
|
+
probeEachMinute();
|
|
747
803
|
void durably((local) => local.fact('targets', targets)).catch(() => {
|
|
748
804
|
journal.record(`target record failed ${name}`);
|
|
749
805
|
});
|
|
@@ -781,87 +837,51 @@ export function composeRuntime(options) {
|
|
|
781
837
|
readinessWatches.delete(watch);
|
|
782
838
|
};
|
|
783
839
|
}
|
|
784
|
-
function
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
}
|
|
790
|
-
function exceeded(error) {
|
|
791
|
-
return (typeof error === 'object' &&
|
|
792
|
-
error !== null &&
|
|
793
|
-
error.name === 'QuotaExceededError');
|
|
794
|
-
}
|
|
795
|
-
async function estimated() {
|
|
796
|
-
const estimate = options.shell.estimate === undefined ? null : await options.shell.estimate();
|
|
797
|
-
return estimate === null ? null : Math.max(0, estimate.total - estimate.usage);
|
|
798
|
-
}
|
|
799
|
-
function pause(locator, cause) {
|
|
800
|
-
paused.add(locator);
|
|
801
|
-
capacity = cause;
|
|
802
|
-
journal.record(`capacity ${cause.cause} ${locator} ${cause.needed} ${cause.available ?? '-'}`);
|
|
840
|
+
function filled() {
|
|
841
|
+
if (full || closed)
|
|
842
|
+
return;
|
|
843
|
+
full = true;
|
|
844
|
+
journal.record('space full');
|
|
803
845
|
queueReadiness();
|
|
804
|
-
|
|
846
|
+
probeEachMinute();
|
|
847
|
+
}
|
|
848
|
+
function installedTarget() {
|
|
849
|
+
return Object.values(targets).some((target) => target.established);
|
|
850
|
+
}
|
|
851
|
+
function probeEachMinute() {
|
|
852
|
+
if (!full || probing !== null || closed || !installedTarget())
|
|
805
853
|
return;
|
|
806
|
-
// A
|
|
807
|
-
|
|
808
|
-
void
|
|
854
|
+
// A device whose storage filled after its install tries a write each minute, so space freed elsewhere ends its read-only mode without a Retry.
|
|
855
|
+
probing = setInterval(() => {
|
|
856
|
+
void probed();
|
|
809
857
|
}, 60_000);
|
|
810
858
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
resume();
|
|
859
|
+
function probed() {
|
|
860
|
+
testing ??= database.ask({ kind: 'space.probe' }).then(() => {
|
|
861
|
+
testing = null;
|
|
862
|
+
spaceFreed();
|
|
863
|
+
return true;
|
|
864
|
+
}, () => {
|
|
865
|
+
testing = null;
|
|
866
|
+
return false;
|
|
867
|
+
});
|
|
868
|
+
return testing;
|
|
822
869
|
}
|
|
823
|
-
function
|
|
824
|
-
if (
|
|
825
|
-
clearInterval(rechecking);
|
|
826
|
-
rechecking = null;
|
|
827
|
-
if (paused.size === 0 && capacity === null)
|
|
870
|
+
function spaceFreed() {
|
|
871
|
+
if (!full)
|
|
828
872
|
return;
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
873
|
+
full = false;
|
|
874
|
+
if (probing !== null)
|
|
875
|
+
clearInterval(probing);
|
|
876
|
+
probing = null;
|
|
877
|
+
unpreparable.clear();
|
|
878
|
+
journal.record('space freed');
|
|
832
879
|
queueReadiness();
|
|
833
880
|
if (!closed && authorization !== undefined)
|
|
834
881
|
void sync('unstreamed');
|
|
835
882
|
}
|
|
836
|
-
async function reclaimed(local) {
|
|
837
|
-
const unreferenced = [...transfers.keys()].filter((locator) => !discovered.has(locator));
|
|
838
|
-
for (const locator of unreferenced) {
|
|
839
|
-
await local.abandon(locator);
|
|
840
|
-
transfers.delete(locator);
|
|
841
|
-
}
|
|
842
|
-
if (unreferenced.length > 0)
|
|
843
|
-
journal.record(`capacity reclaimed ${unreferenced.length}`);
|
|
844
|
-
return unreferenced.length > 0;
|
|
845
|
-
}
|
|
846
|
-
async function admitted(local, locator, needed) {
|
|
847
|
-
if (paused.has(locator))
|
|
848
|
-
return false;
|
|
849
|
-
let available = await estimated().catch(() => null);
|
|
850
|
-
if (available === null || available >= needed)
|
|
851
|
-
return true;
|
|
852
|
-
if (await reclaimed(local))
|
|
853
|
-
available = await estimated().catch(() => null);
|
|
854
|
-
if (available === null || available >= needed)
|
|
855
|
-
return true;
|
|
856
|
-
pause(locator, { cause: 'insufficient-space', needed, available });
|
|
857
|
-
return false;
|
|
858
|
-
}
|
|
859
|
-
async function exceededOn(locator, bytes) {
|
|
860
|
-
const available = await estimated().catch(() => null);
|
|
861
|
-
pause(locator, { cause: 'storage-exceeded', needed: bytes, available });
|
|
862
|
-
}
|
|
863
883
|
function seedRoomHeld(room, version) {
|
|
864
|
-
return
|
|
884
|
+
return indexed(room) && reachedBy(room, version);
|
|
865
885
|
}
|
|
866
886
|
function seedHeld(at) {
|
|
867
887
|
return Object.entries(at).every(([room, version]) => (roomsListed && !rooms.has(room)) || seedRoomHeld(room, version));
|
|
@@ -982,81 +1002,6 @@ export function composeRuntime(options) {
|
|
|
982
1002
|
const held = answered.cover;
|
|
983
1003
|
return { ...answered.rows, coverage: held.coverage };
|
|
984
1004
|
}
|
|
985
|
-
async function everyRow(plan) {
|
|
986
|
-
const window = { ...plan, limit: MAXIMUM_WINDOW, after: null, total: false };
|
|
987
|
-
const first = await rowsQuery(window);
|
|
988
|
-
if ('pending' in first || first.next === undefined)
|
|
989
|
-
return first;
|
|
990
|
-
const documents = 'documents' in first ? [...first.documents] : [];
|
|
991
|
-
const groups = 'groups' in first ? [...first.groups] : [];
|
|
992
|
-
for (let next = first.next; next !== undefined;) {
|
|
993
|
-
const values = JSON.parse(next);
|
|
994
|
-
const page = await rowsQuery({
|
|
995
|
-
...window,
|
|
996
|
-
after: { text: next, values: values.length === 0 ? null : values },
|
|
997
|
-
});
|
|
998
|
-
if ('pending' in page)
|
|
999
|
-
return page;
|
|
1000
|
-
if ('documents' in page)
|
|
1001
|
-
documents.push(...page.documents);
|
|
1002
|
-
else
|
|
1003
|
-
groups.push(...page.groups);
|
|
1004
|
-
if (JSON.stringify(page.coverage) !== JSON.stringify(first.coverage))
|
|
1005
|
-
return everyRow(plan);
|
|
1006
|
-
next = page.next;
|
|
1007
|
-
}
|
|
1008
|
-
return 'documents' in first
|
|
1009
|
-
? { documents, coverage: first.coverage }
|
|
1010
|
-
: { groups, coverage: first.coverage };
|
|
1011
|
-
}
|
|
1012
|
-
async function indexConversation(conversation) {
|
|
1013
|
-
searchIndex.indexMessages(conversation, [], false);
|
|
1014
|
-
let after = null;
|
|
1015
|
-
for (;;) {
|
|
1016
|
-
const batch = await durably((local) => local.messagesAfter(conversation, after));
|
|
1017
|
-
if (closed)
|
|
1018
|
-
return;
|
|
1019
|
-
searchIndex.indexMessages(conversation, batch.map(({ identity, text }) => ({ conversation, identity, text })), batch.length === 0);
|
|
1020
|
-
if (batch.length === 0)
|
|
1021
|
-
return;
|
|
1022
|
-
after = batch[batch.length - 1].identity;
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
function indexNext() {
|
|
1026
|
-
const room = [...roomsToIndex].find((locator) => !searchIndex.rooms().has(locator) && !unindexable.has(locator));
|
|
1027
|
-
if (room !== undefined) {
|
|
1028
|
-
roomsToIndex.delete(room);
|
|
1029
|
-
return () => prepared.has(room)
|
|
1030
|
-
? searchIndex.indexRoom(room).catch((error) => unbuilt(room, error))
|
|
1031
|
-
: undefined;
|
|
1032
|
-
}
|
|
1033
|
-
const conversation = [...conversationsToIndex].find((one) => !searchIndex.conversations().has(one));
|
|
1034
|
-
if (conversation === undefined)
|
|
1035
|
-
return null;
|
|
1036
|
-
conversationsToIndex.delete(conversation);
|
|
1037
|
-
return () => indexConversation(conversation);
|
|
1038
|
-
}
|
|
1039
|
-
function index(rooms, named) {
|
|
1040
|
-
for (const room of rooms)
|
|
1041
|
-
roomsToIndex.add(room);
|
|
1042
|
-
for (const conversation of named)
|
|
1043
|
-
conversationsToIndex.add(conversation);
|
|
1044
|
-
if (indexing)
|
|
1045
|
-
return;
|
|
1046
|
-
indexing = true;
|
|
1047
|
-
void (async () => {
|
|
1048
|
-
try {
|
|
1049
|
-
for (let unit = indexNext(); unit !== null && !closed; unit = indexNext()) {
|
|
1050
|
-
await yielded();
|
|
1051
|
-
await unit();
|
|
1052
|
-
queueReadiness();
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
finally {
|
|
1056
|
-
indexing = false;
|
|
1057
|
-
}
|
|
1058
|
-
})().catch(() => { });
|
|
1059
|
-
}
|
|
1060
1005
|
function publishChangeSet(set) {
|
|
1061
1006
|
applyChangeSet(set);
|
|
1062
1007
|
if (set.rooms.length > 0 ||
|
|
@@ -1086,9 +1031,10 @@ export function composeRuntime(options) {
|
|
|
1086
1031
|
held,
|
|
1087
1032
|
record: { ...held.record, model: revoked, selectionModel: revoked },
|
|
1088
1033
|
}));
|
|
1034
|
+
let answer = null;
|
|
1089
1035
|
try {
|
|
1090
1036
|
const local = await openLocal();
|
|
1091
|
-
await local.commit({
|
|
1037
|
+
answer = await local.commit({
|
|
1092
1038
|
rooms: records.map(({ locator, record }) => ({
|
|
1093
1039
|
locator,
|
|
1094
1040
|
key: record.key,
|
|
@@ -1114,8 +1060,8 @@ export function composeRuntime(options) {
|
|
|
1114
1060
|
for (const { locator, held, record } of records) {
|
|
1115
1061
|
if (replicas.get(locator) !== held)
|
|
1116
1062
|
continue;
|
|
1117
|
-
|
|
1118
|
-
replicas.set(locator, { ...held, record });
|
|
1063
|
+
const indexed = answer === null ? held.indexed : committedRoom(locator, answer).indexed;
|
|
1064
|
+
replicas.set(locator, { ...held, record, indexed });
|
|
1119
1065
|
}
|
|
1120
1066
|
for (const locator of [...rooms.keys()]) {
|
|
1121
1067
|
const room = rooms.get(locator);
|
|
@@ -1255,9 +1201,7 @@ export function composeRuntime(options) {
|
|
|
1255
1201
|
unreadable.delete(locator);
|
|
1256
1202
|
stale.delete(locator);
|
|
1257
1203
|
repairable.delete(locator);
|
|
1258
|
-
|
|
1259
|
-
paused.delete(locator);
|
|
1260
|
-
prepared.delete(locator);
|
|
1204
|
+
unpreparable.delete(locator);
|
|
1261
1205
|
scopedRefusals.delete(locator);
|
|
1262
1206
|
sourceRefusals.delete(locator);
|
|
1263
1207
|
declarationRefusals.delete(locator);
|
|
@@ -1271,7 +1215,6 @@ export function composeRuntime(options) {
|
|
|
1271
1215
|
}
|
|
1272
1216
|
if (fresh.length === 0)
|
|
1273
1217
|
return fresh;
|
|
1274
|
-
searchIndex.forget(fresh);
|
|
1275
1218
|
publishChangeSet(mergeChangeSets(removed.map(({ locator, drafts }) => changeSet(locator, { documents: null, room: true, queue: true, drafts }))));
|
|
1276
1219
|
liveQueries.recover();
|
|
1277
1220
|
moveTo({ ...status, ...attention() });
|
|
@@ -1365,7 +1308,6 @@ export function composeRuntime(options) {
|
|
|
1365
1308
|
}
|
|
1366
1309
|
}
|
|
1367
1310
|
replicas.clear();
|
|
1368
|
-
prepared.clear();
|
|
1369
1311
|
queuesWithoutReplica.clear();
|
|
1370
1312
|
for (const [locator, queue] of queuedBatches) {
|
|
1371
1313
|
if (!held.rooms.has(locator))
|
|
@@ -1383,9 +1325,8 @@ export function composeRuntime(options) {
|
|
|
1383
1325
|
overlaid: held.overlaid.get(record.key) ?? new Set(),
|
|
1384
1326
|
count: counts[index] ?? 0,
|
|
1385
1327
|
confirmed: 0,
|
|
1328
|
+
indexed: !held.unindexed.has(record.key),
|
|
1386
1329
|
});
|
|
1387
|
-
if (!held.unprepared.has(record.key))
|
|
1388
|
-
prepared.add(record.locator);
|
|
1389
1330
|
const existing = rooms.get(record.locator);
|
|
1390
1331
|
if (existing === undefined)
|
|
1391
1332
|
list(record, false);
|
|
@@ -1431,8 +1372,7 @@ export function composeRuntime(options) {
|
|
|
1431
1372
|
async function open() {
|
|
1432
1373
|
if (closed)
|
|
1433
1374
|
throw new Error('runtime closed');
|
|
1434
|
-
const opened =
|
|
1435
|
-
store = opened;
|
|
1375
|
+
const opened = openRuntimeStore(isFenced, database);
|
|
1436
1376
|
const interrupted = await opened.fences();
|
|
1437
1377
|
for (const locator of interrupted) {
|
|
1438
1378
|
fences.set(locator, { at: discoveries, erased: false });
|
|
@@ -1478,10 +1418,29 @@ export function composeRuntime(options) {
|
|
|
1478
1418
|
return opening;
|
|
1479
1419
|
}
|
|
1480
1420
|
function exclusive(work) {
|
|
1421
|
+
gathering = null;
|
|
1481
1422
|
const next = mutations.then(work, work);
|
|
1482
1423
|
mutations = next.then(() => undefined, () => undefined);
|
|
1483
1424
|
return next;
|
|
1484
1425
|
}
|
|
1426
|
+
function gathered(key, input, merge, work) {
|
|
1427
|
+
const tail = gathering;
|
|
1428
|
+
if (tail !== null && tail.key === key) {
|
|
1429
|
+
const merged = merge(tail.input, input);
|
|
1430
|
+
if (merged !== null) {
|
|
1431
|
+
tail.input = merged;
|
|
1432
|
+
return tail.done;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
const entry = { key, input, done: Promise.resolve() };
|
|
1436
|
+
entry.done = exclusive(() => {
|
|
1437
|
+
if (gathering === entry)
|
|
1438
|
+
gathering = null;
|
|
1439
|
+
return work(entry.input);
|
|
1440
|
+
});
|
|
1441
|
+
gathering = entry;
|
|
1442
|
+
return entry.done;
|
|
1443
|
+
}
|
|
1485
1444
|
function withdrawable(locator) {
|
|
1486
1445
|
return (batch) => {
|
|
1487
1446
|
const state = pendings.get(pendingKey(locator, batch.id));
|
|
@@ -1563,23 +1522,11 @@ export function composeRuntime(options) {
|
|
|
1563
1522
|
},
|
|
1564
1523
|
};
|
|
1565
1524
|
}
|
|
1566
|
-
function
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
fingerprint(compositionOf(before)) === fingerprint(compositionOf(after))) {
|
|
1570
|
-
return;
|
|
1571
|
-
}
|
|
1572
|
-
if (before !== undefined && before.key === after.key)
|
|
1573
|
-
prepared.delete(locator);
|
|
1574
|
-
}
|
|
1575
|
-
function committedRoom(locator, record, answer) {
|
|
1576
|
-
if (answer.prepared[locator] === false)
|
|
1577
|
-
prepared.delete(locator);
|
|
1578
|
-
else if (answer.prepared[locator] === true)
|
|
1579
|
-
prepared.add(locator);
|
|
1580
|
-
if (!prepared.has(locator))
|
|
1525
|
+
function committedRoom(locator, answer) {
|
|
1526
|
+
const indexed = answer.indexed[locator] ?? replicas.get(locator)?.indexed ?? false;
|
|
1527
|
+
if (!indexed)
|
|
1581
1528
|
prepare([locator]);
|
|
1582
|
-
return { count: answer.counts[locator] ?? replicas.get(locator)?.count ?? 0 };
|
|
1529
|
+
return { count: answer.counts[locator] ?? replicas.get(locator)?.count ?? 0, indexed };
|
|
1583
1530
|
}
|
|
1584
1531
|
function replayed(record, confirmedLayer, queue) {
|
|
1585
1532
|
return stepped(replaying(confirmedLayer, queue, record.locator, compositionOf(record)));
|
|
@@ -1729,14 +1676,14 @@ export function composeRuntime(options) {
|
|
|
1729
1676
|
const confirmedChanged = settling.commit?.published !== undefined ||
|
|
1730
1677
|
settling.commit?.refreshed === true ||
|
|
1731
1678
|
overrides !== undefined;
|
|
1732
|
-
|
|
1733
|
-
const { count } = committedRoom(locator, record, answer);
|
|
1679
|
+
const { count, indexed } = committedRoom(locator, answer);
|
|
1734
1680
|
replicas.set(locator, {
|
|
1735
1681
|
record,
|
|
1736
1682
|
queue: kept,
|
|
1737
1683
|
overlaid: touched,
|
|
1738
1684
|
count,
|
|
1739
1685
|
confirmed: (before?.confirmed ?? 0) + (confirmedChanged ? 1 : 0),
|
|
1686
|
+
indexed,
|
|
1740
1687
|
});
|
|
1741
1688
|
return mergeChangeSets([
|
|
1742
1689
|
changeSet(locator, {
|
|
@@ -1782,7 +1729,7 @@ export function composeRuntime(options) {
|
|
|
1782
1729
|
};
|
|
1783
1730
|
}
|
|
1784
1731
|
function publish(local, room, record, landing, live) {
|
|
1785
|
-
|
|
1732
|
+
const landed = exclusive(async () => {
|
|
1786
1733
|
if (!live()) {
|
|
1787
1734
|
caughtUpTo(room.locator);
|
|
1788
1735
|
return true;
|
|
@@ -1819,9 +1766,8 @@ export function composeRuntime(options) {
|
|
|
1819
1766
|
});
|
|
1820
1767
|
}
|
|
1821
1768
|
catch (error) {
|
|
1822
|
-
if (!
|
|
1769
|
+
if (!refusedForSpace(error))
|
|
1823
1770
|
throw error;
|
|
1824
|
-
await exceededOn(room.locator, null);
|
|
1825
1771
|
caughtUpTo(room.locator);
|
|
1826
1772
|
return true;
|
|
1827
1773
|
}
|
|
@@ -1833,7 +1779,7 @@ export function composeRuntime(options) {
|
|
|
1833
1779
|
for (const batch of accepted)
|
|
1834
1780
|
pendings.delete(pendingKey(room.locator, batch.id));
|
|
1835
1781
|
stale.delete(room.locator);
|
|
1836
|
-
|
|
1782
|
+
unpreparable.delete(room.locator);
|
|
1837
1783
|
if (unreadable.delete(room.locator))
|
|
1838
1784
|
scopedRefusals.delete(room.locator);
|
|
1839
1785
|
sourceRefusals.delete(room.locator);
|
|
@@ -1848,6 +1794,7 @@ export function composeRuntime(options) {
|
|
|
1848
1794
|
}
|
|
1849
1795
|
return true;
|
|
1850
1796
|
});
|
|
1797
|
+
return landing.kind === 'refreshed' ? landed : landed.finally(() => installed(room.locator));
|
|
1851
1798
|
}
|
|
1852
1799
|
async function savedRoom(locator) {
|
|
1853
1800
|
const local = await openLocal();
|
|
@@ -2244,6 +2191,8 @@ export function composeRuntime(options) {
|
|
|
2244
2191
|
submitLater(locator);
|
|
2245
2192
|
}
|
|
2246
2193
|
async function committed(locator, target, saved, consumed, writtenBy, files = []) {
|
|
2194
|
+
if (full)
|
|
2195
|
+
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
2247
2196
|
const into = movedInto(locator, saved);
|
|
2248
2197
|
if (into !== null) {
|
|
2249
2198
|
return committedMove(locator, into, target, saved, consumed, writtenBy, files);
|
|
@@ -2522,11 +2471,6 @@ export function composeRuntime(options) {
|
|
|
2522
2471
|
return (await openLocal()).file(localId, change?.kind === 'binary' ? change.mediaType : '');
|
|
2523
2472
|
};
|
|
2524
2473
|
}
|
|
2525
|
-
function binaryRoom(request) {
|
|
2526
|
-
if (request.kind === 'path')
|
|
2527
|
-
return request.locator;
|
|
2528
|
-
return options.adapter.referrerScope(request.referrer)?.locator ?? null;
|
|
2529
|
-
}
|
|
2530
2474
|
async function transferBinary(request, transfer, answer) {
|
|
2531
2475
|
if (closed || refusal !== null)
|
|
2532
2476
|
return UNCERTAIN;
|
|
@@ -2539,16 +2483,13 @@ export function composeRuntime(options) {
|
|
|
2539
2483
|
if (closed || authorization === undefined || refusal !== null)
|
|
2540
2484
|
return UNCERTAIN;
|
|
2541
2485
|
const since = fencings;
|
|
2542
|
-
journal.record(request.
|
|
2543
|
-
? `transfer binary ${request.locator} ${request.path}`
|
|
2544
|
-
: `transfer binary ${request.fileRef}`);
|
|
2486
|
+
journal.record(`transfer binary ${request.locator} ${request.path}`);
|
|
2545
2487
|
try {
|
|
2546
2488
|
const result = await transfer(authorization);
|
|
2547
2489
|
const settled = result;
|
|
2548
2490
|
journal.record(`outcome binary ${settled.kind}`);
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
return failure({ kind: 'room-not-readable', locator });
|
|
2491
|
+
if (fencedSince(request.locator, since))
|
|
2492
|
+
return failure({ kind: 'room-not-readable', locator: request.locator });
|
|
2552
2493
|
if (settled.kind === 'scoped-refusal') {
|
|
2553
2494
|
const room = replicas.get(settled.locator)?.record;
|
|
2554
2495
|
if (room !== undefined)
|
|
@@ -2595,7 +2536,7 @@ export function composeRuntime(options) {
|
|
|
2595
2536
|
const holds = (at) => {
|
|
2596
2537
|
const kept = confirmedLayer.binaries[at];
|
|
2597
2538
|
return (kept?.state === 'accepted' &&
|
|
2598
|
-
kept.
|
|
2539
|
+
kept.sha256 === record.sha256 &&
|
|
2599
2540
|
confirmedLayer.revisions[at] === record.revision);
|
|
2600
2541
|
};
|
|
2601
2542
|
let confirmedAt = path;
|
|
@@ -2609,7 +2550,6 @@ export function composeRuntime(options) {
|
|
|
2609
2550
|
locator,
|
|
2610
2551
|
path: confirmedAt,
|
|
2611
2552
|
revision: record.revision,
|
|
2612
|
-
fileRef: record.fileRef,
|
|
2613
2553
|
};
|
|
2614
2554
|
}
|
|
2615
2555
|
async function readBinaryPath(locator, path, revision) {
|
|
@@ -2640,18 +2580,11 @@ export function composeRuntime(options) {
|
|
|
2640
2580
|
return absentBinary(path);
|
|
2641
2581
|
return transferBinary(asked, (held) => options.adapter.link(held, asked), binaryLinkAnswer);
|
|
2642
2582
|
}
|
|
2643
|
-
async function readBinaryReference(reference) {
|
|
2644
|
-
await ready;
|
|
2645
|
-
const scope = options.adapter.referrerScope(reference.referrer);
|
|
2646
|
-
if (scope === null)
|
|
2647
|
-
return absentReference(reference.fileRef);
|
|
2648
|
-
return readBinary({ kind: 'reference', fileRef: reference.fileRef, referrer: reference.referrer }, { locator: scope.locator, path: scope.path ?? '', revision: reference.fileRef });
|
|
2649
|
-
}
|
|
2650
2583
|
function readPart(file, part) {
|
|
2651
|
-
return
|
|
2584
|
+
return database.ask({ kind: 'file.part', key: file, part });
|
|
2652
2585
|
}
|
|
2653
2586
|
async function cleanUp() {
|
|
2654
|
-
const freed = await
|
|
2587
|
+
const freed = await database.ask({ kind: 'opened.clean' });
|
|
2655
2588
|
return freed + ((await options.shell.cleanUp?.()) ?? 0);
|
|
2656
2589
|
}
|
|
2657
2590
|
function declaredAgents() {
|
|
@@ -2878,41 +2811,108 @@ export function composeRuntime(options) {
|
|
|
2878
2811
|
return unreadableRoom(texts, walk, named);
|
|
2879
2812
|
return null;
|
|
2880
2813
|
}
|
|
2881
|
-
function refreshingOf(local, key, liveness) {
|
|
2814
|
+
function refreshingOf(local, key, liveness, replaying = null) {
|
|
2815
|
+
const write = replaying === null ? exclusive : installWrite;
|
|
2882
2816
|
return {
|
|
2817
|
+
async turn() {
|
|
2818
|
+
if (replaying === null)
|
|
2819
|
+
return;
|
|
2820
|
+
installing(replaying, 'replaying');
|
|
2821
|
+
await screened();
|
|
2822
|
+
},
|
|
2883
2823
|
prior: async (paths) => new Map((await local.entries(key, 'refreshed', paths)).map((entry) => [entry.path, entry])),
|
|
2884
2824
|
async stage(page) {
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
const
|
|
2825
|
+
for (let written = 0;;) {
|
|
2826
|
+
if (replaying !== null)
|
|
2827
|
+
await screened();
|
|
2828
|
+
const end = batchEnd(page.entries, written);
|
|
2829
|
+
const last = end === page.entries.length;
|
|
2830
|
+
const entries = page.entries.slice(written, end);
|
|
2831
|
+
const staged = await write(async () => {
|
|
2889
2832
|
if (!liveness())
|
|
2890
2833
|
return false;
|
|
2891
|
-
await local.refresh(key, last
|
|
2834
|
+
await timed(() => local.refresh(key, last
|
|
2892
2835
|
? { ...page, entries }
|
|
2893
|
-
: { entries, removals: [], folders: [], folderRemovals: [] });
|
|
2836
|
+
: { entries, removals: [], folders: [], folderRemovals: [] }));
|
|
2894
2837
|
return true;
|
|
2895
2838
|
});
|
|
2896
2839
|
if (!staged)
|
|
2897
2840
|
return false;
|
|
2841
|
+
if (last)
|
|
2842
|
+
return true;
|
|
2843
|
+
written = end;
|
|
2898
2844
|
}
|
|
2899
|
-
return true;
|
|
2900
2845
|
},
|
|
2901
2846
|
};
|
|
2902
2847
|
}
|
|
2903
|
-
function
|
|
2904
|
-
|
|
2848
|
+
function batchEnd(entries, from) {
|
|
2849
|
+
let end = from;
|
|
2905
2850
|
let spent = 0;
|
|
2906
|
-
|
|
2907
|
-
const size = (
|
|
2908
|
-
if (spent + size >
|
|
2909
|
-
|
|
2910
|
-
spent = 0;
|
|
2911
|
-
}
|
|
2912
|
-
batches.at(-1).push(entry);
|
|
2851
|
+
while (end < entries.length) {
|
|
2852
|
+
const size = (entries[end].content?.length ?? 0) + ROW_WEIGHT;
|
|
2853
|
+
if (spent + size > stageBytes && end > from)
|
|
2854
|
+
break;
|
|
2913
2855
|
spent += size;
|
|
2856
|
+
end += 1;
|
|
2857
|
+
}
|
|
2858
|
+
return end;
|
|
2859
|
+
}
|
|
2860
|
+
async function timed(work) {
|
|
2861
|
+
const started = performance.now();
|
|
2862
|
+
const done = await work();
|
|
2863
|
+
const spent = performance.now() - started;
|
|
2864
|
+
if (spent < STAGE_AIM_MS / 2)
|
|
2865
|
+
stageBytes = Math.min(STAGE_BYTES, Math.round(stageBytes * 1.5));
|
|
2866
|
+
else if (spent > STAGE_AIM_MS * 2) {
|
|
2867
|
+
stageBytes = Math.max(STAGE_FLOOR, Math.round(stageBytes / 1.5));
|
|
2914
2868
|
}
|
|
2915
|
-
return
|
|
2869
|
+
return done;
|
|
2870
|
+
}
|
|
2871
|
+
function installWrite(work) {
|
|
2872
|
+
const next = installWrites.then(() => exclusive(work), () => exclusive(work));
|
|
2873
|
+
installWrites = next.then(() => undefined, () => undefined);
|
|
2874
|
+
return next;
|
|
2875
|
+
}
|
|
2876
|
+
function screened() {
|
|
2877
|
+
if (onScreen || closed)
|
|
2878
|
+
return Promise.resolve();
|
|
2879
|
+
queueReadiness();
|
|
2880
|
+
return new Promise((resolve) => shownWaiters.push(resolve));
|
|
2881
|
+
}
|
|
2882
|
+
async function heldPart() {
|
|
2883
|
+
while (partsFree === 0)
|
|
2884
|
+
await new Promise((admit) => partWaiters.push(admit));
|
|
2885
|
+
partsFree -= 1;
|
|
2886
|
+
return freed();
|
|
2887
|
+
}
|
|
2888
|
+
function freed() {
|
|
2889
|
+
let released = false;
|
|
2890
|
+
return () => {
|
|
2891
|
+
if (released)
|
|
2892
|
+
return;
|
|
2893
|
+
released = true;
|
|
2894
|
+
partsFree += 1;
|
|
2895
|
+
partWaiters.shift()?.();
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
function installing(locator, step, share = null) {
|
|
2899
|
+
installs.set(locator, { step, share });
|
|
2900
|
+
queueReadiness();
|
|
2901
|
+
}
|
|
2902
|
+
function installed(locator) {
|
|
2903
|
+
if (installs.delete(locator))
|
|
2904
|
+
queueReadiness();
|
|
2905
|
+
}
|
|
2906
|
+
function screen(value) {
|
|
2907
|
+
if (value === onScreen)
|
|
2908
|
+
return;
|
|
2909
|
+
onScreen = value;
|
|
2910
|
+
journal.record(`installation ${value ? 'shown' : 'hidden'}`);
|
|
2911
|
+
queueReadiness();
|
|
2912
|
+
if (!value)
|
|
2913
|
+
return;
|
|
2914
|
+
for (const resolve of shownWaiters.splice(0))
|
|
2915
|
+
resolve();
|
|
2916
2916
|
}
|
|
2917
2917
|
async function pullRefresh(authorized, held, liveness) {
|
|
2918
2918
|
if (!liveness())
|
|
@@ -2940,6 +2940,8 @@ export function composeRuntime(options) {
|
|
|
2940
2940
|
return { kind: 'advanced', record: result.record, base };
|
|
2941
2941
|
}
|
|
2942
2942
|
catch (error) {
|
|
2943
|
+
if (refusedForSpace(error))
|
|
2944
|
+
return unconfirmed;
|
|
2943
2945
|
journal.record(`transfer refresh ${record.locator} transport-uncertainty`);
|
|
2944
2946
|
throw error;
|
|
2945
2947
|
}
|
|
@@ -2966,38 +2968,105 @@ export function composeRuntime(options) {
|
|
|
2966
2968
|
function staging(local, liveness) {
|
|
2967
2969
|
return {
|
|
2968
2970
|
held: (locator) => transfers.get(locator) ?? null,
|
|
2969
|
-
async
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2971
|
+
async turn(locator, step) {
|
|
2972
|
+
installing(locator, step);
|
|
2973
|
+
for (;;) {
|
|
2974
|
+
await screened();
|
|
2975
|
+
const release = step === 'downloading' ? await heldPart() : () => { };
|
|
2976
|
+
if (onScreen || closed)
|
|
2977
|
+
return release;
|
|
2978
|
+
release();
|
|
2979
|
+
}
|
|
2980
|
+
},
|
|
2981
|
+
spare() {
|
|
2982
|
+
if (!onScreen || closed || partsFree === 0)
|
|
2983
|
+
return null;
|
|
2984
|
+
partsFree -= 1;
|
|
2985
|
+
return freed();
|
|
2986
|
+
},
|
|
2987
|
+
arriving(locator) {
|
|
2988
|
+
let ended = false;
|
|
2989
|
+
return {
|
|
2990
|
+
at(token, bytes) {
|
|
2991
|
+
if (ended)
|
|
2992
|
+
return;
|
|
2993
|
+
const held = arrivals.get(locator) ?? new Map();
|
|
2994
|
+
arrivals.set(locator, held.set(token, bytes));
|
|
2995
|
+
const now = performance.now();
|
|
2996
|
+
if (now - arrivalReported < ARRIVAL_REPORT_MS)
|
|
2997
|
+
return;
|
|
2998
|
+
arrivalReported = now;
|
|
2999
|
+
queueReadiness();
|
|
3000
|
+
},
|
|
3001
|
+
end() {
|
|
3002
|
+
ended = true;
|
|
3003
|
+
if (arrivals.delete(locator))
|
|
3004
|
+
queueReadiness();
|
|
3005
|
+
},
|
|
3006
|
+
};
|
|
3007
|
+
},
|
|
3008
|
+
received: (locator, token) => local.transferPart(locator, token),
|
|
3009
|
+
keep: (locator, token, received) => installWrite(async () => {
|
|
3010
|
+
if (liveness())
|
|
3011
|
+
await local.keepPart(locator, token, received);
|
|
3012
|
+
}),
|
|
3013
|
+
announced(locator) {
|
|
3014
|
+
const later = announced.get(locator);
|
|
3015
|
+
return later === undefined ? [] : [later];
|
|
3016
|
+
},
|
|
3017
|
+
async stage(record, page, composition, received) {
|
|
3018
|
+
const { locator } = record;
|
|
3019
|
+
const whole = received?.byteLength ?? 0;
|
|
3020
|
+
const from = record.writing?.written ?? 0;
|
|
3021
|
+
const token = record.writing?.token ?? null;
|
|
3022
|
+
const entries = page.entries.length;
|
|
3023
|
+
let held = record;
|
|
3024
|
+
if (token !== null)
|
|
3025
|
+
arrivals.get(locator)?.delete(token);
|
|
3026
|
+
installing(locator, 'writing', { written: from, entries, bytes: whole });
|
|
3027
|
+
for (let written = from;;) {
|
|
3028
|
+
await screened();
|
|
3029
|
+
if (!liveness())
|
|
3030
|
+
return null;
|
|
3031
|
+
const end = batchEnd(page.entries, written);
|
|
3032
|
+
const last = end === entries;
|
|
3033
|
+
const next = last
|
|
3034
|
+
? {
|
|
3035
|
+
...held,
|
|
3036
|
+
writing: null,
|
|
3037
|
+
done: {
|
|
3038
|
+
parts: held.done.parts + (received === null ? 0 : 1),
|
|
3039
|
+
bytes: held.done.bytes + whole,
|
|
3040
|
+
},
|
|
2982
3041
|
}
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
await exceededOn(record.locator, bytes);
|
|
3042
|
+
: { ...held, writing: { token, written: end } };
|
|
3043
|
+
const key = await installWrite(async () => {
|
|
3044
|
+
if (!liveness())
|
|
2987
3045
|
return null;
|
|
2988
|
-
|
|
3046
|
+
return timed(() => local.stage(next, composition, {
|
|
3047
|
+
entries: page.entries.slice(written, end),
|
|
3048
|
+
folders: last ? page.folders : [],
|
|
3049
|
+
}, last && received !== null && token !== null ? token : undefined));
|
|
2989
3050
|
});
|
|
2990
|
-
if (
|
|
3051
|
+
if (key === null)
|
|
2991
3052
|
return null;
|
|
2992
|
-
|
|
3053
|
+
const arrived = !transfers.has(locator);
|
|
3054
|
+
held = { ...next, key };
|
|
3055
|
+
transfers.set(locator, held);
|
|
3056
|
+
if (arrived)
|
|
3057
|
+
liveQueries.recover();
|
|
3058
|
+
if (last) {
|
|
3059
|
+
installing(locator, held.pageToken === null ? 'replaying' : 'downloading');
|
|
3060
|
+
liveQueries.recover();
|
|
3061
|
+
return held;
|
|
3062
|
+
}
|
|
3063
|
+
installing(locator, 'writing', { written: end, entries, bytes: whole });
|
|
3064
|
+
written = end;
|
|
2993
3065
|
}
|
|
2994
|
-
transfers.set(record.locator, { ...record, key });
|
|
2995
|
-
liveQueries.recover();
|
|
2996
|
-
return key;
|
|
2997
3066
|
},
|
|
2998
|
-
async refreshing(key) {
|
|
3067
|
+
async refreshing(locator, key) {
|
|
2999
3068
|
await exclusive(() => local.unrefresh(key));
|
|
3000
|
-
return refreshingOf(local, key, liveness);
|
|
3069
|
+
return refreshingOf(local, key, liveness, locator);
|
|
3001
3070
|
},
|
|
3002
3071
|
abandon: (locator) => exclusive(async () => {
|
|
3003
3072
|
if (!liveness())
|
|
@@ -3043,13 +3112,18 @@ export function composeRuntime(options) {
|
|
|
3043
3112
|
}, (models, held) => (liveness() ? remodel(room.locator, models, held) : held), liveness, staging(local, liveness), reusable(room, local));
|
|
3044
3113
|
const detail = result.kind === 'staged' ? result.record.roomVersion : result.kind;
|
|
3045
3114
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
3046
|
-
if (result.kind !== 'staged')
|
|
3115
|
+
if (result.kind !== 'staged') {
|
|
3047
3116
|
caughtUpTo(room.locator);
|
|
3117
|
+
installed(room.locator);
|
|
3118
|
+
}
|
|
3048
3119
|
return result;
|
|
3049
3120
|
}
|
|
3050
3121
|
catch (error) {
|
|
3051
|
-
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
3052
3122
|
caughtUpTo(room.locator);
|
|
3123
|
+
installed(room.locator);
|
|
3124
|
+
if (refusedForSpace(error))
|
|
3125
|
+
return unconfirmed;
|
|
3126
|
+
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
3053
3127
|
throw error;
|
|
3054
3128
|
}
|
|
3055
3129
|
finally {
|
|
@@ -3073,8 +3147,9 @@ export function composeRuntime(options) {
|
|
|
3073
3147
|
}
|
|
3074
3148
|
return advanceOnce(room, local, authorized, live);
|
|
3075
3149
|
}
|
|
3076
|
-
async function advanceOnce(room, local, authorized,
|
|
3077
|
-
|
|
3150
|
+
async function advanceOnce(room, local, authorized, running) {
|
|
3151
|
+
const live = () => running() && !full;
|
|
3152
|
+
if (!live())
|
|
3078
3153
|
return unconfirmed;
|
|
3079
3154
|
const held = replicas.get(room.locator);
|
|
3080
3155
|
if (held !== undefined) {
|
|
@@ -3114,12 +3189,11 @@ export function composeRuntime(options) {
|
|
|
3114
3189
|
return unconfirmed;
|
|
3115
3190
|
}
|
|
3116
3191
|
const bootstrapped = await pullBootstrap(authorized, room, local, live);
|
|
3117
|
-
if (
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
return null;
|
|
3192
|
+
if (bootstrapped.kind === 'staged') {
|
|
3193
|
+
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed }, live);
|
|
3194
|
+
return live() ? null : unconfirmed;
|
|
3195
|
+
}
|
|
3196
|
+
return live() ? bootstrapped : unconfirmed;
|
|
3123
3197
|
}
|
|
3124
3198
|
async function withhold(withheld, room, local, progress) {
|
|
3125
3199
|
if (withheld.kind === 'unconfirmed')
|
|
@@ -3753,9 +3827,8 @@ export function composeRuntime(options) {
|
|
|
3753
3827
|
},
|
|
3754
3828
|
],
|
|
3755
3829
|
});
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
replicas.set(room.locator, { ...held, record, overlaid: diff.touched, count });
|
|
3830
|
+
const { count, indexed } = committedRoom(room.locator, answer);
|
|
3831
|
+
replicas.set(room.locator, { ...held, record, overlaid: diff.touched, count, indexed });
|
|
3759
3832
|
changed = changeSet(room.locator, {
|
|
3760
3833
|
documents: answer.changed[room.locator] ?? [],
|
|
3761
3834
|
room: true,
|
|
@@ -3892,10 +3965,9 @@ export function composeRuntime(options) {
|
|
|
3892
3965
|
if (!live() || authorization === undefined)
|
|
3893
3966
|
return;
|
|
3894
3967
|
const bootstrapped = await pullBootstrap(authorization, room, local, live);
|
|
3895
|
-
if (!live())
|
|
3896
|
-
return;
|
|
3897
3968
|
if (bootstrapped.kind !== 'staged') {
|
|
3898
|
-
|
|
3969
|
+
if (live())
|
|
3970
|
+
await withhold(bootstrapped, room, local, progress);
|
|
3899
3971
|
return;
|
|
3900
3972
|
}
|
|
3901
3973
|
await publish(local, room, bootstrapped.record, { kind: 'bootstrapped', refreshed: bootstrapped.refreshed }, live);
|
|
@@ -4541,8 +4613,10 @@ export function composeRuntime(options) {
|
|
|
4541
4613
|
if (closed)
|
|
4542
4614
|
return;
|
|
4543
4615
|
closed = true;
|
|
4544
|
-
if (
|
|
4545
|
-
clearInterval(
|
|
4616
|
+
if (probing !== null)
|
|
4617
|
+
clearInterval(probing);
|
|
4618
|
+
for (const resolve of shownWaiters.splice(0))
|
|
4619
|
+
resolve();
|
|
4546
4620
|
connectionLive();
|
|
4547
4621
|
lockListeners.clear();
|
|
4548
4622
|
readinessWatches.clear();
|
|
@@ -4553,10 +4627,6 @@ export function composeRuntime(options) {
|
|
|
4553
4627
|
withdrawServiceMode?.();
|
|
4554
4628
|
conversations.close();
|
|
4555
4629
|
void recovery.retire();
|
|
4556
|
-
store?.close();
|
|
4557
|
-
void opening?.then((opened) => {
|
|
4558
|
-
opened.close();
|
|
4559
|
-
}, () => { });
|
|
4560
4630
|
roomListeners.clear();
|
|
4561
4631
|
refusalListeners.clear();
|
|
4562
4632
|
storageLossListeners.clear();
|
|
@@ -4632,9 +4702,8 @@ export function composeRuntime(options) {
|
|
|
4632
4702
|
}
|
|
4633
4703
|
async function searched(input, reach, locked) {
|
|
4634
4704
|
const asked = searchPlanOf(input);
|
|
4635
|
-
const plan = { ...asked, limit: capped(asked.limit) };
|
|
4636
4705
|
const filter = {
|
|
4637
|
-
...filterOf(
|
|
4706
|
+
...filterOf(asked.documents ?? {}, roomFacts(reach), planReach(reach)),
|
|
4638
4707
|
locked,
|
|
4639
4708
|
};
|
|
4640
4709
|
const unlocking = (filter.rooms ?? [])
|
|
@@ -4643,65 +4712,41 @@ export function composeRuntime(options) {
|
|
|
4643
4712
|
for (;;) {
|
|
4644
4713
|
const cover = covered(filter);
|
|
4645
4714
|
awaitPreparation(cover);
|
|
4646
|
-
const pending = [
|
|
4647
|
-
...unlocking,
|
|
4648
|
-
...('pending' in cover ? cover.pending : []),
|
|
4649
|
-
];
|
|
4650
|
-
const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
|
|
4651
|
-
const held = covered({
|
|
4652
|
-
...filter,
|
|
4653
|
-
rooms: reachedRooms(filter).filter((room) => !awaited.has(room)),
|
|
4654
|
-
});
|
|
4655
|
-
const covering = 'pending' in held ? [] : held.coverage.rooms;
|
|
4656
|
-
const unindexed = covering
|
|
4657
|
-
.filter(({ room }) => !searchIndex.rooms().has(room))
|
|
4658
|
-
.map(({ room }) => room);
|
|
4659
|
-
for (const room of unindexed)
|
|
4660
|
-
pending.push({ room, awaits: 'index' });
|
|
4661
|
-
const listed = conversations.searchable();
|
|
4662
|
-
const named = reach !== null && !reach.conversations
|
|
4663
|
-
? []
|
|
4664
|
-
: (plan.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
|
|
4665
|
-
const waiting = named.filter((one) => !searchIndex.conversations().has(one));
|
|
4666
|
-
for (const conversation of waiting)
|
|
4667
|
-
pending.push({ conversation, awaits: 'index' });
|
|
4668
|
-
if (unindexed.length > 0 || waiting.length > 0)
|
|
4669
|
-
index(unindexed, waiting);
|
|
4715
|
+
const pending = [...unlocking, ...('pending' in cover ? cover.pending : [])];
|
|
4670
4716
|
const complete = pending.length === 0 && !('pending' in cover);
|
|
4717
|
+
const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
|
|
4671
4718
|
const answering = complete
|
|
4672
4719
|
? filter
|
|
4673
|
-
: {
|
|
4674
|
-
...filter,
|
|
4675
|
-
rooms: covering
|
|
4676
|
-
.filter(({ room }) => searchIndex.rooms().has(room))
|
|
4677
|
-
.map(({ room }) => room),
|
|
4678
|
-
};
|
|
4720
|
+
: { ...filter, rooms: reachedRooms(filter).filter((room) => !awaited.has(room)) };
|
|
4679
4721
|
const answered = covered(answering);
|
|
4680
4722
|
if ('pending' in answered)
|
|
4681
4723
|
continue;
|
|
4682
|
-
const
|
|
4683
|
-
const
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4724
|
+
const listed = conversations.searchable();
|
|
4725
|
+
const named = reach !== null && !reach.conversations
|
|
4726
|
+
? []
|
|
4727
|
+
: (asked.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
|
|
4728
|
+
const hits = await (await openLocal()).search({
|
|
4729
|
+
text: asked.text,
|
|
4730
|
+
limit: capped(asked.limit),
|
|
4731
|
+
after: asked.after,
|
|
4732
|
+
total: asked.total,
|
|
4733
|
+
documents: {
|
|
4734
|
+
plan: answering,
|
|
4735
|
+
rooms: answered.rooms,
|
|
4736
|
+
beneath: asked.collection
|
|
4737
|
+
? answered.rooms.map(({ locator, key }) => ({
|
|
4738
|
+
key,
|
|
4739
|
+
roots: replicas.get(locator)?.record.typedSelection?.roots ?? [],
|
|
4740
|
+
}))
|
|
4741
|
+
: [],
|
|
4742
|
+
},
|
|
4743
|
+
conversations: named,
|
|
4744
|
+
});
|
|
4745
|
+
if (JSON.stringify(covered(answering)) !== JSON.stringify(answered))
|
|
4690
4746
|
continue;
|
|
4691
|
-
const hits = searchIndex.search(plan, selected === null ? null : selected.documents, searchedConversations, answering.rooms !== null
|
|
4692
|
-
? new Set(answering.rooms)
|
|
4693
|
-
: locked.length === 0
|
|
4694
|
-
? null
|
|
4695
|
-
: new Set(reachedRooms(answering)), plan.collection
|
|
4696
|
-
? new Map(answered.coverage.rooms.map(({ room }) => [
|
|
4697
|
-
room,
|
|
4698
|
-
replicas.get(room)?.record.typedSelection?.roots ?? [],
|
|
4699
|
-
]))
|
|
4700
|
-
: null);
|
|
4701
4747
|
const coverage = {
|
|
4702
4748
|
rooms: answered.coverage.rooms,
|
|
4703
|
-
conversations:
|
|
4704
|
-
index: searchIndex.generation(),
|
|
4749
|
+
conversations: named,
|
|
4705
4750
|
overlay: answered.coverage.overlay,
|
|
4706
4751
|
};
|
|
4707
4752
|
return complete
|
|
@@ -4758,7 +4803,7 @@ export function composeRuntime(options) {
|
|
|
4758
4803
|
};
|
|
4759
4804
|
}
|
|
4760
4805
|
if (topic === 'readiness') {
|
|
4761
|
-
return watchReadiness({ target: '', code: 'stored',
|
|
4806
|
+
return watchReadiness({ target: '', code: 'stored', space: null, search: 'unused' }, null, deliver);
|
|
4762
4807
|
}
|
|
4763
4808
|
if (topic === 'rooms') {
|
|
4764
4809
|
roomListeners.add(deliver);
|
|
@@ -4844,33 +4889,17 @@ export function composeRuntime(options) {
|
|
|
4844
4889
|
const record = roomDrafts.get(room)?.get(path);
|
|
4845
4890
|
return record === undefined ? null : ((await roomDraftsOf([record]))[0] ?? null);
|
|
4846
4891
|
},
|
|
4847
|
-
write(room, path, revision, input) {
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
const
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
? undefined
|
|
4859
|
-
: await heldFile(parts.file.blob, binaryBase(await visibleOf(room, [path]), path));
|
|
4860
|
-
const record = {
|
|
4861
|
-
locator: room,
|
|
4862
|
-
path,
|
|
4863
|
-
revision: roomDraftRevision(),
|
|
4864
|
-
parts: held(current?.parts, parts, file),
|
|
4865
|
-
returned: current?.returned ?? null,
|
|
4866
|
-
};
|
|
4867
|
-
await local.keepRoomDraft(record, parts.file === undefined || file === undefined
|
|
4868
|
-
? []
|
|
4869
|
-
: [{ key: file.localId, blob: parts.file.blob }]);
|
|
4870
|
-
rememberRoomDraft(record);
|
|
4871
|
-
publishChangeSet(changeSet(room, { drafts: [path] }));
|
|
4872
|
-
return { outcome: 'written', roomDraft: (await roomDraftsOf([record]))[0] ?? null };
|
|
4873
|
-
});
|
|
4892
|
+
write(room, path, revision, input, next = roomDraftRevision()) {
|
|
4893
|
+
if (typeof next !== 'string' || next === '')
|
|
4894
|
+
return Promise.reject(runtimeError('room-draft-malformed'));
|
|
4895
|
+
return gathered(`room-draft\u0000${room}\u0000${path}`, { revision, input, next }, (earlier, later) => {
|
|
4896
|
+
if (later.revision !== earlier.next)
|
|
4897
|
+
return null;
|
|
4898
|
+
const parts = gatheredParts(earlier.input, later.input);
|
|
4899
|
+
return parts === null
|
|
4900
|
+
? null
|
|
4901
|
+
: { revision: earlier.revision, input: parts, next: later.next };
|
|
4902
|
+
}, (written) => writtenRoomDraft(room, path, written.revision, written.input, written.next));
|
|
4874
4903
|
},
|
|
4875
4904
|
discard(room, path, revision) {
|
|
4876
4905
|
return exclusive(async () => {
|
|
@@ -4897,13 +4926,13 @@ export function composeRuntime(options) {
|
|
|
4897
4926
|
});
|
|
4898
4927
|
},
|
|
4899
4928
|
write(app, value) {
|
|
4900
|
-
return
|
|
4929
|
+
return exclusive(() => keptPresentation(app, () => structuredClone(value)));
|
|
4901
4930
|
},
|
|
4902
4931
|
edit(app, members) {
|
|
4903
|
-
return
|
|
4904
|
-
...Object.entries(plain(held) ? held : {}).filter(([member]) => !Object.hasOwn(
|
|
4905
|
-
...Object.entries(
|
|
4906
|
-
]));
|
|
4932
|
+
return gathered(`presentation\u0000${app}`, structuredClone(members), (earlier, later) => ({ ...earlier, ...later }), (edited) => keptPresentation(app, (held) => Object.fromEntries([
|
|
4933
|
+
...Object.entries(plain(held) ? held : {}).filter(([member]) => !Object.hasOwn(edited, member)),
|
|
4934
|
+
...Object.entries(edited).filter(([, value]) => value !== null),
|
|
4935
|
+
])));
|
|
4907
4936
|
},
|
|
4908
4937
|
},
|
|
4909
4938
|
save(room, named, writtenBy) {
|
|
@@ -4922,7 +4951,6 @@ export function composeRuntime(options) {
|
|
|
4922
4951
|
part: readPart,
|
|
4923
4952
|
read: readBinaryPath,
|
|
4924
4953
|
link: linkBinaryPath,
|
|
4925
|
-
readReference: readBinaryReference,
|
|
4926
4954
|
},
|
|
4927
4955
|
async call(name, input) {
|
|
4928
4956
|
await ready;
|
|
@@ -4943,9 +4971,10 @@ export function composeRuntime(options) {
|
|
|
4943
4971
|
},
|
|
4944
4972
|
async retry() {
|
|
4945
4973
|
journal.record('retry');
|
|
4946
|
-
|
|
4974
|
+
unpreparable.clear();
|
|
4947
4975
|
queueReadiness();
|
|
4948
|
-
|
|
4976
|
+
if (full)
|
|
4977
|
+
await probed();
|
|
4949
4978
|
},
|
|
4950
4979
|
observe: observing(watch),
|
|
4951
4980
|
async reauthorize() {
|
|
@@ -4967,7 +4996,6 @@ export function composeRuntime(options) {
|
|
|
4967
4996
|
const reachable = {
|
|
4968
4997
|
md,
|
|
4969
4998
|
watch,
|
|
4970
|
-
referrerScope: (referrer) => options.adapter.referrerScope(referrer),
|
|
4971
4999
|
plan: planned,
|
|
4972
5000
|
query: (plan) => rowsQuery(plan),
|
|
4973
5001
|
watchPlan,
|
|
@@ -4991,6 +5019,11 @@ export function composeRuntime(options) {
|
|
|
4991
5019
|
journal.record(`persistence ${answer}`);
|
|
4992
5020
|
queueReadiness();
|
|
4993
5021
|
},
|
|
4994
|
-
|
|
5022
|
+
shown: screen,
|
|
5023
|
+
within: (reach, port) => {
|
|
5024
|
+
if (full)
|
|
5025
|
+
void probed();
|
|
5026
|
+
return narrowed(reachable, reach, port);
|
|
5027
|
+
},
|
|
4995
5028
|
};
|
|
4996
5029
|
}
|