@agentshouse/mdruntime 0.1.0-cb2e390ee7b89 → 0.1.0-cb4f25b82f4b6
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 +23 -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 +163 -125
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +7 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +40 -41
- package/dist/binary.d.ts +0 -5
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +1 -1
- package/dist/bootstrap.d.ts +30 -11
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +309 -113
- 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 -16
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +17 -89
- package/dist/change.d.ts +32 -1
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +14 -3
- package/dist/chat-types.d.ts +5 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +13 -0
- package/dist/client.d.ts +36 -24
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +118 -23
- package/dist/collection.d.ts +29 -21
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +48 -290
- package/dist/compile.d.ts +18 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +724 -0
- package/dist/connection.d.ts +7 -134
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +107 -3606
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/{chat/context.js → context.js} +1 -4
- package/dist/conversation-live.d.ts +143 -0
- package/dist/conversation-live.d.ts.map +1 -0
- package/dist/conversation-live.js +254 -0
- package/dist/conversation-row.d.ts +33 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +53 -0
- package/dist/conversation.d.ts +73 -49
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1010 -794
- 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 +20 -4
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +9 -0
- package/dist/entry.d.ts +5 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +33 -28
- 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 +197 -0
- package/dist/file.d.ts +2 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +7 -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 +21 -11
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +113 -49
- package/dist/identity.d.ts +0 -2
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -6
- package/dist/index.d.ts +39 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -4
- package/dist/install.d.ts +54 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +530 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +277 -0
- package/dist/listing.d.ts +48 -0
- package/dist/listing.d.ts.map +1 -0
- package/dist/listing.js +139 -0
- package/dist/live.d.ts +23 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +165 -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 +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +218 -0
- package/dist/memory.d.ts +17 -8
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +273 -121
- package/dist/messages.d.ts +56 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +158 -0
- package/dist/model.d.ts +8 -11
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +6 -23
- 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 +17 -3
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +107 -17
- 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 +47 -9
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +56 -2
- package/dist/overlay.d.ts +7 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +123 -182
- 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 +20 -0
- package/dist/preparation.d.ts.map +1 -0
- package/dist/preparation.js +227 -0
- package/dist/presence.d.ts +52 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +220 -0
- package/dist/queries.d.ts +16 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +282 -0
- package/dist/query.d.ts +21 -203
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -491
- 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 +450 -0
- package/dist/reach.d.ts +20 -9
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +341 -111
- package/dist/react.d.ts +16 -4
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +35 -15
- 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 +14 -18
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +2 -8
- package/dist/reads.d.ts +38 -10
- package/dist/reads.d.ts.map +1 -1
- package/dist/reads.js +163 -83
- package/dist/recovery.d.ts +5 -2
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +65 -8
- package/dist/refresh.d.ts +18 -15
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +145 -60
- 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 +49 -36
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +320 -196
- 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 +28 -11
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +194 -118
- package/dist/room-drafts.d.ts +24 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +217 -0
- package/dist/rows.d.ts +31 -30
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +60 -120
- 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 +43 -49
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +776 -275
- package/dist/saves.d.ts +32 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +410 -0
- package/dist/search.d.ts +12 -95
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -266
- package/dist/seed.d.ts +11 -2
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +71 -1
- package/dist/shared.d.ts +6 -7
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +75 -104
- package/dist/shown.d.ts +33 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +302 -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 +54 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +82 -0
- package/dist/status.d.ts +4 -5
- 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 +34 -0
- package/dist/store/readiness.d.ts +10 -0
- package/dist/store/readiness.d.ts.map +1 -0
- package/dist/store/readiness.js +52 -0
- package/dist/store/records.d.ts +214 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +438 -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/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 +111 -164
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +159 -754
- package/dist/stored.d.ts +157 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +649 -0
- package/dist/surface.d.ts +88 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +220 -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 +415 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +761 -0
- package/dist/tallies.d.ts +18 -0
- package/dist/tallies.d.ts.map +1 -0
- package/dist/tallies.js +129 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/turn.d.ts +5 -3
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +51 -21
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +104 -0
- package/dist/windows.d.ts +9 -16
- package/dist/windows.d.ts.map +1 -1
- package/dist/windows.js +11 -75
- package/dist/wire.d.ts +89 -9
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +29 -6
- package/package.json +19 -19
- package/AGENTS.md +0 -299
- package/dist/acquisition.d.ts +0 -14
- package/dist/acquisition.d.ts.map +0 -1
- package/dist/acquisition.js +0 -24
- package/dist/attempt.d.ts +0 -16
- package/dist/attempt.d.ts.map +0 -1
- package/dist/attempt.js +0 -70
- package/dist/chat/acts.d.ts +0 -33
- package/dist/chat/acts.d.ts.map +0 -1
- package/dist/chat/acts.js +0 -36
- package/dist/chat/context.d.ts +0 -3
- package/dist/chat/context.d.ts.map +0 -1
- package/dist/chat/earlier.d.ts +0 -10
- package/dist/chat/earlier.d.ts.map +0 -1
- package/dist/chat/earlier.js +0 -54
- 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 -61
- package/dist/chat/kit.d.ts.map +0 -1
- package/dist/chat/kit.js +0 -123
- package/dist/chat/lasting.d.ts +0 -6
- package/dist/chat/lasting.d.ts.map +0 -1
- package/dist/chat/lasting.js +0 -19
- package/dist/chat/list.d.ts +0 -27
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -99
- package/dist/chat/markdown.d.ts +0 -20
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -188
- 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 -22
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -155
- 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 -635
- package/dist/chat/question.d.ts +0 -9
- package/dist/chat/question.d.ts.map +0 -1
- package/dist/chat/question.js +0 -69
- package/dist/chat/questions.d.ts.map +0 -1
- package/dist/chat/read.d.ts.map +0 -1
- package/dist/chat/ready.d.ts +0 -34
- 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/fence.d.ts +0 -6
- package/dist/fence.d.ts.map +0 -1
- package/dist/fence.js +0 -10
- 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 -25
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -761
package/dist/connection.js
CHANGED
|
@@ -1,3620 +1,121 @@
|
|
|
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 { binaryRecord } from './binary.js';
|
|
17
|
-
import { absentBinary, binaryAnswer, binaryLinkAnswer, calledResult, failure, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
|
|
1
|
+
import { submitLater } from './acceptance.js';
|
|
2
|
+
import { invokeAction, called, readConversationHistory, resendLater } from './actions.js';
|
|
3
|
+
import { builtInModel, roomTypesOf } from './attached-model.js';
|
|
4
|
+
import { createLiveConversations } from './conversation-live.js';
|
|
5
|
+
import { writeRule } from './declaration.js';
|
|
6
|
+
import { refusedForSpace } from './durability.js';
|
|
7
|
+
import { uncaught } from './failure.js';
|
|
8
|
+
import { createFigures } from './figures.js';
|
|
9
|
+
import { scheduleCurrency, spaceFreed, start } from './lifecycle.js';
|
|
10
|
+
import { createLiveQueries } from './live.js';
|
|
11
|
+
import { open } from './opening.js';
|
|
12
|
+
import { deliverOwedReadiness, filled, prepare } from './preparation.js';
|
|
13
|
+
import { createPresence } from './presence.js';
|
|
14
|
+
import { answeredPlan, figuresCover, roomTallies } from './queries.js';
|
|
15
|
+
import { readAnswers } from './reads.js';
|
|
18
16
|
import { createRecovery } from './recovery.js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { same, saving, STARTING, transferring, } from './status.js';
|
|
25
|
-
import { detectStorageLoss, nextGeneration } from './durability.js';
|
|
26
|
-
import { demanded, narrower, WHOLE_DEMAND, wider } from './acquisition.js';
|
|
27
|
-
import { readinessOf, SETTLED_ROOMS, } from './readiness.js';
|
|
28
|
-
import { FencedMap } from './fence.js';
|
|
29
|
-
import { tally } from './shared.js';
|
|
30
|
-
import { inChunks, ROW_WEIGHT, yielded } from './turn.js';
|
|
31
|
-
import { identityTime, roomDraftRevision } from './identity.js';
|
|
32
|
-
import { beneath, normalize } from './paths.js';
|
|
33
|
-
import { openRuntimeStore, } from './store.js';
|
|
34
|
-
import { unconfirmed } from './transfer.js';
|
|
35
|
-
import { governs, holds, narrowed, planReach, } from './reach.js';
|
|
36
|
-
function roomsAlone(plan) {
|
|
37
|
-
return (plan.reach === null &&
|
|
38
|
-
plan.root.types === null &&
|
|
39
|
-
plan.root.where === null &&
|
|
40
|
-
plan.root.pathPrefix === null &&
|
|
41
|
-
plan.paths === null &&
|
|
42
|
-
plan.hop === null &&
|
|
43
|
-
plan.backlinks === null);
|
|
44
|
-
}
|
|
45
|
-
function within(root, path) {
|
|
46
|
-
return root === '' || beneath(root, path);
|
|
47
|
-
}
|
|
48
|
-
function declaredSelection(room) {
|
|
49
|
-
const selection = room.typedSelection;
|
|
50
|
-
if (selection === undefined)
|
|
51
|
-
return true;
|
|
52
|
-
if (!Array.isArray(selection.roots) || selection.roots.length === 0)
|
|
53
|
-
return false;
|
|
54
|
-
return selection.roots.every((root, index) => validRoot(root) &&
|
|
55
|
-
root !== '' &&
|
|
56
|
-
selection.roots.every((other, at) => at === index || !within(other, root)));
|
|
57
|
-
}
|
|
58
|
-
const OPENING_LANE = 'opening';
|
|
59
|
-
const CONVERSATION_LANE = 'conversations';
|
|
60
|
-
const ROOM_READS = 256;
|
|
17
|
+
import { createRetries } from './retried.js';
|
|
18
|
+
import { publishChangeSet, show } from './shown.js';
|
|
19
|
+
import { createSocket } from './socket.js';
|
|
20
|
+
import { createStore } from './store.js';
|
|
21
|
+
import { composedRuntime } from './surface.js';
|
|
61
22
|
const ROOM_ATTEMPTS = 16;
|
|
62
|
-
function operationOf(input) {
|
|
63
|
-
const operation = input?.operation_id;
|
|
64
|
-
return typeof operation === 'string' ? operation : null;
|
|
65
|
-
}
|
|
66
|
-
const outside = new Map();
|
|
67
|
-
function outsideOf(roots) {
|
|
68
|
-
const key = JSON.stringify(roots);
|
|
69
|
-
const held = outside.get(key);
|
|
70
|
-
if (held !== undefined)
|
|
71
|
-
return held;
|
|
72
|
-
const counted = (path) => (roots.some((root) => beneath(root, path)) ? 0 : 1);
|
|
73
|
-
const made = {
|
|
74
|
-
documents: (path) => (markdownPath(path) ? counted(path) : 0),
|
|
75
|
-
binaries: counted,
|
|
76
|
-
};
|
|
77
|
-
outside.set(key, made);
|
|
78
|
-
return made;
|
|
79
|
-
}
|
|
80
|
-
function abandonedOn(signal) {
|
|
81
|
-
return new Promise((resolve) => {
|
|
82
|
-
if (signal.aborted)
|
|
83
|
-
resolve(null);
|
|
84
|
-
else
|
|
85
|
-
signal.addEventListener('abort', () => resolve(null), { once: true });
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
function roomLane(locator) {
|
|
89
|
-
return `room ${locator}`;
|
|
90
|
-
}
|
|
91
23
|
export function composeRuntime(options) {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const queuesWithoutReplica = new FencedMap(isFenced);
|
|
104
|
-
const pendings = new Map();
|
|
105
|
-
const seeds = new Map();
|
|
106
|
-
const seedWatchers = new Map();
|
|
107
|
-
const roomDrafts = new FencedMap(isFenced);
|
|
108
|
-
const presentations = new Map();
|
|
109
|
-
let limits = [];
|
|
110
|
-
let capabilities = [];
|
|
111
|
-
const rooms = new FencedMap(isFenced);
|
|
112
|
-
const counted = new Map();
|
|
113
|
-
const unreadable = new Set();
|
|
114
|
-
const isModelFenced = (key) => isFenced(key.slice(key.indexOf(' ') + 1));
|
|
115
|
-
const models = new FencedMap(isModelFenced);
|
|
116
|
-
const liveModels = new FencedMap(isModelFenced);
|
|
117
|
-
const invalidModels = new Set();
|
|
118
|
-
const discovered = new Map();
|
|
119
|
-
const reads = doorReads(options.adapter, (locator) => discovered.get(locator), () => generation);
|
|
120
|
-
let roomsListed = false;
|
|
121
|
-
const scopedRefusals = new Map();
|
|
122
|
-
const sourceRefusals = new Map();
|
|
123
|
-
const declarationRefusals = new Set();
|
|
124
|
-
function modelKey(locator, attached) {
|
|
125
|
-
return `${attached} ${locator}`;
|
|
126
|
-
}
|
|
127
|
-
function modelOf(locator, attached = 'collection') {
|
|
128
|
-
return models.get(modelKey(locator, attached)) ?? NO_MODEL;
|
|
129
|
-
}
|
|
130
|
-
function savedModel(locator, attached) {
|
|
131
|
-
return invalidModels.has(modelKey(locator, attached))
|
|
132
|
-
? { status: 'unavailable', reason: 'revoked' }
|
|
133
|
-
: modelOf(locator, attached);
|
|
134
|
-
}
|
|
135
|
-
function compositionFor(record) {
|
|
136
|
-
return compositionOf({
|
|
137
|
-
...record,
|
|
138
|
-
model: savedModel(record.locator, 'collection'),
|
|
139
|
-
selectionModel: savedModel(record.locator, 'typed-selection'),
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
let refusal = null;
|
|
143
|
-
let storageLoss = null;
|
|
144
|
-
let status = STARTING;
|
|
145
|
-
let shown = STARTING;
|
|
146
|
-
let discoveryPaused = false;
|
|
147
|
-
let closed = false;
|
|
148
|
-
let stopping = false;
|
|
149
|
-
let withdrawStream;
|
|
150
|
-
const announced = new Map();
|
|
151
|
-
let underway = null;
|
|
152
|
-
let streamed = null;
|
|
153
|
-
let handed = false;
|
|
154
|
-
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
155
|
-
const repairing = new Set();
|
|
156
|
-
const calling = new Set();
|
|
157
|
-
const repairable = new Map();
|
|
158
|
-
let outcomeRetention = null;
|
|
159
|
-
const answering = new Map();
|
|
160
|
-
let complete = false;
|
|
161
|
-
let recorded = false;
|
|
162
|
-
const stale = new Set();
|
|
163
|
-
let generation = 0;
|
|
164
|
-
let discovering = null;
|
|
165
|
-
let opened = null;
|
|
166
|
-
const owed = new Set();
|
|
167
|
-
const lanes = new Map();
|
|
168
|
-
let roomReads = { generation, free: ROOM_READS, waiting: [] };
|
|
169
|
-
const working = new Set();
|
|
170
|
-
let mutations = Promise.resolve();
|
|
171
|
-
const roomListeners = new Set();
|
|
172
|
-
const refusalListeners = new Set();
|
|
173
|
-
const storageLossListeners = new Set();
|
|
174
|
-
const statusListeners = new Set();
|
|
175
|
-
const queueSubscriptions = new Set();
|
|
176
|
-
const roomDraftListeners = new Set();
|
|
177
|
-
const presentationListeners = new Set();
|
|
178
|
-
const readSide = createReadSide();
|
|
179
|
-
let withdrawConversations;
|
|
180
|
-
let currencyQueued = null;
|
|
181
|
-
let socketOpen = false;
|
|
182
|
-
let serviceMode = 'open';
|
|
183
|
-
let haltedRefusal = false;
|
|
184
|
-
let openSignals = 0;
|
|
185
|
-
const serviceModeListeners = new Set();
|
|
186
|
-
let member = null;
|
|
187
|
-
const memberListeners = new Set();
|
|
188
|
-
const prepared = new Set();
|
|
189
|
-
const projecting = new Set();
|
|
190
|
-
const unpreparedDemand = new Set();
|
|
191
|
-
const unindexable = new Set();
|
|
192
|
-
let preparing = false;
|
|
193
|
-
let demand = WHOLE_DEMAND;
|
|
194
|
-
let narrowing = null;
|
|
195
|
-
let targets = {};
|
|
196
|
-
let persistence = options.shell.persistence;
|
|
197
|
-
let capacity = null;
|
|
198
|
-
const paused = new Set();
|
|
199
|
-
let rechecking = null;
|
|
200
|
-
const readinessWatches = new Set();
|
|
201
|
-
let readinessQueued = false;
|
|
202
|
-
const catching = new Set();
|
|
203
|
-
const transfers = new FencedMap(isFenced);
|
|
204
|
-
const conversations = createConversationStore({
|
|
205
|
-
agents: declaredAgents,
|
|
206
|
-
documents: ({ locator }) => rowsQuery(planOf({ rooms: [locator], select: { fields: 'all' } }, roomFacts())),
|
|
207
|
-
call: (name, input) => invokeAction(name, input),
|
|
208
|
-
durably,
|
|
209
|
-
stream: () => options.adapter.conversations,
|
|
210
|
-
publish: publishChangeSet,
|
|
211
|
-
online: () => !status.providerUnreachable && refusal === null,
|
|
212
|
-
closed: () => closed,
|
|
213
|
-
unanswered: resendLater,
|
|
214
|
-
expired: (operation) => pastHorizon(operation),
|
|
215
|
-
});
|
|
216
|
-
const searchIndex = createSearchIndex({
|
|
217
|
-
room: (room) => readSide.room(room),
|
|
218
|
-
document: (room, path) => readSide.document(room, path),
|
|
219
|
-
});
|
|
220
|
-
const roomsToIndex = new Set();
|
|
221
|
-
const conversationsToIndex = new Set();
|
|
222
|
-
let indexing = false;
|
|
223
|
-
const journal = createLog();
|
|
224
|
-
let notifyReady = () => { };
|
|
225
|
-
const ready = new Promise((resolve) => {
|
|
226
|
-
notifyReady = resolve;
|
|
227
|
-
});
|
|
228
|
-
function rememberRoomDraft(record) {
|
|
229
|
-
const room = roomDrafts.get(record.locator) ?? new Map();
|
|
230
|
-
room.set(record.path, record);
|
|
231
|
-
roomDrafts.set(record.locator, room);
|
|
232
|
-
}
|
|
233
|
-
function forgetRoomDraft(locator, path) {
|
|
234
|
-
const room = roomDrafts.get(locator);
|
|
235
|
-
if (room === undefined)
|
|
236
|
-
return;
|
|
237
|
-
room.delete(path);
|
|
238
|
-
if (room.size === 0)
|
|
239
|
-
roomDrafts.delete(locator);
|
|
240
|
-
}
|
|
241
|
-
function roomDraftOf(record) {
|
|
242
|
-
return structuredClone({
|
|
243
|
-
room: record.locator,
|
|
244
|
-
path: record.path,
|
|
245
|
-
revision: record.revision,
|
|
246
|
-
parts: record.parts,
|
|
247
|
-
changedUnderneath: underneath(record.parts, record.path, replicas.get(record.locator)?.visible),
|
|
248
|
-
returned: record.returned,
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
function snapshotRoomDrafts() {
|
|
252
|
-
const records = [...roomDrafts.values()].flatMap((room) => [...room.values()]);
|
|
253
|
-
records.sort((left, right) => {
|
|
254
|
-
if (left.locator !== right.locator)
|
|
255
|
-
return left.locator < right.locator ? -1 : 1;
|
|
256
|
-
if (left.path !== right.path)
|
|
257
|
-
return left.path < right.path ? -1 : 1;
|
|
258
|
-
return 0;
|
|
259
|
-
});
|
|
260
|
-
return records.map(roomDraftOf);
|
|
261
|
-
}
|
|
262
|
-
function publishRoomDrafts(waitingOnly) {
|
|
263
|
-
if (closed)
|
|
264
|
-
return;
|
|
265
|
-
const value = snapshotRoomDrafts();
|
|
266
|
-
for (const listener of roomDraftListeners) {
|
|
267
|
-
if (waitingOnly && listener.ready)
|
|
268
|
-
continue;
|
|
269
|
-
listener.ready = true;
|
|
270
|
-
listener.notify(value);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
function presentationOf(app) {
|
|
274
|
-
const value = presentations.get(app);
|
|
275
|
-
return value === undefined ? null : structuredClone(value);
|
|
276
|
-
}
|
|
277
|
-
function keepPresentation(app, next) {
|
|
278
|
-
return exclusive(async () => {
|
|
279
|
-
const local = await openLocal();
|
|
280
|
-
const stored = next(presentations.get(app));
|
|
281
|
-
await local.present({ app, value: stored });
|
|
282
|
-
presentations.set(app, stored);
|
|
283
|
-
publishChangeSet(changeSet('', null, null, { presentation: app }));
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
function emitPresentation(app) {
|
|
287
|
-
if (closed)
|
|
288
|
-
return;
|
|
289
|
-
for (const listener of presentationListeners) {
|
|
290
|
-
if (listener.app === app && listener.ready)
|
|
291
|
-
listener.notify(presentationOf(app));
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
function pending() {
|
|
295
|
-
return (!localSaves &&
|
|
296
|
-
(queuesWithoutReplica.size > 0 ||
|
|
297
|
-
[...replicas.values()].some((replica) => replica.queue.length > 0)));
|
|
298
|
-
}
|
|
299
|
-
function attention() {
|
|
300
|
-
const attentionRequired = [];
|
|
301
|
-
for (const record of [...roomDrafts.values()].flatMap((room) => [...room.values()])) {
|
|
302
|
-
if (record.returned === null)
|
|
303
|
-
continue;
|
|
304
|
-
attentionRequired.push({
|
|
305
|
-
room: record.locator,
|
|
306
|
-
path: record.path,
|
|
307
|
-
returned: structuredClone(record.returned),
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
for (const [room, refusal] of sourceRefusals)
|
|
311
|
-
attentionRequired.push({ room, refusal: structuredClone(refusal) });
|
|
312
|
-
attentionRequired.sort((left, right) => {
|
|
313
|
-
if (left.room !== right.room)
|
|
314
|
-
return left.room < right.room ? -1 : 1;
|
|
315
|
-
if ('refusal' in left || 'refusal' in right) {
|
|
316
|
-
if ('refusal' in left && 'refusal' in right)
|
|
317
|
-
return 0;
|
|
318
|
-
return 'refusal' in left ? -1 : 1;
|
|
319
|
-
}
|
|
320
|
-
if (left.path !== right.path)
|
|
321
|
-
return left.path < right.path ? -1 : 1;
|
|
322
|
-
return 0;
|
|
323
|
-
});
|
|
324
|
-
let reset = null;
|
|
325
|
-
for (const { submission } of pendings.values()) {
|
|
326
|
-
if (submission.state !== 'reset')
|
|
327
|
-
continue;
|
|
328
|
-
if (reset === null || submission.reset < reset)
|
|
329
|
-
reset = submission.reset;
|
|
330
|
-
}
|
|
331
|
-
return { attentionRequired, reset };
|
|
332
|
-
}
|
|
333
|
-
function documentsIn(locator) {
|
|
334
|
-
const replica = replicas.get(locator);
|
|
335
|
-
if (replica === undefined)
|
|
336
|
-
return 0;
|
|
337
|
-
const roots = replica.confirmed.record.typedSelection?.roots ?? [];
|
|
338
|
-
const weighed = outsideOf(roots);
|
|
339
|
-
return (tally(replica.visible.documents, weighed.documents) +
|
|
340
|
-
tally(replica.visible.binaries, weighed.binaries));
|
|
341
|
-
}
|
|
342
|
-
function listed() {
|
|
343
|
-
return structuredClone([...rooms.values()].map((room) => {
|
|
344
|
-
const documents = documentsIn(room.locator);
|
|
345
|
-
counted.set(room.locator, documents);
|
|
346
|
-
return { ...room, documents };
|
|
347
|
-
}));
|
|
348
|
-
}
|
|
349
|
-
function roomFacts(reach = null) {
|
|
350
|
-
const held = [...rooms.values()]
|
|
351
|
-
.filter((room) => holds(reach, room.locator))
|
|
352
|
-
.map((room) => {
|
|
353
|
-
const governed = governs(reach, room);
|
|
354
|
-
const attached = [
|
|
355
|
-
...(governed.collection ? [modelOf(room.locator)] : []),
|
|
356
|
-
...(governed.selection ? [modelOf(room.locator, 'typed-selection')] : []),
|
|
357
|
-
];
|
|
358
|
-
return {
|
|
359
|
-
locator: room.locator,
|
|
360
|
-
available: attached.every((model) => model.status !== 'unavailable'),
|
|
361
|
-
types: attached.flatMap((model) => model.status === 'unavailable' ? [] : Object.values(model.types)),
|
|
362
|
-
};
|
|
363
|
-
});
|
|
364
|
-
return { listed: roomsListed, rooms: held };
|
|
365
|
-
}
|
|
366
|
-
function emitRooms() {
|
|
367
|
-
readSide.recover();
|
|
368
|
-
queueReadiness();
|
|
369
|
-
const value = listed();
|
|
370
|
-
for (const listener of roomListeners)
|
|
371
|
-
listener(value);
|
|
372
|
-
}
|
|
373
|
-
function emitRefusal() {
|
|
374
|
-
queueReadiness();
|
|
375
|
-
for (const listener of refusalListeners)
|
|
376
|
-
listener(refusal);
|
|
377
|
-
}
|
|
378
|
-
function emitStorageLoss() {
|
|
379
|
-
queueReadiness();
|
|
380
|
-
for (const listener of storageLossListeners)
|
|
381
|
-
listener(storageLoss);
|
|
382
|
-
}
|
|
383
|
-
function emitMember() {
|
|
384
|
-
for (const listener of memberListeners)
|
|
385
|
-
listener(member);
|
|
386
|
-
}
|
|
387
|
-
function moveTo(next) {
|
|
388
|
-
if (closed)
|
|
389
|
-
return;
|
|
390
|
-
status = next;
|
|
391
|
-
show();
|
|
392
|
-
}
|
|
393
|
-
function show() {
|
|
394
|
-
if (closed)
|
|
395
|
-
return;
|
|
396
|
-
queueReadiness();
|
|
397
|
-
const next = status.state === 'synced' && demand.conversations && !conversations.current()
|
|
398
|
-
? { ...status, state: 'syncing' }
|
|
399
|
-
: status;
|
|
400
|
-
if (same(shown, next))
|
|
401
|
-
return;
|
|
402
|
-
shown = next;
|
|
403
|
-
for (const listener of statusListeners)
|
|
404
|
-
listener(shown);
|
|
405
|
-
}
|
|
406
|
-
function entryOf(batch) {
|
|
407
|
-
if (localSaves)
|
|
408
|
-
return Object.freeze({ batch, state: 'local' });
|
|
409
|
-
const state = pendings.get(batch.id);
|
|
410
|
-
if (state?.accepted !== undefined) {
|
|
411
|
-
return Object.freeze({ batch, state: 'accepted', roomVersion: state.accepted });
|
|
412
|
-
}
|
|
413
|
-
return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
|
|
414
|
-
}
|
|
415
|
-
function entriesOf(locator) {
|
|
416
|
-
return (replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []).map(entryOf);
|
|
417
|
-
}
|
|
418
|
-
function emitQueue(locator) {
|
|
419
|
-
const entries = entriesOf(locator);
|
|
420
|
-
for (const subscription of queueSubscriptions) {
|
|
421
|
-
if (subscription.locator === locator)
|
|
422
|
-
subscription.notify(entries);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
function applyChangeSet(set) {
|
|
426
|
-
const held = {
|
|
427
|
-
...set,
|
|
428
|
-
documents: set.documents.filter(({ locator }) => prepared.has(locator) || projecting.has(locator)),
|
|
429
|
-
};
|
|
430
|
-
readSide.apply(held);
|
|
431
|
-
searchIndex.changed(held);
|
|
432
|
-
}
|
|
433
|
-
async function referenced(locator) {
|
|
434
|
-
for (;;) {
|
|
435
|
-
const record = replicas.get(locator)?.confirmed.record;
|
|
436
|
-
if (record === undefined || closed)
|
|
437
|
-
return;
|
|
438
|
-
const held = { text: record.documents, binaries: record.binaries };
|
|
439
|
-
const composition = compositionOf(record);
|
|
440
|
-
if (referencesIndexed(held, composition))
|
|
441
|
-
return;
|
|
442
|
-
await indexReferrers(held, composition);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
async function prepareReplica(locator) {
|
|
446
|
-
await referenced(locator);
|
|
447
|
-
const replica = replicas.get(locator);
|
|
448
|
-
if (replica === undefined || closed || prepared.has(locator))
|
|
449
|
-
return;
|
|
450
|
-
const visible = replica.visible;
|
|
451
|
-
const paths = [
|
|
452
|
-
...new Set([...Object.keys(visible.documents), ...Object.keys(visible.binaries)]),
|
|
453
|
-
];
|
|
454
|
-
const entered = [];
|
|
455
|
-
projecting.add(locator);
|
|
456
|
-
try {
|
|
457
|
-
await inChunks(paths, (path) => ROW_WEIGHT + (visible.documents[path]?.length ?? 0), (chunk) => {
|
|
458
|
-
const current = replicas.get(locator);
|
|
459
|
-
if (current === undefined || closed)
|
|
460
|
-
return;
|
|
461
|
-
const set = entering(locator, current, chunk);
|
|
462
|
-
readSide.apply(set);
|
|
463
|
-
entered.push(set);
|
|
464
|
-
});
|
|
465
|
-
if (!replicas.has(locator) || closed)
|
|
466
|
-
return;
|
|
467
|
-
prepared.add(locator);
|
|
468
|
-
}
|
|
469
|
-
finally {
|
|
470
|
-
projecting.delete(locator);
|
|
471
|
-
}
|
|
472
|
-
conversations.changed(mergeChangeSets(entered));
|
|
473
|
-
readSide.recover();
|
|
474
|
-
queueReadiness();
|
|
475
|
-
}
|
|
476
|
-
function unprepared() {
|
|
477
|
-
return [...unpreparedDemand, ...replicas.keys()].find((locator) => !prepared.has(locator) && !unindexable.has(locator));
|
|
478
|
-
}
|
|
479
|
-
function unbuilt(locator, error) {
|
|
480
|
-
unindexable.add(locator);
|
|
481
|
-
journal.record(`index failed ${locator} ${error instanceof Error ? error.name : 'unknown'}`);
|
|
482
|
-
queueReadiness();
|
|
483
|
-
}
|
|
484
|
-
function prepare(locators = []) {
|
|
485
|
-
for (const locator of locators)
|
|
486
|
-
unpreparedDemand.add(locator);
|
|
487
|
-
if (preparing)
|
|
488
|
-
return;
|
|
489
|
-
preparing = true;
|
|
490
|
-
void (async () => {
|
|
491
|
-
try {
|
|
492
|
-
for (let waiting = unprepared(); waiting !== undefined; waiting = unprepared()) {
|
|
493
|
-
await yielded();
|
|
494
|
-
if (closed)
|
|
495
|
-
return;
|
|
496
|
-
unpreparedDemand.delete(waiting);
|
|
497
|
-
await prepareReplica(waiting).catch((error) => unbuilt(waiting, error));
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
finally {
|
|
501
|
-
preparing = false;
|
|
502
|
-
}
|
|
503
|
-
})();
|
|
504
|
-
}
|
|
505
|
-
function reachedRooms(plan) {
|
|
506
|
-
const reach = plan.reach;
|
|
507
|
-
const reached = reach === null ? null : new Set([...reach.rooms, ...reach.documents.map(roomOfKey)]);
|
|
508
|
-
const named = plan.rooms ??
|
|
509
|
-
(reached === null
|
|
510
|
-
? [...new Set([...rooms.keys(), ...replicas.keys(), ...queuesWithoutReplica.keys()])]
|
|
511
|
-
: [...reached]);
|
|
512
|
-
return [...new Set(named)].filter((locator) => reached === null || reached.has(locator)).sort();
|
|
513
|
-
}
|
|
514
|
-
function awaited(locator) {
|
|
515
|
-
return (!roomsListed ||
|
|
516
|
-
(rooms.has(locator) && discovered.get(locator)?.replica.admission === 'admitted'));
|
|
517
|
-
}
|
|
518
|
-
function covered(plan) {
|
|
519
|
-
const locators = reachedRooms(plan);
|
|
520
|
-
if (locators.length === 0 && plan.rooms === null && plan.reach === null && !roomsListed) {
|
|
521
|
-
return { pending: [] };
|
|
522
|
-
}
|
|
523
|
-
const pending = [];
|
|
524
|
-
const coverage = [];
|
|
525
|
-
const overlay = [];
|
|
526
|
-
for (const locator of locators) {
|
|
527
|
-
const replica = replicas.get(locator);
|
|
528
|
-
if (replica === undefined) {
|
|
529
|
-
if (awaited(locator))
|
|
530
|
-
pending.push({ room: locator, awaits: 'acquisition' });
|
|
531
|
-
continue;
|
|
532
|
-
}
|
|
533
|
-
if (!prepared.has(locator)) {
|
|
534
|
-
pending.push({ room: locator, awaits: 'preparation' });
|
|
535
|
-
continue;
|
|
536
|
-
}
|
|
537
|
-
coverage.push({
|
|
538
|
-
room: locator,
|
|
539
|
-
generation: replica.confirmed.record.transfer,
|
|
540
|
-
current: !(catching.has(locator) || owed.has(locator) || transfers.has(locator)),
|
|
541
|
-
});
|
|
542
|
-
for (const batch of replica.queue)
|
|
543
|
-
overlay.push(batch.id);
|
|
544
|
-
}
|
|
545
|
-
if (pending.length > 0)
|
|
546
|
-
return { pending };
|
|
547
|
-
return { coverage: { rooms: coverage, indexes: [], overlay } };
|
|
548
|
-
}
|
|
549
|
-
function awaitPreparation(cover) {
|
|
550
|
-
if (!('pending' in cover))
|
|
551
|
-
return;
|
|
552
|
-
const waiting = cover.pending.filter(({ awaits }) => awaits === 'preparation');
|
|
553
|
-
if (waiting.length > 0)
|
|
554
|
-
prepare(waiting.map(({ room }) => room));
|
|
555
|
-
}
|
|
556
|
-
function acquires(room) {
|
|
557
|
-
return (demanded(demand, room) ||
|
|
558
|
-
(replicas.get(room.locator)?.queue.length ?? 0) > 0 ||
|
|
559
|
-
queuesWithoutReplica.has(room.locator));
|
|
560
|
-
}
|
|
561
|
-
function acquiredLocators() {
|
|
562
|
-
return [...discovered.values()]
|
|
563
|
-
.filter(acquires)
|
|
564
|
-
.map((room) => room.locator)
|
|
565
|
-
.sort();
|
|
566
|
-
}
|
|
567
|
-
function heldLocators(acquired) {
|
|
568
|
-
return demand.whole ? acquired : [...new Set([...acquired, ...demand.rooms])].sort();
|
|
569
|
-
}
|
|
570
|
-
function heldDemand(acquired) {
|
|
571
|
-
if (demand.whole)
|
|
572
|
-
return null;
|
|
573
|
-
return {
|
|
574
|
-
rooms: heldLocators(acquired.map((room) => room.locator)),
|
|
575
|
-
conversations: demand.conversations,
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
function applyDemand(next) {
|
|
579
|
-
const before = heldLocators(acquiredLocators());
|
|
580
|
-
const { whole, conversations: conversed } = demand;
|
|
581
|
-
demand = next;
|
|
582
|
-
const after = heldLocators(acquiredLocators());
|
|
583
|
-
queueReadiness();
|
|
584
|
-
if (!conversed && next.conversations)
|
|
585
|
-
scheduleCurrency();
|
|
586
|
-
if (JSON.stringify(before) === JSON.stringify(after) &&
|
|
587
|
-
whole === next.whole &&
|
|
588
|
-
conversed === next.conversations)
|
|
589
|
-
return;
|
|
590
|
-
journal.record(`acquisition ${next.whole ? 'whole' : after.length}`);
|
|
591
|
-
if (options.adapter.roomStream === undefined)
|
|
592
|
-
void sync('unstreamed');
|
|
593
|
-
else if (socketOpen)
|
|
594
|
-
options.adapter.roomStream.reopen();
|
|
595
|
-
}
|
|
596
|
-
function acquire(next) {
|
|
597
|
-
if (closed)
|
|
598
|
-
return;
|
|
599
|
-
if (narrowing !== null)
|
|
600
|
-
clearTimeout(narrowing);
|
|
601
|
-
narrowing = null;
|
|
602
|
-
const widening = wider(next, demand);
|
|
603
|
-
if (!widening && !narrower(next, demand))
|
|
604
|
-
return;
|
|
605
|
-
if (widening || !roomsListed) {
|
|
606
|
-
applyDemand(next);
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
// A narrower demand waits five seconds, so a tab that reloads returns before the stream reopens twice.
|
|
610
|
-
narrowing = setTimeout(() => {
|
|
611
|
-
narrowing = null;
|
|
612
|
-
applyDemand(next);
|
|
613
|
-
}, 5_000);
|
|
614
|
-
}
|
|
615
|
-
function requiredRooms(reach) {
|
|
616
|
-
if (reach === null)
|
|
617
|
-
return { listed: roomsListed, rooms: [...rooms.keys()].sort() };
|
|
618
|
-
const named = [...new Set(reach.rooms)].sort();
|
|
619
|
-
if (roomsListed)
|
|
620
|
-
return { listed: true, rooms: named.filter((room) => rooms.has(room)) };
|
|
621
|
-
return { listed: named.every((room) => replicas.has(room)), rooms: named };
|
|
622
|
-
}
|
|
623
|
-
function roomState(locator, search) {
|
|
624
|
-
if (sourceRefusals.has(locator))
|
|
625
|
-
return 'refused';
|
|
626
|
-
const listedRoom = discovered.get(locator);
|
|
627
|
-
if (roomsListed &&
|
|
628
|
-
(listedRoom === undefined ||
|
|
629
|
-
listedRoom.replica.admission !== 'admitted' ||
|
|
630
|
-
unreadable.has(locator)))
|
|
631
|
-
return 'unadmitted';
|
|
632
|
-
if (!replicas.has(locator))
|
|
633
|
-
return 'acquiring';
|
|
634
|
-
if (!prepared.has(locator))
|
|
635
|
-
return 'preparing';
|
|
636
|
-
if (search && !searchIndex.rooms().has(locator))
|
|
637
|
-
return 'indexing';
|
|
638
|
-
return 'complete';
|
|
639
|
-
}
|
|
640
|
-
function conversationCoverage(reach, search) {
|
|
641
|
-
const declared = reach === null || reach.conversations;
|
|
642
|
-
if (!declared || options.adapter.conversations === undefined || declaredAgents() === undefined)
|
|
643
|
-
return 'undeclared';
|
|
644
|
-
if (!conversations.known() || !conversations.current())
|
|
645
|
-
return 'acquiring';
|
|
646
|
-
if (!search)
|
|
647
|
-
return 'complete';
|
|
648
|
-
const unindexed = conversations
|
|
649
|
-
.searchable()
|
|
650
|
-
.filter((one) => conversations.acquired(one) && !searchIndex.conversations().has(one));
|
|
651
|
-
if (unindexed.length === 0)
|
|
652
|
-
return 'complete';
|
|
653
|
-
index([], unindexed);
|
|
654
|
-
return 'indexing';
|
|
655
|
-
}
|
|
656
|
-
function connectionOf() {
|
|
657
|
-
if (socketOpen)
|
|
658
|
-
return 'connected';
|
|
659
|
-
if (status.providerUnreachable)
|
|
660
|
-
return 'unreachable';
|
|
661
|
-
return options.adapter.roomStream === undefined && status.lastCompleted !== null
|
|
662
|
-
? 'connected'
|
|
663
|
-
: 'connecting';
|
|
664
|
-
}
|
|
665
|
-
function refusedIn(locators) {
|
|
666
|
-
return locators.flatMap((room) => {
|
|
667
|
-
const held = sourceRefusals.get(room);
|
|
668
|
-
if (held === undefined)
|
|
669
|
-
return [];
|
|
670
|
-
const { file, location } = held.fields;
|
|
671
|
-
return [
|
|
672
|
-
{
|
|
673
|
-
room,
|
|
674
|
-
file: typeof file === 'string' ? file : '',
|
|
675
|
-
rule: held.code,
|
|
676
|
-
location: typeof location === 'string' ? location : null,
|
|
677
|
-
},
|
|
678
|
-
];
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
function readinessFor(port, reach) {
|
|
682
|
-
const required = requiredRooms(reach);
|
|
683
|
-
const search = port.search === 'required';
|
|
684
|
-
const coverage = required.rooms.map((room) => ({ room, state: roomState(room, search) }));
|
|
685
|
-
const building = (state) => coverage
|
|
686
|
-
.filter((room) => room.state === state && !unindexable.has(room.room))
|
|
687
|
-
.map(({ room }) => room);
|
|
688
|
-
const preparing = building('preparing');
|
|
689
|
-
if (preparing.length > 0)
|
|
690
|
-
prepare(preparing);
|
|
691
|
-
const indexing = building('indexing');
|
|
692
|
-
if (indexing.length > 0)
|
|
693
|
-
index(indexing, []);
|
|
694
|
-
return readinessOf({
|
|
695
|
-
code: port.code,
|
|
696
|
-
coverage: {
|
|
697
|
-
listed: required.listed,
|
|
698
|
-
rooms: coverage,
|
|
699
|
-
conversations: conversationCoverage(reach, search),
|
|
700
|
-
search: search ? 'required' : 'unused',
|
|
701
|
-
},
|
|
702
|
-
persistence,
|
|
703
|
-
connection: connectionOf(),
|
|
704
|
-
house: refusal !== null ? 'refused' : writesHeld() ? 'write-halted' : 'open',
|
|
705
|
-
capacity: capacity !== null &&
|
|
706
|
-
coverage.some(({ room, state }) => paused.has(room) || !SETTLED_ROOMS.has(state))
|
|
707
|
-
? capacity
|
|
708
|
-
: port.capacity,
|
|
709
|
-
refused: refusedIn(required.rooms),
|
|
710
|
-
lost: storageLoss !== null,
|
|
711
|
-
established: targets[port.target]?.established === true,
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
function establish(name) {
|
|
715
|
-
targets = { ...targets, [name]: { established: true } };
|
|
716
|
-
queueReadiness();
|
|
717
|
-
void durably((local) => local.keepTargets(targets)).catch(() => {
|
|
718
|
-
journal.record(`target record failed ${name}`);
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
function deliverReadiness(watch) {
|
|
722
|
-
if (!readinessWatches.has(watch))
|
|
723
|
-
return;
|
|
724
|
-
const readiness = readinessFor(watch.port, watch.reach);
|
|
725
|
-
if (readiness.ready && targets[watch.port.target]?.established !== true) {
|
|
726
|
-
establish(watch.port.target);
|
|
727
|
-
}
|
|
728
|
-
const shown = JSON.stringify(readiness);
|
|
729
|
-
if (shown === watch.last)
|
|
730
|
-
return;
|
|
731
|
-
watch.last = shown;
|
|
732
|
-
watch.deliver(structuredClone(readiness));
|
|
733
|
-
}
|
|
734
|
-
function queueReadiness() {
|
|
735
|
-
if (readinessQueued || closed || readinessWatches.size === 0)
|
|
736
|
-
return;
|
|
737
|
-
readinessQueued = true;
|
|
738
|
-
queueMicrotask(() => {
|
|
739
|
-
readinessQueued = false;
|
|
740
|
-
if (closed)
|
|
741
|
-
return;
|
|
742
|
-
for (const watch of [...readinessWatches])
|
|
743
|
-
deliverReadiness(watch);
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
function watchReadiness(port, reach, deliver) {
|
|
747
|
-
const watch = { port, reach, deliver, last: null };
|
|
748
|
-
readinessWatches.add(watch);
|
|
749
|
-
void openLocal().then(() => deliverReadiness(watch), () => deliverReadiness(watch));
|
|
750
|
-
return () => {
|
|
751
|
-
readinessWatches.delete(watch);
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
function layerBytes(layer) {
|
|
755
|
-
let bytes = 0;
|
|
756
|
-
for (const [path, text] of Object.entries(layer.documents))
|
|
757
|
-
bytes += (path.length + text.length) * 2;
|
|
758
|
-
return bytes;
|
|
759
|
-
}
|
|
760
|
-
function grownBy(next, previous) {
|
|
761
|
-
if (previous === undefined)
|
|
762
|
-
return layerBytes(next);
|
|
763
|
-
const documents = Object.fromEntries(Object.entries(next.documents).filter(([path, text]) => previous.documents[path] !== text));
|
|
764
|
-
return layerBytes({ ...next, documents });
|
|
765
|
-
}
|
|
766
|
-
function exceeded(error) {
|
|
767
|
-
return (typeof error === 'object' &&
|
|
768
|
-
error !== null &&
|
|
769
|
-
error.name === 'QuotaExceededError');
|
|
770
|
-
}
|
|
771
|
-
async function estimated() {
|
|
772
|
-
const estimate = options.shell.estimate === undefined ? null : await options.shell.estimate();
|
|
773
|
-
return estimate === null ? null : Math.max(0, estimate.quota - estimate.usage);
|
|
774
|
-
}
|
|
775
|
-
function pause(locator, cause) {
|
|
776
|
-
paused.add(locator);
|
|
777
|
-
capacity = cause;
|
|
778
|
-
journal.record(`capacity ${cause.cause} ${locator} ${cause.needed} ${cause.available ?? '-'}`);
|
|
779
|
-
queueReadiness();
|
|
780
|
-
if (rechecking !== null)
|
|
781
|
-
return;
|
|
782
|
-
// A paused preparation measures the browser's space again each minute, so space freed elsewhere resumes it without a retry.
|
|
783
|
-
rechecking = setInterval(() => {
|
|
784
|
-
void recheck();
|
|
785
|
-
}, 60_000);
|
|
786
|
-
}
|
|
787
|
-
async function recheck() {
|
|
788
|
-
const held = capacity;
|
|
789
|
-
if (held === null || closed)
|
|
790
|
-
return;
|
|
791
|
-
const available = await estimated().catch(() => null);
|
|
792
|
-
if (available === null || available < (held.needed ?? 0))
|
|
793
|
-
return;
|
|
794
|
-
if (held.cause === 'quota-exceeded' && (held.available === null || available <= held.available))
|
|
795
|
-
return;
|
|
796
|
-
resume();
|
|
797
|
-
}
|
|
798
|
-
function resume() {
|
|
799
|
-
if (rechecking !== null)
|
|
800
|
-
clearInterval(rechecking);
|
|
801
|
-
rechecking = null;
|
|
802
|
-
if (paused.size === 0 && capacity === null)
|
|
803
|
-
return;
|
|
804
|
-
paused.clear();
|
|
805
|
-
capacity = null;
|
|
806
|
-
journal.record('capacity resumed');
|
|
807
|
-
queueReadiness();
|
|
808
|
-
if (!closed && authorization !== undefined)
|
|
809
|
-
void sync('unstreamed');
|
|
810
|
-
}
|
|
811
|
-
async function reclaimed(local) {
|
|
812
|
-
const unreferenced = [...transfers.keys()].filter((locator) => {
|
|
813
|
-
const room = discovered.get(locator);
|
|
814
|
-
return room === undefined || !acquires(room);
|
|
815
|
-
});
|
|
816
|
-
for (const locator of unreferenced) {
|
|
817
|
-
await local.abandon(locator);
|
|
818
|
-
transfers.delete(locator);
|
|
819
|
-
}
|
|
820
|
-
if (unreferenced.length > 0)
|
|
821
|
-
journal.record(`capacity reclaimed ${unreferenced.length}`);
|
|
822
|
-
return unreferenced.length > 0;
|
|
823
|
-
}
|
|
824
|
-
async function admitted(local, locator, needed) {
|
|
825
|
-
if (paused.has(locator))
|
|
826
|
-
return false;
|
|
827
|
-
let available = await estimated().catch(() => null);
|
|
828
|
-
if (available === null || available >= needed)
|
|
829
|
-
return true;
|
|
830
|
-
if (await reclaimed(local))
|
|
831
|
-
available = await estimated().catch(() => null);
|
|
832
|
-
if (available === null || available >= needed)
|
|
833
|
-
return true;
|
|
834
|
-
pause(locator, { cause: 'insufficient-space', needed, available });
|
|
835
|
-
return false;
|
|
836
|
-
}
|
|
837
|
-
async function exceededOn(locator, bytes) {
|
|
838
|
-
const available = await estimated().catch(() => null);
|
|
839
|
-
pause(locator, { cause: 'quota-exceeded', needed: bytes, available });
|
|
840
|
-
}
|
|
841
|
-
function seedRoomHeld(room, version) {
|
|
842
|
-
return prepared.has(room) && reachedBy(room, version);
|
|
843
|
-
}
|
|
844
|
-
function seedHeld(at) {
|
|
845
|
-
return Object.entries(at).every(([room, version]) => (roomsListed && !rooms.has(room)) || seedRoomHeld(room, version));
|
|
846
|
-
}
|
|
847
|
-
function seedApplies(plan) {
|
|
848
|
-
const seed = seeds.get(plan.key);
|
|
849
|
-
if (seed === undefined)
|
|
850
|
-
return null;
|
|
851
|
-
if (!seedHeld(seed.at))
|
|
852
|
-
return seed;
|
|
853
|
-
seeds.delete(plan.key);
|
|
854
|
-
return null;
|
|
855
|
-
}
|
|
856
|
-
function seedBehind(plan, cover) {
|
|
857
|
-
const seed = seedApplies(plan);
|
|
858
|
-
if (seed === null || 'pending' in cover)
|
|
859
|
-
return cover;
|
|
860
|
-
return {
|
|
861
|
-
pending: Object.entries(seed.at)
|
|
862
|
-
.filter(([room, version]) => !seedRoomHeld(room, version))
|
|
863
|
-
.map(([room]) => ({ room, awaits: 'acquisition' })),
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
function seed(entry) {
|
|
867
|
-
if (seedHeld(entry.at))
|
|
868
|
-
return;
|
|
869
|
-
for (const { query, rows } of entry.queries) {
|
|
870
|
-
const key = queryKey(query);
|
|
871
|
-
seeds.set(key, { rows, at: entry.at });
|
|
872
|
-
for (const shown of seedWatchers.get(key) ?? [])
|
|
873
|
-
shown();
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
async function rowsQuery(plan) {
|
|
877
|
-
const before = covered(plan);
|
|
878
|
-
awaitPreparation(before);
|
|
879
|
-
const seeded = seedApplies(plan);
|
|
880
|
-
if (seeded !== null)
|
|
881
|
-
return seededResult(seeded).result;
|
|
882
|
-
if ('pending' in before)
|
|
883
|
-
return before;
|
|
884
|
-
const { rows, at: cover } = await readSide.query(plan, () => covered(plan));
|
|
885
|
-
awaitPreparation(cover);
|
|
886
|
-
if ('pending' in cover)
|
|
887
|
-
return cover;
|
|
888
|
-
return { ...rows, coverage: cover.coverage };
|
|
889
|
-
}
|
|
890
|
-
async function indexConversation(conversation) {
|
|
891
|
-
searchIndex.indexMessages(conversation, [], false);
|
|
892
|
-
let after = null;
|
|
893
|
-
for (;;) {
|
|
894
|
-
const batch = await durably((local) => local.messagesAfter(conversation, after));
|
|
895
|
-
if (closed)
|
|
896
|
-
return;
|
|
897
|
-
searchIndex.indexMessages(conversation, batch.map(({ identity, text }) => ({ conversation, identity, text })), batch.length === 0);
|
|
898
|
-
if (batch.length === 0)
|
|
899
|
-
return;
|
|
900
|
-
after = batch[batch.length - 1].identity;
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
function indexNext() {
|
|
904
|
-
const room = [...roomsToIndex].find((locator) => !searchIndex.rooms().has(locator) && !unindexable.has(locator));
|
|
905
|
-
if (room !== undefined) {
|
|
906
|
-
roomsToIndex.delete(room);
|
|
907
|
-
return () => prepared.has(room)
|
|
908
|
-
? searchIndex.indexRoom(room).catch((error) => unbuilt(room, error))
|
|
909
|
-
: undefined;
|
|
910
|
-
}
|
|
911
|
-
const conversation = [...conversationsToIndex].find((one) => !searchIndex.conversations().has(one));
|
|
912
|
-
if (conversation === undefined)
|
|
913
|
-
return null;
|
|
914
|
-
conversationsToIndex.delete(conversation);
|
|
915
|
-
return () => indexConversation(conversation);
|
|
916
|
-
}
|
|
917
|
-
function index(rooms, named) {
|
|
918
|
-
for (const room of rooms)
|
|
919
|
-
roomsToIndex.add(room);
|
|
920
|
-
for (const conversation of named)
|
|
921
|
-
conversationsToIndex.add(conversation);
|
|
922
|
-
if (indexing)
|
|
923
|
-
return;
|
|
924
|
-
indexing = true;
|
|
925
|
-
void (async () => {
|
|
926
|
-
try {
|
|
927
|
-
for (let unit = indexNext(); unit !== null && !closed; unit = indexNext()) {
|
|
928
|
-
await yielded();
|
|
929
|
-
await unit();
|
|
930
|
-
queueReadiness();
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
finally {
|
|
934
|
-
indexing = false;
|
|
935
|
-
}
|
|
936
|
-
})().catch(() => { });
|
|
937
|
-
}
|
|
938
|
-
function publishChangeSet(set) {
|
|
939
|
-
applyChangeSet(set);
|
|
940
|
-
if (set.rooms.length > 0 ||
|
|
941
|
-
[...new Set(set.documents.map(({ locator }) => locator))].some((locator) => rooms.has(locator) && counted.get(locator) !== documentsIn(locator)))
|
|
942
|
-
emitRooms();
|
|
943
|
-
for (const locator of set.queues)
|
|
944
|
-
emitQueue(locator);
|
|
945
|
-
if (set.drafts.length > 0 ||
|
|
946
|
-
set.documents.some((change) => roomDrafts.get(change.locator)?.has(change.path) === true)) {
|
|
947
|
-
publishRoomDrafts(false);
|
|
948
|
-
}
|
|
949
|
-
for (const app of set.presentations)
|
|
950
|
-
emitPresentation(app);
|
|
951
|
-
conversations.changed(set);
|
|
952
|
-
}
|
|
953
|
-
async function refuse(reason, provider = null) {
|
|
954
|
-
discoveryPaused = true;
|
|
955
|
-
try {
|
|
956
|
-
if (reason === 'revoked-authorization') {
|
|
957
|
-
await exclusive(async () => {
|
|
958
|
-
const revoked = { status: 'unavailable', reason: 'revoked' };
|
|
959
|
-
try {
|
|
960
|
-
const local = await openLocal();
|
|
961
|
-
await local.revokeModels([...replicas.values()].map((replica) => replica.confirmed.record));
|
|
962
|
-
}
|
|
963
|
-
catch {
|
|
964
|
-
journal.record('model revocation persistence failed');
|
|
965
|
-
}
|
|
966
|
-
const keys = new Set(models.keys());
|
|
967
|
-
for (const locator of rooms.keys()) {
|
|
968
|
-
keys.add(modelKey(locator, 'collection'));
|
|
969
|
-
keys.add(modelKey(locator, 'typed-selection'));
|
|
970
|
-
}
|
|
971
|
-
for (const key of keys) {
|
|
972
|
-
invalidModels.add(key);
|
|
973
|
-
models.set(key, revoked);
|
|
974
|
-
liveModels.delete(key);
|
|
975
|
-
}
|
|
976
|
-
const changed = [];
|
|
977
|
-
for (const [locator, room] of rooms) {
|
|
978
|
-
const before = replicas.get(locator) ?? null;
|
|
979
|
-
if (before !== null) {
|
|
980
|
-
const record = {
|
|
981
|
-
...before.confirmed.record,
|
|
982
|
-
model: revoked,
|
|
983
|
-
selectionModel: revoked,
|
|
984
|
-
};
|
|
985
|
-
replicas.set(locator, replicaOf(confirmedOf(record, before.confirmed), before.queue, before));
|
|
986
|
-
}
|
|
987
|
-
rooms.set(locator, {
|
|
988
|
-
...room,
|
|
989
|
-
model: modelTypes(revoked),
|
|
990
|
-
typedSelection: room.typedSelection === null
|
|
991
|
-
? null
|
|
992
|
-
: { ...room.typedSelection, model: modelTypes(revoked) },
|
|
993
|
-
});
|
|
994
|
-
changed.push(changeSet(locator, before, replicas.get(locator) ?? null, { room: true }));
|
|
995
|
-
}
|
|
996
|
-
publishChangeSet(mergeChangeSets(changed));
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
finally {
|
|
1001
|
-
refusal = {
|
|
1002
|
-
code: reason,
|
|
1003
|
-
retryable: false,
|
|
1004
|
-
reauthorizationOffered: reason === 'revoked-authorization',
|
|
1005
|
-
refusal: provider,
|
|
1006
|
-
};
|
|
1007
|
-
journal.record(`connection refused ${reason}`);
|
|
1008
|
-
emitRefusal();
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
function list(room, synchronizationAvailable) {
|
|
1012
|
-
const selection = room.typedSelection ?? null;
|
|
1013
|
-
rooms.set(room.locator, {
|
|
1014
|
-
name: room.name,
|
|
1015
|
-
locator: room.locator,
|
|
1016
|
-
handle: room.handle,
|
|
1017
|
-
operations: room.operations,
|
|
1018
|
-
replica: room.replica,
|
|
1019
|
-
model: modelTypes(modelOf(room.locator)),
|
|
1020
|
-
typedSelection: selection === null
|
|
1021
|
-
? null
|
|
1022
|
-
: {
|
|
1023
|
-
roots: selection.roots,
|
|
1024
|
-
model: modelTypes(modelOf(room.locator, 'typed-selection')),
|
|
1025
|
-
},
|
|
1026
|
-
providerOwnedPaths: room.providerOwnedPaths,
|
|
1027
|
-
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
1028
|
-
synchronizationAvailable,
|
|
1029
|
-
refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
|
-
async function applyDiscovery(found, started) {
|
|
1033
|
-
for (const room of found) {
|
|
1034
|
-
const fence = fences.get(room.locator);
|
|
1035
|
-
if (fence !== undefined && fence.erased && fence.at < started)
|
|
1036
|
-
fences.delete(room.locator);
|
|
1037
|
-
}
|
|
1038
|
-
await loseRooms(found
|
|
1039
|
-
.filter((room) => room.replica.admission !== 'admitted' && heldLocally(room.locator))
|
|
1040
|
-
.map((room) => room.locator));
|
|
1041
|
-
const listed = found.filter((room) => !fences.has(room.locator));
|
|
1042
|
-
const listedLocators = new Set(listed.map((room) => room.locator));
|
|
1043
|
-
for (const locator of listedLocators)
|
|
1044
|
-
scopedRefusals.delete(locator);
|
|
1045
|
-
discovered.clear();
|
|
1046
|
-
for (const room of listed)
|
|
1047
|
-
discovered.set(room.locator, room);
|
|
1048
|
-
const unlisted = unlistedHeld(new Set(found.map((room) => room.locator)));
|
|
1049
|
-
if (options.adapter.exhaustiveDiscovery === true)
|
|
1050
|
-
await loseRooms(unlisted);
|
|
1051
|
-
else
|
|
1052
|
-
unlistedKept(unlisted);
|
|
1053
|
-
for (const room of listed) {
|
|
1054
|
-
const held = replicas.get(room.locator)?.confirmed.record;
|
|
1055
|
-
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
1056
|
-
list(held, false);
|
|
1057
|
-
continue;
|
|
1058
|
-
}
|
|
1059
|
-
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
1060
|
-
}
|
|
1061
|
-
roomsListed = true;
|
|
1062
|
-
emitRooms();
|
|
1063
|
-
return listed;
|
|
1064
|
-
}
|
|
1065
|
-
function heldLocally(locator) {
|
|
1066
|
-
return (replicas.has(locator) ||
|
|
1067
|
-
roomDrafts.has(locator) ||
|
|
1068
|
-
transfers.has(locator) ||
|
|
1069
|
-
queuesWithoutReplica.has(locator));
|
|
1070
|
-
}
|
|
1071
|
-
function unlistedHeld(listed) {
|
|
1072
|
-
return [
|
|
1073
|
-
...new Set([
|
|
1074
|
-
...rooms.keys(),
|
|
1075
|
-
...roomDrafts.keys(),
|
|
1076
|
-
...transfers.keys(),
|
|
1077
|
-
...queuesWithoutReplica.keys(),
|
|
1078
|
-
]),
|
|
1079
|
-
].filter((locator) => !listed.has(locator));
|
|
1080
|
-
}
|
|
1081
|
-
function unlistedKept(unlisted) {
|
|
1082
|
-
for (const locator of unlisted) {
|
|
1083
|
-
const held = replicas.get(locator)?.confirmed.record;
|
|
1084
|
-
if (held !== undefined)
|
|
1085
|
-
list(held, false);
|
|
1086
|
-
else
|
|
1087
|
-
rooms.delete(locator);
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
function fence(locators) {
|
|
1091
|
-
const fresh = [...new Set(locators)].filter((locator) => !fences.has(locator));
|
|
1092
|
-
const removed = fresh.map((locator) => changeSet(locator, replicas.get(locator) ?? null, null, {
|
|
1093
|
-
room: true,
|
|
1094
|
-
queue: true,
|
|
1095
|
-
drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
|
|
1096
|
-
}));
|
|
1097
|
-
for (const locator of fresh) {
|
|
1098
|
-
fences.set(locator, { at: discoveries, erased: false });
|
|
1099
|
-
lastFenced.set(locator, ++fencings);
|
|
1100
|
-
for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
|
|
1101
|
-
pendings.delete(batch.id);
|
|
1102
|
-
replicas.delete(locator);
|
|
1103
|
-
queuesWithoutReplica.delete(locator);
|
|
1104
|
-
rooms.delete(locator);
|
|
1105
|
-
roomDrafts.delete(locator);
|
|
1106
|
-
transfers.delete(locator);
|
|
1107
|
-
catching.delete(locator);
|
|
1108
|
-
discovered.delete(locator);
|
|
1109
|
-
announced.delete(locator);
|
|
1110
|
-
unreadable.delete(locator);
|
|
1111
|
-
stale.delete(locator);
|
|
1112
|
-
repairable.delete(locator);
|
|
1113
|
-
unindexable.delete(locator);
|
|
1114
|
-
paused.delete(locator);
|
|
1115
|
-
scopedRefusals.delete(locator);
|
|
1116
|
-
sourceRefusals.delete(locator);
|
|
1117
|
-
declarationRefusals.delete(locator);
|
|
1118
|
-
for (const attached of ['collection', 'typed-selection']) {
|
|
1119
|
-
const key = modelKey(locator, attached);
|
|
1120
|
-
models.delete(key);
|
|
1121
|
-
liveModels.delete(key);
|
|
1122
|
-
invalidModels.delete(key);
|
|
1123
|
-
}
|
|
1124
|
-
journal.record(`room lost ${locator}`);
|
|
1125
|
-
}
|
|
1126
|
-
if (fresh.length === 0)
|
|
1127
|
-
return fresh;
|
|
1128
|
-
publishChangeSet(mergeChangeSets(removed));
|
|
1129
|
-
for (const locator of fresh)
|
|
1130
|
-
prepared.delete(locator);
|
|
1131
|
-
searchIndex.forget(fresh);
|
|
1132
|
-
readSide.recover();
|
|
1133
|
-
moveTo({ ...status, ...attention() });
|
|
1134
|
-
return fresh;
|
|
1135
|
-
}
|
|
1136
|
-
async function erased(local, locators) {
|
|
1137
|
-
await local.erase(locators);
|
|
1138
|
-
for (const locator of locators) {
|
|
1139
|
-
const held = fences.get(locator);
|
|
1140
|
-
if (held !== undefined)
|
|
1141
|
-
held.erased = true;
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
async function loseRooms(locators) {
|
|
1145
|
-
const fresh = fence(locators);
|
|
1146
|
-
if (fresh.length > 0)
|
|
1147
|
-
await durably((local) => erased(local, fresh));
|
|
1148
|
-
}
|
|
1149
|
-
function restored(key, persisted) {
|
|
1150
|
-
const prior = models.get(key);
|
|
1151
|
-
if (prior !== undefined &&
|
|
1152
|
-
prior.status !== 'unavailable' &&
|
|
1153
|
-
(persisted.status === 'unavailable' || !sameModel(prior.snapshot, persisted.snapshot))) {
|
|
1154
|
-
invalidModels.add(key);
|
|
1155
|
-
}
|
|
1156
|
-
const model = persisted.status === 'unavailable' ||
|
|
1157
|
-
liveModels.get(key) === JSON.stringify(persisted.snapshot)
|
|
1158
|
-
? persisted
|
|
1159
|
-
: cached(persisted);
|
|
1160
|
-
models.set(key, model);
|
|
1161
|
-
if (model.status === 'unavailable') {
|
|
1162
|
-
liveModels.delete(key);
|
|
1163
|
-
invalidModels.delete(key);
|
|
1164
|
-
}
|
|
1165
|
-
return model;
|
|
1166
|
-
}
|
|
1167
|
-
async function hydrate(opened) {
|
|
1168
|
-
const [records, queues, kept, presented, held, chatDrafts, lastCompleted, declared, named, known, staged, heldTargets,] = await Promise.all([
|
|
1169
|
-
opened.all(),
|
|
1170
|
-
opened.queues(),
|
|
1171
|
-
opened.roomDrafts(),
|
|
1172
|
-
opened.presentation(),
|
|
1173
|
-
opened.conversations(),
|
|
1174
|
-
opened.chatDrafts(),
|
|
1175
|
-
opened.lastCompleted(),
|
|
1176
|
-
opened.providerLimits(),
|
|
1177
|
-
opened.capabilities(),
|
|
1178
|
-
opened.member(),
|
|
1179
|
-
opened.transfers(),
|
|
1180
|
-
opened.targets(),
|
|
1181
|
-
]);
|
|
1182
|
-
targets = heldTargets;
|
|
1183
|
-
transfers.clear();
|
|
1184
|
-
for (const record of staged)
|
|
1185
|
-
transfers.set(record.locator, record);
|
|
1186
|
-
limits = declared;
|
|
1187
|
-
capabilities = named;
|
|
1188
|
-
member = known;
|
|
1189
|
-
roomDrafts.clear();
|
|
1190
|
-
for (const record of kept)
|
|
1191
|
-
rememberRoomDraft(record);
|
|
1192
|
-
presentations.clear();
|
|
1193
|
-
for (const record of presented)
|
|
1194
|
-
presentations.set(record.app, record.value);
|
|
1195
|
-
conversations.hydrate(held, chatDrafts);
|
|
1196
|
-
const queuedBatches = new Map(queues.map((queue) => [queue.locator, queue.pending.map(({ batch }) => batch)]));
|
|
1197
|
-
pendings.clear();
|
|
1198
|
-
for (const queue of queues) {
|
|
1199
|
-
for (const { batch, state } of queue.pending)
|
|
1200
|
-
pendings.set(batch.id, state);
|
|
1201
|
-
}
|
|
1202
|
-
const previousReplicas = new Map(replicas);
|
|
1203
|
-
replicas.clear();
|
|
1204
|
-
queuesWithoutReplica.clear();
|
|
1205
|
-
const replicated = new Set(records.map((record) => record.locator));
|
|
1206
|
-
for (const [locator, queue] of queuedBatches) {
|
|
1207
|
-
if (!replicated.has(locator))
|
|
1208
|
-
queuesWithoutReplica.set(locator, queue);
|
|
1209
|
-
}
|
|
1210
|
-
for (const record of records) {
|
|
1211
|
-
const model = restored(modelKey(record.locator, 'collection'), record.model);
|
|
1212
|
-
const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
|
|
1213
|
-
const prior = previousReplicas.get(record.locator);
|
|
1214
|
-
const confirmed = confirmedOf({ ...record, model, selectionModel }, prior?.confirmed);
|
|
1215
|
-
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? [], prior));
|
|
1216
|
-
const existing = rooms.get(record.locator);
|
|
1217
|
-
if (existing === undefined)
|
|
1218
|
-
list(record, false);
|
|
1219
|
-
else {
|
|
1220
|
-
rooms.set(record.locator, {
|
|
1221
|
-
...existing,
|
|
1222
|
-
roomVersion: record.roomVersion,
|
|
1223
|
-
operations: record.operations,
|
|
1224
|
-
replica: record.replica,
|
|
1225
|
-
model: modelTypes(model),
|
|
1226
|
-
typedSelection: record.typedSelection === null
|
|
1227
|
-
? null
|
|
1228
|
-
: {
|
|
1229
|
-
roots: record.typedSelection.roots,
|
|
1230
|
-
model: modelTypes(selectionModel),
|
|
1231
|
-
},
|
|
1232
|
-
});
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
return {
|
|
1236
|
-
lastCompleted,
|
|
1237
|
-
locators: new Set([...queuedBatches.keys(), ...replicas.keys()]),
|
|
1238
|
-
read: mergeChangeSets([...new Set([...previousReplicas.keys(), ...replicas.keys()])].map((locator) => changeSet(locator, previousReplicas.get(locator) ?? null, replicas.get(locator) ?? null))),
|
|
1239
|
-
};
|
|
1240
|
-
}
|
|
1241
|
-
async function readState(opened) {
|
|
1242
|
-
const { lastCompleted, locators, read } = await hydrate(opened);
|
|
1243
|
-
applyChangeSet(read);
|
|
1244
|
-
prepare();
|
|
1245
|
-
if (replicas.size > 0)
|
|
1246
|
-
emitRooms();
|
|
1247
|
-
if (member !== null)
|
|
1248
|
-
emitMember();
|
|
1249
|
-
for (const locator of locators)
|
|
1250
|
-
emitQueue(locator);
|
|
1251
|
-
publishRoomDrafts(false);
|
|
1252
|
-
return lastCompleted;
|
|
1253
|
-
}
|
|
1254
|
-
async function open() {
|
|
1255
|
-
if (closed)
|
|
1256
|
-
throw new Error('runtime closed');
|
|
1257
|
-
const opened = await openRuntimeStore(isFenced);
|
|
1258
|
-
store = opened;
|
|
1259
|
-
const interrupted = await opened.fences();
|
|
1260
|
-
for (const locator of interrupted) {
|
|
1261
|
-
fences.set(locator, { at: discoveries, erased: false });
|
|
1262
|
-
lastFenced.set(locator, ++fencings);
|
|
1263
|
-
}
|
|
1264
|
-
const kept = options.shell.generation;
|
|
1265
|
-
const storedGeneration = await opened.generation();
|
|
1266
|
-
const loss = detectStorageLoss(storedGeneration, kept);
|
|
1267
|
-
const generation = nextGeneration(storedGeneration, kept);
|
|
1268
|
-
if (generation !== storedGeneration)
|
|
1269
|
-
await opened.rememberGeneration(generation);
|
|
1270
|
-
if (generation !== kept)
|
|
1271
|
-
await options.shell.keepGeneration(generation);
|
|
1272
|
-
if (loss !== null) {
|
|
1273
|
-
storageLoss = loss;
|
|
1274
|
-
emitStorageLoss();
|
|
1275
|
-
}
|
|
1276
|
-
const lastCompleted = await readState(opened);
|
|
1277
|
-
if (interrupted.length > 0)
|
|
1278
|
-
await erased(opened, interrupted);
|
|
1279
|
-
moveTo({ ...status, lastCompleted, ...attention() });
|
|
1280
|
-
return opened;
|
|
1281
|
-
}
|
|
1282
|
-
function durably(work) {
|
|
1283
|
-
return exclusive(async () => {
|
|
1284
|
-
const local = await openLocal();
|
|
1285
|
-
return work(local);
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
function openLocal() {
|
|
1289
|
-
opening ??= open().catch((error) => {
|
|
1290
|
-
opening = undefined;
|
|
1291
|
-
throw error;
|
|
1292
|
-
});
|
|
1293
|
-
return opening;
|
|
1294
|
-
}
|
|
1295
|
-
function exclusive(work) {
|
|
1296
|
-
const next = mutations.then(work, work);
|
|
1297
|
-
mutations = next.then(() => undefined, () => undefined);
|
|
1298
|
-
return next;
|
|
1299
|
-
}
|
|
1300
|
-
function withdrawable(batch) {
|
|
1301
|
-
const state = pendings.get(batch.id);
|
|
1302
|
-
return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
|
|
1303
|
-
}
|
|
1304
|
-
async function settle(local, locator, before, confirmed, queue, settling = {}) {
|
|
1305
|
-
const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
1306
|
-
const { withdrawn, kept } = standing(confirmed, queue, withdrawable, localSaves);
|
|
1307
|
-
const founded = kept.filter((batch) => !queue.includes(batch));
|
|
1308
|
-
const queueChanged = kept.length !== previousQueue.length || founded.length > 0;
|
|
1309
|
-
const unqueued = withdrawn.map((batch) => ({
|
|
1310
|
-
batch,
|
|
1311
|
-
returned: localSaves || landed(batch, confirmed) ? null : returnedBy('withdrawn'),
|
|
1312
|
-
}));
|
|
1313
|
-
const returning = [
|
|
1314
|
-
...[...unqueued].reverse(),
|
|
1315
|
-
...(settling.leaving === undefined ? [] : [settling.leaving]),
|
|
1316
|
-
];
|
|
1317
|
-
const written = new Map();
|
|
1318
|
-
for (const { batch, returned } of returning) {
|
|
1319
|
-
if (returned === null)
|
|
1320
|
-
continue;
|
|
1321
|
-
for (const saved of pendings.get(batch.id)?.drafts ?? []) {
|
|
1322
|
-
const existing = written.get(saved.path) ?? roomDrafts.get(locator)?.get(saved.path);
|
|
1323
|
-
written.set(saved.path, {
|
|
1324
|
-
locator,
|
|
1325
|
-
path: saved.path,
|
|
1326
|
-
revision: roomDraftRevision(),
|
|
1327
|
-
parts: returnedInto(existing?.parts, saved.parts),
|
|
1328
|
-
returned,
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
await local.keepAuthored({ locator, queue: kept, founded, roomDrafts: [...written.values()] });
|
|
1333
|
-
const staying = new Set(kept.map((batch) => batch.id));
|
|
1334
|
-
for (const batch of previousQueue) {
|
|
1335
|
-
if (!staying.has(batch.id))
|
|
1336
|
-
pendings.delete(batch.id);
|
|
1337
|
-
}
|
|
1338
|
-
for (const record of written.values())
|
|
1339
|
-
rememberRoomDraft(record);
|
|
1340
|
-
try {
|
|
1341
|
-
await local.write({
|
|
1342
|
-
next: confirmed.record,
|
|
1343
|
-
previous: before?.confirmed.record ?? null,
|
|
1344
|
-
...(settling.stored === undefined ? {} : { stored: settling.stored }),
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
catch (error) {
|
|
1348
|
-
const held = before === undefined ? null : replicaOf(before.confirmed, kept, before);
|
|
1349
|
-
if (held !== null)
|
|
1350
|
-
replicas.set(locator, held);
|
|
1351
|
-
else if (kept.length > 0)
|
|
1352
|
-
queuesWithoutReplica.set(locator, kept);
|
|
1353
|
-
else
|
|
1354
|
-
queuesWithoutReplica.delete(locator);
|
|
1355
|
-
publishChangeSet(changeSet(locator, before ?? null, held, {
|
|
1356
|
-
queue: queueChanged,
|
|
1357
|
-
drafts: [...written.keys()],
|
|
1358
|
-
}));
|
|
1359
|
-
moveTo({ ...status, ...attention() });
|
|
1360
|
-
throw error;
|
|
1361
|
-
}
|
|
1362
|
-
return () => {
|
|
1363
|
-
queuesWithoutReplica.delete(locator);
|
|
1364
|
-
for (const { batch, returned } of unqueued) {
|
|
1365
|
-
const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
|
|
1366
|
-
journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
|
|
1367
|
-
}
|
|
1368
|
-
replicas.set(locator, replicaOf(confirmed, kept, before));
|
|
1369
|
-
return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
|
|
1370
|
-
room: settling.room,
|
|
1371
|
-
queue: queueChanged,
|
|
1372
|
-
drafts: [...written.keys()],
|
|
1373
|
-
});
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
function adoptModels(locator, record) {
|
|
1377
|
-
for (const [attached, model] of [
|
|
1378
|
-
['collection', record.model],
|
|
1379
|
-
['typed-selection', record.selectionModel],
|
|
1380
|
-
]) {
|
|
1381
|
-
const key = modelKey(locator, attached);
|
|
1382
|
-
models.set(key, model);
|
|
1383
|
-
invalidModels.delete(key);
|
|
1384
|
-
if (model.status === 'current')
|
|
1385
|
-
liveModels.set(key, JSON.stringify(model.snapshot));
|
|
1386
|
-
else if (model.status === 'unavailable')
|
|
1387
|
-
liveModels.delete(key);
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
function remodel(locator, answers, held) {
|
|
1391
|
-
const taken = (answer, previous, attached) => {
|
|
1392
|
-
if (answer === undefined)
|
|
1393
|
-
return previous;
|
|
1394
|
-
const { state, refused } = attachedModel(answer, previous);
|
|
1395
|
-
if (refused !== null) {
|
|
1396
|
-
const where = refused.location === undefined ? '' : ` ${refused.location}`;
|
|
1397
|
-
journal.record(`model invalid ${locator} ${attached} ${refused.identity} ${refused.rule}${where}`);
|
|
1398
|
-
}
|
|
1399
|
-
models.set(modelKey(locator, attached), state);
|
|
1400
|
-
return state;
|
|
1401
|
-
};
|
|
1402
|
-
return {
|
|
1403
|
-
collection: taken(answers?.collection, held.collection, 'collection'),
|
|
1404
|
-
selection: taken(answers?.selection, held.selection, 'typed-selection'),
|
|
1405
|
-
};
|
|
1406
|
-
}
|
|
1407
|
-
function publish(local, room, confirmed, landing, live) {
|
|
1408
|
-
const { base, stored } = landing;
|
|
1409
|
-
return exclusive(async () => {
|
|
1410
|
-
if (stored !== undefined) {
|
|
1411
|
-
await indexReferrers({ text: confirmed.record.documents, binaries: confirmed.record.binaries }, compositionOf(confirmed.record));
|
|
1412
|
-
}
|
|
1413
|
-
if (!live()) {
|
|
1414
|
-
caughtUpTo(room.locator);
|
|
1415
|
-
return true;
|
|
1416
|
-
}
|
|
1417
|
-
const prior = replicas.get(room.locator);
|
|
1418
|
-
if (base !== undefined &&
|
|
1419
|
-
(prior?.confirmed.record.roomVersion !== base.record.roomVersion ||
|
|
1420
|
-
prior.confirmed.record.revisions !== base.record.revisions)) {
|
|
1421
|
-
return false;
|
|
1422
|
-
}
|
|
1423
|
-
adoptModels(room.locator, confirmed.record);
|
|
1424
|
-
const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
|
|
1425
|
-
const roomVersion = confirmed.record.roomVersion;
|
|
1426
|
-
const accepted = queue.filter((batch) => {
|
|
1427
|
-
const accepted = pendings.get(batch.id)?.accepted;
|
|
1428
|
-
return (accepted !== undefined && options.adapter.reached(room.locator, roomVersion, accepted));
|
|
1429
|
-
});
|
|
1430
|
-
let settled;
|
|
1431
|
-
try {
|
|
1432
|
-
settled = await settle(local, room.locator, prior, confirmed, queue.filter((batch) => !accepted.includes(batch)), { room: true, ...(stored === undefined ? {} : { stored }) });
|
|
1433
|
-
}
|
|
1434
|
-
catch (error) {
|
|
1435
|
-
if (!exceeded(error))
|
|
1436
|
-
throw error;
|
|
1437
|
-
await exceededOn(room.locator, grownBy(confirmed.record, prior?.confirmed.record));
|
|
1438
|
-
caughtUpTo(room.locator);
|
|
1439
|
-
return true;
|
|
1440
|
-
}
|
|
1441
|
-
const set = settled();
|
|
1442
|
-
if (stored !== undefined) {
|
|
1443
|
-
transfers.delete(room.locator);
|
|
1444
|
-
catching.delete(room.locator);
|
|
1445
|
-
}
|
|
1446
|
-
for (const batch of accepted)
|
|
1447
|
-
pendings.delete(batch.id);
|
|
1448
|
-
stale.delete(room.locator);
|
|
1449
|
-
unindexable.delete(room.locator);
|
|
1450
|
-
if (unreadable.delete(room.locator))
|
|
1451
|
-
scopedRefusals.delete(room.locator);
|
|
1452
|
-
sourceRefusals.delete(room.locator);
|
|
1453
|
-
declarationRefusals.delete(room.locator);
|
|
1454
|
-
list(room, true);
|
|
1455
|
-
if (prior === undefined)
|
|
1456
|
-
prepared.add(room.locator);
|
|
1457
|
-
publishChangeSet(set);
|
|
1458
|
-
moveTo({ ...status, ...attention() });
|
|
1459
|
-
options.adapter.roomStream?.published(room.locator, roomVersion);
|
|
1460
|
-
return true;
|
|
1461
|
-
});
|
|
1462
|
-
}
|
|
1463
|
-
async function saveRoomDrafts(locator, named, writtenBy) {
|
|
1464
|
-
const local = await openLocal();
|
|
1465
|
-
await referenced(locator);
|
|
1466
|
-
const replica = replicas.get(locator);
|
|
1467
|
-
const room = rooms.get(locator);
|
|
1468
|
-
if (replica === undefined || room === undefined) {
|
|
1469
|
-
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
1470
|
-
}
|
|
1471
|
-
const saved = [];
|
|
1472
|
-
for (const { path, revision } of named) {
|
|
1473
|
-
const record = roomDrafts.get(locator)?.get(path);
|
|
1474
|
-
if (record === undefined || record.revision !== revision) {
|
|
1475
|
-
return { outcome: 'refused', code: 'stale', retryable: false, file: path };
|
|
1476
|
-
}
|
|
1477
|
-
if (!saved.some((draft) => draft.path === path))
|
|
1478
|
-
saved.push({ path, parts: record.parts });
|
|
1479
|
-
}
|
|
1480
|
-
const changed = saved.flatMap(({ path, parts }) => changedParts(path, underneath(parts, path, replica.visible)));
|
|
1481
|
-
if (changed.length > 0)
|
|
1482
|
-
return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
|
|
1483
|
-
const parts = saved.map(({ path, parts }) => changesOf(path, parts));
|
|
1484
|
-
const changes = parts.flatMap(({ changes }) => changes);
|
|
1485
|
-
const requeue = requeued(replica.queue, changes, replica.visible, withdrawable, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
1486
|
-
if (requeue.kind === 'refused')
|
|
1487
|
-
return { outcome: 'refused', ...requeue.refusal };
|
|
1488
|
-
const foreign = [...requeue.withdrawn.keys()].some((origin) => {
|
|
1489
|
-
const writer = replica.queue.find((batch) => batch.id === origin)?.writtenBy;
|
|
1490
|
-
return writer?.app !== writtenBy?.app || writer?.release !== writtenBy?.release;
|
|
1491
|
-
});
|
|
1492
|
-
if (foreign) {
|
|
1493
|
-
return {
|
|
1494
|
-
outcome: 'refused',
|
|
1495
|
-
code: 'unknown-revision',
|
|
1496
|
-
retryable: false,
|
|
1497
|
-
file: [...requeue.queued].at(-1),
|
|
1498
|
-
};
|
|
1499
|
-
}
|
|
1500
|
-
const withdrawing = requeue.withdrawn.size > 0;
|
|
1501
|
-
const input = {
|
|
1502
|
-
local: localSaves,
|
|
1503
|
-
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
1504
|
-
composition: compositionFor(replica.confirmed.record),
|
|
1505
|
-
operations: room.operations,
|
|
1506
|
-
providerOwnedPaths: room.providerOwnedPaths,
|
|
1507
|
-
providerLimits: limits,
|
|
1508
|
-
handle: room.handle,
|
|
1509
|
-
visible: replica.visible,
|
|
1510
|
-
changes,
|
|
1511
|
-
bases: parts.flatMap(({ bases }) => bases),
|
|
1512
|
-
queued: queuedDocuments(replica.queue),
|
|
1513
|
-
writtenBy,
|
|
1514
|
-
clean: {
|
|
1515
|
-
text: replica.confirmed.record.documents,
|
|
1516
|
-
binaries: replica.confirmed.record.binaries,
|
|
1517
|
-
composition: compositionOf(replica.confirmed.record),
|
|
1518
|
-
},
|
|
1519
|
-
};
|
|
1520
|
-
const checked = withdrawing
|
|
1521
|
-
? buildBatch({ ...input, queued: new Set([...input.queued, ...requeue.queued]) })
|
|
1522
|
-
: null;
|
|
1523
|
-
if (checked?.kind === 'refused')
|
|
1524
|
-
return { outcome: 'refused', ...checked.refusal };
|
|
1525
|
-
const owners = [...requeue.owners].flatMap((origin) => {
|
|
1526
|
-
const batch = requeue.withdrawn.get(origin);
|
|
1527
|
-
return batch === undefined || batch === null ? [] : [{ origin, batch }];
|
|
1528
|
-
});
|
|
1529
|
-
if (owners.length > 1) {
|
|
1530
|
-
return {
|
|
1531
|
-
outcome: 'refused',
|
|
1532
|
-
code: 'unknown-revision',
|
|
1533
|
-
retryable: false,
|
|
1534
|
-
file: [...requeue.queued].at(-1),
|
|
1535
|
-
};
|
|
1536
|
-
}
|
|
1537
|
-
const built = !withdrawing
|
|
1538
|
-
? buildBatch(input)
|
|
1539
|
-
: requeue.changes.length === 0
|
|
1540
|
-
? null
|
|
1541
|
-
: buildBatch({
|
|
1542
|
-
...input,
|
|
1543
|
-
visible: replicaOf(replica.confirmed, requeue.queue, replica).visible,
|
|
1544
|
-
changes: requeue.changes,
|
|
1545
|
-
queued: queuedDocuments(requeue.queue),
|
|
1546
|
-
});
|
|
1547
|
-
if (built?.kind === 'refused')
|
|
1548
|
-
return { outcome: 'refused', ...built.refusal };
|
|
1549
|
-
const [owner] = owners;
|
|
1550
|
-
const join = built === null || owner === undefined
|
|
1551
|
-
? null
|
|
1552
|
-
: joined(requeue.queue, owner.batch, built.batch);
|
|
1553
|
-
if (join?.kind === 'refused')
|
|
1554
|
-
return { outcome: 'refused', ...join.refusal };
|
|
1555
|
-
const withdrawn = new Map(requeue.withdrawn);
|
|
1556
|
-
if (join !== null && owner !== undefined)
|
|
1557
|
-
withdrawn.set(owner.origin, join.batch);
|
|
1558
|
-
const drafted = new Map();
|
|
1559
|
-
for (const [origin, replaced] of withdrawn) {
|
|
1560
|
-
if (replaced === null)
|
|
1561
|
-
continue;
|
|
1562
|
-
const carried = withdrawnDrafts(pendings.get(origin)?.drafts ?? [], changes);
|
|
1563
|
-
drafted.set(replaced.id, replaced === join?.batch
|
|
1564
|
-
? [
|
|
1565
|
-
...carried.map((held) => joinedDraft(held, saved)),
|
|
1566
|
-
...saved.filter((draft) => !carried.some((held) => held.path === draft.path)),
|
|
1567
|
-
]
|
|
1568
|
-
: carried);
|
|
1569
|
-
}
|
|
1570
|
-
if (built !== null && join === null)
|
|
1571
|
-
drafted.set(built.batch.id, saved);
|
|
1572
|
-
const batches = join !== null ? join.queue : built === null ? requeue.queue : [...requeue.queue, built.batch];
|
|
1573
|
-
for (const replaced of withdrawn.values()) {
|
|
1574
|
-
if (replaced === null || !batches.includes(replaced))
|
|
1575
|
-
continue;
|
|
1576
|
-
const limited = limitedIn(batches, replaced, limits, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
|
|
1577
|
-
if (limited !== null)
|
|
1578
|
-
return { outcome: 'refused', ...limited };
|
|
1579
|
-
}
|
|
1580
|
-
const consumed = saved.map(({ path }) => path);
|
|
1581
|
-
try {
|
|
1582
|
-
await local.keepAuthored({
|
|
1583
|
-
locator,
|
|
1584
|
-
queue: batches,
|
|
1585
|
-
saved: drafted,
|
|
1586
|
-
consumedRoomDrafts: consumed,
|
|
1587
|
-
});
|
|
1588
|
-
}
|
|
1589
|
-
catch {
|
|
1590
|
-
return { outcome: 'refused', code: 'durable-write', retryable: true };
|
|
1591
|
-
}
|
|
1592
|
-
if (fences.has(locator))
|
|
1593
|
-
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
1594
|
-
for (const origin of withdrawn.keys())
|
|
1595
|
-
pendings.delete(origin);
|
|
1596
|
-
for (const [id, drafts] of drafted) {
|
|
1597
|
-
pendings.set(id, { drafts, submission: { state: 'waiting' }, lost: false });
|
|
1598
|
-
}
|
|
1599
|
-
if (!localSaves)
|
|
1600
|
-
recorded = false;
|
|
1601
|
-
for (const path of consumed)
|
|
1602
|
-
forgetRoomDraft(locator, path);
|
|
1603
|
-
replicas.set(locator, replicaOf(replica.confirmed, batches, replica));
|
|
1604
|
-
publishChangeSet(changeSet(locator, replica, replicas.get(locator) ?? null, {
|
|
1605
|
-
queue: true,
|
|
1606
|
-
drafts: consumed,
|
|
1607
|
-
}));
|
|
1608
|
-
moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
|
|
1609
|
-
submitLater(locator);
|
|
1610
|
-
const identifier = join?.batch.id ??
|
|
1611
|
-
built?.batch.id ??
|
|
1612
|
-
[...requeue.withdrawn].map(([origin, replaced]) => replaced?.id ?? origin).at(-1);
|
|
1613
|
-
return { outcome: 'saved', identifier };
|
|
1614
|
-
}
|
|
1615
|
-
async function mark(local, locator, identifier, submission, lost) {
|
|
1616
|
-
const state = pendings.get(identifier);
|
|
1617
|
-
if (!continuing() ||
|
|
1618
|
-
state === undefined ||
|
|
1619
|
-
(JSON.stringify(state.submission) === JSON.stringify(submission) && state.lost === lost)) {
|
|
1620
|
-
return;
|
|
1621
|
-
}
|
|
1622
|
-
await local.submit(locator, identifier, submission, lost);
|
|
1623
|
-
if (fences.has(locator))
|
|
1624
|
-
return;
|
|
1625
|
-
pendings.set(identifier, { ...state, submission, lost });
|
|
1626
|
-
const replica = replicas.get(locator);
|
|
1627
|
-
if (replica !== undefined)
|
|
1628
|
-
publishChangeSet(changeSet(locator, replica, replica, { queue: true }));
|
|
1629
|
-
moveTo({ ...status, ...attention() });
|
|
1630
|
-
}
|
|
1631
|
-
async function keepUpload(local, locator, identifier, upload) {
|
|
1632
|
-
if (!continuing())
|
|
1633
|
-
throw new Error('connection refused');
|
|
1634
|
-
await local.keepUpload(locator, identifier, upload);
|
|
1635
|
-
if (fences.has(locator))
|
|
1636
|
-
throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
|
|
1637
|
-
pendings.set(identifier, { ...pendings.get(identifier), upload });
|
|
1638
|
-
if (!continuing())
|
|
1639
|
-
throw new Error('connection refused');
|
|
1640
|
-
}
|
|
1641
|
-
async function readHistory(locator, path, query = {}) {
|
|
1642
|
-
if (closed || refusal !== null)
|
|
1643
|
-
return UNCERTAIN;
|
|
1644
|
-
try {
|
|
1645
|
-
await ensureAuthorization();
|
|
1646
|
-
}
|
|
1647
|
-
catch {
|
|
1648
|
-
return UNCERTAIN;
|
|
1649
|
-
}
|
|
1650
|
-
if (closed || authorization === undefined || refusal !== null)
|
|
1651
|
-
return UNCERTAIN;
|
|
1652
|
-
const since = fencings;
|
|
1653
|
-
const transfer = ['transfer', 'history', locator, path];
|
|
1654
|
-
if (query.revision !== undefined)
|
|
1655
|
-
transfer.push(query.revision);
|
|
1656
|
-
if (query.pageToken !== undefined)
|
|
1657
|
-
transfer.push(query.pageToken);
|
|
1658
|
-
journal.record(transfer.join(' '));
|
|
1659
|
-
try {
|
|
1660
|
-
const result = await options.adapter.history(authorization, {
|
|
1661
|
-
locator,
|
|
1662
|
-
path,
|
|
1663
|
-
...(query.revision === undefined ? {} : { revision: query.revision }),
|
|
1664
|
-
...(query.pageToken === undefined ? {} : { pageToken: query.pageToken }),
|
|
1665
|
-
});
|
|
1666
|
-
journal.record(`outcome history ${result.kind}`);
|
|
1667
|
-
if (fencedSince(locator, since))
|
|
1668
|
-
return failure({ kind: 'room-not-readable', locator });
|
|
1669
|
-
if (result.kind === 'scoped-refusal') {
|
|
1670
|
-
const room = replicas.get(result.locator)?.confirmed.record;
|
|
1671
|
-
if (room !== undefined)
|
|
1672
|
-
await scoped(result, room, await openLocal());
|
|
1673
|
-
}
|
|
1674
|
-
if (result.kind === 'revoked-authorization')
|
|
1675
|
-
await refuse('revoked-authorization');
|
|
1676
|
-
return historyAnswer(result);
|
|
1677
|
-
}
|
|
1678
|
-
catch {
|
|
1679
|
-
journal.record('outcome history transport-uncertainty');
|
|
1680
|
-
return UNCERTAIN;
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
function queuedBlob(locator, path, localId) {
|
|
1684
|
-
for (const batch of replicas.get(locator)?.queue ?? []) {
|
|
1685
|
-
const change = batch.changes[queuedFile(batch, path, localId)];
|
|
1686
|
-
if (change?.kind === 'binary')
|
|
1687
|
-
return change.blob;
|
|
1688
|
-
}
|
|
1689
|
-
return null;
|
|
1690
|
-
}
|
|
1691
|
-
function binaryRoom(request) {
|
|
1692
|
-
if (request.kind === 'path')
|
|
1693
|
-
return request.locator;
|
|
1694
|
-
const scope = options.adapter.referrerScope(request.referrer);
|
|
1695
|
-
return scope?.kind === 'room' ? scope.locator : null;
|
|
1696
|
-
}
|
|
1697
|
-
async function transferBinary(request, transfer, answer) {
|
|
1698
|
-
if (closed || refusal !== null)
|
|
1699
|
-
return UNCERTAIN;
|
|
1700
|
-
try {
|
|
1701
|
-
await ensureAuthorization();
|
|
1702
|
-
}
|
|
1703
|
-
catch {
|
|
1704
|
-
return UNCERTAIN;
|
|
1705
|
-
}
|
|
1706
|
-
if (closed || authorization === undefined || refusal !== null)
|
|
1707
|
-
return UNCERTAIN;
|
|
1708
|
-
const since = fencings;
|
|
1709
|
-
journal.record(request.kind === 'path'
|
|
1710
|
-
? `transfer binary ${request.locator} ${request.path}`
|
|
1711
|
-
: `transfer binary ${request.fileRef}`);
|
|
1712
|
-
try {
|
|
1713
|
-
const result = await transfer(authorization);
|
|
1714
|
-
const settled = result;
|
|
1715
|
-
journal.record(`outcome binary ${settled.kind}`);
|
|
1716
|
-
const locator = binaryRoom(request);
|
|
1717
|
-
if (locator !== null && fencedSince(locator, since))
|
|
1718
|
-
return failure({ kind: 'room-not-readable', locator });
|
|
1719
|
-
if (settled.kind === 'scoped-refusal') {
|
|
1720
|
-
const room = replicas.get(settled.locator)?.confirmed.record;
|
|
1721
|
-
if (room !== undefined)
|
|
1722
|
-
await scoped(settled, room, await openLocal());
|
|
1723
|
-
}
|
|
1724
|
-
if (settled.kind === 'revoked-authorization')
|
|
1725
|
-
await refuse('revoked-authorization');
|
|
1726
|
-
return answer(result);
|
|
1727
|
-
}
|
|
1728
|
-
catch {
|
|
1729
|
-
journal.record('outcome binary transport-uncertainty');
|
|
1730
|
-
return UNCERTAIN;
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
function readBinary(request) {
|
|
1734
|
-
return transferBinary(request, (held) => options.adapter.binary(held, request), binaryAnswer);
|
|
1735
|
-
}
|
|
1736
|
-
function binaryPath(locator, path, revision) {
|
|
1737
|
-
if (revision !== undefined)
|
|
1738
|
-
return { kind: 'path', locator, path, revision };
|
|
1739
|
-
const visible = replicas.get(locator)?.visible;
|
|
1740
|
-
const held = visible?.binaries[path];
|
|
1741
|
-
if (visible === undefined || held === undefined)
|
|
1742
|
-
return null;
|
|
1743
|
-
if (held.state === 'pending')
|
|
1744
|
-
return { kind: 'pending', localId: held.localId };
|
|
1745
|
-
const record = binaryRecord(path, held, visible.revisions[path]);
|
|
1746
|
-
if (record.state !== 'accepted')
|
|
1747
|
-
return null;
|
|
1748
|
-
const replica = replicas.get(locator);
|
|
1749
|
-
const queued = (replica?.queue ?? []).flatMap((batch) => batch.changes);
|
|
1750
|
-
const holds = (at) => {
|
|
1751
|
-
const kept = replica?.confirmed.record.binaries[at];
|
|
1752
|
-
return (kept?.state === 'accepted' &&
|
|
1753
|
-
kept.fileRef === record.fileRef &&
|
|
1754
|
-
replica?.confirmed.record.revisions[at] === record.revision);
|
|
1755
|
-
};
|
|
1756
|
-
let confirmed = path;
|
|
1757
|
-
for (let index = queued.length - 1; index >= 0 && !holds(confirmed); index -= 1) {
|
|
1758
|
-
const change = queued[index];
|
|
1759
|
-
if (change.kind === 'move' && change.to === confirmed)
|
|
1760
|
-
confirmed = change.path;
|
|
1761
|
-
}
|
|
1762
|
-
return {
|
|
1763
|
-
kind: 'path',
|
|
1764
|
-
locator,
|
|
1765
|
-
path: confirmed,
|
|
1766
|
-
revision: record.revision,
|
|
1767
|
-
fileRef: record.fileRef,
|
|
1768
|
-
};
|
|
1769
|
-
}
|
|
1770
|
-
async function readBinaryPath(locator, path, revision) {
|
|
1771
|
-
await ready;
|
|
1772
|
-
const asked = binaryPath(locator, path, revision);
|
|
1773
|
-
if (asked === null)
|
|
1774
|
-
return absentBinary(path);
|
|
1775
|
-
if (asked.kind === 'pending') {
|
|
1776
|
-
const blob = queuedBlob(locator, path, asked.localId);
|
|
1777
|
-
return blob === null ? absentBinary(path) : { kind: 'binary', blob };
|
|
1778
|
-
}
|
|
1779
|
-
return readBinary(asked);
|
|
1780
|
-
}
|
|
1781
|
-
async function linkBinaryPath(locator, path, revision) {
|
|
1782
|
-
await ready;
|
|
1783
|
-
const asked = binaryPath(locator, path, revision);
|
|
1784
|
-
if (asked === null || asked.kind === 'pending')
|
|
1785
|
-
return absentBinary(path);
|
|
1786
|
-
return transferBinary(asked, (held) => options.adapter.link(held, asked), binaryLinkAnswer);
|
|
1787
|
-
}
|
|
1788
|
-
async function readBinaryReference(reference) {
|
|
1789
|
-
await ready;
|
|
1790
|
-
return readBinary({
|
|
1791
|
-
kind: 'reference',
|
|
1792
|
-
fileRef: reference.fileRef,
|
|
1793
|
-
referrer: reference.referrer,
|
|
1794
|
-
});
|
|
1795
|
-
}
|
|
1796
|
-
function declaredAgents() {
|
|
1797
|
-
for (const capability of capabilities) {
|
|
1798
|
-
if (capability.conversationAgents !== undefined)
|
|
1799
|
-
return capability.conversationAgents;
|
|
1800
|
-
}
|
|
1801
|
-
return undefined;
|
|
1802
|
-
}
|
|
1803
|
-
function declaredAction(name) {
|
|
1804
|
-
for (const capability of capabilities) {
|
|
1805
|
-
for (const action of capability.actions ?? []) {
|
|
1806
|
-
if (action.name === name)
|
|
1807
|
-
return action;
|
|
1808
|
-
}
|
|
1809
|
-
}
|
|
1810
|
-
return undefined;
|
|
1811
|
-
}
|
|
1812
|
-
async function invokeAction(name, input, dispatching) {
|
|
1813
|
-
await ready;
|
|
1814
|
-
if (closed || refusal !== null)
|
|
1815
|
-
return UNCERTAIN;
|
|
1816
|
-
if (declaredAction(name) === undefined)
|
|
1817
|
-
return undeclared(name);
|
|
1818
|
-
try {
|
|
1819
|
-
await ensureAuthorization();
|
|
1820
|
-
}
|
|
1821
|
-
catch {
|
|
1822
|
-
return UNCERTAIN;
|
|
1823
|
-
}
|
|
1824
|
-
if (closed || authorization === undefined || refusal !== null)
|
|
1825
|
-
return UNCERTAIN;
|
|
1826
|
-
await dispatching?.();
|
|
1827
|
-
journal.record(`transfer action ${name}`);
|
|
1828
|
-
try {
|
|
1829
|
-
const result = await options.adapter.action(authorization, { name, input });
|
|
1830
|
-
if (result.kind === 'scoped-refusal') {
|
|
1831
|
-
const room = replicas.get(result.locator)?.confirmed.record;
|
|
1832
|
-
if (room !== undefined)
|
|
1833
|
-
await scoped(result, room, await openLocal());
|
|
1834
|
-
}
|
|
1835
|
-
if (result.kind === 'revoked-authorization')
|
|
1836
|
-
await refuse('revoked-authorization');
|
|
1837
|
-
journal.record(`outcome action ${result.kind}`);
|
|
1838
|
-
if (result.kind === 'rejected')
|
|
1839
|
-
journal.record(`refusal rejected ${result.code}`);
|
|
1840
|
-
return calledResult(result);
|
|
1841
|
-
}
|
|
1842
|
-
catch {
|
|
1843
|
-
journal.record('outcome action transport-uncertainty');
|
|
1844
|
-
return UNCERTAIN;
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
function unsettled(result) {
|
|
1848
|
-
return result.kind !== 'action' && result.retryable;
|
|
1849
|
-
}
|
|
1850
|
-
async function recoveredAction(name, input) {
|
|
1851
|
-
const operation = operationOf(input);
|
|
1852
|
-
if (operation === null)
|
|
1853
|
-
return invokeAction(name, input);
|
|
1854
|
-
const kept = async () => (await openLocal()).keepCall({ operation, name, input });
|
|
1855
|
-
calling.add(operation);
|
|
1856
|
-
try {
|
|
1857
|
-
let result = await invokeAction(name, input, kept);
|
|
1858
|
-
if (unsettled(result)) {
|
|
1859
|
-
await recovery.attempt(`action ${operation}`, async () => {
|
|
1860
|
-
if (closed || refusal !== null)
|
|
1861
|
-
return 'done';
|
|
1862
|
-
if (!socketOpen)
|
|
1863
|
-
return 'again';
|
|
1864
|
-
result = await invokeAction(name, input, kept);
|
|
1865
|
-
return unsettled(result) ? 'again' : 'done';
|
|
1866
|
-
}, { again: true });
|
|
1867
|
-
}
|
|
1868
|
-
if (!unsettled(result))
|
|
1869
|
-
await (await openLocal()).dropCall(operation);
|
|
1870
|
-
return result;
|
|
1871
|
-
}
|
|
1872
|
-
finally {
|
|
1873
|
-
calling.delete(operation);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
async function resendCalls(signal) {
|
|
1877
|
-
if (!socketOpen || closed || stopping)
|
|
1878
|
-
return 'done';
|
|
1879
|
-
const local = await openLocal();
|
|
1880
|
-
for (const held of await local.calls()) {
|
|
1881
|
-
if (calling.has(held.operation))
|
|
1882
|
-
continue;
|
|
1883
|
-
if (closed || signal.aborted)
|
|
1884
|
-
return 'done';
|
|
1885
|
-
if (pastHorizon(held.operation)) {
|
|
1886
|
-
await local.dropCall(held.operation);
|
|
1887
|
-
continue;
|
|
1888
|
-
}
|
|
1889
|
-
calling.add(held.operation);
|
|
1890
|
-
try {
|
|
1891
|
-
if (unsettled(await invokeAction(held.name, held.input)))
|
|
1892
|
-
return 'again';
|
|
1893
|
-
await local.dropCall(held.operation);
|
|
1894
|
-
}
|
|
1895
|
-
finally {
|
|
1896
|
-
calling.delete(held.operation);
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
return 'done';
|
|
1900
|
-
}
|
|
1901
|
-
function continuing() {
|
|
1902
|
-
return !closed && !discoveryPaused;
|
|
1903
|
-
}
|
|
1904
|
-
function reachedBy(locator, roomVersion) {
|
|
1905
|
-
const held = replicas.get(locator);
|
|
1906
|
-
return (held !== undefined &&
|
|
1907
|
-
options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion));
|
|
1908
|
-
}
|
|
1909
|
-
function writtenAfter(locator, roomVersion, path) {
|
|
1910
|
-
const written = replicas.get(locator)?.confirmed.record.written[path];
|
|
1911
|
-
return (written !== undefined &&
|
|
1912
|
-
options.adapter.reached(locator, written, roomVersion) &&
|
|
1913
|
-
!options.adapter.reached(locator, roomVersion, written));
|
|
1914
|
-
}
|
|
1915
|
-
async function pullRefresh(authorized, confirmed, live) {
|
|
1916
|
-
if (!live())
|
|
1917
|
-
return unconfirmed;
|
|
1918
|
-
const turn = generation;
|
|
1919
|
-
try {
|
|
1920
|
-
const result = await refreshRoom(reads, authorized, confirmed, (models, held) => (live() ? remodel(confirmed.record.locator, models, held) : held), live);
|
|
1921
|
-
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
1922
|
-
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
1923
|
-
return result;
|
|
1924
|
-
}
|
|
1925
|
-
catch (error) {
|
|
1926
|
-
journal.record(`transfer refresh ${confirmed.record.locator} transport-uncertainty`);
|
|
1927
|
-
throw error;
|
|
1928
|
-
}
|
|
1929
|
-
finally {
|
|
1930
|
-
reads.finished(confirmed.record.locator, turn);
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
function staging(local, live) {
|
|
1934
|
-
return {
|
|
1935
|
-
held: (locator) => transfers.get(locator) ?? null,
|
|
1936
|
-
staged: (record) => local.staged(record),
|
|
1937
|
-
stage: (record, page) => exclusive(async () => {
|
|
1938
|
-
if (!live())
|
|
1939
|
-
return false;
|
|
1940
|
-
const bytes = layerBytes(page);
|
|
1941
|
-
if (!(await admitted(local, record.locator, bytes)))
|
|
1942
|
-
return false;
|
|
24
|
+
const { adapter, shell } = options;
|
|
25
|
+
const roomTypes = roomTypesOf(adapter.roomTypes, adapter.nameableRoomTypes);
|
|
26
|
+
const builtIn = builtInModel(roomTypes);
|
|
27
|
+
const typedPaths = adapter.typedPaths ?? [];
|
|
28
|
+
const retries = createRetries(shell);
|
|
29
|
+
const socket = createSocket(adapter, retries, options.authorization);
|
|
30
|
+
const presence = createPresence();
|
|
31
|
+
const store = createStore({
|
|
32
|
+
retries,
|
|
33
|
+
database: {
|
|
34
|
+
async ask(request) {
|
|
1943
35
|
try {
|
|
1944
|
-
await
|
|
36
|
+
return await options.database.ask(request);
|
|
1945
37
|
}
|
|
1946
38
|
catch (error) {
|
|
1947
|
-
if (
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
return false;
|
|
1951
|
-
}
|
|
1952
|
-
transfers.set(record.locator, record);
|
|
1953
|
-
readSide.recover();
|
|
1954
|
-
return true;
|
|
1955
|
-
}),
|
|
1956
|
-
abandon: (locator) => exclusive(async () => {
|
|
1957
|
-
if (!live())
|
|
1958
|
-
return;
|
|
1959
|
-
await local.abandon(locator);
|
|
1960
|
-
transfers.delete(locator);
|
|
1961
|
-
readSide.recover();
|
|
1962
|
-
}),
|
|
1963
|
-
};
|
|
1964
|
-
}
|
|
1965
|
-
function reusable(room) {
|
|
1966
|
-
const record = replicas.get(room.locator)?.confirmed.record;
|
|
1967
|
-
if (record === undefined || record.replica.selection !== room.replica.selection)
|
|
1968
|
-
return null;
|
|
1969
|
-
return { documents: record.documents, revisions: record.revisions };
|
|
1970
|
-
}
|
|
1971
|
-
async function pullBootstrap(authorized, room, local, live) {
|
|
1972
|
-
if (!live())
|
|
1973
|
-
return unconfirmed;
|
|
1974
|
-
const turn = generation;
|
|
1975
|
-
if (replicas.has(room.locator)) {
|
|
1976
|
-
catching.add(room.locator);
|
|
1977
|
-
readSide.recover();
|
|
1978
|
-
}
|
|
1979
|
-
try {
|
|
1980
|
-
const result = await bootstrapRoom(reads, authorized, room, {
|
|
1981
|
-
collection: modelOf(room.locator),
|
|
1982
|
-
selection: modelOf(room.locator, 'typed-selection'),
|
|
1983
|
-
}, (models, held) => (live() ? remodel(room.locator, models, held) : held), live, staging(local, live), reusable(room));
|
|
1984
|
-
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
1985
|
-
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
1986
|
-
if (result.kind !== 'staged')
|
|
1987
|
-
caughtUpTo(room.locator);
|
|
1988
|
-
return result;
|
|
1989
|
-
}
|
|
1990
|
-
catch (error) {
|
|
1991
|
-
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
1992
|
-
caughtUpTo(room.locator);
|
|
1993
|
-
throw error;
|
|
1994
|
-
}
|
|
1995
|
-
finally {
|
|
1996
|
-
reads.finished(room.locator, turn);
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
function caughtUpTo(locator) {
|
|
2000
|
-
if (catching.delete(locator))
|
|
2001
|
-
readSide.recover();
|
|
2002
|
-
}
|
|
2003
|
-
function behind(room) {
|
|
2004
|
-
const held = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
2005
|
-
return (held !== undefined &&
|
|
2006
|
-
[announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later)));
|
|
2007
|
-
}
|
|
2008
|
-
async function advance(room, local, authorized, live) {
|
|
2009
|
-
const carried = reads.carried(room.locator);
|
|
2010
|
-
const withheld = await advanceOnce(room, local, authorized, live);
|
|
2011
|
-
if (withheld !== null || reads.carried(room.locator) === carried || !behind(room)) {
|
|
2012
|
-
return withheld;
|
|
2013
|
-
}
|
|
2014
|
-
return advanceOnce(room, local, authorized, live);
|
|
2015
|
-
}
|
|
2016
|
-
async function advanceOnce(room, local, authorized, live) {
|
|
2017
|
-
await referenced(room.locator);
|
|
2018
|
-
if (!live() || paused.has(room.locator))
|
|
2019
|
-
return unconfirmed;
|
|
2020
|
-
const held = replicas.get(room.locator);
|
|
2021
|
-
if (held !== undefined) {
|
|
2022
|
-
const refreshed = await pullRefresh(authorized, held.confirmed, live);
|
|
2023
|
-
if (!live())
|
|
2024
|
-
return unconfirmed;
|
|
2025
|
-
if (refreshed.kind === 'still-current') {
|
|
2026
|
-
stale.delete(room.locator);
|
|
2027
|
-
if (sourceRefusals.has(room.locator))
|
|
2028
|
-
return null;
|
|
2029
|
-
if (unreadable.delete(room.locator)) {
|
|
2030
|
-
scopedRefusals.delete(room.locator);
|
|
2031
|
-
list(room, true);
|
|
2032
|
-
emitRooms();
|
|
2033
|
-
}
|
|
2034
|
-
return null;
|
|
2035
|
-
}
|
|
2036
|
-
if (refreshed.kind === 'advanced') {
|
|
2037
|
-
if (!(await publish(local, room, refreshed.confirmed, { base: held.confirmed }, live))) {
|
|
2038
|
-
const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
|
|
2039
|
-
const reached = now !== undefined &&
|
|
2040
|
-
options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
|
|
2041
|
-
return reached ? null : advance(room, local, authorized, live);
|
|
39
|
+
if (refusedForSpace(error))
|
|
40
|
+
filled(runtime);
|
|
41
|
+
throw error;
|
|
2042
42
|
}
|
|
2043
|
-
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
2044
|
-
return null;
|
|
2045
|
-
}
|
|
2046
|
-
if (refreshed.kind !== 'beyond-horizon')
|
|
2047
|
-
return refreshed;
|
|
2048
|
-
if (opened !== null) {
|
|
2049
|
-
owed.add(room.locator);
|
|
2050
|
-
readSide.recover();
|
|
2051
|
-
return unconfirmed;
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
const bootstrapped = await pullBootstrap(authorized, room, local, live);
|
|
2055
|
-
if (!live())
|
|
2056
|
-
return unconfirmed;
|
|
2057
|
-
if (bootstrapped.kind !== 'staged')
|
|
2058
|
-
return bootstrapped;
|
|
2059
|
-
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored }, live);
|
|
2060
|
-
return null;
|
|
2061
|
-
}
|
|
2062
|
-
async function withhold(withheld, room, local, progress) {
|
|
2063
|
-
if (withheld.kind === 'unconfirmed')
|
|
2064
|
-
return;
|
|
2065
|
-
if (withheld.kind === 'refused')
|
|
2066
|
-
stale.delete(room.locator);
|
|
2067
|
-
else
|
|
2068
|
-
stale.add(room.locator);
|
|
2069
|
-
if (withheld.kind === 'transport-uncertainty' || withheld.kind === 'retryable-refusal') {
|
|
2070
|
-
const reset = withheld.kind === 'retryable-refusal' ? withheld.reset : undefined;
|
|
2071
|
-
repairable.set(room.locator, reset);
|
|
2072
|
-
repairLater(room.locator, reset === undefined ? { again: true } : { at: Date.parse(reset) });
|
|
2073
|
-
}
|
|
2074
|
-
else
|
|
2075
|
-
repairable.delete(room.locator);
|
|
2076
|
-
if (withheld.kind === 'scoped-refusal') {
|
|
2077
|
-
await scoped(withheld, room, local);
|
|
2078
|
-
}
|
|
2079
|
-
else if (withheld.kind === 'transport-uncertainty') {
|
|
2080
|
-
progress.reachable = false;
|
|
2081
|
-
}
|
|
2082
|
-
else if (withheld.kind === 'retryable-refusal') {
|
|
2083
|
-
journal.record(`refusal ${withheld.kind} ${withheld.code}`);
|
|
2084
|
-
}
|
|
2085
|
-
else if (withheld.kind === 'room-not-readable') {
|
|
2086
|
-
unreadable.add(room.locator);
|
|
2087
|
-
list(room, false);
|
|
2088
|
-
emitRooms();
|
|
2089
|
-
}
|
|
2090
|
-
else if (withheld.kind === 'refused') {
|
|
2091
|
-
refusedSource(room, withheld);
|
|
2092
|
-
}
|
|
2093
|
-
else if (withheld.kind === 'revoked-authorization') {
|
|
2094
|
-
await refuse('revoked-authorization');
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
function refusedSource(room, { refusal }) {
|
|
2098
|
-
sourceRefusals.set(room.locator, {
|
|
2099
|
-
code: refusal.rule,
|
|
2100
|
-
retryable: false,
|
|
2101
|
-
fields: {
|
|
2102
|
-
file: refusal.file,
|
|
2103
|
-
...(refusal.location === undefined ? {} : { location: refusal.location }),
|
|
2104
|
-
...(refusal.referrers === undefined ? {} : { referrers: refusal.referrers }),
|
|
2105
43
|
},
|
|
2106
|
-
});
|
|
2107
|
-
declarationRefusals.delete(room.locator);
|
|
2108
|
-
unreadable.add(room.locator);
|
|
2109
|
-
list(room, false);
|
|
2110
|
-
emitRooms();
|
|
2111
|
-
moveTo({ ...status, ...attention() });
|
|
2112
|
-
journal.record(`source refused ${room.locator} ${refusal.rule} ${refusal.file}`);
|
|
2113
|
-
}
|
|
2114
|
-
function leaving(batch, result) {
|
|
2115
|
-
if (result.kind === 'expired')
|
|
2116
|
-
return { batch, returned: returnedBy('expired') };
|
|
2117
|
-
return {
|
|
2118
|
-
batch,
|
|
2119
|
-
returned: {
|
|
2120
|
-
kind: result.kind,
|
|
2121
|
-
code: result.code,
|
|
2122
|
-
fields: result.fields,
|
|
2123
|
-
retryable: result.retryable,
|
|
2124
|
-
},
|
|
2125
|
-
};
|
|
2126
|
-
}
|
|
2127
|
-
function logAccepted(locator, submitted, accepted) {
|
|
2128
|
-
journal.record(`outcome ${locator} ${submitted.id} accepted ${accepted.roomVersion}` +
|
|
2129
|
-
(accepted.author === undefined ? '' : ` author ${accepted.author}`) +
|
|
2130
|
-
(accepted.time === undefined ? '' : ` time ${accepted.time}`));
|
|
2131
|
-
}
|
|
2132
|
-
async function adoptAccepted(room, local, submitted, accepted) {
|
|
2133
|
-
await referenced(room.locator);
|
|
2134
|
-
if (!continuing())
|
|
2135
|
-
return unconfirmed;
|
|
2136
|
-
const current = replicas.get(room.locator);
|
|
2137
|
-
if (current === undefined || !current.queue.some((batch) => batch.id === submitted.id)) {
|
|
2138
|
-
return null;
|
|
2139
|
-
}
|
|
2140
|
-
const held = current.confirmed.record.roomVersion;
|
|
2141
|
-
logAccepted(room.locator, submitted, accepted);
|
|
2142
|
-
const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
|
|
2143
|
-
? { kind: 'staged', confirmed: current.confirmed }
|
|
2144
|
-
: applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
|
|
2145
|
-
? accepted.roomVersion
|
|
2146
|
-
: held, accepted.roomVersion);
|
|
2147
|
-
if (applied.kind !== 'staged') {
|
|
2148
|
-
const state = pendings.get(submitted.id);
|
|
2149
|
-
await local.accept(room.locator, submitted.id, accepted.roomVersion);
|
|
2150
|
-
if (state !== undefined && !fences.has(room.locator))
|
|
2151
|
-
pendings.set(submitted.id, { ...state, accepted: accepted.roomVersion });
|
|
2152
|
-
emitQueue(room.locator);
|
|
2153
|
-
return applied;
|
|
2154
|
-
}
|
|
2155
|
-
const settled = await settle(local, room.locator, current, applied.confirmed, current.queue.filter((batch) => batch.id !== submitted.id), { room: true });
|
|
2156
|
-
pendings.delete(submitted.id);
|
|
2157
|
-
unreadable.delete(room.locator);
|
|
2158
|
-
sourceRefusals.delete(room.locator);
|
|
2159
|
-
declarationRefusals.delete(room.locator);
|
|
2160
|
-
const set = settled();
|
|
2161
|
-
list(room, true);
|
|
2162
|
-
publishChangeSet(set);
|
|
2163
|
-
moveTo({ ...status, ...attention() });
|
|
2164
|
-
return null;
|
|
2165
|
-
}
|
|
2166
|
-
async function adopted(room, local, submitted, accepted) {
|
|
2167
|
-
const failed = await exclusive(() => adoptAccepted(room, local, submitted, accepted));
|
|
2168
|
-
if (failed === null)
|
|
2169
|
-
return true;
|
|
2170
|
-
if (failed.kind === 'refused' && continuing())
|
|
2171
|
-
refusedSource(room, failed);
|
|
2172
|
-
return false;
|
|
2173
|
-
}
|
|
2174
|
-
async function submitRoom(room, local, authorized, progress, signal) {
|
|
2175
|
-
while (continuing() && authorization !== undefined && !signal.aborted) {
|
|
2176
|
-
const replica = replicas.get(room.locator);
|
|
2177
|
-
const head = replica?.queue[0];
|
|
2178
|
-
if (replica === undefined || head === undefined)
|
|
2179
|
-
return 'done';
|
|
2180
|
-
if (pendings.get(head.id)?.accepted !== undefined)
|
|
2181
|
-
return 'done';
|
|
2182
|
-
const state = pendings.get(head.id);
|
|
2183
|
-
const { submission, lost: earlier } = state ?? {
|
|
2184
|
-
submission: { state: 'waiting' },
|
|
2185
|
-
lost: false,
|
|
2186
|
-
};
|
|
2187
|
-
if (submission.state === 'reset' && Date.now() < Date.parse(submission.reset)) {
|
|
2188
|
-
progress.complete = false;
|
|
2189
|
-
return { at: Date.parse(submission.reset) };
|
|
2190
|
-
}
|
|
2191
|
-
if (writesHeld()) {
|
|
2192
|
-
progress.complete = false;
|
|
2193
|
-
journal.record(`submission ${room.locator} ${head.id} held`);
|
|
2194
|
-
return 'done';
|
|
2195
|
-
}
|
|
2196
|
-
const lost = earlier || submission.state === 'submitted';
|
|
2197
|
-
if (lost && pastHorizon(head.id)) {
|
|
2198
|
-
await discardPending(room, local, signal);
|
|
2199
|
-
return 'done';
|
|
2200
|
-
}
|
|
2201
|
-
let result;
|
|
2202
|
-
journal.record(`submission ${room.locator} ${head.id}`);
|
|
2203
|
-
await exclusive(() => mark(local, room.locator, head.id, { state: 'submitted' }, lost));
|
|
2204
|
-
if (discoveryPaused || fences.has(room.locator))
|
|
2205
|
-
return 'done';
|
|
2206
|
-
if (signal.aborted || writesHeld()) {
|
|
2207
|
-
await exclusive(() => mark(local, room.locator, head.id, submission, earlier));
|
|
2208
|
-
if (signal.aborted)
|
|
2209
|
-
return 'done';
|
|
2210
|
-
progress.complete = false;
|
|
2211
|
-
journal.record(`submission ${room.locator} ${head.id} held`);
|
|
2212
|
-
return 'done';
|
|
2213
|
-
}
|
|
2214
|
-
const opensBefore = openSignals;
|
|
2215
|
-
const answered = awaitingAnswer(room.locator);
|
|
2216
|
-
try {
|
|
2217
|
-
result = await options.adapter.mutate(authorized, {
|
|
2218
|
-
locator: room.locator,
|
|
2219
|
-
changeBatch: head,
|
|
2220
|
-
...(state?.upload === undefined ? {} : { upload: state.upload }),
|
|
2221
|
-
}, (upload) => exclusive(() => keepUpload(local, room.locator, head.id, upload)));
|
|
2222
|
-
}
|
|
2223
|
-
catch {
|
|
2224
|
-
answered();
|
|
2225
|
-
progress.complete = false;
|
|
2226
|
-
if (continuing())
|
|
2227
|
-
progress.reachable = false;
|
|
2228
|
-
return 'again';
|
|
2229
|
-
}
|
|
2230
|
-
if (signal.aborted) {
|
|
2231
|
-
answered();
|
|
2232
|
-
return 'done';
|
|
2233
|
-
}
|
|
2234
|
-
if (result.kind !== 'accepted' || !continuing())
|
|
2235
|
-
answered();
|
|
2236
|
-
if (!continuing())
|
|
2237
|
-
return 'done';
|
|
2238
|
-
if (result.kind === 'transport-uncertainty') {
|
|
2239
|
-
progress.complete = false;
|
|
2240
|
-
progress.reachable = false;
|
|
2241
|
-
return 'again';
|
|
2242
|
-
}
|
|
2243
|
-
if (result.kind === 'room-not-readable') {
|
|
2244
|
-
progress.complete = false;
|
|
2245
|
-
unreadable.add(room.locator);
|
|
2246
|
-
list(room, false);
|
|
2247
|
-
emitRooms();
|
|
2248
|
-
return 'done';
|
|
2249
|
-
}
|
|
2250
|
-
if (result.kind === 'revoked-authorization') {
|
|
2251
|
-
await refuse('revoked-authorization');
|
|
2252
|
-
return 'done';
|
|
2253
|
-
}
|
|
2254
|
-
if (result.kind === 'scoped-refusal') {
|
|
2255
|
-
await scoped(result, room, local);
|
|
2256
|
-
progress.complete = false;
|
|
2257
|
-
return 'done';
|
|
2258
|
-
}
|
|
2259
|
-
if (result.kind === 'write-halted') {
|
|
2260
|
-
journal.record(`outcome ${room.locator} ${head.id} write-halted`);
|
|
2261
|
-
await exclusive(() => mark(local, room.locator, head.id, { state: 'waiting' }, lost));
|
|
2262
|
-
if (serviceMode === 'open' && openSignals !== opensBefore)
|
|
2263
|
-
continue;
|
|
2264
|
-
haltedRefusal = true;
|
|
2265
|
-
progress.complete = false;
|
|
2266
|
-
return 'done';
|
|
2267
|
-
}
|
|
2268
|
-
if (result.kind === 'conflicted' ||
|
|
2269
|
-
result.kind === 'rejected' ||
|
|
2270
|
-
result.kind === 'expired' ||
|
|
2271
|
-
result.kind === 'retryable-refusal') {
|
|
2272
|
-
if (result.kind === 'expired' && lost && result.restored !== true) {
|
|
2273
|
-
journal.record(`outcome ${room.locator} ${head.id} expired`);
|
|
2274
|
-
await discardPending(room, local, signal);
|
|
2275
|
-
return 'done';
|
|
2276
|
-
}
|
|
2277
|
-
if (result.kind === 'expired') {
|
|
2278
|
-
await exclusive(() => mark(local, room.locator, head.id, { state: 'waiting' }, false));
|
|
2279
|
-
const withheld = await advance(room, local, authorized, continuing);
|
|
2280
|
-
if (withheld !== null) {
|
|
2281
|
-
await withhold(withheld, room, local, progress);
|
|
2282
|
-
return 'done';
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}` +
|
|
2286
|
-
(result.kind === 'conflicted' && result.roomVersion !== undefined
|
|
2287
|
-
? ` ${result.roomVersion}`
|
|
2288
|
-
: ''));
|
|
2289
|
-
if (result.kind !== 'expired')
|
|
2290
|
-
journal.record(`refusal ${result.kind} ${result.code}`);
|
|
2291
|
-
}
|
|
2292
|
-
if (result.kind === 'retryable-refusal') {
|
|
2293
|
-
const reset = result.reset;
|
|
2294
|
-
progress.complete = false;
|
|
2295
|
-
await exclusive(() => mark(local, room.locator, head.id, reset === undefined ? { state: 'waiting' } : { state: 'reset', reset }, lost));
|
|
2296
|
-
return reset === undefined ? 'again' : { at: Date.parse(reset) };
|
|
2297
|
-
}
|
|
2298
|
-
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
2299
|
-
const left = leaving(head, result);
|
|
2300
|
-
await exclusive(async () => {
|
|
2301
|
-
if (!continuing())
|
|
2302
|
-
return;
|
|
2303
|
-
const current = replicas.get(room.locator);
|
|
2304
|
-
if (current === undefined || !current.queue.some((batch) => batch.id === head.id)) {
|
|
2305
|
-
return;
|
|
2306
|
-
}
|
|
2307
|
-
const settled = await settle(local, room.locator, current, current.confirmed, current.queue.filter((batch) => batch.id !== head.id), {
|
|
2308
|
-
leaving: (result.kind === 'expired' && landed(head, current.confirmed)) ||
|
|
2309
|
-
(result.kind === 'conflicted' && landed(head, current.confirmed, result.affected))
|
|
2310
|
-
? { ...left, returned: null }
|
|
2311
|
-
: left,
|
|
2312
|
-
});
|
|
2313
|
-
publishChangeSet(settled());
|
|
2314
|
-
moveTo({ ...status, ...attention() });
|
|
2315
|
-
});
|
|
2316
|
-
if (result.kind === 'expired')
|
|
2317
|
-
continue;
|
|
2318
|
-
const withheld = await advance(room, local, authorized, continuing);
|
|
2319
|
-
if (withheld !== null) {
|
|
2320
|
-
await withhold(withheld, room, local, progress);
|
|
2321
|
-
return 'done';
|
|
2322
|
-
}
|
|
2323
|
-
continue;
|
|
2324
|
-
}
|
|
2325
|
-
const kept = await adopted(room, local, head, result).finally(answered);
|
|
2326
|
-
if (!kept) {
|
|
2327
|
-
progress.complete = false;
|
|
2328
|
-
return 'done';
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
return 'done';
|
|
2332
|
-
}
|
|
2333
|
-
function pastHorizon(identity) {
|
|
2334
|
-
return (outcomeRetention !== null && identityTime(identity) + outcomeRetention * 1000 <= Date.now());
|
|
2335
|
-
}
|
|
2336
|
-
async function discardPending(room, local, signal) {
|
|
2337
|
-
const { locator } = room;
|
|
2338
|
-
const discarded = await exclusive(async () => {
|
|
2339
|
-
if (fences.has(locator))
|
|
2340
|
-
return false;
|
|
2341
|
-
const before = replicas.get(locator);
|
|
2342
|
-
const queue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
|
|
2343
|
-
fences.set(locator, { at: discoveries, erased: false });
|
|
2344
|
-
lastFenced.set(locator, ++fencings);
|
|
2345
|
-
try {
|
|
2346
|
-
await local.discard(locator);
|
|
2347
|
-
}
|
|
2348
|
-
finally {
|
|
2349
|
-
fences.delete(locator);
|
|
2350
|
-
}
|
|
2351
|
-
for (const batch of queue)
|
|
2352
|
-
pendings.delete(batch.id);
|
|
2353
|
-
queuesWithoutReplica.delete(locator);
|
|
2354
|
-
const after = before === undefined ? null : replicaOf(before.confirmed, [], before);
|
|
2355
|
-
if (after !== null)
|
|
2356
|
-
replicas.set(locator, after);
|
|
2357
|
-
journal.record(`room discarded ${locator}`);
|
|
2358
|
-
publishChangeSet(changeSet(locator, before ?? null, after, { queue: true }));
|
|
2359
|
-
moveTo({ ...status, ...attention() });
|
|
2360
|
-
return true;
|
|
2361
|
-
});
|
|
2362
|
-
if (!discarded)
|
|
2363
|
-
return;
|
|
2364
|
-
const progress = { complete: true, reachable: true };
|
|
2365
|
-
await roomTask(locator, async (live) => {
|
|
2366
|
-
await Promise.race([
|
|
2367
|
-
reload(room, local, progress, () => live() && !signal.aborted),
|
|
2368
|
-
abandonedOn(signal),
|
|
2369
|
-
]);
|
|
2370
|
-
});
|
|
2371
|
-
if (signal.aborted)
|
|
2372
|
-
return;
|
|
2373
|
-
await rest(progress);
|
|
2374
|
-
}
|
|
2375
|
-
function awaitingAnswer(locator) {
|
|
2376
|
-
let release = () => { };
|
|
2377
|
-
const awaited = new Promise((resolve) => {
|
|
2378
|
-
release = resolve;
|
|
2379
|
-
});
|
|
2380
|
-
answering.set(locator, { generation, answered: awaited });
|
|
2381
|
-
return () => {
|
|
2382
|
-
if (answering.get(locator)?.answered === awaited)
|
|
2383
|
-
answering.delete(locator);
|
|
2384
|
-
release();
|
|
2385
|
-
};
|
|
2386
|
-
}
|
|
2387
|
-
async function retainModel(local, room, attached, next) {
|
|
2388
|
-
const key = modelKey(room.locator, attached);
|
|
2389
|
-
const previous = modelOf(room.locator, attached);
|
|
2390
|
-
const heldBefore = replicas.get(room.locator)?.confirmed.record;
|
|
2391
|
-
if (JSON.stringify(previous) === JSON.stringify(next) &&
|
|
2392
|
-
(heldBefore === undefined ||
|
|
2393
|
-
(JSON.stringify(heldBefore.operations) === JSON.stringify(room.operations) &&
|
|
2394
|
-
JSON.stringify(heldBefore.replica) === JSON.stringify(room.replica) &&
|
|
2395
|
-
JSON.stringify(heldBefore.typedSelection) ===
|
|
2396
|
-
JSON.stringify(room.typedSelection ?? null))))
|
|
2397
|
-
return;
|
|
2398
|
-
if (next.status === 'unavailable') {
|
|
2399
|
-
invalidModels.add(key);
|
|
2400
|
-
liveModels.delete(key);
|
|
2401
|
-
}
|
|
2402
|
-
await exclusive(async () => {
|
|
2403
|
-
if (!continuing())
|
|
2404
|
-
return;
|
|
2405
|
-
const held = replicas.get(room.locator);
|
|
2406
|
-
if (held !== undefined) {
|
|
2407
|
-
const declared = {
|
|
2408
|
-
...held.confirmed.record,
|
|
2409
|
-
operations: room.operations,
|
|
2410
|
-
replica: room.replica,
|
|
2411
|
-
typedSelection: room.typedSelection ?? null,
|
|
2412
|
-
...(attached === 'collection' ? { model: next } : { selectionModel: next }),
|
|
2413
|
-
};
|
|
2414
|
-
const reclassified = sameMembership(declared, held.confirmed.record)
|
|
2415
|
-
? { kind: 'staged', confirmed: confirmedOf(declared, held.confirmed) }
|
|
2416
|
-
: readReclassified(declared, held.confirmed);
|
|
2417
|
-
const record = reclassified.kind === 'staged'
|
|
2418
|
-
? declared
|
|
2419
|
-
: {
|
|
2420
|
-
...declared,
|
|
2421
|
-
typedSelection: held.confirmed.record.typedSelection,
|
|
2422
|
-
};
|
|
2423
|
-
const read = reclassified.kind === 'deferred'
|
|
2424
|
-
? { kind: 'staged', confirmed: confirmedOf(record, held.confirmed) }
|
|
2425
|
-
: reclassified;
|
|
2426
|
-
await local.write({ next: record, previous: held.confirmed.record });
|
|
2427
|
-
if (read.kind === 'staged') {
|
|
2428
|
-
replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
|
|
2429
|
-
if (declarationRefusals.delete(room.locator)) {
|
|
2430
|
-
sourceRefusals.delete(room.locator);
|
|
2431
|
-
unreadable.delete(room.locator);
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
else if (read.kind === 'refused') {
|
|
2435
|
-
replicas.set(room.locator, { ...held, confirmed: { ...held.confirmed, record } });
|
|
2436
|
-
refusedSource(room, read);
|
|
2437
|
-
declarationRefusals.add(room.locator);
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
models.set(key, next);
|
|
2441
|
-
if (next.status === 'current') {
|
|
2442
|
-
liveModels.set(key, JSON.stringify(next.snapshot));
|
|
2443
|
-
invalidModels.delete(key);
|
|
2444
|
-
}
|
|
2445
|
-
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
2446
|
-
if (held === undefined)
|
|
2447
|
-
emitRooms();
|
|
2448
|
-
else
|
|
2449
|
-
publishChangeSet(changeSet(room.locator, held, replicas.get(room.locator) ?? null, { room: true }));
|
|
2450
|
-
});
|
|
2451
|
-
}
|
|
2452
|
-
async function scoped(result, room, local) {
|
|
2453
|
-
if ((result.scope === 'content' || result.scope === 'sync') && !result.retryable) {
|
|
2454
|
-
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
2455
|
-
await loseRooms([room.locator]);
|
|
2456
|
-
return;
|
|
2457
|
-
}
|
|
2458
|
-
scopedRefusals.set(room.locator, {
|
|
2459
|
-
code: result.code,
|
|
2460
|
-
fields: result.fields,
|
|
2461
|
-
retryable: result.retryable,
|
|
2462
|
-
});
|
|
2463
|
-
let scopedAway = room;
|
|
2464
|
-
if (result.scope === 'model')
|
|
2465
|
-
await retainModel(local, room, 'collection', {
|
|
2466
|
-
status: 'unavailable',
|
|
2467
|
-
reason: 'not-readable',
|
|
2468
|
-
});
|
|
2469
|
-
else if (result.scope !== 'operation') {
|
|
2470
|
-
unreadable.add(room.locator);
|
|
2471
|
-
scopedAway = {
|
|
2472
|
-
...room,
|
|
2473
|
-
replica: { ...room.replica, admission: 'unavailable' },
|
|
2474
|
-
operations: {
|
|
2475
|
-
...room.operations,
|
|
2476
|
-
...(result.scope === 'content' ? { read: [] } : {}),
|
|
2477
|
-
...(result.scope === 'sync' ? { sync: [] } : {}),
|
|
2478
|
-
},
|
|
2479
|
-
};
|
|
2480
|
-
await retainModel(local, scopedAway, 'collection', modelOf(room.locator));
|
|
2481
|
-
}
|
|
2482
|
-
if (!continuing())
|
|
2483
|
-
return;
|
|
2484
|
-
list(scopedAway, scopedAway.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
2485
|
-
emitRooms();
|
|
2486
|
-
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.code}`);
|
|
2487
|
-
}
|
|
2488
|
-
function heldRooms(listed) {
|
|
2489
|
-
return listed.flatMap((room) => {
|
|
2490
|
-
const record = replicas.get(room.locator)?.confirmed.record;
|
|
2491
|
-
if (record === undefined || record.replica.selection !== room.replica.selection)
|
|
2492
|
-
return [];
|
|
2493
|
-
return [
|
|
2494
|
-
{
|
|
2495
|
-
locator: room.locator,
|
|
2496
|
-
roomVersion: record.roomVersion,
|
|
2497
|
-
documents: record.documents,
|
|
2498
|
-
revisions: record.revisions,
|
|
2499
|
-
},
|
|
2500
|
-
];
|
|
2501
|
-
});
|
|
2502
|
-
}
|
|
2503
|
-
async function synchronize(listed, local, progress, attempt, live) {
|
|
2504
|
-
const acquired = listed.filter(acquires);
|
|
2505
|
-
const prepared = await Promise.allSettled(acquired.map((room) => prepareRoom(room, local, progress)));
|
|
2506
|
-
if (!live())
|
|
2507
|
-
return;
|
|
2508
|
-
const ready = acquired.filter((_, index) => {
|
|
2509
|
-
const outcome = prepared[index];
|
|
2510
|
-
return outcome?.status === 'fulfilled' && outcome.value;
|
|
2511
|
-
});
|
|
2512
|
-
if (attempt === 'opened') {
|
|
2513
|
-
options.adapter.roomStream?.hold(heldRooms(ready), heldDemand(acquired));
|
|
2514
|
-
handed = true;
|
|
2515
|
-
}
|
|
2516
|
-
discovering?.release();
|
|
2517
|
-
const loaded = await Promise.allSettled(ready
|
|
2518
|
-
.filter((room) => attempt === 'unstreamed' || !replicas.has(room.locator))
|
|
2519
|
-
.map((room) => roomTask(room.locator, (held) => loadRoom(room, local, progress, held))));
|
|
2520
|
-
const failed = [...prepared, ...loaded].find((room) => room.status === 'rejected');
|
|
2521
|
-
if (failed !== undefined)
|
|
2522
|
-
throw failed.reason;
|
|
2523
|
-
}
|
|
2524
|
-
async function prepareRoom(room, local, progress) {
|
|
2525
|
-
if (closed || authorization === undefined) {
|
|
2526
|
-
progress.complete = false;
|
|
2527
|
-
return false;
|
|
2528
|
-
}
|
|
2529
|
-
const prior = replicas.get(room.locator)?.confirmed.record;
|
|
2530
|
-
if (prior !== undefined && prior.replica.selection !== room.replica.selection) {
|
|
2531
|
-
list(prior, false);
|
|
2532
|
-
emitRooms();
|
|
2533
|
-
journal.record(`selection changed ${room.locator}`);
|
|
2534
|
-
progress.complete = false;
|
|
2535
|
-
return false;
|
|
2536
|
-
}
|
|
2537
|
-
if (prior !== undefined)
|
|
2538
|
-
await retainModel(local, room, 'collection', modelOf(room.locator));
|
|
2539
|
-
if (discoveryPaused || authorization === undefined)
|
|
2540
|
-
return false;
|
|
2541
|
-
return room.replica.admission === 'admitted';
|
|
2542
|
-
}
|
|
2543
|
-
async function caughtUp(room, local, progress, live) {
|
|
2544
|
-
if (authorization === undefined || !live() || !behind(room))
|
|
2545
|
-
return;
|
|
2546
|
-
const withheld = await advance(room, local, authorization, live);
|
|
2547
|
-
if (withheld !== null)
|
|
2548
|
-
await withhold(withheld, room, local, progress);
|
|
2549
|
-
}
|
|
2550
|
-
function revoked(locator) {
|
|
2551
|
-
return ['collection', 'typed-selection'].some((attached) => {
|
|
2552
|
-
const model = modelOf(locator, attached);
|
|
2553
|
-
return model.status === 'unavailable' && model.reason === 'revoked';
|
|
2554
|
-
});
|
|
2555
|
-
}
|
|
2556
|
-
async function loadRoom(room, local, progress, live) {
|
|
2557
|
-
if (!live() || authorization === undefined)
|
|
2558
|
-
return;
|
|
2559
|
-
if (replicas.has(room.locator) && revoked(room.locator)) {
|
|
2560
|
-
await reload(room, local, progress, live);
|
|
2561
|
-
return;
|
|
2562
|
-
}
|
|
2563
|
-
const withheld = await advance(room, local, authorization, live);
|
|
2564
|
-
if (withheld !== null) {
|
|
2565
|
-
await withhold(withheld, room, local, progress);
|
|
2566
|
-
return;
|
|
2567
|
-
}
|
|
2568
|
-
await caughtUp(room, local, progress, live);
|
|
2569
|
-
}
|
|
2570
|
-
async function reload(room, local, progress, live) {
|
|
2571
|
-
try {
|
|
2572
|
-
if (!live() || authorization === undefined)
|
|
2573
|
-
return;
|
|
2574
|
-
const bootstrapped = await pullBootstrap(authorization, room, local, live);
|
|
2575
|
-
if (!live())
|
|
2576
|
-
return;
|
|
2577
|
-
if (bootstrapped.kind !== 'staged') {
|
|
2578
|
-
await withhold(bootstrapped, room, local, progress);
|
|
2579
|
-
return;
|
|
2580
|
-
}
|
|
2581
|
-
await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored }, live);
|
|
2582
|
-
await caughtUp(room, local, progress, live);
|
|
2583
|
-
}
|
|
2584
|
-
finally {
|
|
2585
|
-
caughtUpTo(room.locator);
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
async function discoverRooms(local, progress, attempt, live) {
|
|
2589
|
-
if (closed || authorization === undefined) {
|
|
2590
|
-
notifyReady();
|
|
2591
|
-
return;
|
|
2592
|
-
}
|
|
2593
|
-
moveTo(transferring(status));
|
|
2594
|
-
discoveries += 1;
|
|
2595
|
-
const started = discoveries;
|
|
2596
|
-
let result;
|
|
2597
|
-
try {
|
|
2598
|
-
result = await options.adapter.discover(authorization);
|
|
2599
|
-
}
|
|
2600
|
-
catch {
|
|
2601
|
-
result = { kind: 'transport-uncertainty' };
|
|
2602
|
-
}
|
|
2603
|
-
if (!live()) {
|
|
2604
|
-
progress.complete = false;
|
|
2605
|
-
notifyReady();
|
|
2606
|
-
return;
|
|
2607
|
-
}
|
|
2608
|
-
journal.record(`connection discover ${result.kind}`);
|
|
2609
|
-
if (result.kind === 'transport-uncertainty') {
|
|
2610
|
-
for (const room of rooms.values()) {
|
|
2611
|
-
await retainModel(local, room, 'collection', cached(modelOf(room.locator)));
|
|
2612
|
-
if (room.typedSelection !== null) {
|
|
2613
|
-
await retainModel(local, room, 'typed-selection', cached(modelOf(room.locator, 'typed-selection')));
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
progress.reachable = false;
|
|
2617
|
-
progress.complete = false;
|
|
2618
|
-
notifyReady();
|
|
2619
|
-
return;
|
|
2620
|
-
}
|
|
2621
|
-
if (result.kind === 'contract-unavailable') {
|
|
2622
|
-
journal.record(`connection contract unavailable ${result.code}`);
|
|
2623
|
-
await refuse('missing-required-guarantee', {
|
|
2624
|
-
code: result.code,
|
|
2625
|
-
fields: result.fields,
|
|
2626
|
-
retryable: result.retryable,
|
|
2627
|
-
});
|
|
2628
|
-
notifyReady();
|
|
2629
|
-
return;
|
|
2630
|
-
}
|
|
2631
|
-
if (result.kind === 'revoked-authorization') {
|
|
2632
|
-
await refuse('revoked-authorization');
|
|
2633
|
-
notifyReady();
|
|
2634
|
-
return;
|
|
2635
|
-
}
|
|
2636
|
-
for (const room of result.rooms) {
|
|
2637
|
-
if (room.operations === undefined ||
|
|
2638
|
-
!['read', 'sync', 'write', 'delete'].every((operation) => Array.isArray(room.operations[operation]) &&
|
|
2639
|
-
room.operations[operation].every((scope) => typeof scope === 'string')) ||
|
|
2640
|
-
room.replica === undefined ||
|
|
2641
|
-
typeof room.replica.selection !== 'string' ||
|
|
2642
|
-
room.replica.selection.length === 0 ||
|
|
2643
|
-
!['admitted', 'unavailable'].includes(room.replica.admission)) {
|
|
2644
|
-
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
2645
|
-
await refuse('missing-required-guarantee');
|
|
2646
|
-
notifyReady();
|
|
2647
|
-
return;
|
|
2648
|
-
}
|
|
2649
|
-
if (!declaredSelection(room)) {
|
|
2650
|
-
journal.record(`invalid typed selection declaration ${room.locator}`);
|
|
2651
|
-
await refuse('missing-required-guarantee');
|
|
2652
|
-
notifyReady();
|
|
2653
|
-
return;
|
|
2654
|
-
}
|
|
2655
|
-
}
|
|
2656
|
-
if (refusal !== null) {
|
|
2657
|
-
refusal = null;
|
|
2658
|
-
emitRefusal();
|
|
2659
|
-
}
|
|
2660
|
-
const previousCapabilities = capabilities;
|
|
2661
|
-
capabilities = result.capabilities;
|
|
2662
|
-
outcomeRetention = result.retention.outcome;
|
|
2663
|
-
notifyReady();
|
|
2664
|
-
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits) ||
|
|
2665
|
-
JSON.stringify(result.capabilities) !== JSON.stringify(previousCapabilities)) {
|
|
2666
|
-
limits = result.providerLimits;
|
|
2667
|
-
await local.declare(limits, capabilities);
|
|
2668
|
-
}
|
|
2669
|
-
const signedIn = result.member ?? null;
|
|
2670
|
-
if (JSON.stringify(signedIn) !== JSON.stringify(member)) {
|
|
2671
|
-
await local.keepMember(signedIn);
|
|
2672
|
-
member = signedIn;
|
|
2673
|
-
emitMember();
|
|
2674
|
-
}
|
|
2675
|
-
await synchronize(await applyDiscovery(result.rooms, started), local, progress, attempt, live);
|
|
2676
|
-
}
|
|
2677
|
-
async function ensureAuthorization() {
|
|
2678
|
-
if (authorization !== undefined)
|
|
2679
|
-
return;
|
|
2680
|
-
try {
|
|
2681
|
-
authorization = await options.adapter.authorize();
|
|
2682
|
-
journal.record('connection authorize');
|
|
2683
|
-
}
|
|
2684
|
-
catch (error) {
|
|
2685
|
-
journal.record('connection authorize transport-uncertainty');
|
|
2686
|
-
throw error;
|
|
2687
|
-
}
|
|
2688
|
-
}
|
|
2689
|
-
async function admittedRead(work) {
|
|
2690
|
-
if (roomReads.generation !== generation) {
|
|
2691
|
-
roomReads = { generation, free: ROOM_READS, waiting: [] };
|
|
2692
|
-
}
|
|
2693
|
-
const reads = roomReads;
|
|
2694
|
-
while (reads.free === 0)
|
|
2695
|
-
await new Promise((admit) => reads.waiting.push(admit));
|
|
2696
|
-
reads.free -= 1;
|
|
2697
|
-
try {
|
|
2698
|
-
await work();
|
|
2699
|
-
}
|
|
2700
|
-
finally {
|
|
2701
|
-
reads.free += 1;
|
|
2702
|
-
reads.waiting.shift()?.();
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
function roomTask(locator, work) {
|
|
2706
|
-
return task(roomLane(locator), (live) => admittedRead(() => work(live)));
|
|
2707
|
-
}
|
|
2708
|
-
function task(lane, work) {
|
|
2709
|
-
if (stopping)
|
|
2710
|
-
return Promise.resolve();
|
|
2711
|
-
const held = lanes.get(lane);
|
|
2712
|
-
const current = held?.generation === generation ? held : { generation, chain: Promise.resolve() };
|
|
2713
|
-
const turn = current.generation;
|
|
2714
|
-
const live = () => turn === generation && continuing();
|
|
2715
|
-
const run = current.chain.then(() => work(live)).catch(() => undefined);
|
|
2716
|
-
current.chain = run;
|
|
2717
|
-
lanes.set(lane, current);
|
|
2718
|
-
working.add(run);
|
|
2719
|
-
void run.finally(() => working.delete(run));
|
|
2720
|
-
return run;
|
|
2721
|
-
}
|
|
2722
|
-
function sync(attempt) {
|
|
2723
|
-
return task(OPENING_LANE, async (live) => {
|
|
2724
|
-
if (!live()) {
|
|
2725
|
-
notifyReady();
|
|
2726
|
-
return;
|
|
2727
|
-
}
|
|
2728
|
-
const progress = { complete: true, reachable: true };
|
|
2729
|
-
const gate = discover();
|
|
2730
|
-
if (attempt === 'opened')
|
|
2731
|
-
opened = generation;
|
|
2732
|
-
underway = attempt === 'opened' ? { generation, progress } : null;
|
|
2733
|
-
if (attempt === 'opened')
|
|
2734
|
-
streamed = progress;
|
|
2735
|
-
complete = false;
|
|
2736
|
-
recorded = false;
|
|
2737
|
-
moveTo(transferring(status));
|
|
2738
|
-
handed = false;
|
|
2739
|
-
try {
|
|
2740
|
-
const local = await openLocal();
|
|
2741
|
-
if (!live())
|
|
2742
|
-
return;
|
|
2743
|
-
await ensureAuthorization();
|
|
2744
|
-
if (!live())
|
|
2745
|
-
return;
|
|
2746
|
-
await discoverRooms(local, progress, attempt, live);
|
|
2747
|
-
}
|
|
2748
|
-
catch {
|
|
2749
|
-
progress.complete = false;
|
|
2750
|
-
if (attempt === 'opened' && !handed)
|
|
2751
|
-
progress.reachable = false;
|
|
2752
|
-
}
|
|
2753
|
-
finally {
|
|
2754
|
-
gate.release();
|
|
2755
|
-
notifyReady();
|
|
2756
|
-
}
|
|
2757
|
-
if (!live())
|
|
2758
|
-
return;
|
|
2759
|
-
if (attempt !== 'opened') {
|
|
2760
|
-
await finishOpening(progress, live);
|
|
2761
|
-
return;
|
|
2762
|
-
}
|
|
2763
|
-
if (handed || closed || progress.reachable)
|
|
2764
|
-
return;
|
|
2765
|
-
underway = null;
|
|
2766
|
-
opened = null;
|
|
2767
|
-
journal.record('connection reopen');
|
|
2768
|
-
await rest(progress, live);
|
|
2769
|
-
options.adapter.roomStream?.reopen();
|
|
2770
|
-
});
|
|
2771
|
-
}
|
|
2772
|
-
function rediscover() {
|
|
2773
|
-
return task(OPENING_LANE, async (live) => {
|
|
2774
|
-
if (!live() || !socketOpen)
|
|
2775
|
-
return;
|
|
2776
|
-
const progress = { complete: true, reachable: true };
|
|
2777
|
-
const gate = discover();
|
|
2778
|
-
moveTo(transferring(status));
|
|
2779
|
-
try {
|
|
2780
|
-
const local = await openLocal();
|
|
2781
|
-
if (!live())
|
|
2782
|
-
return;
|
|
2783
|
-
await ensureAuthorization();
|
|
2784
|
-
if (!live())
|
|
2785
|
-
return;
|
|
2786
|
-
await discoverRooms(local, progress, 'authorized', live);
|
|
2787
|
-
}
|
|
2788
|
-
catch {
|
|
2789
|
-
progress.complete = false;
|
|
2790
|
-
}
|
|
2791
|
-
finally {
|
|
2792
|
-
gate.release();
|
|
2793
|
-
notifyReady();
|
|
2794
|
-
}
|
|
2795
|
-
if (!live())
|
|
2796
|
-
return;
|
|
2797
|
-
if (!progress.complete)
|
|
2798
|
-
complete = false;
|
|
2799
|
-
await rest(progress, live);
|
|
2800
|
-
});
|
|
2801
|
-
}
|
|
2802
|
-
function closedSocket(revoked, lost) {
|
|
2803
|
-
const opened = socketOpen;
|
|
2804
|
-
socketOpen = false;
|
|
2805
|
-
if (lost) {
|
|
2806
|
-
reads.lost();
|
|
2807
|
-
conversations.lost();
|
|
2808
|
-
}
|
|
2809
|
-
if (revoked) {
|
|
2810
|
-
void refuse('revoked-authorization');
|
|
2811
|
-
return;
|
|
2812
|
-
}
|
|
2813
|
-
complete = false;
|
|
2814
|
-
if (streamed !== null)
|
|
2815
|
-
streamed.complete = false;
|
|
2816
|
-
moveTo({
|
|
2817
|
-
...status,
|
|
2818
|
-
state: status.state === 'synced' ? 'syncing' : status.state,
|
|
2819
|
-
providerUnreachable: status.providerUnreachable || !opened,
|
|
2820
|
-
});
|
|
2821
|
-
}
|
|
2822
|
-
function applyOpening(expired) {
|
|
2823
|
-
const progress = underway?.generation === generation ? underway.progress : null;
|
|
2824
|
-
const cycle = progress ?? { complete: true, reachable: true };
|
|
2825
|
-
const replacing = reloadNow((room) => expired.includes(room.locator), cycle);
|
|
2826
|
-
if (opened === generation)
|
|
2827
|
-
opened = null;
|
|
2828
|
-
return task(OPENING_LANE, async (live) => {
|
|
2829
|
-
underway = null;
|
|
2830
|
-
if (!live())
|
|
2831
|
-
return;
|
|
2832
|
-
if (progress === null) {
|
|
2833
|
-
await replacing;
|
|
2834
|
-
await rest(cycle, live);
|
|
2835
|
-
return;
|
|
2836
|
-
}
|
|
2837
|
-
await roomsCarried(expired);
|
|
2838
|
-
if (!live())
|
|
2839
|
-
return;
|
|
2840
|
-
const replaced = new Set(owed);
|
|
2841
|
-
owed.clear();
|
|
2842
|
-
try {
|
|
2843
|
-
const local = await openLocal();
|
|
2844
|
-
const held = [...discovered.values()].filter((room) => replicas.has(room.locator));
|
|
2845
|
-
const reloading = held.filter((room) => !expired.includes(room.locator) &&
|
|
2846
|
-
(replaced.has(room.locator) || revoked(room.locator)));
|
|
2847
|
-
for (const room of reloading)
|
|
2848
|
-
catching.add(room.locator);
|
|
2849
|
-
readSide.recover();
|
|
2850
|
-
const reloads = Promise.allSettled(reloading.map((room) => roomTask(room.locator, (current) => reload(room, local, progress, current))));
|
|
2851
|
-
const reloaded = [...(await reloads), ...(await replacing)];
|
|
2852
|
-
if (progress.reachable) {
|
|
2853
|
-
for (const room of held) {
|
|
2854
|
-
if (!expired.includes(room.locator) && !reloading.includes(room)) {
|
|
2855
|
-
await confirmModels(room, local);
|
|
2856
|
-
}
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
const failed = reloaded.find((outcome) => outcome.status === 'rejected');
|
|
2860
|
-
if (failed !== undefined)
|
|
2861
|
-
throw failed.reason;
|
|
2862
|
-
}
|
|
2863
|
-
catch {
|
|
2864
|
-
progress.complete = false;
|
|
2865
|
-
progress.reachable = false;
|
|
2866
|
-
}
|
|
2867
|
-
if (live())
|
|
2868
|
-
await finishOpening(progress, live);
|
|
2869
|
-
});
|
|
2870
|
-
}
|
|
2871
|
-
async function reloadNow(replaced, progress) {
|
|
2872
|
-
try {
|
|
2873
|
-
const local = await openLocal();
|
|
2874
|
-
return await Promise.allSettled([...discovered.values()]
|
|
2875
|
-
.filter((room) => replicas.has(room.locator) && replaced(room))
|
|
2876
|
-
.map((room) => roomTask(room.locator, (live) => reload(room, local, progress, live))));
|
|
2877
|
-
}
|
|
2878
|
-
catch {
|
|
2879
|
-
progress.reachable = false;
|
|
2880
|
-
return [];
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
async function confirmModels(room, local) {
|
|
2884
|
-
for (const attached of ['collection', 'typed-selection']) {
|
|
2885
|
-
const model = modelOf(room.locator, attached);
|
|
2886
|
-
if (model.status === 'cached') {
|
|
2887
|
-
await retainModel(local, room, attached, { ...model, status: 'current' });
|
|
2888
|
-
}
|
|
2889
|
-
}
|
|
2890
|
-
}
|
|
2891
|
-
function submitLater(locator) {
|
|
2892
|
-
if (localSaves)
|
|
2893
|
-
return;
|
|
2894
|
-
void recovery.attempt(`submit ${locator}`, (signal) => submitPass(locator, signal));
|
|
2895
|
-
}
|
|
2896
|
-
async function submitPass(locator, signal) {
|
|
2897
|
-
const room = discovered.get(locator);
|
|
2898
|
-
if (closed || stopping || discoveryPaused || room === undefined)
|
|
2899
|
-
return 'done';
|
|
2900
|
-
const progress = { complete: true, reachable: true };
|
|
2901
|
-
let next;
|
|
2902
|
-
try {
|
|
2903
|
-
await ensureAuthorization();
|
|
2904
|
-
if (authorization === undefined)
|
|
2905
|
-
return 'done';
|
|
2906
|
-
next = await submitRoom(room, await openLocal(), authorization, progress, signal);
|
|
2907
|
-
}
|
|
2908
|
-
catch {
|
|
2909
|
-
progress.reachable = false;
|
|
2910
|
-
next = 'again';
|
|
2911
|
-
}
|
|
2912
|
-
if (signal.aborted)
|
|
2913
|
-
return 'done';
|
|
2914
|
-
await rest(progress);
|
|
2915
|
-
return next === 'again' && !socketOpen ? 'done' : next;
|
|
2916
|
-
}
|
|
2917
|
-
function repairLater(locator, when) {
|
|
2918
|
-
if (!socketOpen || repairing.has(locator))
|
|
2919
|
-
return;
|
|
2920
|
-
void recovery.attempt(`repair ${locator}`, (signal) => repairPass(locator, signal), when);
|
|
2921
|
-
}
|
|
2922
|
-
async function repairPass(locator, signal) {
|
|
2923
|
-
const room = discovered.get(locator);
|
|
2924
|
-
if (!socketOpen || closed || stopping || discoveryPaused || !stale.has(locator))
|
|
2925
|
-
return 'done';
|
|
2926
|
-
if (room === undefined || room.replica.admission !== 'admitted')
|
|
2927
|
-
return 'done';
|
|
2928
|
-
const progress = { complete: true, reachable: true };
|
|
2929
|
-
repairing.add(locator);
|
|
2930
|
-
try {
|
|
2931
|
-
const local = await openLocal();
|
|
2932
|
-
await roomTask(locator, async (live) => {
|
|
2933
|
-
const current = () => live() && !signal.aborted;
|
|
2934
|
-
if (!current() || authorization === undefined)
|
|
2935
|
-
return;
|
|
2936
|
-
const withheld = await Promise.race([
|
|
2937
|
-
advance(room, local, authorization, current),
|
|
2938
|
-
abandonedOn(signal),
|
|
2939
|
-
]);
|
|
2940
|
-
if (withheld !== null && current())
|
|
2941
|
-
await withhold(withheld, room, local, progress);
|
|
2942
|
-
});
|
|
2943
|
-
}
|
|
2944
|
-
catch {
|
|
2945
|
-
stale.add(locator);
|
|
2946
|
-
progress.reachable = false;
|
|
2947
|
-
}
|
|
2948
|
-
finally {
|
|
2949
|
-
repairing.delete(locator);
|
|
2950
|
-
}
|
|
2951
|
-
if (signal.aborted)
|
|
2952
|
-
return 'done';
|
|
2953
|
-
await rest(progress);
|
|
2954
|
-
if (!stale.has(locator)) {
|
|
2955
|
-
if ((replicas.get(locator)?.queue.length ?? 0) > 0)
|
|
2956
|
-
submitLater(locator);
|
|
2957
|
-
return 'done';
|
|
2958
|
-
}
|
|
2959
|
-
if (!repairable.has(locator))
|
|
2960
|
-
return 'done';
|
|
2961
|
-
const reset = repairable.get(locator);
|
|
2962
|
-
return reset === undefined ? 'again' : { at: Date.parse(reset) };
|
|
2963
|
-
}
|
|
2964
|
-
function resendLater() {
|
|
2965
|
-
if (!socketOpen)
|
|
2966
|
-
return;
|
|
2967
|
-
void recovery.attempt('resend', resendPass, { again: true });
|
|
2968
|
-
}
|
|
2969
|
-
async function resendPass(signal) {
|
|
2970
|
-
if (!socketOpen || closed || stopping || discoveryPaused)
|
|
2971
|
-
return 'done';
|
|
2972
|
-
const reading = { complete: true, reachable: true };
|
|
2973
|
-
try {
|
|
2974
|
-
await conversations.resend(reading);
|
|
2975
|
-
}
|
|
2976
|
-
catch {
|
|
2977
|
-
reading.reachable = false;
|
|
2978
|
-
}
|
|
2979
|
-
if (signal.aborted)
|
|
2980
|
-
return 'done';
|
|
2981
|
-
return reading.reachable && reading.complete ? 'done' : 'again';
|
|
2982
|
-
}
|
|
2983
|
-
function submitAll() {
|
|
2984
|
-
for (const [locator, replica] of replicas) {
|
|
2985
|
-
if (replica.queue.length > 0)
|
|
2986
|
-
submitLater(locator);
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
async function finishOpening(progress, live) {
|
|
2990
|
-
try {
|
|
2991
|
-
if (!live()) {
|
|
2992
|
-
progress.complete = false;
|
|
2993
|
-
return;
|
|
2994
|
-
}
|
|
2995
|
-
reads.applied();
|
|
2996
|
-
submitAll();
|
|
2997
|
-
for (const locator of stale)
|
|
2998
|
-
if (repairable.has(locator))
|
|
2999
|
-
repairLater(locator, {});
|
|
3000
|
-
await conversations.reconcile();
|
|
3001
|
-
if (status.providerUnreachable)
|
|
3002
|
-
conversations.reconnected();
|
|
3003
|
-
const reading = { complete: true, reachable: true };
|
|
3004
|
-
if (demand.conversations)
|
|
3005
|
-
await conversations.catchUp(reading);
|
|
3006
|
-
if (progress.reachable && reading.reachable)
|
|
3007
|
-
await conversations.resend(reading);
|
|
3008
|
-
if (progress.reachable)
|
|
3009
|
-
void recovery.attempt('calls', resendCalls);
|
|
3010
|
-
if (!reading.reachable) {
|
|
3011
|
-
progress.reachable = false;
|
|
3012
|
-
resendLater();
|
|
3013
|
-
}
|
|
3014
|
-
}
|
|
3015
|
-
catch {
|
|
3016
|
-
progress.complete = false;
|
|
3017
|
-
}
|
|
3018
|
-
finally {
|
|
3019
|
-
if (live()) {
|
|
3020
|
-
complete = progress.complete;
|
|
3021
|
-
await rest(progress, live);
|
|
3022
|
-
}
|
|
3023
|
-
}
|
|
3024
|
-
}
|
|
3025
|
-
async function rest(progress, live = () => !closed) {
|
|
3026
|
-
if (!live())
|
|
3027
|
-
return;
|
|
3028
|
-
let lastCompleted = status.lastCompleted;
|
|
3029
|
-
let synced = complete && stale.size === 0 && !pending();
|
|
3030
|
-
if (synced && !recorded) {
|
|
3031
|
-
const completedAt = new Date().toISOString();
|
|
3032
|
-
try {
|
|
3033
|
-
await (await openLocal()).complete(completedAt);
|
|
3034
|
-
if (!live())
|
|
3035
|
-
return;
|
|
3036
|
-
recorded = true;
|
|
3037
|
-
lastCompleted = completedAt;
|
|
3038
|
-
}
|
|
3039
|
-
catch {
|
|
3040
|
-
synced = false;
|
|
3041
|
-
}
|
|
3042
|
-
}
|
|
3043
|
-
moveTo({
|
|
3044
|
-
state: pending() ? 'pending' : synced && complete ? 'synced' : 'syncing',
|
|
3045
|
-
lastCompleted,
|
|
3046
|
-
providerUnreachable: !progress.reachable,
|
|
3047
|
-
...attention(),
|
|
3048
|
-
});
|
|
3049
|
-
}
|
|
3050
|
-
function discover() {
|
|
3051
|
-
let release = () => { };
|
|
3052
|
-
const done = new Promise((resolve) => {
|
|
3053
|
-
release = resolve;
|
|
3054
|
-
});
|
|
3055
|
-
discovering = { generation, done, release };
|
|
3056
|
-
return discovering;
|
|
3057
|
-
}
|
|
3058
|
-
function applyRoom(locator, roomVersion) {
|
|
3059
|
-
announced.set(locator, roomVersion);
|
|
3060
|
-
return task(roomLane(locator), async (live) => {
|
|
3061
|
-
if (discovering?.generation === generation)
|
|
3062
|
-
await discovering.done;
|
|
3063
|
-
const saving = answering.get(locator);
|
|
3064
|
-
if (saving?.generation === generation)
|
|
3065
|
-
await saving.answered;
|
|
3066
|
-
if (!live() || authorization === undefined)
|
|
3067
|
-
return;
|
|
3068
|
-
const room = discovered.get(locator);
|
|
3069
|
-
const held = replicas.get(locator);
|
|
3070
|
-
if (room === undefined || room.replica.admission !== 'admitted' || !acquires(room))
|
|
3071
|
-
return;
|
|
3072
|
-
if (held !== undefined &&
|
|
3073
|
-
(held.confirmed.record.replica.selection !== room.replica.selection ||
|
|
3074
|
-
options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion))) {
|
|
3075
|
-
return;
|
|
3076
|
-
}
|
|
3077
|
-
const during = underway?.generation === generation ? underway.progress : null;
|
|
3078
|
-
const progress = during ?? { complete: true, reachable: true };
|
|
3079
|
-
if (during === null)
|
|
3080
|
-
moveTo(transferring(status));
|
|
3081
|
-
try {
|
|
3082
|
-
const local = await openLocal();
|
|
3083
|
-
await admittedRead(async () => {
|
|
3084
|
-
if (!live() || authorization === undefined)
|
|
3085
|
-
return;
|
|
3086
|
-
const withheld = await advance(room, local, authorization, live);
|
|
3087
|
-
if (withheld !== null)
|
|
3088
|
-
await withhold(withheld, room, local, progress);
|
|
3089
|
-
});
|
|
3090
|
-
}
|
|
3091
|
-
catch {
|
|
3092
|
-
stale.add(locator);
|
|
3093
|
-
}
|
|
3094
|
-
finally {
|
|
3095
|
-
if (during === null)
|
|
3096
|
-
await rest(progress, live);
|
|
3097
|
-
}
|
|
3098
|
-
});
|
|
3099
|
-
}
|
|
3100
|
-
function roomsCarried(replacing) {
|
|
3101
|
-
const replaced = new Set(replacing.map(roomLane));
|
|
3102
|
-
return Promise.all([...lanes]
|
|
3103
|
-
.filter(([lane, held]) => lane !== OPENING_LANE && !replaced.has(lane) && held.generation === generation)
|
|
3104
|
-
.map(([, held]) => held.chain));
|
|
3105
|
-
}
|
|
3106
|
-
const receipts = {
|
|
3107
|
-
heads: async () => (await openLocal()).receivedHeads(),
|
|
3108
|
-
parts: async (key, count) => (await openLocal()).receivedParts(key, count),
|
|
3109
|
-
keep: async (key, head, part) => (await openLocal()).keepReceived(key, head, part),
|
|
3110
|
-
drop: async (keys) => (await openLocal()).dropReceived(keys),
|
|
3111
|
-
};
|
|
3112
|
-
function observeStream() {
|
|
3113
|
-
const stream = options.adapter.roomStream;
|
|
3114
|
-
if (stream === undefined || withdrawStream !== undefined)
|
|
3115
|
-
return;
|
|
3116
|
-
withdrawStream = stream.observe((event) => {
|
|
3117
|
-
if (closed || stopping)
|
|
3118
|
-
return;
|
|
3119
|
-
if (event.kind === 'opened') {
|
|
3120
|
-
socketOpen = true;
|
|
3121
|
-
generation += 1;
|
|
3122
|
-
reads.opened();
|
|
3123
|
-
conversations.reopened();
|
|
3124
|
-
recovery.resume();
|
|
3125
|
-
void sync('opened');
|
|
3126
|
-
}
|
|
3127
|
-
else if (event.kind === 'closed')
|
|
3128
|
-
closedSocket(event.revoked, event.lost);
|
|
3129
|
-
else if (event.kind === 'opening')
|
|
3130
|
-
void applyOpening(event.expired);
|
|
3131
|
-
else if (event.kind === 'authority-changed')
|
|
3132
|
-
void rediscover();
|
|
3133
|
-
else
|
|
3134
|
-
void applyRoom(event.locator, event.roomVersion);
|
|
3135
|
-
}, receipts);
|
|
3136
|
-
}
|
|
3137
|
-
function writesHeld() {
|
|
3138
|
-
return serviceMode === 'write-halted' || haltedRefusal;
|
|
3139
|
-
}
|
|
3140
|
-
function signalled(mode) {
|
|
3141
|
-
if (closed)
|
|
3142
|
-
return;
|
|
3143
|
-
if (mode === 'open')
|
|
3144
|
-
openSignals += 1;
|
|
3145
|
-
const releasing = mode === 'open' && (haltedRefusal || serviceMode === 'write-halted');
|
|
3146
|
-
if (mode === 'open')
|
|
3147
|
-
haltedRefusal = false;
|
|
3148
|
-
if (mode !== serviceMode) {
|
|
3149
|
-
serviceMode = mode;
|
|
3150
|
-
queueReadiness();
|
|
3151
|
-
journal.record(`service mode ${mode}`);
|
|
3152
|
-
for (const listener of serviceModeListeners)
|
|
3153
|
-
listener(mode);
|
|
3154
|
-
}
|
|
3155
|
-
if (releasing)
|
|
3156
|
-
submitAll();
|
|
3157
|
-
}
|
|
3158
|
-
function observeConversations() {
|
|
3159
|
-
const stream = options.adapter.conversations;
|
|
3160
|
-
if (stream === undefined || withdrawConversations !== undefined)
|
|
3161
|
-
return;
|
|
3162
|
-
withdrawConversations = stream.observe((event) => {
|
|
3163
|
-
if (event.kind === 'list' || event.kind === 'listed' || event.kind === 'unlisted')
|
|
3164
|
-
queueReadiness();
|
|
3165
|
-
if (conversations.receive(event)) {
|
|
3166
|
-
void ready.then(scheduleCurrency);
|
|
3167
|
-
return;
|
|
3168
|
-
}
|
|
3169
|
-
if (event.kind !== 'gap' || conversations.current())
|
|
3170
|
-
return;
|
|
3171
|
-
show();
|
|
3172
|
-
scheduleCurrency();
|
|
3173
|
-
});
|
|
3174
|
-
}
|
|
3175
|
-
function scheduleCurrency() {
|
|
3176
|
-
if (currencyQueued === generation || closed || stopping)
|
|
3177
|
-
return;
|
|
3178
|
-
currencyQueued = generation;
|
|
3179
|
-
void task(CONVERSATION_LANE, runCurrency);
|
|
3180
|
-
}
|
|
3181
|
-
async function runCurrency(live) {
|
|
3182
|
-
if (currencyQueued === generation)
|
|
3183
|
-
currencyQueued = null;
|
|
3184
|
-
if (!live())
|
|
3185
|
-
return;
|
|
3186
|
-
await conversations.reconcile();
|
|
3187
|
-
if (conversations.current() || !demand.conversations) {
|
|
3188
|
-
show();
|
|
3189
|
-
return;
|
|
3190
|
-
}
|
|
3191
|
-
const progress = { complete: false, reachable: !status.providerUnreachable };
|
|
3192
|
-
moveTo(transferring(status));
|
|
3193
|
-
try {
|
|
3194
|
-
await conversations.catchUp(progress);
|
|
3195
|
-
}
|
|
3196
|
-
catch {
|
|
3197
|
-
progress.reachable = false;
|
|
3198
|
-
}
|
|
3199
|
-
await rest(progress, live);
|
|
3200
|
-
}
|
|
3201
|
-
function close() {
|
|
3202
|
-
if (closed)
|
|
3203
|
-
return;
|
|
3204
|
-
closed = true;
|
|
3205
|
-
if (narrowing !== null)
|
|
3206
|
-
clearTimeout(narrowing);
|
|
3207
|
-
if (rechecking !== null)
|
|
3208
|
-
clearInterval(rechecking);
|
|
3209
|
-
readinessWatches.clear();
|
|
3210
|
-
withdrawStream?.();
|
|
3211
|
-
withdrawStream = undefined;
|
|
3212
|
-
withdrawConversations?.();
|
|
3213
|
-
withdrawConversations = undefined;
|
|
3214
|
-
withdrawServiceMode?.();
|
|
3215
|
-
conversations.close();
|
|
3216
|
-
void recovery.retire();
|
|
3217
|
-
store?.close();
|
|
3218
|
-
void opening?.then((opened) => {
|
|
3219
|
-
opened.close();
|
|
3220
|
-
}, () => { });
|
|
3221
|
-
roomListeners.clear();
|
|
3222
|
-
refusalListeners.clear();
|
|
3223
|
-
storageLossListeners.clear();
|
|
3224
|
-
statusListeners.clear();
|
|
3225
|
-
serviceModeListeners.clear();
|
|
3226
|
-
memberListeners.clear();
|
|
3227
|
-
readSide.close();
|
|
3228
|
-
queueSubscriptions.clear();
|
|
3229
|
-
roomDraftListeners.clear();
|
|
3230
|
-
presentationListeners.clear();
|
|
3231
|
-
}
|
|
3232
|
-
async function stop() {
|
|
3233
|
-
stopping = true;
|
|
3234
|
-
await recovery.retire();
|
|
3235
|
-
await Promise.all(working);
|
|
3236
|
-
await mutations;
|
|
3237
|
-
close();
|
|
3238
|
-
}
|
|
3239
|
-
const withdrawServiceMode = options.adapter.serviceMode?.observe(signalled);
|
|
3240
|
-
observeConversations();
|
|
3241
|
-
options.signal?.addEventListener('abort', close, { once: true });
|
|
3242
|
-
if (options.signal?.aborted)
|
|
3243
|
-
close();
|
|
3244
|
-
observeStream();
|
|
3245
|
-
void openLocal().then(() => {
|
|
3246
|
-
if (capabilities.length > 0)
|
|
3247
|
-
notifyReady();
|
|
3248
|
-
}, () => {
|
|
3249
|
-
notifyReady();
|
|
3250
|
-
});
|
|
3251
|
-
if (options.adapter.roomStream === undefined)
|
|
3252
|
-
void sync('unstreamed');
|
|
3253
|
-
function planned(input, reach) {
|
|
3254
|
-
return planOf(input, roomFacts(reach), planReach(reach));
|
|
3255
|
-
}
|
|
3256
|
-
function watchPlan(plan, deliver) {
|
|
3257
|
-
let seeded = false;
|
|
3258
|
-
let pending = true;
|
|
3259
|
-
const shown = () => {
|
|
3260
|
-
const seed = seeds.get(plan.key);
|
|
3261
|
-
if (seed === undefined || seeded || !pending)
|
|
3262
|
-
return;
|
|
3263
|
-
seeded = true;
|
|
3264
|
-
deliver(seededResult(seed));
|
|
3265
|
-
};
|
|
3266
|
-
const watchers = seedWatchers.get(plan.key) ?? new Set();
|
|
3267
|
-
seedWatchers.set(plan.key, watchers);
|
|
3268
|
-
watchers.add(shown);
|
|
3269
|
-
const stop = readSide.watch(plan, identityOf(plan), () => {
|
|
3270
|
-
const cover = covered(plan);
|
|
3271
|
-
awaitPreparation(cover);
|
|
3272
|
-
return seedBehind(plan, cover);
|
|
3273
|
-
}, (message) => {
|
|
3274
|
-
const answered = message;
|
|
3275
|
-
pending = whole(answered) && 'pending' in answered.result;
|
|
3276
|
-
if (pending && seeds.has(plan.key)) {
|
|
3277
|
-
shown();
|
|
3278
|
-
return;
|
|
3279
|
-
}
|
|
3280
|
-
seeded = false;
|
|
3281
|
-
deliver(message);
|
|
3282
|
-
});
|
|
3283
|
-
return () => {
|
|
3284
|
-
watchers.delete(shown);
|
|
3285
|
-
if (watchers.size === 0)
|
|
3286
|
-
seedWatchers.delete(plan.key);
|
|
3287
|
-
stop();
|
|
3288
|
-
};
|
|
3289
|
-
}
|
|
3290
|
-
async function searched(input, reach) {
|
|
3291
|
-
const plan = searchPlanOf(input);
|
|
3292
|
-
const filter = filterOf(plan.documents ?? {}, roomFacts(reach), planReach(reach));
|
|
3293
|
-
for (;;) {
|
|
3294
|
-
const cover = covered(filter);
|
|
3295
|
-
awaitPreparation(cover);
|
|
3296
|
-
const pending = 'pending' in cover ? [...cover.pending] : [];
|
|
3297
|
-
const awaited = new Set('pending' in cover ? cover.pending.map(({ room }) => room) : []);
|
|
3298
|
-
const held = covered({
|
|
3299
|
-
...filter,
|
|
3300
|
-
rooms: reachedRooms(filter).filter((room) => !awaited.has(room)),
|
|
3301
|
-
});
|
|
3302
|
-
const covering = 'pending' in held ? [] : held.coverage.rooms;
|
|
3303
|
-
const unindexed = covering
|
|
3304
|
-
.filter(({ room }) => !searchIndex.rooms().has(room))
|
|
3305
|
-
.map(({ room }) => room);
|
|
3306
|
-
for (const room of unindexed)
|
|
3307
|
-
pending.push({ room, awaits: 'index' });
|
|
3308
|
-
const listed = conversations.searchable();
|
|
3309
|
-
const named = reach !== null && !reach.conversations
|
|
3310
|
-
? []
|
|
3311
|
-
: (plan.conversations ?? listed).filter((one) => listed.includes(one) && conversations.acquired(one));
|
|
3312
|
-
const waiting = named.filter((one) => !searchIndex.conversations().has(one));
|
|
3313
|
-
for (const conversation of waiting)
|
|
3314
|
-
pending.push({ conversation, awaits: 'index' });
|
|
3315
|
-
if (unindexed.length > 0 || waiting.length > 0)
|
|
3316
|
-
index(unindexed, waiting);
|
|
3317
|
-
const complete = pending.length === 0 && !('pending' in cover);
|
|
3318
|
-
const answering = complete
|
|
3319
|
-
? filter
|
|
3320
|
-
: {
|
|
3321
|
-
...filter,
|
|
3322
|
-
rooms: covering
|
|
3323
|
-
.filter(({ room }) => searchIndex.rooms().has(room))
|
|
3324
|
-
.map(({ room }) => room),
|
|
3325
|
-
};
|
|
3326
|
-
const answered = covered(answering);
|
|
3327
|
-
if ('pending' in answered)
|
|
3328
|
-
continue;
|
|
3329
|
-
const searchedConversations = named.filter((one) => searchIndex.conversations().has(one));
|
|
3330
|
-
const seen = searchIndex.changes();
|
|
3331
|
-
await searchIndex.caughtUp();
|
|
3332
|
-
const selected = roomsAlone(answering) ? null : await rowsQuery(answering);
|
|
3333
|
-
if (selected !== null && 'pending' in selected)
|
|
3334
|
-
continue;
|
|
3335
|
-
if (searchIndex.changes() !== seen ||
|
|
3336
|
-
JSON.stringify(covered(answering)) !== JSON.stringify(answered))
|
|
3337
|
-
continue;
|
|
3338
|
-
const hits = searchIndex.search(plan, selected === null ? null : selected.documents, searchedConversations, answering.rooms === null ? null : new Set(answering.rooms), plan.collection
|
|
3339
|
-
? new Map(answered.coverage.rooms.map(({ room }) => [
|
|
3340
|
-
room,
|
|
3341
|
-
replicas.get(room)?.confirmed.record.typedSelection?.roots ?? [],
|
|
3342
|
-
]))
|
|
3343
|
-
: null);
|
|
3344
|
-
const coverage = {
|
|
3345
|
-
rooms: answered.coverage.rooms,
|
|
3346
|
-
conversations: searchedConversations,
|
|
3347
|
-
index: searchIndex.generation(),
|
|
3348
|
-
overlay: answered.coverage.overlay,
|
|
3349
|
-
};
|
|
3350
|
-
return complete
|
|
3351
|
-
? { ...hits, coverage }
|
|
3352
|
-
: { pending, documents: hits.documents, messages: hits.messages, coverage };
|
|
3353
|
-
}
|
|
3354
|
-
}
|
|
3355
|
-
const watch = (topic, input, deliver) => {
|
|
3356
|
-
if (topic === 'room-drafts') {
|
|
3357
|
-
const listener = { notify: deliver, ready: false };
|
|
3358
|
-
roomDraftListeners.add(listener);
|
|
3359
|
-
void exclusive(async () => {
|
|
3360
|
-
await openLocal();
|
|
3361
|
-
if (closed || !roomDraftListeners.has(listener))
|
|
3362
|
-
return;
|
|
3363
|
-
listener.ready = true;
|
|
3364
|
-
}).then(() => {
|
|
3365
|
-
if (closed || !listener.ready || !roomDraftListeners.has(listener))
|
|
3366
|
-
return;
|
|
3367
|
-
listener.notify(snapshotRoomDrafts());
|
|
3368
|
-
}, () => { });
|
|
3369
|
-
return () => {
|
|
3370
|
-
roomDraftListeners.delete(listener);
|
|
3371
|
-
};
|
|
3372
|
-
}
|
|
3373
|
-
if (topic === 'conversation') {
|
|
3374
|
-
return conversations.watch(input[0], input[1], deliver);
|
|
3375
|
-
}
|
|
3376
|
-
if (topic === 'conversations')
|
|
3377
|
-
return conversations.watchList(deliver);
|
|
3378
|
-
if (topic === 'agents')
|
|
3379
|
-
return conversations.watchAgents(deliver);
|
|
3380
|
-
if (topic === 'presentation') {
|
|
3381
|
-
const listener = {
|
|
3382
|
-
app: input[0],
|
|
3383
|
-
notify: deliver,
|
|
3384
|
-
ready: false,
|
|
3385
|
-
};
|
|
3386
|
-
presentationListeners.add(listener);
|
|
3387
|
-
void exclusive(async () => {
|
|
3388
|
-
await openLocal();
|
|
3389
|
-
}).then(() => {
|
|
3390
|
-
if (closed || !presentationListeners.has(listener))
|
|
3391
|
-
return;
|
|
3392
|
-
listener.ready = true;
|
|
3393
|
-
listener.notify(presentationOf(listener.app));
|
|
3394
|
-
}, () => { });
|
|
3395
|
-
return () => {
|
|
3396
|
-
presentationListeners.delete(listener);
|
|
3397
|
-
};
|
|
3398
|
-
}
|
|
3399
|
-
if (topic === 'readiness') {
|
|
3400
|
-
return watchReadiness({ target: '', code: 'stored', capacity: null, search: 'unused' }, null, deliver);
|
|
3401
|
-
}
|
|
3402
|
-
if (topic === 'rooms') {
|
|
3403
|
-
roomListeners.add(deliver);
|
|
3404
|
-
deliver(listed());
|
|
3405
|
-
return () => {
|
|
3406
|
-
roomListeners.delete(deliver);
|
|
3407
|
-
};
|
|
3408
|
-
}
|
|
3409
|
-
if (topic === 'connection-refusal') {
|
|
3410
|
-
refusalListeners.add(deliver);
|
|
3411
|
-
deliver(refusal);
|
|
3412
|
-
return () => {
|
|
3413
|
-
refusalListeners.delete(deliver);
|
|
3414
|
-
};
|
|
3415
|
-
}
|
|
3416
|
-
if (topic === 'storage-loss') {
|
|
3417
|
-
storageLossListeners.add(deliver);
|
|
3418
|
-
deliver(storageLoss);
|
|
3419
|
-
return () => {
|
|
3420
|
-
storageLossListeners.delete(deliver);
|
|
3421
|
-
};
|
|
3422
|
-
}
|
|
3423
|
-
if (topic === 'sync-status') {
|
|
3424
|
-
statusListeners.add(deliver);
|
|
3425
|
-
deliver(shown);
|
|
3426
|
-
return () => {
|
|
3427
|
-
statusListeners.delete(deliver);
|
|
3428
|
-
};
|
|
3429
|
-
}
|
|
3430
|
-
if (topic === 'service-mode') {
|
|
3431
|
-
serviceModeListeners.add(deliver);
|
|
3432
|
-
deliver(serviceMode);
|
|
3433
|
-
return () => {
|
|
3434
|
-
serviceModeListeners.delete(deliver);
|
|
3435
|
-
};
|
|
3436
|
-
}
|
|
3437
|
-
if (topic === 'member') {
|
|
3438
|
-
memberListeners.add(deliver);
|
|
3439
|
-
deliver(member);
|
|
3440
|
-
return () => {
|
|
3441
|
-
memberListeners.delete(deliver);
|
|
3442
|
-
};
|
|
3443
|
-
}
|
|
3444
|
-
if (topic === 'queue') {
|
|
3445
|
-
const queue = { locator: input[0], notify: deliver };
|
|
3446
|
-
queueSubscriptions.add(queue);
|
|
3447
|
-
queue.notify(entriesOf(queue.locator));
|
|
3448
|
-
return () => {
|
|
3449
|
-
queueSubscriptions.delete(queue);
|
|
3450
|
-
};
|
|
3451
|
-
}
|
|
3452
|
-
return watchPlan(planned(input[0], null), deliver);
|
|
3453
|
-
};
|
|
3454
|
-
const md = {
|
|
3455
|
-
loaded() {
|
|
3456
|
-
return openLocal().then(() => undefined);
|
|
3457
|
-
},
|
|
3458
|
-
rooms() {
|
|
3459
|
-
return listed();
|
|
3460
|
-
},
|
|
3461
|
-
query: ((input) => rowsQuery(planned(input, null))),
|
|
3462
|
-
search: (input) => searched(input, null),
|
|
3463
|
-
types(locator) {
|
|
3464
|
-
return {
|
|
3465
|
-
collection: modelTypes(modelOf(locator)),
|
|
3466
|
-
typedSelection: rooms.get(locator)?.typedSelection === undefined ||
|
|
3467
|
-
rooms.get(locator)?.typedSelection === null
|
|
3468
|
-
? null
|
|
3469
|
-
: modelTypes(modelOf(locator, 'typed-selection')),
|
|
3470
|
-
};
|
|
3471
44
|
},
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
},
|
|
3486
|
-
write(room, path, revision, input) {
|
|
3487
|
-
return exclusive(async () => {
|
|
3488
|
-
const parts = readParts(input);
|
|
3489
|
-
if (path === '' || normalize(path) !== path)
|
|
3490
|
-
throw runtimeError('room-draft-malformed');
|
|
3491
|
-
const local = await openLocal();
|
|
3492
|
-
const current = roomDrafts.get(room)?.get(path);
|
|
3493
|
-
if ((current?.revision ?? null) !== revision) {
|
|
3494
|
-
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
3495
|
-
}
|
|
3496
|
-
const file = parts.file === undefined
|
|
3497
|
-
? undefined
|
|
3498
|
-
: await heldFile(parts.file.blob, binaryBase(replicas.get(room)?.visible, path));
|
|
3499
|
-
const record = {
|
|
3500
|
-
locator: room,
|
|
3501
|
-
path,
|
|
3502
|
-
revision: roomDraftRevision(),
|
|
3503
|
-
parts: held(current?.parts, parts, file),
|
|
3504
|
-
returned: current?.returned ?? null,
|
|
3505
|
-
};
|
|
3506
|
-
await local.keepRoomDraft(record);
|
|
3507
|
-
rememberRoomDraft(record);
|
|
3508
|
-
publishChangeSet(changeSet(room, null, null, { drafts: [path] }));
|
|
3509
|
-
return { outcome: 'written', roomDraft: roomDraftOf(record) };
|
|
3510
|
-
});
|
|
3511
|
-
},
|
|
3512
|
-
discard(room, path, revision) {
|
|
3513
|
-
return exclusive(async () => {
|
|
3514
|
-
const local = await openLocal();
|
|
3515
|
-
const current = roomDrafts.get(room)?.get(path);
|
|
3516
|
-
if (current === undefined || current.revision !== revision) {
|
|
3517
|
-
return staleWrite(current === undefined ? null : roomDraftOf(current));
|
|
3518
|
-
}
|
|
3519
|
-
await local.discardRoomDraft(room, path);
|
|
3520
|
-
forgetRoomDraft(room, path);
|
|
3521
|
-
publishChangeSet(changeSet(room, null, null, { drafts: [path] }));
|
|
3522
|
-
moveTo({ ...status, ...attention() });
|
|
3523
|
-
return { outcome: 'written', roomDraft: null };
|
|
3524
|
-
});
|
|
3525
|
-
},
|
|
45
|
+
roomTypes,
|
|
46
|
+
builtIn,
|
|
47
|
+
typedPaths,
|
|
48
|
+
persistence: shell.persistence,
|
|
49
|
+
lost: presence.lost,
|
|
50
|
+
opening: (local) => open(runtime, local),
|
|
51
|
+
unpublished: uncaught,
|
|
52
|
+
changed(set) {
|
|
53
|
+
if (set.unindexed.length > 0)
|
|
54
|
+
prepare(runtime, set.unindexed);
|
|
55
|
+
for (const locator of set.due)
|
|
56
|
+
submitLater(runtime, locator);
|
|
57
|
+
return publishChangeSet(runtime, set);
|
|
3526
58
|
},
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
59
|
+
deliverReadiness: (owed) => deliverOwedReadiness(runtime, owed),
|
|
60
|
+
spaceFreed: () => spaceFreed(runtime),
|
|
61
|
+
});
|
|
62
|
+
const recovery = (options.recovery ?? createRecovery()).scope(ROOM_ATTEMPTS);
|
|
63
|
+
const runtime = {
|
|
64
|
+
store,
|
|
65
|
+
socket,
|
|
66
|
+
presence,
|
|
67
|
+
recovery,
|
|
68
|
+
retries,
|
|
69
|
+
shell,
|
|
70
|
+
localSaves: adapter.localSaves === true,
|
|
71
|
+
roomTypes,
|
|
72
|
+
builtIn,
|
|
73
|
+
typedPaths,
|
|
74
|
+
writeRule: (declaration) => writeRule(declaration, adapter.pathRule ?? (() => true), adapter.localSaves === true),
|
|
75
|
+
usableWhenHeld: options.usableWhenHeld ?? [],
|
|
76
|
+
build: options.build ?? '',
|
|
77
|
+
conversations: createLiveConversations({
|
|
78
|
+
call: (name, input, attempt) => invokeAction(runtime, name, input, { attempt }),
|
|
79
|
+
history: (request) => readConversationHistory(runtime, request),
|
|
80
|
+
durably: store.durably,
|
|
81
|
+
local: store.open,
|
|
82
|
+
again: () => {
|
|
83
|
+
void socket.ready.then(() => scheduleCurrency(runtime));
|
|
3538
84
|
},
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
...Object.entries(plain(held) ? held : {}).filter(([member]) => !Object.hasOwn(members, member)),
|
|
3542
|
-
...Object.entries(structuredClone(members)).filter(([, value]) => value !== null),
|
|
3543
|
-
]));
|
|
85
|
+
settled: () => {
|
|
86
|
+
show(runtime);
|
|
3544
87
|
},
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
resume();
|
|
3576
|
-
},
|
|
3577
|
-
observe: observing(watch),
|
|
3578
|
-
async reauthorize() {
|
|
3579
|
-
if (refusal === null || !refusal.reauthorizationOffered)
|
|
3580
|
-
return;
|
|
3581
|
-
journal.record('connection reauthorize');
|
|
3582
|
-
await Promise.all([...lanes.values()]
|
|
3583
|
-
.filter((held) => held.generation === generation)
|
|
3584
|
-
.map((held) => held.chain));
|
|
3585
|
-
await mutations;
|
|
3586
|
-
authorization = undefined;
|
|
3587
|
-
discoveryPaused = false;
|
|
3588
|
-
if (options.adapter.roomStream === undefined)
|
|
3589
|
-
await sync('unstreamed');
|
|
3590
|
-
else
|
|
3591
|
-
await task(OPENING_LANE, ensureAuthorization);
|
|
3592
|
-
},
|
|
3593
|
-
};
|
|
3594
|
-
const reachable = {
|
|
3595
|
-
md,
|
|
3596
|
-
watch,
|
|
3597
|
-
referrerScope: (referrer) => options.adapter.referrerScope(referrer),
|
|
3598
|
-
plan: planned,
|
|
3599
|
-
query: (plan) => rowsQuery(plan),
|
|
3600
|
-
watchPlan,
|
|
3601
|
-
search: searched,
|
|
3602
|
-
watchReadiness,
|
|
3603
|
-
};
|
|
3604
|
-
return {
|
|
3605
|
-
md,
|
|
3606
|
-
seed,
|
|
3607
|
-
stop,
|
|
3608
|
-
lose: loseRooms,
|
|
3609
|
-
watch,
|
|
3610
|
-
acquire,
|
|
3611
|
-
persisted(answer) {
|
|
3612
|
-
if (answer === persistence)
|
|
3613
|
-
return;
|
|
3614
|
-
persistence = answer;
|
|
3615
|
-
journal.record(`persistence ${answer}`);
|
|
3616
|
-
queueReadiness();
|
|
3617
|
-
},
|
|
3618
|
-
within: (reach, port) => narrowed(reachable, reach, port),
|
|
88
|
+
stream: socket.conversations,
|
|
89
|
+
publish: store.changed,
|
|
90
|
+
online: socket.reached,
|
|
91
|
+
full: store.space.full,
|
|
92
|
+
closed: socket.closed,
|
|
93
|
+
unanswered: () => resendLater(runtime),
|
|
94
|
+
heard: (operation, lost) => socket.lanes.settle(operation, lost),
|
|
95
|
+
recovery: recovery.scope(),
|
|
96
|
+
retries,
|
|
97
|
+
}),
|
|
98
|
+
liveQueries: createLiveQueries((plan, cover) => answeredPlan(runtime, plan, cover), store.covering),
|
|
99
|
+
figures: createFigures({
|
|
100
|
+
cover: () => figuresCover(runtime),
|
|
101
|
+
rooms: (rooms, since) => roomTallies(runtime, rooms, since),
|
|
102
|
+
conversations: (conversations, since) => store.ask({ kind: 'conversation.tallies', conversations, since }),
|
|
103
|
+
}),
|
|
104
|
+
lastAnswers: readAnswers({
|
|
105
|
+
database: store,
|
|
106
|
+
build: options.build ?? '',
|
|
107
|
+
unkept: adapter.unkeptReads ?? [],
|
|
108
|
+
leading: adapter.leadingReads ?? [],
|
|
109
|
+
recovery: recovery.scope(),
|
|
110
|
+
retries,
|
|
111
|
+
call: (name, input, attempt) => called(runtime, name, input, attempt),
|
|
112
|
+
online: socket.reached,
|
|
113
|
+
streaming: socket.isOpen,
|
|
114
|
+
closed: socket.closed,
|
|
115
|
+
full: store.space.full,
|
|
116
|
+
filled: () => filled(runtime),
|
|
117
|
+
}),
|
|
3619
118
|
};
|
|
119
|
+
start(runtime, options.signal);
|
|
120
|
+
return composedRuntime(runtime);
|
|
3620
121
|
}
|