@agentshouse/mdruntime 0.1.0-cec6690d5712e → 0.1.0-cedd665dffa4c
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/README.md +24 -19
- package/dist/acceptance.d.ts +3 -0
- package/dist/acceptance.d.ts.map +1 -0
- package/dist/acceptance.js +299 -0
- package/dist/act-lanes.d.ts +30 -0
- package/dist/act-lanes.d.ts.map +1 -0
- package/dist/act-lanes.js +225 -0
- package/dist/actions.d.ts +26 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +302 -0
- package/dist/adapter.d.ts +207 -94
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +6 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +35 -41
- package/dist/binary.d.ts +18 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +31 -11
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +311 -92
- package/dist/cell.d.ts +6 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +9 -0
- package/dist/chain.d.ts +12 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +13 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +17 -62
- package/dist/change.d.ts +46 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +39 -0
- package/dist/chat-types.d.ts +3 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +11 -0
- package/dist/client.d.ts +51 -27
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +133 -26
- package/dist/collection.d.ts +34 -9
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +55 -79
- package/dist/compile.d.ts +18 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +723 -0
- package/dist/connection.d.ts +8 -111
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +113 -2221
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/{chat/context.js → context.js} +2 -5
- package/dist/conversation-live.d.ts +150 -0
- package/dist/conversation-live.d.ts.map +1 -0
- package/dist/conversation-live.js +259 -0
- package/dist/conversation-row.d.ts +40 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +66 -0
- package/dist/conversation.d.ts +84 -45
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1118 -584
- package/dist/declaration.d.ts +10 -0
- package/dist/declaration.d.ts.map +1 -0
- package/dist/declaration.js +24 -0
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/discovery.d.ts +10 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +150 -0
- package/dist/durability.d.ts +23 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +9 -0
- package/dist/entry.d.ts +20 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +161 -0
- package/dist/expectation.d.ts +26 -13
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +72 -19
- package/dist/failure.d.ts +3 -0
- package/dist/failure.d.ts.map +1 -1
- package/dist/failure.js +6 -0
- package/dist/figures.d.ts +49 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +199 -0
- package/dist/file.d.ts +2 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +8 -122
- package/dist/holdings.d.ts +29 -0
- package/dist/holdings.d.ts.map +1 -0
- package/dist/holdings.js +95 -0
- package/dist/host.d.ts +29 -4
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +179 -25
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +3 -0
- package/dist/index.d.ts +42 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -2
- package/dist/install.d.ts +60 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +537 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +278 -0
- package/dist/listing.d.ts +52 -0
- package/dist/listing.d.ts.map +1 -0
- package/dist/listing.js +152 -0
- package/dist/live.d.ts +25 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +206 -0
- package/dist/loaded.d.ts +5 -0
- package/dist/loaded.d.ts.map +1 -0
- package/dist/loaded.js +40 -0
- package/dist/locks.d.ts +7 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +32 -0
- package/dist/lost.d.ts +5 -0
- package/dist/lost.d.ts.map +1 -0
- package/dist/lost.js +49 -0
- package/dist/media.d.ts +13 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +269 -0
- package/dist/memory.d.ts +26 -8
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +307 -97
- package/dist/messages.d.ts +58 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +162 -0
- package/dist/model.d.ts +11 -12
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +7 -15
- package/dist/models.d.ts +10 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +153 -0
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +42 -9
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +238 -33
- package/dist/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/opening.d.ts +4 -0
- package/dist/opening.d.ts.map +1 -0
- package/dist/opening.js +53 -0
- package/dist/outcome.d.ts +63 -5
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +70 -0
- package/dist/overlay.d.ts +21 -6
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +163 -187
- package/dist/page-metadata.d.ts +8 -0
- package/dist/page-metadata.d.ts.map +1 -0
- package/dist/page-metadata.js +19 -0
- package/dist/parts.d.ts +4 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +13 -0
- package/dist/paths.d.ts +2 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +26 -0
- package/dist/preparation.d.ts +41 -0
- package/dist/preparation.d.ts.map +1 -0
- package/dist/preparation.js +314 -0
- package/dist/presence.d.ts +58 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +231 -0
- package/dist/queries.d.ts +16 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +308 -0
- package/dist/query.d.ts +51 -183
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -464
- package/dist/{chat/questions.d.ts → questions.d.ts} +5 -7
- package/dist/questions.d.ts.map +1 -0
- package/dist/{chat/questions.js → questions.js} +11 -7
- package/dist/queue.d.ts +40 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +480 -0
- package/dist/reach.d.ts +20 -5
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +357 -80
- package/dist/react.d.ts +23 -7
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +40 -18
- package/dist/{chat/read.d.ts → read.d.ts} +2 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/{chat/read.js → read.js} +4 -0
- package/dist/readiness.d.ts +55 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +25 -0
- package/dist/reads.d.ts +39 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +178 -0
- package/dist/recovery.d.ts +28 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +254 -0
- package/dist/refresh.d.ts +17 -14
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +145 -58
- package/dist/refusal.d.ts +5 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +55 -0
- package/dist/replica.d.ts +53 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +349 -167
- package/dist/retried.d.ts +30 -0
- package/dist/retried.d.ts.map +1 -0
- package/dist/retried.js +72 -0
- package/dist/room-draft.d.ts +82 -11
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +490 -78
- package/dist/room-drafts.d.ts +24 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +220 -0
- package/dist/rows.d.ts +31 -35
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +60 -126
- package/dist/runtime.d.ts +34 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/save.d.ts +45 -22
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +977 -215
- package/dist/saves.d.ts +32 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +426 -0
- package/dist/search.d.ts +15 -55
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -121
- package/dist/seed.d.ts +25 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +145 -0
- package/dist/shared.d.ts +10 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +339 -0
- package/dist/shown.d.ts +33 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +309 -0
- package/dist/socket/admission.d.ts +7 -0
- package/dist/socket/admission.d.ts.map +1 -0
- package/dist/socket/admission.js +39 -0
- package/dist/socket/answers.d.ts +6 -0
- package/dist/socket/answers.d.ts.map +1 -0
- package/dist/socket/answers.js +20 -0
- package/dist/socket/lanes.d.ts +28 -0
- package/dist/socket/lanes.d.ts.map +1 -0
- package/dist/socket/lanes.js +103 -0
- package/dist/socket/reads.d.ts +10 -0
- package/dist/socket/reads.d.ts.map +1 -0
- package/dist/socket/reads.js +31 -0
- package/dist/socket/session.d.ts +38 -0
- package/dist/socket/session.d.ts.map +1 -0
- package/dist/socket/session.js +23 -0
- package/dist/socket.d.ts +67 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +164 -0
- package/dist/source.d.ts +59 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +93 -0
- package/dist/status.d.ts +5 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +40 -0
- package/dist/store/readiness.d.ts +11 -0
- package/dist/store/readiness.d.ts.map +1 -0
- package/dist/store/readiness.js +68 -0
- package/dist/store/records.d.ts +217 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +464 -0
- package/dist/store/room-held.d.ts +8 -0
- package/dist/store/room-held.d.ts.map +1 -0
- package/dist/store/room-held.js +17 -0
- package/dist/store/rooms-shown.d.ts +8 -0
- package/dist/store/rooms-shown.d.ts.map +1 -0
- package/dist/store/rooms-shown.js +13 -0
- package/dist/store/space.d.ts +11 -0
- package/dist/store/space.d.ts.map +1 -0
- package/dist/store/space.js +41 -0
- package/dist/store/watchers.d.ts +17 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +56 -0
- package/dist/store/writes.d.ts +24 -0
- package/dist/store/writes.d.ts.map +1 -0
- package/dist/store/writes.js +125 -0
- package/dist/store.d.ts +114 -116
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +164 -494
- package/dist/stored.d.ts +167 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +703 -0
- package/dist/surface.d.ts +89 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +222 -0
- package/dist/sync.d.ts +19 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +535 -0
- package/dist/tables.d.ts +419 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +758 -0
- package/dist/tallies.d.ts +18 -0
- package/dist/tallies.d.ts.map +1 -0
- package/dist/tallies.js +128 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +75 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +98 -0
- package/dist/windows.d.ts +11 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +16 -0
- package/dist/wire.d.ts +221 -34
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +74 -31
- package/package.json +28 -24
- package/AGENTS.md +0 -299
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
- package/dist/attempt.d.ts +0 -16
- package/dist/attempt.d.ts.map +0 -1
- package/dist/attempt.js +0 -57
- package/dist/chat/acts.d.ts +0 -34
- 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/input.d.ts +0 -9
- package/dist/chat/input.d.ts.map +0 -1
- package/dist/chat/input.js +0 -99
- package/dist/chat/kit.d.ts +0 -60
- 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 -20
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -59
- package/dist/chat/markdown.d.ts +0 -19
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -187
- package/dist/chat/message.d.ts +0 -6
- package/dist/chat/message.d.ts.map +0 -1
- package/dist/chat/message.js +0 -192
- package/dist/chat/messages.d.ts +0 -21
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -151
- package/dist/chat/name.d.ts +0 -6
- package/dist/chat/name.d.ts.map +0 -1
- package/dist/chat/name.js +0 -37
- package/dist/chat/opened.d.ts +0 -11
- package/dist/chat/opened.d.ts.map +0 -1
- package/dist/chat/opened.js +0 -17
- package/dist/chat/overlay.d.ts +0 -31
- package/dist/chat/overlay.d.ts.map +0 -1
- package/dist/chat/overlay.js +0 -97
- package/dist/chat/parts.d.ts +0 -57
- package/dist/chat/parts.d.ts.map +0 -1
- package/dist/chat/parts.js +0 -119
- 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 -63
- package/dist/chat/provider.d.ts.map +0 -1
- package/dist/chat/provider.js +0 -645
- package/dist/chat/question.d.ts +0 -9
- package/dist/chat/question.d.ts.map +0 -1
- package/dist/chat/question.js +0 -80
- package/dist/chat/questions.d.ts.map +0 -1
- package/dist/chat/read.d.ts.map +0 -1
- package/dist/chat/ready.d.ts +0 -33
- package/dist/chat/ready.d.ts.map +0 -1
- package/dist/chat/ready.js +0 -91
- package/dist/chat/style.d.ts +0 -4
- package/dist/chat/style.d.ts.map +0 -1
- package/dist/chat/style.js +0 -302
- package/dist/chat.d.ts +0 -14
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -8
- package/dist/log.d.ts +0 -5
- package/dist/log.d.ts.map +0 -1
- package/dist/log.js +0 -14
- package/dist/read-side.d.ts +0 -18
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -433
package/dist/connection.js
CHANGED
|
@@ -1,2231 +1,123 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
import { unconfirmed } from './transfer.js';
|
|
25
|
-
import { governs, holds, narrowed, planReach, } from './reach.js';
|
|
26
|
-
function within(root, path) {
|
|
27
|
-
return root === '' || beneath(root, path);
|
|
28
|
-
}
|
|
29
|
-
function declaredSelection(room) {
|
|
30
|
-
const selection = room.typedSelection;
|
|
31
|
-
if (selection === undefined)
|
|
32
|
-
return true;
|
|
33
|
-
if (!Array.isArray(selection.roots) || selection.roots.length === 0)
|
|
34
|
-
return false;
|
|
35
|
-
return selection.roots.every((root, index) => validRoot(root) &&
|
|
36
|
-
root !== '' &&
|
|
37
|
-
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
38
|
-
}
|
|
1
|
+
import { notReadyWaitMs } from '@agentshouse/mdmodel';
|
|
2
|
+
import { submitLater } from './acceptance.js';
|
|
3
|
+
import { invokeAction, called, readConversationHistory, resendLater } from './actions.js';
|
|
4
|
+
import { builtInModel, NO_ROOM_TYPES } from './attached-model.js';
|
|
5
|
+
import { createLiveConversations } from './conversation-live.js';
|
|
6
|
+
import { writeRule } from './declaration.js';
|
|
7
|
+
import { refusedForSpace } from './durability.js';
|
|
8
|
+
import { uncaught } from './failure.js';
|
|
9
|
+
import { createFigures } from './figures.js';
|
|
10
|
+
import { scheduleCurrency, spaceFreed, start } from './lifecycle.js';
|
|
11
|
+
import { createLiveQueries } from './live.js';
|
|
12
|
+
import { open } from './opening.js';
|
|
13
|
+
import { deliverOwedReadiness, filled, prepare } from './preparation.js';
|
|
14
|
+
import { createPresence } from './presence.js';
|
|
15
|
+
import { answeredPlan, figuresCover, roomTallies } from './queries.js';
|
|
16
|
+
import { readAnswers } from './reads.js';
|
|
17
|
+
import { createRecovery } from './recovery.js';
|
|
18
|
+
import { createRetries } from './retried.js';
|
|
19
|
+
import { publishChangeSet, show } from './shown.js';
|
|
20
|
+
import { createSocket } from './socket.js';
|
|
21
|
+
import { createStore } from './store.js';
|
|
22
|
+
import { composedRuntime } from './surface.js';
|
|
23
|
+
const ROOM_ATTEMPTS = 16;
|
|
39
24
|
export function composeRuntime(options) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const models = new Map();
|
|
54
|
-
const liveModels = new Map();
|
|
55
|
-
const invalidModels = new Set();
|
|
56
|
-
const discovered = new Map();
|
|
57
|
-
let roomsListed = false;
|
|
58
|
-
const scopedRefusals = new Map();
|
|
59
|
-
const sourceRefusals = new Map();
|
|
60
|
-
const declarationRefusals = new Set();
|
|
61
|
-
function modelKey(locator, attached) {
|
|
62
|
-
return `${attached} ${locator}`;
|
|
63
|
-
}
|
|
64
|
-
function modelOf(locator, attached = 'collection') {
|
|
65
|
-
return models.get(modelKey(locator, attached)) ?? NO_MODEL;
|
|
66
|
-
}
|
|
67
|
-
function savedModel(locator, attached) {
|
|
68
|
-
return invalidModels.has(modelKey(locator, attached))
|
|
69
|
-
? { status: 'unavailable', reason: 'revoked' }
|
|
70
|
-
: modelOf(locator, attached);
|
|
71
|
-
}
|
|
72
|
-
function compositionFor(record) {
|
|
73
|
-
return compositionOf({
|
|
74
|
-
...record,
|
|
75
|
-
model: savedModel(record.locator, 'collection'),
|
|
76
|
-
selectionModel: savedModel(record.locator, 'typed-selection'),
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
let refusal = null;
|
|
80
|
-
let storageLoss = null;
|
|
81
|
-
let status = STARTING;
|
|
82
|
-
let shown = STARTING;
|
|
83
|
-
let discoveryPaused = false;
|
|
84
|
-
let resetTimer;
|
|
85
|
-
let closed = false;
|
|
86
|
-
let stopping = false;
|
|
87
|
-
let withdrawStream;
|
|
88
|
-
const announced = new Map();
|
|
89
|
-
let underway = null;
|
|
90
|
-
let streamed = null;
|
|
91
|
-
let handed = false;
|
|
92
|
-
const submitting = new Map();
|
|
93
|
-
const answering = new Map();
|
|
94
|
-
let complete = false;
|
|
95
|
-
let recorded = false;
|
|
96
|
-
const stale = new Set();
|
|
97
|
-
let chain = Promise.resolve();
|
|
98
|
-
let mutations = Promise.resolve();
|
|
99
|
-
const roomListeners = new Set();
|
|
100
|
-
const refusalListeners = new Set();
|
|
101
|
-
const storageLossListeners = new Set();
|
|
102
|
-
const statusListeners = new Set();
|
|
103
|
-
const queueSubscriptions = new Set();
|
|
104
|
-
const roomDraftListeners = new Set();
|
|
105
|
-
const presentationListeners = new Set();
|
|
106
|
-
const readSide = createReadSide();
|
|
107
|
-
let withdrawConversations;
|
|
108
|
-
let currencyQueued = false;
|
|
109
|
-
let socketOpen = false;
|
|
110
|
-
let serviceMode = 'open';
|
|
111
|
-
let haltedRefusal = false;
|
|
112
|
-
let openSignals = 0;
|
|
113
|
-
const serviceModeListeners = new Set();
|
|
114
|
-
let member = null;
|
|
115
|
-
const memberListeners = new Set();
|
|
116
|
-
const materialized = new Set();
|
|
117
|
-
let everyRoom = false;
|
|
118
|
-
const transfers = new Map();
|
|
119
|
-
const conversations = createConversationStore({
|
|
120
|
-
agents: declaredAgents,
|
|
121
|
-
documents: (locator) => rowsQuery(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
|
|
122
|
-
call: (name, input) => invokeAction(name, input),
|
|
123
|
-
durably,
|
|
124
|
-
stream: () => options.adapter.conversations,
|
|
125
|
-
publish: publishChangeSet,
|
|
126
|
-
closed: () => closed,
|
|
127
|
-
});
|
|
128
|
-
const searchIndex = createSearchIndex({
|
|
129
|
-
documents: () => readSide.documents(),
|
|
130
|
-
document: (room, path) => readSide.document(room, path),
|
|
131
|
-
messages: () => conversations.messages(),
|
|
132
|
-
message: (conversation, identity) => conversations.message(conversation, identity),
|
|
133
|
-
});
|
|
134
|
-
const journal = createLog();
|
|
135
|
-
let notifyReady = () => { };
|
|
136
|
-
const ready = new Promise((resolve) => {
|
|
137
|
-
notifyReady = resolve;
|
|
138
|
-
});
|
|
139
|
-
function rememberRoomDraft(record) {
|
|
140
|
-
const room = roomDrafts.get(record.locator) ?? new Map();
|
|
141
|
-
room.set(record.path, record);
|
|
142
|
-
roomDrafts.set(record.locator, room);
|
|
143
|
-
}
|
|
144
|
-
function forgetRoomDraft(locator, path) {
|
|
145
|
-
const room = roomDrafts.get(locator);
|
|
146
|
-
if (room === undefined)
|
|
147
|
-
return;
|
|
148
|
-
room.delete(path);
|
|
149
|
-
if (room.size === 0)
|
|
150
|
-
roomDrafts.delete(locator);
|
|
151
|
-
}
|
|
152
|
-
function roomDraftOf(record) {
|
|
153
|
-
return structuredClone({
|
|
154
|
-
room: record.locator,
|
|
155
|
-
path: record.path,
|
|
156
|
-
revision: record.revision,
|
|
157
|
-
parts: record.parts,
|
|
158
|
-
changedUnderneath: underneath(record.parts, record.path, replicas.get(record.locator)?.visible),
|
|
159
|
-
returned: record.returned,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
function snapshotRoomDrafts() {
|
|
163
|
-
const records = [...roomDrafts.values()].flatMap((room) => [...room.values()]);
|
|
164
|
-
records.sort((left, right) => {
|
|
165
|
-
if (left.locator !== right.locator)
|
|
166
|
-
return left.locator < right.locator ? -1 : 1;
|
|
167
|
-
if (left.path !== right.path)
|
|
168
|
-
return left.path < right.path ? -1 : 1;
|
|
169
|
-
return 0;
|
|
170
|
-
});
|
|
171
|
-
return records.map(roomDraftOf);
|
|
172
|
-
}
|
|
173
|
-
function publishRoomDrafts(waitingOnly) {
|
|
174
|
-
if (closed)
|
|
175
|
-
return;
|
|
176
|
-
const value = snapshotRoomDrafts();
|
|
177
|
-
for (const listener of roomDraftListeners) {
|
|
178
|
-
if (waitingOnly && listener.ready)
|
|
179
|
-
continue;
|
|
180
|
-
listener.ready = true;
|
|
181
|
-
listener.notify(value);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
function presentationOf(app) {
|
|
185
|
-
const value = presentations.get(app);
|
|
186
|
-
return value === undefined ? null : structuredClone(value);
|
|
187
|
-
}
|
|
188
|
-
function emitPresentation(app) {
|
|
189
|
-
if (closed)
|
|
190
|
-
return;
|
|
191
|
-
for (const listener of presentationListeners) {
|
|
192
|
-
if (listener.app === app && listener.ready)
|
|
193
|
-
listener.notify(presentationOf(app));
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
function pending() {
|
|
197
|
-
return !localSaves && [...replicas.values()].some((replica) => replica.queue.length > 0);
|
|
198
|
-
}
|
|
199
|
-
function attention() {
|
|
200
|
-
const attentionRequired = [];
|
|
201
|
-
for (const record of [...roomDrafts.values()].flatMap((room) => [...room.values()])) {
|
|
202
|
-
if (record.returned === null)
|
|
203
|
-
continue;
|
|
204
|
-
attentionRequired.push({
|
|
205
|
-
room: record.locator,
|
|
206
|
-
path: record.path,
|
|
207
|
-
returned: structuredClone(record.returned),
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
attentionRequired.sort((left, right) => {
|
|
211
|
-
if (left.room !== right.room)
|
|
212
|
-
return left.room < right.room ? -1 : 1;
|
|
213
|
-
if (left.path !== right.path)
|
|
214
|
-
return left.path < right.path ? -1 : 1;
|
|
215
|
-
return 0;
|
|
216
|
-
});
|
|
217
|
-
let reset = null;
|
|
218
|
-
for (const { submission } of pendings.values()) {
|
|
219
|
-
if (submission.state !== 'reset')
|
|
220
|
-
continue;
|
|
221
|
-
if (reset === null || submission.reset < reset)
|
|
222
|
-
reset = submission.reset;
|
|
223
|
-
}
|
|
224
|
-
return { attentionRequired, reset };
|
|
225
|
-
}
|
|
226
|
-
function documentsIn(locator) {
|
|
227
|
-
const replica = replicas.get(locator);
|
|
228
|
-
if (replica === undefined)
|
|
229
|
-
return 0;
|
|
230
|
-
const roots = replica.confirmed.record.typedSelection?.roots ?? [];
|
|
231
|
-
let count = 0;
|
|
232
|
-
for (const path of Object.keys(replica.visible.documents)) {
|
|
233
|
-
if (markdownPath(path) && !roots.some((root) => beneath(root, path)))
|
|
234
|
-
count += 1;
|
|
235
|
-
}
|
|
236
|
-
return count;
|
|
237
|
-
}
|
|
238
|
-
function listed() {
|
|
239
|
-
return structuredClone([...rooms.values()].map((room) => {
|
|
240
|
-
const documents = documentsIn(room.locator);
|
|
241
|
-
counted.set(room.locator, documents);
|
|
242
|
-
return { ...room, documents };
|
|
243
|
-
}));
|
|
244
|
-
}
|
|
245
|
-
function roomFacts(reach = null) {
|
|
246
|
-
const held = [...rooms.values()]
|
|
247
|
-
.filter((room) => holds(reach, room.locator))
|
|
248
|
-
.map((room) => {
|
|
249
|
-
const governed = governs(reach, room);
|
|
250
|
-
const attached = [
|
|
251
|
-
...(governed.collection ? [modelOf(room.locator)] : []),
|
|
252
|
-
...(governed.selection ? [modelOf(room.locator, 'typed-selection')] : []),
|
|
253
|
-
];
|
|
254
|
-
return {
|
|
255
|
-
locator: room.locator,
|
|
256
|
-
available: attached.every((model) => model.status !== 'unavailable'),
|
|
257
|
-
types: attached.flatMap((model) => model.status === 'unavailable' ? [] : Object.values(model.types)),
|
|
258
|
-
};
|
|
259
|
-
});
|
|
260
|
-
return { listed: roomsListed, rooms: held };
|
|
261
|
-
}
|
|
262
|
-
function emitRooms() {
|
|
263
|
-
const value = listed();
|
|
264
|
-
for (const listener of roomListeners)
|
|
265
|
-
listener(value);
|
|
266
|
-
}
|
|
267
|
-
function emitRefusal() {
|
|
268
|
-
for (const listener of refusalListeners)
|
|
269
|
-
listener(refusal);
|
|
270
|
-
}
|
|
271
|
-
function emitStorageLoss() {
|
|
272
|
-
for (const listener of storageLossListeners)
|
|
273
|
-
listener(storageLoss);
|
|
274
|
-
}
|
|
275
|
-
function emitMember() {
|
|
276
|
-
for (const listener of memberListeners)
|
|
277
|
-
listener(member);
|
|
278
|
-
}
|
|
279
|
-
function moveTo(next) {
|
|
280
|
-
if (closed)
|
|
281
|
-
return;
|
|
282
|
-
status = next;
|
|
283
|
-
show();
|
|
284
|
-
}
|
|
285
|
-
function show() {
|
|
286
|
-
if (closed)
|
|
287
|
-
return;
|
|
288
|
-
const next = status.state === 'synced' && !conversations.current()
|
|
289
|
-
? { ...status, state: 'syncing' }
|
|
290
|
-
: status;
|
|
291
|
-
if (same(shown, next))
|
|
292
|
-
return;
|
|
293
|
-
shown = next;
|
|
294
|
-
for (const listener of statusListeners)
|
|
295
|
-
listener(shown);
|
|
296
|
-
}
|
|
297
|
-
function entryOf(batch) {
|
|
298
|
-
if (localSaves)
|
|
299
|
-
return Object.freeze({ batch, state: 'local' });
|
|
300
|
-
const state = pendings.get(batch.id);
|
|
301
|
-
if (state?.accepted !== undefined) {
|
|
302
|
-
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
303
|
-
}
|
|
304
|
-
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
305
|
-
}
|
|
306
|
-
function entriesOf(locator) {
|
|
307
|
-
const replica = replicas.get(locator);
|
|
308
|
-
if (replica === undefined)
|
|
309
|
-
return [];
|
|
310
|
-
return replica.queue.map(entryOf);
|
|
311
|
-
}
|
|
312
|
-
function emitQueue(locator) {
|
|
313
|
-
const entries = entriesOf(locator);
|
|
314
|
-
for (const subscription of queueSubscriptions) {
|
|
315
|
-
if (subscription.locator === locator)
|
|
316
|
-
subscription.notify(entries);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
function applyChangeSet(set) {
|
|
320
|
-
const held = everyRoom
|
|
321
|
-
? set
|
|
322
|
-
: { ...set, documents: set.documents.filter(({ locator }) => materialized.has(locator)) };
|
|
323
|
-
readSide.apply(held);
|
|
324
|
-
searchIndex.changed(held);
|
|
325
|
-
}
|
|
326
|
-
function materialize(locators) {
|
|
327
|
-
if (everyRoom)
|
|
328
|
-
return;
|
|
329
|
-
if (locators === null)
|
|
330
|
-
everyRoom = true;
|
|
331
|
-
const sets = [];
|
|
332
|
-
for (const locator of locators ?? [...replicas.keys()]) {
|
|
333
|
-
if (materialized.has(locator))
|
|
334
|
-
continue;
|
|
335
|
-
materialized.add(locator);
|
|
336
|
-
const replica = replicas.get(locator);
|
|
337
|
-
if (replica !== undefined)
|
|
338
|
-
sets.push(changeSet(locator, null, replica));
|
|
339
|
-
}
|
|
340
|
-
if (sets.length === 0)
|
|
341
|
-
return;
|
|
342
|
-
const set = mergeChangeSets(sets);
|
|
343
|
-
readSide.apply(set);
|
|
344
|
-
searchIndex.changed(set);
|
|
345
|
-
}
|
|
346
|
-
function reachedRooms(plan) {
|
|
347
|
-
if (plan.rooms !== null)
|
|
348
|
-
return plan.rooms;
|
|
349
|
-
if (plan.reach === null)
|
|
350
|
-
return null;
|
|
351
|
-
return [...plan.reach.rooms, ...plan.reach.documents.map(roomOfKey)];
|
|
352
|
-
}
|
|
353
|
-
function rowsQuery(plan) {
|
|
354
|
-
materialize(reachedRooms(plan));
|
|
355
|
-
return readSide.query(plan);
|
|
356
|
-
}
|
|
357
|
-
function publishChangeSet(set) {
|
|
358
|
-
applyChangeSet(set);
|
|
359
|
-
if (set.rooms.length > 0 ||
|
|
360
|
-
[...new Set(set.documents.map(({ locator }) => locator))].some((locator) => rooms.has(locator) && counted.get(locator) !== documentsIn(locator)))
|
|
361
|
-
emitRooms();
|
|
362
|
-
for (const locator of set.queues)
|
|
363
|
-
emitQueue(locator);
|
|
364
|
-
if (set.drafts.length > 0 ||
|
|
365
|
-
set.documents.some((change) => roomDrafts.get(change.locator)?.has(change.path) === true)) {
|
|
366
|
-
publishRoomDrafts(false);
|
|
367
|
-
}
|
|
368
|
-
for (const app of set.presentations)
|
|
369
|
-
emitPresentation(app);
|
|
370
|
-
conversations.changed(set);
|
|
371
|
-
}
|
|
372
|
-
function stopTimer() {
|
|
373
|
-
if (resetTimer === undefined)
|
|
374
|
-
return;
|
|
375
|
-
clearTimeout(resetTimer);
|
|
376
|
-
resetTimer = undefined;
|
|
377
|
-
}
|
|
378
|
-
function armTimer() {
|
|
379
|
-
stopTimer();
|
|
380
|
-
if (closed || stopping)
|
|
381
|
-
return;
|
|
382
|
-
let earliest = null;
|
|
383
|
-
for (const { submission } of pendings.values()) {
|
|
384
|
-
if (submission.state !== 'reset')
|
|
385
|
-
continue;
|
|
386
|
-
const at = Date.parse(submission.reset);
|
|
387
|
-
if (earliest === null || at < earliest)
|
|
388
|
-
earliest = at;
|
|
389
|
-
}
|
|
390
|
-
if (earliest === null)
|
|
391
|
-
return;
|
|
392
|
-
// A Save House held back goes again only once the reset its answer named has passed.
|
|
393
|
-
resetTimer = setTimeout(() => {
|
|
394
|
-
resetTimer = undefined;
|
|
395
|
-
submitAll();
|
|
396
|
-
}, Math.max(0, earliest - Date.now()));
|
|
397
|
-
}
|
|
398
|
-
async function refuse(reason, provider = null) {
|
|
399
|
-
stopTimer();
|
|
400
|
-
discoveryPaused = true;
|
|
401
|
-
try {
|
|
402
|
-
if (reason === 'revoked-authorization') {
|
|
403
|
-
await exclusive(async () => {
|
|
404
|
-
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
405
|
-
try {
|
|
406
|
-
const local = await openLocal();
|
|
407
|
-
await local.revokeModels([...replicas.values()].map((replica) => ({
|
|
408
|
-
record: replica.confirmed.record,
|
|
409
|
-
queue: replica.queue,
|
|
410
|
-
})));
|
|
411
|
-
}
|
|
412
|
-
catch {
|
|
413
|
-
journal.record('model revocation persistence failed');
|
|
414
|
-
}
|
|
415
|
-
const keys = new Set(models.keys());
|
|
416
|
-
for (const locator of rooms.keys()) {
|
|
417
|
-
keys.add(modelKey(locator, 'collection'));
|
|
418
|
-
keys.add(modelKey(locator, 'typed-selection'));
|
|
419
|
-
}
|
|
420
|
-
for (const key of keys) {
|
|
421
|
-
invalidModels.add(key);
|
|
422
|
-
models.set(key, revoked);
|
|
423
|
-
liveModels.delete(key);
|
|
424
|
-
}
|
|
425
|
-
const changed = [];
|
|
426
|
-
for (const [locator, room] of rooms) {
|
|
427
|
-
const before = replicas.get(locator) ?? null;
|
|
428
|
-
if (before !== null) {
|
|
429
|
-
const record = {
|
|
430
|
-
...before.confirmed.record,
|
|
431
|
-
model: revoked,
|
|
432
|
-
selectionModel: revoked,
|
|
433
|
-
};
|
|
434
|
-
replicas.set(locator, replicaOf(confirmedOf(record, before.confirmed), before.queue, before));
|
|
435
|
-
}
|
|
436
|
-
rooms.set(locator, {
|
|
437
|
-
...room,
|
|
438
|
-
model: modelTypes(revoked),
|
|
439
|
-
typedSelection: room.typedSelection === null
|
|
440
|
-
? null
|
|
441
|
-
: { ...room.typedSelection, model: modelTypes(revoked) },
|
|
442
|
-
});
|
|
443
|
-
changed.push(changeSet(locator, before, replicas.get(locator) ?? null, { room: true }));
|
|
444
|
-
}
|
|
445
|
-
publishChangeSet(mergeChangeSets(changed));
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
finally {
|
|
450
|
-
refusal = {
|
|
451
|
-
code: reason,
|
|
452
|
-
retryable: false,
|
|
453
|
-
reauthorizationOffered: reason === 'revoked-authorization',
|
|
454
|
-
refusal: provider,
|
|
455
|
-
};
|
|
456
|
-
journal.record(`connection refused ${reason}`);
|
|
457
|
-
emitRefusal();
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
function list(room, synchronizationAvailable) {
|
|
461
|
-
const selection = room.typedSelection ?? null;
|
|
462
|
-
rooms.set(room.locator, {
|
|
463
|
-
name: room.name,
|
|
464
|
-
locator: room.locator,
|
|
465
|
-
handle: room.handle,
|
|
466
|
-
operations: room.operations,
|
|
467
|
-
replica: room.replica,
|
|
468
|
-
model: modelTypes(modelOf(room.locator)),
|
|
469
|
-
typedSelection: selection === null
|
|
470
|
-
? null
|
|
471
|
-
: {
|
|
472
|
-
roots: selection.roots,
|
|
473
|
-
model: modelTypes(modelOf(room.locator, 'typed-selection')),
|
|
474
|
-
},
|
|
475
|
-
providerOwnedPaths: room.providerOwnedPaths,
|
|
476
|
-
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
477
|
-
attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
|
|
478
|
-
synchronizationAvailable,
|
|
479
|
-
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
async function applyDiscovery(listed, local) {
|
|
483
|
-
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
484
|
-
for (const locator of listedLocators)
|
|
485
|
-
scopedRefusals.delete(locator);
|
|
486
|
-
discovered.clear();
|
|
487
|
-
for (const room of listed)
|
|
488
|
-
discovered.set(room.locator, room);
|
|
489
|
-
await dropUnlisted(listedLocators, local);
|
|
490
|
-
for (const room of listed) {
|
|
491
|
-
const held = replicas.get(room.locator)?.confirmed.record;
|
|
492
|
-
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
493
|
-
list(held, false);
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
497
|
-
}
|
|
498
|
-
roomsListed = true;
|
|
499
|
-
emitRooms();
|
|
500
|
-
}
|
|
501
|
-
function unlistedHeld(listed) {
|
|
502
|
-
return [...new Set([...rooms.keys(), ...roomDrafts.keys(), ...transfers.keys()])].filter((locator) => !listed.has(locator));
|
|
503
|
-
}
|
|
504
|
-
async function dropUnlisted(listed, local) {
|
|
505
|
-
if (unlistedHeld(listed).length === 0)
|
|
506
|
-
return;
|
|
507
|
-
await exclusive(async () => {
|
|
508
|
-
const unlisted = unlistedHeld(listed);
|
|
509
|
-
await local.dropRooms(unlisted);
|
|
510
|
-
const removed = unlisted.map((locator) => changeSet(locator, replicas.get(locator) ?? null, null, {
|
|
511
|
-
room: true,
|
|
512
|
-
queue: true,
|
|
513
|
-
drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
|
|
514
|
-
}));
|
|
515
|
-
for (const locator of unlisted) {
|
|
516
|
-
for (const batch of replicas.get(locator)?.queue ?? [])
|
|
517
|
-
pendings.delete(batch.id);
|
|
518
|
-
replicas.delete(locator);
|
|
519
|
-
rooms.delete(locator);
|
|
520
|
-
roomDrafts.delete(locator);
|
|
521
|
-
transfers.delete(locator);
|
|
522
|
-
unreadable.delete(locator);
|
|
523
|
-
stale.delete(locator);
|
|
524
|
-
scopedRefusals.delete(locator);
|
|
525
|
-
sourceRefusals.delete(locator);
|
|
526
|
-
declarationRefusals.delete(locator);
|
|
527
|
-
for (const attached of ['collection', 'typed-selection']) {
|
|
528
|
-
const key = modelKey(locator, attached);
|
|
529
|
-
models.delete(key);
|
|
530
|
-
liveModels.delete(key);
|
|
531
|
-
invalidModels.delete(key);
|
|
532
|
-
}
|
|
533
|
-
journal.record(`room unlisted ${locator}`);
|
|
534
|
-
}
|
|
535
|
-
publishChangeSet(mergeChangeSets(removed));
|
|
536
|
-
moveTo({ ...status, ...attention() });
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
function restored(key, persisted) {
|
|
540
|
-
const prior = models.get(key);
|
|
541
|
-
if (prior !== undefined &&
|
|
542
|
-
prior.status !== 'unavailable' &&
|
|
543
|
-
(persisted.status === 'unavailable' || !sameModel(prior.snapshot, persisted.snapshot))) {
|
|
544
|
-
invalidModels.add(key);
|
|
545
|
-
}
|
|
546
|
-
const model = persisted.status === 'unavailable' ||
|
|
547
|
-
liveModels.get(key) === JSON.stringify(persisted.snapshot)
|
|
548
|
-
? persisted
|
|
549
|
-
: cached(persisted);
|
|
550
|
-
models.set(key, model);
|
|
551
|
-
if (model.status === 'unavailable') {
|
|
552
|
-
liveModels.delete(key);
|
|
553
|
-
invalidModels.delete(key);
|
|
554
|
-
}
|
|
555
|
-
return model;
|
|
556
|
-
}
|
|
557
|
-
async function hydrate(opened) {
|
|
558
|
-
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known, staged,] = await Promise.all([
|
|
559
|
-
opened.all(),
|
|
560
|
-
opened.queues(),
|
|
561
|
-
opened.roomDrafts(),
|
|
562
|
-
opened.presentation(),
|
|
563
|
-
opened.conversations(),
|
|
564
|
-
opened.chatDrafts(),
|
|
565
|
-
opened.lastCompleted(),
|
|
566
|
-
opened.providerLimits(),
|
|
567
|
-
opened.capabilities(),
|
|
568
|
-
opened.member(),
|
|
569
|
-
opened.transfers(),
|
|
570
|
-
]);
|
|
571
|
-
transfers.clear();
|
|
572
|
-
for (const record of staged)
|
|
573
|
-
transfers.set(record.locator, record);
|
|
574
|
-
limits = declared;
|
|
575
|
-
capabilities = named;
|
|
576
|
-
member = known;
|
|
577
|
-
roomDrafts.clear();
|
|
578
|
-
for (const record of kept)
|
|
579
|
-
rememberRoomDraft(record);
|
|
580
|
-
presentations.clear();
|
|
581
|
-
for (const record of presented)
|
|
582
|
-
presentations.set(record.app, record.value);
|
|
583
|
-
conversations.hydrate(held, chatDrafts);
|
|
584
|
-
const queuedBatches = new Map(queues.map((queue) => [queue.locator, queue.pending.map(({ batch }) => batch)]));
|
|
585
|
-
pendings.clear();
|
|
586
|
-
for (const queue of queues) {
|
|
587
|
-
for (const { batch, state } of queue.pending)
|
|
588
|
-
pendings.set(batch.id, state);
|
|
589
|
-
}
|
|
590
|
-
const previousReplicas = new Map(replicas);
|
|
591
|
-
replicas.clear();
|
|
592
|
-
for (const record of records) {
|
|
593
|
-
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
594
|
-
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
595
|
-
const prior = previousReplicas.get(record.locator);
|
|
596
|
-
const confirmed = confirmedOf({ ...record, model, selectionModel }, prior?.confirmed);
|
|
597
|
-
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
598
|
-
const existing = rooms.get(record.locator);
|
|
599
|
-
if (existing === undefined)
|
|
600
|
-
list(record, false);
|
|
601
|
-
else {
|
|
602
|
-
rooms.set(record.locator, {
|
|
603
|
-
...existing,
|
|
604
|
-
roomVersion: record.roomVersion,
|
|
605
|
-
attachments: record.attachments,
|
|
606
|
-
operations: record.operations,
|
|
607
|
-
replica: record.replica,
|
|
608
|
-
model: modelTypes(model),
|
|
609
|
-
typedSelection: record.typedSelection === null
|
|
610
|
-
? null
|
|
611
|
-
: {
|
|
612
|
-
roots: record.typedSelection.roots,
|
|
613
|
-
model: modelTypes(selectionModel),
|
|
614
|
-
},
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return {
|
|
619
|
-
lastCompleted,
|
|
620
|
-
locators: new Set([...queuedBatches.keys(), ...replicas.keys()]),
|
|
621
|
-
read: mergeChangeSets([...new Set([...previousReplicas.keys(), ...replicas.keys()])].map((locator) => changeSet(locator, previousReplicas.get(locator) ?? null, replicas.get(locator) ?? null))),
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
async function readState(opened) {
|
|
625
|
-
const { lastCompleted, locators, read } = await hydrate(opened);
|
|
626
|
-
applyChangeSet(read);
|
|
627
|
-
if (replicas.size > 0)
|
|
628
|
-
emitRooms();
|
|
629
|
-
if (member !== null)
|
|
630
|
-
emitMember();
|
|
631
|
-
for (const locator of locators)
|
|
632
|
-
emitQueue(locator);
|
|
633
|
-
publishRoomDrafts(false);
|
|
634
|
-
return lastCompleted;
|
|
635
|
-
}
|
|
636
|
-
async function open() {
|
|
637
|
-
const opened = await openRuntimeStore();
|
|
638
|
-
store = opened;
|
|
639
|
-
const kept = options.shell.generation;
|
|
640
|
-
const storedGeneration = await opened.generation();
|
|
641
|
-
const loss = detectStorageLoss(storedGeneration, kept);
|
|
642
|
-
const generation = nextGeneration(storedGeneration, kept);
|
|
643
|
-
if (generation !== storedGeneration)
|
|
644
|
-
await opened.rememberGeneration(generation);
|
|
645
|
-
if (generation !== kept)
|
|
646
|
-
await options.shell.keepGeneration(generation);
|
|
647
|
-
if (loss !== null) {
|
|
648
|
-
storageLoss = loss;
|
|
649
|
-
emitStorageLoss();
|
|
650
|
-
}
|
|
651
|
-
const lastCompleted = await readState(opened);
|
|
652
|
-
moveTo({ ...status, lastCompleted, ...attention() });
|
|
653
|
-
return opened;
|
|
654
|
-
}
|
|
655
|
-
function durably(work) {
|
|
656
|
-
return exclusive(async () => {
|
|
657
|
-
const local = await openLocal();
|
|
658
|
-
return work(local);
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
function openLocal() {
|
|
662
|
-
opening ??= open().catch((error) => {
|
|
663
|
-
opening = undefined;
|
|
664
|
-
throw error;
|
|
665
|
-
});
|
|
666
|
-
return opening;
|
|
667
|
-
}
|
|
668
|
-
function exclusive(work) {
|
|
669
|
-
const next = mutations.then(work, work);
|
|
670
|
-
mutations = next.then(() => undefined, () => undefined);
|
|
671
|
-
return next;
|
|
672
|
-
}
|
|
673
|
-
function withdrawable(batch) {
|
|
674
|
-
const state = pendings.get(batch.id);
|
|
675
|
-
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
676
|
-
}
|
|
677
|
-
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
678
|
-
const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
|
|
679
|
-
const kept = queue.filter((batch) => !withdrawn.includes(batch));
|
|
680
|
-
const unqueued = withdrawn.map((batch) => ({
|
|
681
|
-
batch,
|
|
682
|
-
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
683
|
-
}));
|
|
684
|
-
const returning = [
|
|
685
|
-
...[...unqueued].reverse(),
|
|
686
|
-
...(settling.leaving === undefined ? [] : [settling.leaving]),
|
|
687
|
-
];
|
|
688
|
-
const written = new Map();
|
|
689
|
-
for (const { batch, returned } of returning) {
|
|
690
|
-
if (returned === null)
|
|
691
|
-
continue;
|
|
692
|
-
for (const saved of pendings.get(batch.id)?.drafts ?? []) {
|
|
693
|
-
const existing = written.get(saved.path) ?? roomDrafts.get(locator)?.get(saved.path);
|
|
694
|
-
written.set(saved.path, {
|
|
695
|
-
locator,
|
|
696
|
-
path: saved.path,
|
|
697
|
-
revision: roomDraftRevision(),
|
|
698
|
-
parts: returnedInto(existing?.parts, saved.parts),
|
|
699
|
-
returned,
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
await local.write({
|
|
704
|
-
next: confirmed.record,
|
|
705
|
-
previous: before?.confirmed.record ?? null,
|
|
706
|
-
...(settling.stored === undefined ? {} : { stored: settling.stored }),
|
|
707
|
-
queue: kept,
|
|
708
|
-
previousQueue: before?.queue ?? [],
|
|
709
|
-
roomDrafts: [...written.values()],
|
|
710
|
-
});
|
|
711
|
-
for (const batch of before?.queue ?? []) {
|
|
712
|
-
if (!kept.includes(batch))
|
|
713
|
-
pendings.delete(batch.id);
|
|
714
|
-
}
|
|
715
|
-
for (const record of written.values())
|
|
716
|
-
rememberRoomDraft(record);
|
|
717
|
-
for (const { batch, returned } of unqueued) {
|
|
718
|
-
const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
|
|
719
|
-
journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
|
|
720
|
-
}
|
|
721
|
-
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
722
|
-
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
723
|
-
room: settling.room,
|
|
724
|
-
queue: kept.length !== (before?.queue.length ?? 0),
|
|
725
|
-
drafts: [...written.keys()],
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
function adoptModels(locator, record) {
|
|
729
|
-
for (const [attached, model] of [
|
|
730
|
-
['collection', record.model],
|
|
731
|
-
['typed-selection', record.selectionModel],
|
|
732
|
-
]) {
|
|
733
|
-
const key = modelKey(locator, attached);
|
|
734
|
-
models.set(key, model);
|
|
735
|
-
invalidModels.delete(key);
|
|
736
|
-
if (model.status === 'current')
|
|
737
|
-
liveModels.set(key, JSON.stringify(model.snapshot));
|
|
738
|
-
else if (model.status === 'unavailable')
|
|
739
|
-
liveModels.delete(key);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
function remodel(locator, answers, held) {
|
|
743
|
-
const taken = (answer, previous, attached) => {
|
|
744
|
-
if (answer === undefined)
|
|
745
|
-
return previous;
|
|
746
|
-
const { state, refused } = attachedModel(answer, previous);
|
|
747
|
-
if (refused !== null) {
|
|
748
|
-
const where = refused.location === undefined ? '' : ` ${refused.location}`;
|
|
749
|
-
journal.record(`model invalid ${locator} ${attached} ${refused.identity} ${refused.rule}${where}`);
|
|
750
|
-
}
|
|
751
|
-
models.set(modelKey(locator, attached), state);
|
|
752
|
-
return state;
|
|
753
|
-
};
|
|
754
|
-
return {
|
|
755
|
-
collection: taken(answers?.collection, held.collection, 'collection'),
|
|
756
|
-
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
function publish(local, room, confirmed, landing = {}) {
|
|
760
|
-
const { base, stored } = landing;
|
|
761
|
-
return exclusive(async () => {
|
|
762
|
-
if (!continuing())
|
|
763
|
-
return true;
|
|
764
|
-
const prior = replicas.get(room.locator);
|
|
765
|
-
if (base !== undefined &&
|
|
766
|
-
(prior?.confirmed.record.roomVersion !== base.record.roomVersion ||
|
|
767
|
-
prior.confirmed.record.revisions !== base.record.revisions)) {
|
|
768
|
-
return false;
|
|
769
|
-
}
|
|
770
|
-
adoptModels(room.locator, confirmed.record);
|
|
771
|
-
const queue = prior?.queue ?? [];
|
|
772
|
-
const roomVersion = confirmed.record.roomVersion;
|
|
773
|
-
const accepted = queue.filter((batch) => {
|
|
774
|
-
const accepted = pendings.get(batch.id)?.accepted;
|
|
775
|
-
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
776
|
-
});
|
|
777
|
-
const set = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true, ...(stored === undefined ? {} : { stored }) });
|
|
778
|
-
if (stored !== undefined)
|
|
779
|
-
transfers.delete(room.locator);
|
|
780
|
-
for (const batch of accepted)
|
|
781
|
-
pendings.delete(batch.id);
|
|
782
|
-
stale.delete(room.locator);
|
|
783
|
-
if (unreadable.delete(room.locator))
|
|
784
|
-
scopedRefusals.delete(room.locator);
|
|
785
|
-
sourceRefusals.delete(room.locator);
|
|
786
|
-
declarationRefusals.delete(room.locator);
|
|
787
|
-
list(room, true);
|
|
788
|
-
publishChangeSet(set);
|
|
789
|
-
moveTo({ ...status, ...attention() });
|
|
790
|
-
return true;
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
794
|
-
const local = await openLocal();
|
|
795
|
-
const replica = replicas.get(locator);
|
|
796
|
-
const room = rooms.get(locator);
|
|
797
|
-
if (replica === undefined || room === undefined) {
|
|
798
|
-
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
799
|
-
}
|
|
800
|
-
const saved = [];
|
|
801
|
-
for (const { path, revision } of named) {
|
|
802
|
-
const record = roomDrafts.get(locator)?.get(path);
|
|
803
|
-
if (record === undefined || record.revision !== revision) {
|
|
804
|
-
return { outcome: 'refused', code: 'stale', retryable: false, file: path };
|
|
805
|
-
}
|
|
806
|
-
if (!saved.some((draft) => draft.path === path))
|
|
807
|
-
saved.push({ path, parts: record.parts });
|
|
808
|
-
}
|
|
809
|
-
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
810
|
-
if (changed.length > 0)
|
|
811
|
-
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
812
|
-
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
813
|
-
const built = buildBatch({
|
|
814
|
-
local: localSaves,
|
|
815
|
-
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
816
|
-
composition: compositionFor(replica.confirmed.record),
|
|
817
|
-
operations: room.operations,
|
|
818
|
-
providerOwnedPaths: room.providerOwnedPaths,
|
|
819
|
-
providerLimits: limits,
|
|
820
|
-
handle: room.handle,
|
|
821
|
-
visible: replica.visible,
|
|
822
|
-
changes: parts.flatMap(({ changes }) => changes),
|
|
823
|
-
bases: parts.flatMap(({ bases }) => bases),
|
|
824
|
-
writtenBy,
|
|
825
|
-
});
|
|
826
|
-
if (built.kind === 'refused')
|
|
827
|
-
return { outcome: 'refused', ...built.refusal };
|
|
828
|
-
const batches = [...replica.queue, built.batch];
|
|
829
|
-
const consumed = saved.map(({ path }) => path);
|
|
830
|
-
try {
|
|
831
|
-
await local.write({
|
|
832
|
-
next: replica.confirmed.record,
|
|
833
|
-
previous: replica.confirmed.record,
|
|
834
|
-
queue: batches,
|
|
835
|
-
previousQueue: replica.queue,
|
|
836
|
-
saved,
|
|
837
|
-
consumedRoomDrafts: consumed,
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
catch {
|
|
841
|
-
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
842
|
-
}
|
|
843
|
-
pendings.set(built.batch.id, { drafts: saved, submission: { state: 'waiting' }, lost: false });
|
|
844
|
-
if (!localSaves)
|
|
845
|
-
recorded = false;
|
|
846
|
-
for (const path of consumed)
|
|
847
|
-
forgetRoomDraft(locator, path);
|
|
848
|
-
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
849
|
-
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
850
|
-
queue: true,
|
|
851
|
-
drafts: consumed,
|
|
852
|
-
}));
|
|
853
|
-
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
854
|
-
submitLater(locator);
|
|
855
|
-
return { outcome: 'saved', identifier: built.batch.id };
|
|
856
|
-
}
|
|
857
|
-
async function mark(local, locator, identifier, submission, lost) {
|
|
858
|
-
const state = pendings.get(identifier);
|
|
859
|
-
if (!continuing() ||
|
|
860
|
-
state === undefined ||
|
|
861
|
-
(JSON.stringify(state.submission) === JSON.stringify(submission) && state.lost === lost)) {
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
await local.submit(locator, identifier, submission, lost);
|
|
865
|
-
pendings.set(identifier, { ...state, submission, lost });
|
|
866
|
-
const replica = replicas.get(locator);
|
|
867
|
-
if (replica !== undefined)
|
|
868
|
-
publishChangeSet(changeSet(locator, replica, replica, { queue: true }));
|
|
869
|
-
moveTo({ ...status, ...attention() });
|
|
870
|
-
}
|
|
871
|
-
async function keepUpload(local, locator, identifier, upload) {
|
|
872
|
-
if (!continuing())
|
|
873
|
-
throw new Error('connection refused');
|
|
874
|
-
await local.keepUpload(locator, identifier, upload);
|
|
875
|
-
pendings.set(identifier, { ...pendings.get(identifier), upload });
|
|
876
|
-
if (!continuing())
|
|
877
|
-
throw new Error('connection refused');
|
|
878
|
-
}
|
|
879
|
-
async function readHistory(locator, path, query = {}) {
|
|
880
|
-
if (closed || refusal !== null)
|
|
881
|
-
return UNCERTAIN;
|
|
882
|
-
try {
|
|
883
|
-
await ensureAuthorization();
|
|
884
|
-
}
|
|
885
|
-
catch {
|
|
886
|
-
return UNCERTAIN;
|
|
887
|
-
}
|
|
888
|
-
if (closed || authorization === undefined || refusal !== null)
|
|
889
|
-
return UNCERTAIN;
|
|
890
|
-
const transfer = ['transfer', 'history', locator, path];
|
|
891
|
-
if (query.revision !== undefined)
|
|
892
|
-
transfer.push(query.revision);
|
|
893
|
-
if (query.pageToken !== undefined)
|
|
894
|
-
transfer.push(query.pageToken);
|
|
895
|
-
journal.record(transfer.join(' '));
|
|
896
|
-
try {
|
|
897
|
-
const result = await options.adapter.history(authorization, {
|
|
898
|
-
locator,
|
|
899
|
-
path,
|
|
900
|
-
...(query.revision === undefined ? {} : { revision: query.revision }),
|
|
901
|
-
...(query.pageToken === undefined ? {} : { pageToken: query.pageToken }),
|
|
902
|
-
});
|
|
903
|
-
journal.record(`outcome history ${result.kind}`);
|
|
904
|
-
if (result.kind === 'scoped-refusal') {
|
|
905
|
-
const room = replicas.get(result.locator)?.confirmed.record;
|
|
906
|
-
if (room !== undefined)
|
|
907
|
-
await scoped(result, room, await openLocal());
|
|
908
|
-
}
|
|
909
|
-
if (result.kind === 'revoked-authorization')
|
|
910
|
-
await refuse('revoked-authorization');
|
|
911
|
-
return historyAnswer(result);
|
|
912
|
-
}
|
|
913
|
-
catch {
|
|
914
|
-
journal.record('outcome history transport-uncertainty');
|
|
915
|
-
return UNCERTAIN;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
function declaredAgents() {
|
|
919
|
-
for (const capability of capabilities) {
|
|
920
|
-
if (capability.conversationAgents !== undefined)
|
|
921
|
-
return capability.conversationAgents;
|
|
922
|
-
}
|
|
923
|
-
return undefined;
|
|
924
|
-
}
|
|
925
|
-
function declaredAction(name) {
|
|
926
|
-
for (const capability of capabilities) {
|
|
927
|
-
for (const action of capability.actions ?? []) {
|
|
928
|
-
if (action.name === name)
|
|
929
|
-
return action;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
return undefined;
|
|
933
|
-
}
|
|
934
|
-
async function invokeAction(name, input) {
|
|
935
|
-
await ready;
|
|
936
|
-
if (closed || refusal !== null)
|
|
937
|
-
return UNCERTAIN;
|
|
938
|
-
if (declaredAction(name) === undefined)
|
|
939
|
-
return undeclared(name);
|
|
940
|
-
try {
|
|
941
|
-
await ensureAuthorization();
|
|
942
|
-
}
|
|
943
|
-
catch {
|
|
944
|
-
return UNCERTAIN;
|
|
945
|
-
}
|
|
946
|
-
if (closed || authorization === undefined || refusal !== null)
|
|
947
|
-
return UNCERTAIN;
|
|
948
|
-
journal.record(`transfer action ${name}`);
|
|
949
|
-
try {
|
|
950
|
-
const result = await options.adapter.action(authorization, { name, input });
|
|
951
|
-
if (result.kind === 'scoped-refusal') {
|
|
952
|
-
const room = replicas.get(result.locator)?.confirmed.record;
|
|
953
|
-
if (room !== undefined)
|
|
954
|
-
await scoped(result, room, await openLocal());
|
|
955
|
-
}
|
|
956
|
-
if (result.kind === 'revoked-authorization')
|
|
957
|
-
await refuse('revoked-authorization');
|
|
958
|
-
journal.record(`outcome action ${result.kind}`);
|
|
959
|
-
if (result.kind === 'rejected')
|
|
960
|
-
journal.record(`refusal rejected ${result.code}`);
|
|
961
|
-
return calledResult(result);
|
|
962
|
-
}
|
|
963
|
-
catch {
|
|
964
|
-
journal.record('outcome action transport-uncertainty');
|
|
965
|
-
return UNCERTAIN;
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
function continuing() {
|
|
969
|
-
return !closed && !discoveryPaused;
|
|
970
|
-
}
|
|
971
|
-
function reachedBy(locator, roomVersion) {
|
|
972
|
-
const held = replicas.get(locator);
|
|
973
|
-
return (held !== undefined &&
|
|
974
|
-
options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion));
|
|
975
|
-
}
|
|
976
|
-
function writtenAfter(locator, roomVersion, path) {
|
|
977
|
-
const written = replicas.get(locator)?.confirmed.record.written[path];
|
|
978
|
-
return (written !== undefined &&
|
|
979
|
-
options.adapter.reached(locator, written, roomVersion) &&
|
|
980
|
-
!options.adapter.reached(locator, roomVersion, written));
|
|
981
|
-
}
|
|
982
|
-
async function pullRefresh(authorized, confirmed) {
|
|
983
|
-
if (!continuing())
|
|
984
|
-
return unconfirmed;
|
|
985
|
-
try {
|
|
986
|
-
const result = await refreshRoom(options.adapter, authorized, confirmed, (models, held) => remodel(confirmed.record.locator, models, held), continuing);
|
|
987
|
-
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
988
|
-
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
989
|
-
return result;
|
|
990
|
-
}
|
|
991
|
-
catch (error) {
|
|
992
|
-
journal.record(`transfer refresh ${confirmed.record.locator} transport-uncertainty`);
|
|
993
|
-
throw error;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
function staging(local) {
|
|
997
|
-
return {
|
|
998
|
-
held: (locator) => transfers.get(locator) ?? null,
|
|
999
|
-
staged: (record) => local.staged(record),
|
|
1000
|
-
stage: (record, page) => exclusive(async () => {
|
|
1001
|
-
if (!continuing())
|
|
1002
|
-
return false;
|
|
1003
|
-
await local.stage(record, page);
|
|
1004
|
-
transfers.set(record.locator, record);
|
|
1005
|
-
return true;
|
|
1006
|
-
}),
|
|
1007
|
-
abandon: (locator) => exclusive(async () => {
|
|
1008
|
-
if (!continuing())
|
|
1009
|
-
return;
|
|
1010
|
-
await local.abandon(locator);
|
|
1011
|
-
transfers.delete(locator);
|
|
1012
|
-
}),
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
async function pullBootstrap(authorized, room, local) {
|
|
1016
|
-
if (!continuing())
|
|
1017
|
-
return unconfirmed;
|
|
1018
|
-
try {
|
|
1019
|
-
const result = await bootstrapRoom(options.adapter, authorized, room, {
|
|
1020
|
-
collection: modelOf(room.locator),
|
|
1021
|
-
selection: modelOf(room.locator, 'typed-selection'),
|
|
1022
|
-
}, (models, held) => remodel(room.locator, models, held), continuing, staging(local));
|
|
1023
|
-
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
1024
|
-
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
1025
|
-
return result;
|
|
1026
|
-
}
|
|
1027
|
-
catch (error) {
|
|
1028
|
-
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
1029
|
-
throw error;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
async function advance(room, local, authorized) {
|
|
1033
|
-
const held = replicas.get(room.locator);
|
|
1034
|
-
if (held !== undefined) {
|
|
1035
|
-
const refreshed = await pullRefresh(authorized, held.confirmed);
|
|
1036
|
-
if (!continuing())
|
|
1037
|
-
return unconfirmed;
|
|
1038
|
-
if (refreshed.kind === 'still-current') {
|
|
1039
|
-
stale.delete(room.locator);
|
|
1040
|
-
if (sourceRefusals.has(room.locator))
|
|
1041
|
-
return null;
|
|
1042
|
-
if (unreadable.delete(room.locator)) {
|
|
1043
|
-
scopedRefusals.delete(room.locator);
|
|
1044
|
-
list(room, true);
|
|
1045
|
-
emitRooms();
|
|
25
|
+
const { adapter, shell } = options;
|
|
26
|
+
const roomTypes = adapter.roomTypes ?? NO_ROOM_TYPES;
|
|
27
|
+
const builtIn = builtInModel(roomTypes);
|
|
28
|
+
const typedPaths = adapter.typedPaths ?? [];
|
|
29
|
+
const retries = createRetries(shell);
|
|
30
|
+
const socket = createSocket(adapter, retries, options.authorization);
|
|
31
|
+
const presence = createPresence(notReadyWaitMs);
|
|
32
|
+
const store = createStore({
|
|
33
|
+
retries,
|
|
34
|
+
database: {
|
|
35
|
+
async ask(request) {
|
|
36
|
+
try {
|
|
37
|
+
return await options.database.ask(request);
|
|
1046
38
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
1052
|
-
const reached = now !== undefined &&
|
|
1053
|
-
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
1054
|
-
return reached ? null : advance(room, local, authorized);
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (refusedForSpace(error))
|
|
41
|
+
filled(runtime);
|
|
42
|
+
throw error;
|
|
1055
43
|
}
|
|
1056
|
-
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
1057
|
-
return null;
|
|
1058
|
-
}
|
|
1059
|
-
if (refreshed.kind !== 'beyond-horizon')
|
|
1060
|
-
return refreshed;
|
|
1061
|
-
}
|
|
1062
|
-
const bootstrapped = await pullBootstrap(authorized, room, local);
|
|
1063
|
-
if (!continuing())
|
|
1064
|
-
return unconfirmed;
|
|
1065
|
-
if (bootstrapped.kind !== 'staged')
|
|
1066
|
-
return bootstrapped;
|
|
1067
|
-
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
1068
|
-
return null;
|
|
1069
|
-
}
|
|
1070
|
-
async function withhold(withheld, room, local, progress) {
|
|
1071
|
-
if (withheld.kind === 'refused')
|
|
1072
|
-
stale.delete(room.locator);
|
|
1073
|
-
else
|
|
1074
|
-
stale.add(room.locator);
|
|
1075
|
-
if (withheld.kind === 'scoped-refusal') {
|
|
1076
|
-
await scoped(withheld, room, local);
|
|
1077
|
-
}
|
|
1078
|
-
else if (withheld.kind === 'transport-uncertainty') {
|
|
1079
|
-
progress.reachable = false;
|
|
1080
|
-
}
|
|
1081
|
-
else if (withheld.kind === 'retryable-refusal') {
|
|
1082
|
-
journal.record(`refusal ${withheld.kind} ${withheld.code}`);
|
|
1083
|
-
}
|
|
1084
|
-
else if (withheld.kind === 'room-not-readable') {
|
|
1085
|
-
unreadable.add(room.locator);
|
|
1086
|
-
list(room, false);
|
|
1087
|
-
emitRooms();
|
|
1088
|
-
}
|
|
1089
|
-
else if (withheld.kind === 'refused') {
|
|
1090
|
-
refusedSource(room, withheld);
|
|
1091
|
-
}
|
|
1092
|
-
else if (withheld.kind === 'revoked-authorization') {
|
|
1093
|
-
await refuse('revoked-authorization');
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
function refusedSource(room, { refusal }) {
|
|
1097
|
-
sourceRefusals.set(room.locator, {
|
|
1098
|
-
code: refusal.rule,
|
|
1099
|
-
retryable: false,
|
|
1100
|
-
fields: {
|
|
1101
|
-
file: refusal.file,
|
|
1102
|
-
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
1103
|
-
...(refusal.referrers === undefined ? {} : { referrers: refusal.referrers }),
|
|
1104
44
|
},
|
|
1105
|
-
});
|
|
1106
|
-
declarationRefusals.delete(room.locator);
|
|
1107
|
-
unreadable.add(room.locator);
|
|
1108
|
-
list(room, false);
|
|
1109
|
-
emitRooms();
|
|
1110
|
-
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
1111
|
-
}
|
|
1112
|
-
function leaving(batch, result) {
|
|
1113
|
-
if (result.kind === 'expired')
|
|
1114
|
-
return { batch, returned: returnedBy('expired') };
|
|
1115
|
-
return {
|
|
1116
|
-
batch,
|
|
1117
|
-
returned: {
|
|
1118
|
-
kind: result.kind,
|
|
1119
|
-
code: result.code,
|
|
1120
|
-
fields: result.fields,
|
|
1121
|
-
retryable: result.retryable,
|
|
1122
|
-
},
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
|
-
function logAccepted(locator, submitted, accepted) {
|
|
1126
|
-
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
1127
|
-
(accepted.author === undefined ? '' : ` author ${accepted.author}`) +
|
|
1128
|
-
(accepted.time === undefined ? '' : ` time ${accepted.time}`));
|
|
1129
|
-
}
|
|
1130
|
-
async function adoptAccepted(room, local, submitted, accepted) {
|
|
1131
|
-
if (!continuing())
|
|
1132
|
-
return unconfirmed;
|
|
1133
|
-
const current = replicas.get(room.locator);
|
|
1134
|
-
if (current === undefined || !current.queue.some((batch) => batch.id === submitted.id)) {
|
|
1135
|
-
return null;
|
|
1136
|
-
}
|
|
1137
|
-
const held = current.confirmed.record.roomVersion;
|
|
1138
|
-
logAccepted(room.locator, submitted, accepted);
|
|
1139
|
-
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
1140
|
-
? { kind: 'staged', confirmed: current.confirmed }
|
|
1141
|
-
: applyReturned(current.confirmed, accepted.documents, accepted.revisions, accepted.changes, accepted.removals, accepted.attachments, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
1142
|
-
? accepted.roomVersion
|
|
1143
|
-
: held, accepted.roomVersion);
|
|
1144
|
-
if (applied.kind !== 'staged') {
|
|
1145
|
-
const state = pendings.get(submitted.id);
|
|
1146
|
-
await local.accept(room.locator, submitted.id, accepted.roomVersion);
|
|
1147
|
-
if (state !== undefined)
|
|
1148
|
-
pendings.set(submitted.id, { ...state, accepted: accepted.roomVersion });
|
|
1149
|
-
emitQueue(room.locator);
|
|
1150
|
-
return applied;
|
|
1151
|
-
}
|
|
1152
|
-
const set = await settle(local, room.locator, current, applied.confirmed, current.queue.filter((batch) => batch.id !== submitted.id), { room: true });
|
|
1153
|
-
pendings.delete(submitted.id);
|
|
1154
|
-
unreadable.delete(room.locator);
|
|
1155
|
-
sourceRefusals.delete(room.locator);
|
|
1156
|
-
declarationRefusals.delete(room.locator);
|
|
1157
|
-
list(room, true);
|
|
1158
|
-
publishChangeSet(set);
|
|
1159
|
-
moveTo({ ...status, ...attention() });
|
|
1160
|
-
return null;
|
|
1161
|
-
}
|
|
1162
|
-
async function adopted(room, local, submitted, accepted) {
|
|
1163
|
-
const failed = await exclusive(() => adoptAccepted(room, local, submitted, accepted));
|
|
1164
|
-
if (failed === null)
|
|
1165
|
-
return true;
|
|
1166
|
-
if (failed.kind === 'refused' && continuing())
|
|
1167
|
-
refusedSource(room, failed);
|
|
1168
|
-
return false;
|
|
1169
|
-
}
|
|
1170
|
-
async function submitRoom(room, local, authorized, progress) {
|
|
1171
|
-
while (continuing() && authorization !== undefined) {
|
|
1172
|
-
const replica = replicas.get(room.locator);
|
|
1173
|
-
const head = replica?.queue[0];
|
|
1174
|
-
if (replica === undefined || head === undefined)
|
|
1175
|
-
return;
|
|
1176
|
-
if (pendings.get(head.id)?.accepted !== undefined)
|
|
1177
|
-
return;
|
|
1178
|
-
const state = pendings.get(head.id);
|
|
1179
|
-
const { submission, lost: earlier } = state ?? {
|
|
1180
|
-
submission: { state: 'waiting' },
|
|
1181
|
-
lost: false,
|
|
1182
|
-
};
|
|
1183
|
-
if (submission.state === 'reset' && Date.now() < Date.parse(submission.reset)) {
|
|
1184
|
-
progress.complete = false;
|
|
1185
|
-
return;
|
|
1186
|
-
}
|
|
1187
|
-
if (writesHeld()) {
|
|
1188
|
-
progress.complete = false;
|
|
1189
|
-
journal.record(`submission ${room.locator} ${head.id} held`);
|
|
1190
|
-
return;
|
|
1191
|
-
}
|
|
1192
|
-
const lost = earlier || submission.state === 'submitted';
|
|
1193
|
-
let result;
|
|
1194
|
-
journal.record(`submission ${room.locator} ${head.id}`);
|
|
1195
|
-
await exclusive(() => mark(local, room.locator, head.id, { state: 'submitted' }, lost));
|
|
1196
|
-
if (discoveryPaused)
|
|
1197
|
-
return;
|
|
1198
|
-
if (writesHeld()) {
|
|
1199
|
-
await exclusive(() => mark(local, room.locator, head.id, submission, earlier));
|
|
1200
|
-
progress.complete = false;
|
|
1201
|
-
journal.record(`submission ${room.locator} ${head.id} held`);
|
|
1202
|
-
return;
|
|
1203
|
-
}
|
|
1204
|
-
const opensBefore = openSignals;
|
|
1205
|
-
const answered = awaitingAnswer(room.locator);
|
|
1206
|
-
try {
|
|
1207
|
-
result = await options.adapter.mutate(authorized, {
|
|
1208
|
-
locator: room.locator,
|
|
1209
|
-
changeBatch: head,
|
|
1210
|
-
...(state?.upload === undefined ? {} : { upload: state.upload }),
|
|
1211
|
-
}, (upload) => exclusive(() => keepUpload(local, room.locator, head.id, upload)));
|
|
1212
|
-
}
|
|
1213
|
-
catch {
|
|
1214
|
-
answered();
|
|
1215
|
-
progress.complete = false;
|
|
1216
|
-
if (continuing())
|
|
1217
|
-
progress.reachable = false;
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
if (result.kind !== 'accepted' || !continuing())
|
|
1221
|
-
answered();
|
|
1222
|
-
if (!continuing())
|
|
1223
|
-
return;
|
|
1224
|
-
if (result.kind === 'transport-uncertainty') {
|
|
1225
|
-
progress.complete = false;
|
|
1226
|
-
progress.reachable = false;
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
if (result.kind === 'room-not-readable') {
|
|
1230
|
-
progress.complete = false;
|
|
1231
|
-
unreadable.add(room.locator);
|
|
1232
|
-
list(room, false);
|
|
1233
|
-
emitRooms();
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1236
|
-
if (result.kind === 'revoked-authorization') {
|
|
1237
|
-
await refuse('revoked-authorization');
|
|
1238
|
-
return;
|
|
1239
|
-
}
|
|
1240
|
-
if (result.kind === 'scoped-refusal') {
|
|
1241
|
-
await scoped(result, room, local);
|
|
1242
|
-
progress.complete = false;
|
|
1243
|
-
return;
|
|
1244
|
-
}
|
|
1245
|
-
if (result.kind === 'write-halted') {
|
|
1246
|
-
journal.record(`outcome ${room.locator} ${head.id} write-halted`);
|
|
1247
|
-
await exclusive(() => mark(local, room.locator, head.id, { state: 'waiting' }, lost));
|
|
1248
|
-
if (serviceMode === 'open' && openSignals !== opensBefore)
|
|
1249
|
-
continue;
|
|
1250
|
-
haltedRefusal = true;
|
|
1251
|
-
progress.complete = false;
|
|
1252
|
-
return;
|
|
1253
|
-
}
|
|
1254
|
-
if (result.kind === 'conflicted' ||
|
|
1255
|
-
result.kind === 'rejected' ||
|
|
1256
|
-
result.kind === 'expired' ||
|
|
1257
|
-
result.kind === 'retryable-refusal') {
|
|
1258
|
-
if (result.kind === 'expired') {
|
|
1259
|
-
const withheld = await advance(room, local, authorized);
|
|
1260
|
-
if (withheld !== null)
|
|
1261
|
-
return withhold(withheld, room, local, progress);
|
|
1262
|
-
}
|
|
1263
|
-
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` +
|
|
1264
|
-
(result.kind === 'conflicted' && result.roomVersion !== undefined
|
|
1265
|
-
? ` ${result.roomVersion}`
|
|
1266
|
-
: ''));
|
|
1267
|
-
if (result.kind !== 'expired')
|
|
1268
|
-
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
1269
|
-
}
|
|
1270
|
-
if (result.kind === 'retryable-refusal') {
|
|
1271
|
-
const reset = result.reset;
|
|
1272
|
-
progress.complete = false;
|
|
1273
|
-
await exclusive(() => mark(local, room.locator, head.id, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
1274
|
-
armTimer();
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
1278
|
-
const left = leaving(head, result);
|
|
1279
|
-
await exclusive(async () => {
|
|
1280
|
-
if (!continuing())
|
|
1281
|
-
return;
|
|
1282
|
-
const current = replicas.get(room.locator);
|
|
1283
|
-
if (current === undefined || !current.queue.some((batch) => batch.id === head.id)) {
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
const set = await settle(local, room.locator, current, current.confirmed, current.queue.filter((batch) => batch.id !== head.id), {
|
|
1287
|
-
leaving: (result.kind === 'expired' && landed(head, current.confirmed)) ||
|
|
1288
|
-
(result.kind === 'conflicted' && landed(head, current.confirmed, result.affected))
|
|
1289
|
-
? { ...left, returned: null }
|
|
1290
|
-
: left,
|
|
1291
|
-
});
|
|
1292
|
-
publishChangeSet(set);
|
|
1293
|
-
moveTo({ ...status, ...attention() });
|
|
1294
|
-
});
|
|
1295
|
-
if (result.kind === 'expired')
|
|
1296
|
-
continue;
|
|
1297
|
-
const withheld = await advance(room, local, authorized);
|
|
1298
|
-
if (withheld !== null)
|
|
1299
|
-
return withhold(withheld, room, local, progress);
|
|
1300
|
-
continue;
|
|
1301
|
-
}
|
|
1302
|
-
const kept = await adopted(room, local, head, result).finally(answered);
|
|
1303
|
-
if (!kept) {
|
|
1304
|
-
progress.complete = false;
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
function awaitingAnswer(locator) {
|
|
1310
|
-
let release = () => { };
|
|
1311
|
-
const awaited = new Promise((resolve) => {
|
|
1312
|
-
release = resolve;
|
|
1313
|
-
});
|
|
1314
|
-
answering.set(locator, awaited);
|
|
1315
|
-
return () => {
|
|
1316
|
-
if (answering.get(locator) === awaited)
|
|
1317
|
-
answering.delete(locator);
|
|
1318
|
-
release();
|
|
1319
|
-
};
|
|
1320
|
-
}
|
|
1321
|
-
async function retainModel(local, room, attached, next) {
|
|
1322
|
-
const key = modelKey(room.locator, attached);
|
|
1323
|
-
const previous = modelOf(room.locator, attached);
|
|
1324
|
-
const heldBefore = replicas.get(room.locator)?.confirmed.record;
|
|
1325
|
-
if (JSON.stringify(previous) === JSON.stringify(next) &&
|
|
1326
|
-
(heldBefore === undefined ||
|
|
1327
|
-
(JSON.stringify(heldBefore.operations) === JSON.stringify(room.operations) &&
|
|
1328
|
-
JSON.stringify(heldBefore.replica) === JSON.stringify(room.replica) &&
|
|
1329
|
-
JSON.stringify(heldBefore.typedSelection) ===
|
|
1330
|
-
JSON.stringify(room.typedSelection ?? null))))
|
|
1331
|
-
return;
|
|
1332
|
-
if (next.status === 'unavailable') {
|
|
1333
|
-
invalidModels.add(key);
|
|
1334
|
-
liveModels.delete(key);
|
|
1335
|
-
}
|
|
1336
|
-
await exclusive(async () => {
|
|
1337
|
-
if (!continuing())
|
|
1338
|
-
return;
|
|
1339
|
-
const held = replicas.get(room.locator);
|
|
1340
|
-
if (held !== undefined) {
|
|
1341
|
-
const declared = {
|
|
1342
|
-
...held.confirmed.record,
|
|
1343
|
-
operations: room.operations,
|
|
1344
|
-
replica: room.replica,
|
|
1345
|
-
typedSelection: room.typedSelection ?? null,
|
|
1346
|
-
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
1347
|
-
};
|
|
1348
|
-
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
1349
|
-
? { kind: 'staged', confirmed: confirmedOf(declared, held.confirmed) }
|
|
1350
|
-
: readReclassified(declared, held.confirmed);
|
|
1351
|
-
const record = reclassified.kind === 'staged'
|
|
1352
|
-
? declared
|
|
1353
|
-
: {
|
|
1354
|
-
...declared,
|
|
1355
|
-
typedSelection: held.confirmed.record.typedSelection,
|
|
1356
|
-
};
|
|
1357
|
-
const read = reclassified.kind === 'deferred'
|
|
1358
|
-
? { kind: 'staged', confirmed: confirmedOf(record, held.confirmed) }
|
|
1359
|
-
: reclassified;
|
|
1360
|
-
await local.write({
|
|
1361
|
-
next: record,
|
|
1362
|
-
previous: held.confirmed.record,
|
|
1363
|
-
queue: held.queue,
|
|
1364
|
-
});
|
|
1365
|
-
if (read.kind === 'staged') {
|
|
1366
|
-
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
|
|
1367
|
-
if (declarationRefusals.delete(room.locator)) {
|
|
1368
|
-
sourceRefusals.delete(room.locator);
|
|
1369
|
-
unreadable.delete(room.locator);
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
else if (read.kind === 'refused') {
|
|
1373
|
-
replicas.set(room.locator, { ...held, confirmed: { ...held.confirmed, record } });
|
|
1374
|
-
refusedSource(room, read);
|
|
1375
|
-
declarationRefusals.add(room.locator);
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
models.set(key, next);
|
|
1379
|
-
if (next.status === 'current') {
|
|
1380
|
-
liveModels.set(key, JSON.stringify(next.snapshot));
|
|
1381
|
-
invalidModels.delete(key);
|
|
1382
|
-
}
|
|
1383
|
-
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
1384
|
-
if (held === undefined)
|
|
1385
|
-
emitRooms();
|
|
1386
|
-
else
|
|
1387
|
-
publishChangeSet(changeSet(room.locator, held, replicas.get(room.locator) ?? null, { room: true }));
|
|
1388
|
-
});
|
|
1389
|
-
}
|
|
1390
|
-
async function scoped(result, room, local) {
|
|
1391
|
-
scopedRefusals.set(room.locator, {
|
|
1392
|
-
code: result.code,
|
|
1393
|
-
fields: result.fields,
|
|
1394
|
-
retryable: result.retryable,
|
|
1395
|
-
});
|
|
1396
|
-
let scopedAway = room;
|
|
1397
|
-
if (result.scope === 'model')
|
|
1398
|
-
await retainModel(local, room, 'collection', {
|
|
1399
|
-
status: 'unavailable',
|
|
1400
|
-
reason: 'not-readable',
|
|
1401
|
-
});
|
|
1402
|
-
else if (result.scope !== 'operation') {
|
|
1403
|
-
unreadable.add(room.locator);
|
|
1404
|
-
scopedAway = {
|
|
1405
|
-
...room,
|
|
1406
|
-
replica: { ...room.replica, admission: 'unavailable' },
|
|
1407
|
-
operations: {
|
|
1408
|
-
...room.operations,
|
|
1409
|
-
...(result.scope === 'content' ? { read: [] } : {}),
|
|
1410
|
-
...(result.scope === 'sync' ? { sync: [] } : {}),
|
|
1411
|
-
},
|
|
1412
|
-
};
|
|
1413
|
-
await retainModel(local, scopedAway, 'collection', modelOf(room.locator));
|
|
1414
|
-
}
|
|
1415
|
-
if (!continuing())
|
|
1416
|
-
return;
|
|
1417
|
-
list(scopedAway, scopedAway.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
1418
|
-
emitRooms();
|
|
1419
|
-
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
1420
|
-
}
|
|
1421
|
-
function heldRooms(listed) {
|
|
1422
|
-
return listed.flatMap((room) => {
|
|
1423
|
-
const record = replicas.get(room.locator)?.confirmed.record;
|
|
1424
|
-
if (record === undefined || record.replica.selection !== room.replica.selection)
|
|
1425
|
-
return [];
|
|
1426
|
-
return [
|
|
1427
|
-
{
|
|
1428
|
-
locator: room.locator,
|
|
1429
|
-
roomVersion: record.roomVersion,
|
|
1430
|
-
documents: record.documents,
|
|
1431
|
-
revisions: record.revisions,
|
|
1432
|
-
attachments: record.attachments,
|
|
1433
|
-
},
|
|
1434
|
-
];
|
|
1435
|
-
});
|
|
1436
|
-
}
|
|
1437
|
-
async function synchronize(listed, local, progress, attempt) {
|
|
1438
|
-
const prepared = await Promise.allSettled(listed.map((room) => prepareRoom(room, local, progress)));
|
|
1439
|
-
const ready = listed.filter((_, index) => {
|
|
1440
|
-
const outcome = prepared[index];
|
|
1441
|
-
return outcome?.status === 'fulfilled' && outcome.value;
|
|
1442
|
-
});
|
|
1443
|
-
if (attempt === 'opened') {
|
|
1444
|
-
options.adapter.roomStream?.hold(heldRooms(ready));
|
|
1445
|
-
handed = true;
|
|
1446
|
-
}
|
|
1447
|
-
const loaded = await Promise.allSettled(ready
|
|
1448
|
-
.filter((room) => attempt === 'unstreamed' || !replicas.has(room.locator))
|
|
1449
|
-
.map((room) => loadRoom(room, local, progress)));
|
|
1450
|
-
const failed = [...prepared, ...loaded].find((room) => room.status === 'rejected');
|
|
1451
|
-
if (failed !== undefined)
|
|
1452
|
-
throw failed.reason;
|
|
1453
|
-
}
|
|
1454
|
-
async function prepareRoom(room, local, progress) {
|
|
1455
|
-
if (closed || authorization === undefined) {
|
|
1456
|
-
progress.complete = false;
|
|
1457
|
-
return false;
|
|
1458
|
-
}
|
|
1459
|
-
const prior = replicas.get(room.locator)?.confirmed.record;
|
|
1460
|
-
if (prior !== undefined && prior.replica.selection !== room.replica.selection) {
|
|
1461
|
-
list(prior, false);
|
|
1462
|
-
emitRooms();
|
|
1463
|
-
journal.record(`selection changed ${room.locator}`);
|
|
1464
|
-
progress.complete = false;
|
|
1465
|
-
return false;
|
|
1466
|
-
}
|
|
1467
|
-
if (prior !== undefined)
|
|
1468
|
-
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
1469
|
-
if (discoveryPaused || authorization === undefined)
|
|
1470
|
-
return false;
|
|
1471
|
-
if (room.replica.admission !== 'admitted') {
|
|
1472
|
-
if (replicas.has(room.locator))
|
|
1473
|
-
progress.complete = false;
|
|
1474
|
-
return false;
|
|
1475
|
-
}
|
|
1476
|
-
return true;
|
|
1477
|
-
}
|
|
1478
|
-
async function caughtUp(room, local, progress) {
|
|
1479
|
-
const held = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
1480
|
-
if (held === undefined || authorization === undefined)
|
|
1481
|
-
return;
|
|
1482
|
-
const behind = [announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later));
|
|
1483
|
-
if (!behind)
|
|
1484
|
-
return;
|
|
1485
|
-
const withheld = await advance(room, local, authorization);
|
|
1486
|
-
if (withheld !== null)
|
|
1487
|
-
await withhold(withheld, room, local, progress);
|
|
1488
|
-
}
|
|
1489
|
-
function revoked(locator) {
|
|
1490
|
-
return ['collection', 'typed-selection'].some((attached) => {
|
|
1491
|
-
const model = modelOf(locator, attached);
|
|
1492
|
-
return model.status === 'unavailable' && model.reason === 'revoked';
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1495
|
-
async function loadRoom(room, local, progress) {
|
|
1496
|
-
if (discoveryPaused || authorization === undefined)
|
|
1497
|
-
return;
|
|
1498
|
-
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
1499
|
-
await reload(room, local, progress);
|
|
1500
|
-
return;
|
|
1501
|
-
}
|
|
1502
|
-
const withheld = await advance(room, local, authorization);
|
|
1503
|
-
if (withheld !== null) {
|
|
1504
|
-
await withhold(withheld, room, local, progress);
|
|
1505
|
-
return;
|
|
1506
|
-
}
|
|
1507
|
-
await caughtUp(room, local, progress);
|
|
1508
|
-
}
|
|
1509
|
-
async function reload(room, local, progress) {
|
|
1510
|
-
if (discoveryPaused || authorization === undefined)
|
|
1511
|
-
return;
|
|
1512
|
-
const bootstrapped = await pullBootstrap(authorization, room, local);
|
|
1513
|
-
if (!continuing())
|
|
1514
|
-
return;
|
|
1515
|
-
if (bootstrapped.kind !== 'staged') {
|
|
1516
|
-
await withhold(bootstrapped, room, local, progress);
|
|
1517
|
-
return;
|
|
1518
|
-
}
|
|
1519
|
-
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
|
|
1520
|
-
await caughtUp(room, local, progress);
|
|
1521
|
-
}
|
|
1522
|
-
async function discoverRooms(local, progress, attempt) {
|
|
1523
|
-
if (closed || authorization === undefined) {
|
|
1524
|
-
notifyReady();
|
|
1525
|
-
return;
|
|
1526
|
-
}
|
|
1527
|
-
moveTo(transferring(status));
|
|
1528
|
-
let result;
|
|
1529
|
-
try {
|
|
1530
|
-
result = await options.adapter.discover(authorization);
|
|
1531
|
-
}
|
|
1532
|
-
catch {
|
|
1533
|
-
result = { kind: 'transport-uncertainty' };
|
|
1534
|
-
}
|
|
1535
|
-
if (closed) {
|
|
1536
|
-
notifyReady();
|
|
1537
|
-
return;
|
|
1538
|
-
}
|
|
1539
|
-
journal.record(`connection discover ${result.kind}`);
|
|
1540
|
-
if (result.kind === 'transport-uncertainty') {
|
|
1541
|
-
for (const room of rooms.values()) {
|
|
1542
|
-
await retainModel(local, room, 'collection', cached(modelOf(room.locator)));
|
|
1543
|
-
if (room.typedSelection !== null) {
|
|
1544
|
-
await retainModel(local, room, 'typed-selection', cached(modelOf(room.locator, 'typed-selection')));
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
progress.reachable = false;
|
|
1548
|
-
progress.complete = false;
|
|
1549
|
-
notifyReady();
|
|
1550
|
-
return;
|
|
1551
|
-
}
|
|
1552
|
-
if (result.kind === 'contract-unavailable') {
|
|
1553
|
-
journal.record(`connection contract unavailable ${result.code}`);
|
|
1554
|
-
await refuse('missing-required-guarantee', {
|
|
1555
|
-
code: result.code,
|
|
1556
|
-
fields: result.fields,
|
|
1557
|
-
retryable: result.retryable,
|
|
1558
|
-
});
|
|
1559
|
-
notifyReady();
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1562
|
-
if (result.kind === 'revoked-authorization') {
|
|
1563
|
-
await refuse('revoked-authorization');
|
|
1564
|
-
notifyReady();
|
|
1565
|
-
return;
|
|
1566
|
-
}
|
|
1567
|
-
for (const room of result.rooms) {
|
|
1568
|
-
if (room.operations === undefined ||
|
|
1569
|
-
!['read', 'sync', 'write', 'delete'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
1570
|
-
room.operations[operation].every((scope) => typeof scope === 'string')) ||
|
|
1571
|
-
room.replica === undefined ||
|
|
1572
|
-
typeof room.replica.selection !== 'string' ||
|
|
1573
|
-
room.replica.selection.length === 0 ||
|
|
1574
|
-
!['admitted', 'unavailable'].includes(room.replica.admission)) {
|
|
1575
|
-
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
1576
|
-
await refuse('missing-required-guarantee');
|
|
1577
|
-
notifyReady();
|
|
1578
|
-
return;
|
|
1579
|
-
}
|
|
1580
|
-
if (!declaredSelection(room)) {
|
|
1581
|
-
journal.record(`invalid typed selection declaration ${room.locator}`);
|
|
1582
|
-
await refuse('missing-required-guarantee');
|
|
1583
|
-
notifyReady();
|
|
1584
|
-
return;
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
if (refusal !== null) {
|
|
1588
|
-
refusal = null;
|
|
1589
|
-
emitRefusal();
|
|
1590
|
-
}
|
|
1591
|
-
const previousCapabilities = capabilities;
|
|
1592
|
-
capabilities = result.capabilities;
|
|
1593
|
-
notifyReady();
|
|
1594
|
-
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
1595
|
-
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)) {
|
|
1596
|
-
limits = result.providerLimits;
|
|
1597
|
-
await local.declare(limits, capabilities);
|
|
1598
|
-
}
|
|
1599
|
-
const signedIn = result.member ?? null;
|
|
1600
|
-
if (JSON.stringify(signedIn) !== JSON.stringify(member)) {
|
|
1601
|
-
await local.keepMember(signedIn);
|
|
1602
|
-
member = signedIn;
|
|
1603
|
-
emitMember();
|
|
1604
|
-
}
|
|
1605
|
-
await applyDiscovery(result.rooms, local);
|
|
1606
|
-
await synchronize(result.rooms, local, progress, attempt);
|
|
1607
|
-
}
|
|
1608
|
-
async function ensureAuthorization() {
|
|
1609
|
-
if (authorization !== undefined)
|
|
1610
|
-
return;
|
|
1611
|
-
try {
|
|
1612
|
-
authorization = await options.adapter.authorize();
|
|
1613
|
-
journal.record('connection authorize');
|
|
1614
|
-
}
|
|
1615
|
-
catch (error) {
|
|
1616
|
-
journal.record('connection authorize transport-uncertainty');
|
|
1617
|
-
throw error;
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
function task(work) {
|
|
1621
|
-
if (stopping)
|
|
1622
|
-
return chain;
|
|
1623
|
-
chain = chain.then(work, work).catch(() => undefined);
|
|
1624
|
-
return chain;
|
|
1625
|
-
}
|
|
1626
|
-
function sync(attempt) {
|
|
1627
|
-
return task(async () => {
|
|
1628
|
-
if (closed || stopping || discoveryPaused) {
|
|
1629
|
-
notifyReady();
|
|
1630
|
-
return;
|
|
1631
|
-
}
|
|
1632
|
-
const progress = { complete: true, reachable: true };
|
|
1633
|
-
underway = attempt === 'opened' ? progress : null;
|
|
1634
|
-
if (attempt === 'opened')
|
|
1635
|
-
streamed = progress;
|
|
1636
|
-
complete = false;
|
|
1637
|
-
recorded = false;
|
|
1638
|
-
moveTo(transferring(status));
|
|
1639
|
-
handed = false;
|
|
1640
|
-
try {
|
|
1641
|
-
const local = await openLocal();
|
|
1642
|
-
if (closed)
|
|
1643
|
-
return;
|
|
1644
|
-
await ensureAuthorization();
|
|
1645
|
-
if (closed)
|
|
1646
|
-
return;
|
|
1647
|
-
await discoverRooms(local, progress, attempt);
|
|
1648
|
-
}
|
|
1649
|
-
catch {
|
|
1650
|
-
progress.complete = false;
|
|
1651
|
-
if (attempt === 'opened' && !handed)
|
|
1652
|
-
progress.reachable = false;
|
|
1653
|
-
}
|
|
1654
|
-
finally {
|
|
1655
|
-
notifyReady();
|
|
1656
|
-
}
|
|
1657
|
-
if (attempt !== 'opened') {
|
|
1658
|
-
await finishOpening(progress);
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1661
|
-
if (handed || closed || progress.reachable)
|
|
1662
|
-
return;
|
|
1663
|
-
underway = null;
|
|
1664
|
-
journal.record('connection reopen');
|
|
1665
|
-
await rest(progress);
|
|
1666
|
-
options.adapter.roomStream?.reopen();
|
|
1667
|
-
});
|
|
1668
|
-
}
|
|
1669
|
-
function closedSocket(revoked) {
|
|
1670
|
-
const opened = socketOpen;
|
|
1671
|
-
socketOpen = false;
|
|
1672
|
-
if (revoked) {
|
|
1673
|
-
void refuse('revoked-authorization');
|
|
1674
|
-
return;
|
|
1675
|
-
}
|
|
1676
|
-
complete = false;
|
|
1677
|
-
if (streamed !== null)
|
|
1678
|
-
streamed.complete = false;
|
|
1679
|
-
moveTo({
|
|
1680
|
-
...status,
|
|
1681
|
-
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
1682
|
-
providerUnreachable: status.providerUnreachable || !opened,
|
|
1683
|
-
});
|
|
1684
|
-
}
|
|
1685
|
-
function applyOpening(expired) {
|
|
1686
|
-
return task(async () => {
|
|
1687
|
-
const progress = underway;
|
|
1688
|
-
underway = null;
|
|
1689
|
-
if (closed || stopping)
|
|
1690
|
-
return;
|
|
1691
|
-
if (progress === null) {
|
|
1692
|
-
await reloadExpired(expired);
|
|
1693
|
-
return;
|
|
1694
|
-
}
|
|
1695
|
-
try {
|
|
1696
|
-
const local = await openLocal();
|
|
1697
|
-
const held = [...discovered.values()].filter((room) => replicas.has(room.locator));
|
|
1698
|
-
const reloading = held.filter((room) => expired.includes(room.locator) || revoked(room.locator));
|
|
1699
|
-
const reloaded = await Promise.allSettled(reloading.map((room) => reload(room, local, progress)));
|
|
1700
|
-
if (progress.reachable) {
|
|
1701
|
-
for (const room of held)
|
|
1702
|
-
if (!reloading.includes(room))
|
|
1703
|
-
await confirmModels(room, local);
|
|
1704
|
-
}
|
|
1705
|
-
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
1706
|
-
if (failed !== undefined)
|
|
1707
|
-
throw failed.reason;
|
|
1708
|
-
}
|
|
1709
|
-
catch {
|
|
1710
|
-
progress.complete = false;
|
|
1711
|
-
progress.reachable = false;
|
|
1712
|
-
}
|
|
1713
|
-
await finishOpening(progress);
|
|
1714
|
-
});
|
|
1715
|
-
}
|
|
1716
|
-
async function reloadExpired(expired) {
|
|
1717
|
-
const progress = { complete: true, reachable: true };
|
|
1718
|
-
try {
|
|
1719
|
-
const local = await openLocal();
|
|
1720
|
-
await Promise.allSettled([...discovered.values()]
|
|
1721
|
-
.filter((room) => replicas.has(room.locator) && expired.includes(room.locator))
|
|
1722
|
-
.map((room) => reload(room, local, progress)));
|
|
1723
|
-
}
|
|
1724
|
-
catch {
|
|
1725
|
-
progress.reachable = false;
|
|
1726
|
-
}
|
|
1727
|
-
await rest(progress);
|
|
1728
|
-
}
|
|
1729
|
-
async function confirmModels(room, local) {
|
|
1730
|
-
for (const attached of ['collection', 'typed-selection']) {
|
|
1731
|
-
const model = modelOf(room.locator, attached);
|
|
1732
|
-
if (model.status === 'cached') {
|
|
1733
|
-
await retainModel(local, room, attached, { ...model, status: 'current' });
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
function submitLater(locator) {
|
|
1738
|
-
if (localSaves)
|
|
1739
|
-
return;
|
|
1740
|
-
const running = submitting.get(locator);
|
|
1741
|
-
if (running !== undefined) {
|
|
1742
|
-
running.again = true;
|
|
1743
|
-
return;
|
|
1744
|
-
}
|
|
1745
|
-
const run = { again: false, done: Promise.resolve() };
|
|
1746
|
-
submitting.set(locator, run);
|
|
1747
|
-
run.done = (async () => {
|
|
1748
|
-
try {
|
|
1749
|
-
do {
|
|
1750
|
-
run.again = false;
|
|
1751
|
-
const room = discovered.get(locator);
|
|
1752
|
-
if (closed || stopping || discoveryPaused || room === undefined)
|
|
1753
|
-
return;
|
|
1754
|
-
const progress = { complete: true, reachable: true };
|
|
1755
|
-
try {
|
|
1756
|
-
await ensureAuthorization();
|
|
1757
|
-
if (authorization === undefined)
|
|
1758
|
-
return;
|
|
1759
|
-
await submitRoom(room, await openLocal(), authorization, progress);
|
|
1760
|
-
}
|
|
1761
|
-
catch {
|
|
1762
|
-
progress.reachable = false;
|
|
1763
|
-
}
|
|
1764
|
-
armTimer();
|
|
1765
|
-
await rest(progress);
|
|
1766
|
-
} while (run.again);
|
|
1767
|
-
}
|
|
1768
|
-
finally {
|
|
1769
|
-
submitting.delete(locator);
|
|
1770
|
-
}
|
|
1771
|
-
})();
|
|
1772
|
-
}
|
|
1773
|
-
function submitAll() {
|
|
1774
|
-
for (const [locator, replica] of replicas) {
|
|
1775
|
-
if (replica.queue.length > 0)
|
|
1776
|
-
submitLater(locator);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
async function finishOpening(progress) {
|
|
1780
|
-
try {
|
|
1781
|
-
if (closed || discoveryPaused) {
|
|
1782
|
-
progress.complete = false;
|
|
1783
|
-
return;
|
|
1784
|
-
}
|
|
1785
|
-
submitAll();
|
|
1786
|
-
await conversations.reconcile();
|
|
1787
|
-
if (status.providerUnreachable)
|
|
1788
|
-
conversations.reconnected();
|
|
1789
|
-
const reading = { complete: true, reachable: true };
|
|
1790
|
-
await conversations.catchUp(reading);
|
|
1791
|
-
if (!reading.reachable)
|
|
1792
|
-
progress.reachable = false;
|
|
1793
|
-
}
|
|
1794
|
-
catch {
|
|
1795
|
-
progress.complete = false;
|
|
1796
|
-
}
|
|
1797
|
-
finally {
|
|
1798
|
-
complete = progress.complete;
|
|
1799
|
-
await rest(progress);
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
async function rest(progress) {
|
|
1803
|
-
if (closed)
|
|
1804
|
-
return;
|
|
1805
|
-
let lastCompleted = status.lastCompleted;
|
|
1806
|
-
let synced = complete && stale.size === 0 && !pending();
|
|
1807
|
-
if (synced && !recorded) {
|
|
1808
|
-
const completedAt = new Date().toISOString();
|
|
1809
|
-
try {
|
|
1810
|
-
await (await openLocal()).complete(completedAt);
|
|
1811
|
-
recorded = true;
|
|
1812
|
-
lastCompleted = completedAt;
|
|
1813
|
-
}
|
|
1814
|
-
catch {
|
|
1815
|
-
synced = false;
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
moveTo({
|
|
1819
|
-
state: pending() ? 'pending' : synced && complete ? 'synced' : 'syncing',
|
|
1820
|
-
lastCompleted,
|
|
1821
|
-
providerUnreachable: !progress.reachable,
|
|
1822
|
-
...attention(),
|
|
1823
|
-
});
|
|
1824
|
-
}
|
|
1825
|
-
function applyRoom(locator, roomVersion) {
|
|
1826
|
-
announced.set(locator, roomVersion);
|
|
1827
|
-
return task(async () => {
|
|
1828
|
-
await answering.get(locator);
|
|
1829
|
-
if (closed || stopping || discoveryPaused || authorization === undefined)
|
|
1830
|
-
return;
|
|
1831
|
-
const room = discovered.get(locator);
|
|
1832
|
-
const held = replicas.get(locator);
|
|
1833
|
-
if (room === undefined || room.replica.admission !== 'admitted')
|
|
1834
|
-
return;
|
|
1835
|
-
if (held !== undefined &&
|
|
1836
|
-
(held.confirmed.record.replica.selection !== room.replica.selection ||
|
|
1837
|
-
options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion))) {
|
|
1838
|
-
return;
|
|
1839
|
-
}
|
|
1840
|
-
const during = underway;
|
|
1841
|
-
const progress = during ?? { complete: true, reachable: true };
|
|
1842
|
-
if (during === null)
|
|
1843
|
-
moveTo(transferring(status));
|
|
1844
|
-
try {
|
|
1845
|
-
const local = await openLocal();
|
|
1846
|
-
const withheld = await advance(room, local, authorization);
|
|
1847
|
-
if (withheld !== null)
|
|
1848
|
-
await withhold(withheld, room, local, progress);
|
|
1849
|
-
}
|
|
1850
|
-
catch {
|
|
1851
|
-
stale.add(locator);
|
|
1852
|
-
}
|
|
1853
|
-
finally {
|
|
1854
|
-
if (during === null)
|
|
1855
|
-
await rest(progress);
|
|
1856
|
-
}
|
|
1857
|
-
});
|
|
1858
|
-
}
|
|
1859
|
-
function observeStream() {
|
|
1860
|
-
const stream = options.adapter.roomStream;
|
|
1861
|
-
if (stream === undefined || withdrawStream !== undefined)
|
|
1862
|
-
return;
|
|
1863
|
-
withdrawStream = stream.observe((event) => {
|
|
1864
|
-
if (closed || stopping)
|
|
1865
|
-
return;
|
|
1866
|
-
if (event.kind === 'opened') {
|
|
1867
|
-
socketOpen = true;
|
|
1868
|
-
void sync('opened');
|
|
1869
|
-
}
|
|
1870
|
-
else if (event.kind === 'closed')
|
|
1871
|
-
closedSocket(event.revoked);
|
|
1872
|
-
else if (event.kind === 'opening')
|
|
1873
|
-
void applyOpening(event.expired);
|
|
1874
|
-
else
|
|
1875
|
-
void applyRoom(event.locator, event.roomVersion);
|
|
1876
|
-
});
|
|
1877
|
-
}
|
|
1878
|
-
function writesHeld() {
|
|
1879
|
-
return serviceMode === 'write-halted' || haltedRefusal;
|
|
1880
|
-
}
|
|
1881
|
-
function signalled(mode) {
|
|
1882
|
-
if (closed)
|
|
1883
|
-
return;
|
|
1884
|
-
if (mode === 'open')
|
|
1885
|
-
openSignals += 1;
|
|
1886
|
-
const releasing = mode === 'open' && (haltedRefusal || serviceMode === 'write-halted');
|
|
1887
|
-
if (mode === 'open')
|
|
1888
|
-
haltedRefusal = false;
|
|
1889
|
-
if (mode !== serviceMode) {
|
|
1890
|
-
serviceMode = mode;
|
|
1891
|
-
journal.record(`service mode ${mode}`);
|
|
1892
|
-
for (const listener of serviceModeListeners)
|
|
1893
|
-
listener(mode);
|
|
1894
|
-
}
|
|
1895
|
-
if (releasing)
|
|
1896
|
-
submitAll();
|
|
1897
|
-
}
|
|
1898
|
-
function observeConversations() {
|
|
1899
|
-
const stream = options.adapter.conversations;
|
|
1900
|
-
if (stream === undefined || withdrawConversations !== undefined)
|
|
1901
|
-
return;
|
|
1902
|
-
withdrawConversations = stream.observe((event) => {
|
|
1903
|
-
if (conversations.receive(event)) {
|
|
1904
|
-
void ready.then(scheduleCurrency);
|
|
1905
|
-
return;
|
|
1906
|
-
}
|
|
1907
|
-
if (event.kind !== 'gap' || conversations.current())
|
|
1908
|
-
return;
|
|
1909
|
-
show();
|
|
1910
|
-
scheduleCurrency();
|
|
1911
|
-
});
|
|
1912
|
-
}
|
|
1913
|
-
function scheduleCurrency() {
|
|
1914
|
-
if (currencyQueued || closed || stopping)
|
|
1915
|
-
return;
|
|
1916
|
-
currencyQueued = true;
|
|
1917
|
-
void task(runCurrency);
|
|
1918
|
-
}
|
|
1919
|
-
async function runCurrency() {
|
|
1920
|
-
currencyQueued = false;
|
|
1921
|
-
if (closed || stopping || discoveryPaused)
|
|
1922
|
-
return;
|
|
1923
|
-
await conversations.reconcile();
|
|
1924
|
-
if (conversations.current()) {
|
|
1925
|
-
show();
|
|
1926
|
-
return;
|
|
1927
|
-
}
|
|
1928
|
-
const progress = { complete: false, reachable: !status.providerUnreachable };
|
|
1929
|
-
moveTo(transferring(status));
|
|
1930
|
-
try {
|
|
1931
|
-
await conversations.catchUp(progress);
|
|
1932
|
-
}
|
|
1933
|
-
catch {
|
|
1934
|
-
progress.reachable = false;
|
|
1935
|
-
}
|
|
1936
|
-
await rest(progress);
|
|
1937
|
-
}
|
|
1938
|
-
function close() {
|
|
1939
|
-
if (closed)
|
|
1940
|
-
return;
|
|
1941
|
-
closed = true;
|
|
1942
|
-
withdrawStream?.();
|
|
1943
|
-
withdrawStream = undefined;
|
|
1944
|
-
withdrawConversations?.();
|
|
1945
|
-
withdrawConversations = undefined;
|
|
1946
|
-
withdrawServiceMode?.();
|
|
1947
|
-
conversations.close();
|
|
1948
|
-
stopTimer();
|
|
1949
|
-
store?.close();
|
|
1950
|
-
void opening?.then((opened) => {
|
|
1951
|
-
opened.close();
|
|
1952
|
-
}, () => { });
|
|
1953
|
-
roomListeners.clear();
|
|
1954
|
-
refusalListeners.clear();
|
|
1955
|
-
storageLossListeners.clear();
|
|
1956
|
-
statusListeners.clear();
|
|
1957
|
-
serviceModeListeners.clear();
|
|
1958
|
-
memberListeners.clear();
|
|
1959
|
-
readSide.close();
|
|
1960
|
-
queueSubscriptions.clear();
|
|
1961
|
-
roomDraftListeners.clear();
|
|
1962
|
-
presentationListeners.clear();
|
|
1963
|
-
}
|
|
1964
|
-
async function stop() {
|
|
1965
|
-
stopping = true;
|
|
1966
|
-
stopTimer();
|
|
1967
|
-
await chain.catch(() => undefined);
|
|
1968
|
-
await Promise.all([...submitting.values()].map((run) => run.done));
|
|
1969
|
-
await mutations;
|
|
1970
|
-
close();
|
|
1971
|
-
}
|
|
1972
|
-
const withdrawServiceMode = options.adapter.serviceMode?.observe(signalled);
|
|
1973
|
-
observeConversations();
|
|
1974
|
-
options.signal?.addEventListener('abort', close, { once: true });
|
|
1975
|
-
if (options.signal?.aborted)
|
|
1976
|
-
close();
|
|
1977
|
-
observeStream();
|
|
1978
|
-
void openLocal().then(() => {
|
|
1979
|
-
if (capabilities.length > 0)
|
|
1980
|
-
notifyReady();
|
|
1981
|
-
}, () => {
|
|
1982
|
-
notifyReady();
|
|
1983
|
-
});
|
|
1984
|
-
if (options.adapter.roomStream === undefined)
|
|
1985
|
-
void sync('unstreamed');
|
|
1986
|
-
function planned(input, reach) {
|
|
1987
|
-
return planOf(input, roomFacts(reach), planReach(reach));
|
|
1988
|
-
}
|
|
1989
|
-
function watchPlan(plan, deliver) {
|
|
1990
|
-
materialize(reachedRooms(plan));
|
|
1991
|
-
return readSide.watch(plan, identityOf(plan), deliver);
|
|
1992
|
-
}
|
|
1993
|
-
async function searched(input, reach) {
|
|
1994
|
-
const plan = searchPlanOf(input);
|
|
1995
|
-
const selected = plan.documents === undefined && reach === null
|
|
1996
|
-
? null
|
|
1997
|
-
: (await rowsQuery(filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach)))).documents;
|
|
1998
|
-
if (selected === null)
|
|
1999
|
-
materialize(null);
|
|
2000
|
-
return searchIndex.search(reach === null || reach.conversations ? plan : { ...plan, conversations: [] }, selected);
|
|
2001
|
-
}
|
|
2002
|
-
const watch = (topic, input, deliver) => {
|
|
2003
|
-
if (topic === 'room-drafts') {
|
|
2004
|
-
const listener = { notify: deliver, ready: false };
|
|
2005
|
-
roomDraftListeners.add(listener);
|
|
2006
|
-
void exclusive(async () => {
|
|
2007
|
-
await openLocal();
|
|
2008
|
-
if (closed || !roomDraftListeners.has(listener))
|
|
2009
|
-
return;
|
|
2010
|
-
listener.ready = true;
|
|
2011
|
-
}).then(() => {
|
|
2012
|
-
if (closed || !listener.ready || !roomDraftListeners.has(listener))
|
|
2013
|
-
return;
|
|
2014
|
-
listener.notify(snapshotRoomDrafts());
|
|
2015
|
-
}, () => { });
|
|
2016
|
-
return () => {
|
|
2017
|
-
roomDraftListeners.delete(listener);
|
|
2018
|
-
};
|
|
2019
|
-
}
|
|
2020
|
-
if (topic === 'conversation') {
|
|
2021
|
-
return conversations.watch(input[0], input[1], deliver);
|
|
2022
|
-
}
|
|
2023
|
-
if (topic === 'conversations')
|
|
2024
|
-
return conversations.watchList(deliver);
|
|
2025
|
-
if (topic === 'agents')
|
|
2026
|
-
return conversations.watchAgents(deliver);
|
|
2027
|
-
if (topic === 'presentation') {
|
|
2028
|
-
const listener = {
|
|
2029
|
-
app: input[0],
|
|
2030
|
-
notify: deliver,
|
|
2031
|
-
ready: false,
|
|
2032
|
-
};
|
|
2033
|
-
presentationListeners.add(listener);
|
|
2034
|
-
void exclusive(async () => {
|
|
2035
|
-
await openLocal();
|
|
2036
|
-
}).then(() => {
|
|
2037
|
-
if (closed || !presentationListeners.has(listener))
|
|
2038
|
-
return;
|
|
2039
|
-
listener.ready = true;
|
|
2040
|
-
listener.notify(presentationOf(listener.app));
|
|
2041
|
-
}, () => { });
|
|
2042
|
-
return () => {
|
|
2043
|
-
presentationListeners.delete(listener);
|
|
2044
|
-
};
|
|
2045
|
-
}
|
|
2046
|
-
if (topic === 'rooms') {
|
|
2047
|
-
roomListeners.add(deliver);
|
|
2048
|
-
deliver(listed());
|
|
2049
|
-
return () => {
|
|
2050
|
-
roomListeners.delete(deliver);
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
|
-
if (topic === 'connection-refusal') {
|
|
2054
|
-
refusalListeners.add(deliver);
|
|
2055
|
-
deliver(refusal);
|
|
2056
|
-
return () => {
|
|
2057
|
-
refusalListeners.delete(deliver);
|
|
2058
|
-
};
|
|
2059
|
-
}
|
|
2060
|
-
if (topic === 'storage-loss') {
|
|
2061
|
-
storageLossListeners.add(deliver);
|
|
2062
|
-
deliver(storageLoss);
|
|
2063
|
-
return () => {
|
|
2064
|
-
storageLossListeners.delete(deliver);
|
|
2065
|
-
};
|
|
2066
|
-
}
|
|
2067
|
-
if (topic === 'sync-status') {
|
|
2068
|
-
statusListeners.add(deliver);
|
|
2069
|
-
deliver(shown);
|
|
2070
|
-
return () => {
|
|
2071
|
-
statusListeners.delete(deliver);
|
|
2072
|
-
};
|
|
2073
|
-
}
|
|
2074
|
-
if (topic === 'service-mode') {
|
|
2075
|
-
serviceModeListeners.add(deliver);
|
|
2076
|
-
deliver(serviceMode);
|
|
2077
|
-
return () => {
|
|
2078
|
-
serviceModeListeners.delete(deliver);
|
|
2079
|
-
};
|
|
2080
|
-
}
|
|
2081
|
-
if (topic === 'member') {
|
|
2082
|
-
memberListeners.add(deliver);
|
|
2083
|
-
deliver(member);
|
|
2084
|
-
return () => {
|
|
2085
|
-
memberListeners.delete(deliver);
|
|
2086
|
-
};
|
|
2087
|
-
}
|
|
2088
|
-
if (topic === 'queue') {
|
|
2089
|
-
const queue = { locator: input[0], notify: deliver };
|
|
2090
|
-
queueSubscriptions.add(queue);
|
|
2091
|
-
queue.notify(entriesOf(queue.locator));
|
|
2092
|
-
return () => {
|
|
2093
|
-
queueSubscriptions.delete(queue);
|
|
2094
|
-
};
|
|
2095
|
-
}
|
|
2096
|
-
return watchPlan(planned(input[0], null), deliver);
|
|
2097
|
-
};
|
|
2098
|
-
const md = {
|
|
2099
|
-
loaded() {
|
|
2100
|
-
return openLocal().then(() => undefined);
|
|
2101
|
-
},
|
|
2102
|
-
rooms() {
|
|
2103
|
-
return listed();
|
|
2104
45
|
},
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
46
|
+
roomTypes,
|
|
47
|
+
builtIn,
|
|
48
|
+
typedPaths,
|
|
49
|
+
persistence: shell.persistence,
|
|
50
|
+
lost: presence.lost,
|
|
51
|
+
losing: presence.losing,
|
|
52
|
+
opening: (local) => open(runtime, local),
|
|
53
|
+
unpublished: uncaught,
|
|
54
|
+
changed(set) {
|
|
55
|
+
if (set.unindexed.length > 0)
|
|
56
|
+
prepare(runtime, set.unindexed);
|
|
57
|
+
for (const locator of set.due)
|
|
58
|
+
submitLater(runtime, locator);
|
|
59
|
+
return publishChangeSet(runtime, set);
|
|
2115
60
|
},
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
locator: room,
|
|
2142
|
-
path,
|
|
2143
|
-
revision: roomDraftRevision(),
|
|
2144
|
-
parts: held(current?.parts, parts),
|
|
2145
|
-
returned: current?.returned ?? null,
|
|
2146
|
-
};
|
|
2147
|
-
await local.keepRoomDraft(record);
|
|
2148
|
-
rememberRoomDraft(record);
|
|
2149
|
-
publishChangeSet(changeSet(room, null, null, { drafts: [path] }));
|
|
2150
|
-
return { outcome: 'written', roomDraft: roomDraftOf(record) };
|
|
2151
|
-
});
|
|
2152
|
-
},
|
|
2153
|
-
discard(room, path, revision) {
|
|
2154
|
-
return exclusive(async () => {
|
|
2155
|
-
const local = await openLocal();
|
|
2156
|
-
const current = roomDrafts.get(room)?.get(path);
|
|
2157
|
-
if (current === undefined || current.revision !== revision) {
|
|
2158
|
-
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
2159
|
-
}
|
|
2160
|
-
await local.discardRoomDraft(room, path);
|
|
2161
|
-
forgetRoomDraft(room, path);
|
|
2162
|
-
publishChangeSet(changeSet(room, null, null, { drafts: [path] }));
|
|
2163
|
-
moveTo({ ...status, ...attention() });
|
|
2164
|
-
return { outcome: 'written', roomDraft: null };
|
|
2165
|
-
});
|
|
2166
|
-
},
|
|
2167
|
-
},
|
|
2168
|
-
conversation: conversations.surface,
|
|
2169
|
-
chatDraft: conversations.chatDraft,
|
|
2170
|
-
presentation: {
|
|
2171
|
-
read(app) {
|
|
2172
|
-
return exclusive(async () => {
|
|
2173
|
-
await openLocal();
|
|
2174
|
-
return presentationOf(app);
|
|
2175
|
-
});
|
|
61
|
+
deliverReadiness: (owed, rooms) => deliverOwedReadiness(runtime, owed, rooms),
|
|
62
|
+
spaceFreed: () => spaceFreed(runtime),
|
|
63
|
+
});
|
|
64
|
+
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
65
|
+
const runtime = {
|
|
66
|
+
store,
|
|
67
|
+
socket,
|
|
68
|
+
presence,
|
|
69
|
+
recovery,
|
|
70
|
+
retries,
|
|
71
|
+
shell,
|
|
72
|
+
localSaves: adapter.localSaves === true,
|
|
73
|
+
roomTypes,
|
|
74
|
+
builtIn,
|
|
75
|
+
typedPaths,
|
|
76
|
+
writeRule: (declaration) => writeRule(declaration, adapter.pathRule ?? (() => true), adapter.localSaves === true),
|
|
77
|
+
usableWhenHeld: options.usableWhenHeld ?? [],
|
|
78
|
+
build: options.build ?? '',
|
|
79
|
+
conversations: createLiveConversations({
|
|
80
|
+
call: (name, input, attempt) => invokeAction(runtime, name, input, { attempt }),
|
|
81
|
+
history: (request) => readConversationHistory(runtime, request),
|
|
82
|
+
durably: store.durably,
|
|
83
|
+
local: store.open,
|
|
84
|
+
again: () => {
|
|
85
|
+
void socket.ready.then(() => scheduleCurrency(runtime));
|
|
2176
86
|
},
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
const local = await openLocal();
|
|
2180
|
-
const stored = structuredClone(value);
|
|
2181
|
-
await local.present({ app, value: stored });
|
|
2182
|
-
presentations.set(app, stored);
|
|
2183
|
-
publishChangeSet(changeSet('', null, null, { presentation: app }));
|
|
2184
|
-
});
|
|
87
|
+
settled: () => {
|
|
88
|
+
show(runtime);
|
|
2185
89
|
},
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
},
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
if (options.adapter.roomStream === undefined)
|
|
2217
|
-
await sync('unstreamed');
|
|
2218
|
-
else
|
|
2219
|
-
await task(ensureAuthorization);
|
|
2220
|
-
},
|
|
2221
|
-
};
|
|
2222
|
-
const reachable = {
|
|
2223
|
-
md,
|
|
2224
|
-
watch,
|
|
2225
|
-
plan: planned,
|
|
2226
|
-
query: (plan) => rowsQuery(plan),
|
|
2227
|
-
watchPlan,
|
|
2228
|
-
search: searched,
|
|
90
|
+
stream: socket.conversations,
|
|
91
|
+
publish: store.changed,
|
|
92
|
+
online: socket.reached,
|
|
93
|
+
full: store.space.full,
|
|
94
|
+
closed: socket.closed,
|
|
95
|
+
unanswered: () => resendLater(runtime),
|
|
96
|
+
heard: (operation, lost) => socket.lanes.settle(operation, lost),
|
|
97
|
+
recovery: recovery.scope(),
|
|
98
|
+
retries,
|
|
99
|
+
}),
|
|
100
|
+
liveQueries: createLiveQueries((plan, cover) => answeredPlan(runtime, plan, cover), store.covering),
|
|
101
|
+
figures: createFigures({
|
|
102
|
+
cover: () => figuresCover(runtime),
|
|
103
|
+
rooms: (rooms, since) => roomTallies(runtime, rooms, since),
|
|
104
|
+
conversations: (conversations, since) => store.ask({ kind: 'conversation.tallies', conversations, since }),
|
|
105
|
+
}),
|
|
106
|
+
lastAnswers: readAnswers({
|
|
107
|
+
database: store,
|
|
108
|
+
build: options.build ?? '',
|
|
109
|
+
unkept: adapter.unkeptReads ?? [],
|
|
110
|
+
leading: adapter.leadingReads ?? [],
|
|
111
|
+
recovery: recovery.scope(),
|
|
112
|
+
retries,
|
|
113
|
+
call: (name, input, attempt) => called(runtime, name, input, attempt),
|
|
114
|
+
online: socket.reached,
|
|
115
|
+
streaming: socket.isOpen,
|
|
116
|
+
closed: socket.closed,
|
|
117
|
+
full: store.space.full,
|
|
118
|
+
filled: () => filled(runtime),
|
|
119
|
+
}),
|
|
2229
120
|
};
|
|
2230
|
-
|
|
121
|
+
start(runtime, options.signal);
|
|
122
|
+
return composedRuntime(runtime);
|
|
2231
123
|
}
|