@agentshouse/mdruntime 0.1.0-caee7332d2235 → 0.1.0-caf080a9a9c60
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 +19 -16
- package/dist/acceptance.d.ts +3 -0
- package/dist/acceptance.d.ts.map +1 -0
- package/dist/acceptance.js +318 -0
- package/dist/act-lanes.d.ts +29 -0
- package/dist/act-lanes.d.ts.map +1 -0
- package/dist/act-lanes.js +177 -0
- package/dist/actions.d.ts +25 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +261 -0
- package/dist/adapter.d.ts +162 -122
- 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 +10 -16
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +17 -94
- 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 +33 -19
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +112 -21
- package/dist/collection.d.ts +29 -22
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +47 -354
- 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 -137
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +111 -3623
- 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 +138 -0
- package/dist/conversation-live.d.ts.map +1 -0
- package/dist/conversation-live.js +249 -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 +66 -49
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +962 -799
- 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 +153 -0
- package/dist/durability.d.ts +19 -4
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +5 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +33 -29
- 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 +20 -8
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +103 -45
- 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 +539 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +279 -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 +33 -0
- package/dist/log.d.ts +1 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/lost.d.ts +5 -0
- package/dist/lost.d.ts.map +1 -0
- package/dist/lost.js +50 -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 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +272 -118
- package/dist/messages.d.ts +49 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +150 -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 +156 -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 +15 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +98 -14
- 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 +59 -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 +69 -169
- 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 +3 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +10 -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 +233 -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 +285 -0
- package/dist/query.d.ts +21 -207
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -493
- 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 +465 -0
- package/dist/reach.d.ts +20 -9
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +306 -111
- package/dist/react.d.ts +15 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +33 -1
- 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 +13 -17
- 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 -79
- package/dist/refusal.d.ts +5 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +57 -0
- package/dist/replica.d.ts +50 -39
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +319 -208
- 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 -117
- 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 -31
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +60 -125
- package/dist/runtime.d.ts +36 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/save.d.ts +43 -50
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +661 -228
- package/dist/saves.d.ts +32 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +413 -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 +4 -9
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +61 -120
- package/dist/shown.d.ts +32 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +290 -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 +21 -0
- package/dist/socket/lanes.d.ts.map +1 -0
- package/dist/socket/lanes.js +80 -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 +68 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +171 -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 +212 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +430 -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 -166
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +158 -797
- package/dist/stored.d.ts +156 -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 +223 -0
- package/dist/sync.d.ts +19 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +540 -0
- package/dist/tables.d.ts +412 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +737 -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 +1 -1
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +31 -20
- 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 +1 -16
- package/dist/windows.d.ts.map +1 -1
- package/dist/windows.js +2 -79
- package/dist/wire.d.ts +72 -8
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +24 -5
- package/package.json +19 -19
- package/AGENTS.md +0 -299
- 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 -21
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -246
- 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 -171
- 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/pieces.d.ts +0 -62
- package/dist/chat/pieces.d.ts.map +0 -1
- package/dist/chat/pieces.js +0 -1110
- package/dist/chat/process.d.ts +0 -20
- package/dist/chat/process.d.ts.map +0 -1
- package/dist/chat/process.js +0 -32
- package/dist/chat/provider.d.ts +0 -63
- package/dist/chat/provider.d.ts.map +0 -1
- package/dist/chat/provider.js +0 -630
- 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/steady.d.ts +0 -3
- package/dist/chat/steady.d.ts.map +0 -1
- package/dist/chat/steady.js +0 -26
- package/dist/chat/style.d.ts +0 -4
- package/dist/chat/style.d.ts.map +0 -1
- package/dist/chat/style.js +0 -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/read-side.d.ts +0 -27
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -826
package/dist/save.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
1
|
+
import { apply, ARRAY_INDEX, ATTACHMENT, create, equalValue, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
|
|
2
|
+
import { immutable, leaving, queuedFile, departure, } from './change.js';
|
|
3
|
+
import { collectionOf, definitionsAt, documentsOf, governing, } from './collection.js';
|
|
4
|
+
import { changeBatchIdentity, roomDraftRevision } from './identity.js';
|
|
5
|
+
import { composed, overlaying } from './overlay.js';
|
|
6
|
+
import { ancestors } from './paths.js';
|
|
7
|
+
import { entriesIn, sorting } from './shared.js';
|
|
8
|
+
import { changedParts, changesOf, moveTarget, withdrawnDrafts, } from './room-draft.js';
|
|
9
|
+
import { paced, ROW_WEIGHT, stepped } from './turn.js';
|
|
10
|
+
import { validatedCollection } from './validation.js';
|
|
11
|
+
function refuse(code, file, location, line) {
|
|
11
12
|
return {
|
|
12
13
|
kind: 'refused',
|
|
13
14
|
refusal: {
|
|
@@ -15,13 +16,14 @@ function refuse(code, file, location) {
|
|
|
15
16
|
retryable: false,
|
|
16
17
|
...(file === undefined ? {} : { file }),
|
|
17
18
|
...(location === undefined ? {} : { location }),
|
|
19
|
+
...(line === undefined ? {} : { line }),
|
|
18
20
|
},
|
|
19
21
|
};
|
|
20
22
|
}
|
|
23
|
+
function refusedBy(refusal, file) {
|
|
24
|
+
return refuse(refusal.rule, file, refusal.location, refusal.line);
|
|
25
|
+
}
|
|
21
26
|
function repointed(document, definition, from, to, moves, settled) {
|
|
22
|
-
const parsed = parse(document.content, from);
|
|
23
|
-
if (!parsed.ok)
|
|
24
|
-
return [];
|
|
25
27
|
const changes = [];
|
|
26
28
|
const names = new Set();
|
|
27
29
|
for (const { pointer, value } of definition === undefined
|
|
@@ -44,15 +46,22 @@ function repointed(document, definition, from, to, moves, settled) {
|
|
|
44
46
|
}
|
|
45
47
|
if (changes.length === 0)
|
|
46
48
|
return [];
|
|
47
|
-
const applied = apply(
|
|
49
|
+
const applied = apply(document, changes, definition);
|
|
48
50
|
if (applied.outcome !== 'applied')
|
|
49
51
|
return [];
|
|
50
52
|
const next = applied.document;
|
|
51
53
|
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
52
54
|
}
|
|
53
|
-
function edited(document, changes) {
|
|
55
|
+
function edited(document, changes, definition) {
|
|
54
56
|
const parts = new Map();
|
|
55
57
|
for (const change of changes) {
|
|
58
|
+
if (change.kind === 'field')
|
|
59
|
+
parts.set(`field ${change.name}`, {
|
|
60
|
+
kind: 'field',
|
|
61
|
+
name: change.name,
|
|
62
|
+
value: change.value,
|
|
63
|
+
prior: document.fields[change.name],
|
|
64
|
+
});
|
|
56
65
|
if (change.kind === 'preamble')
|
|
57
66
|
parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
58
67
|
if (change.kind === 'section')
|
|
@@ -65,11 +74,16 @@ function edited(document, changes) {
|
|
|
65
74
|
}
|
|
66
75
|
if (parts.size === 0)
|
|
67
76
|
return document;
|
|
68
|
-
const applied = apply(document, [...parts.values()]);
|
|
77
|
+
const applied = apply(document, [...parts.values()], definition);
|
|
69
78
|
return applied.outcome === 'applied' ? applied.document : document;
|
|
70
79
|
}
|
|
71
80
|
function bodies(path, before, after) {
|
|
72
81
|
const changes = [];
|
|
82
|
+
for (const [name, value] of Object.entries(after.fields)) {
|
|
83
|
+
if (equalValue(before.fields[name], value))
|
|
84
|
+
continue;
|
|
85
|
+
changes.push({ kind: 'field', path, name, value });
|
|
86
|
+
}
|
|
73
87
|
if (after.preamble !== before.preamble) {
|
|
74
88
|
changes.push({ kind: 'preamble', path, value: after.preamble });
|
|
75
89
|
}
|
|
@@ -80,7 +94,7 @@ function bodies(path, before, after) {
|
|
|
80
94
|
}
|
|
81
95
|
return changes;
|
|
82
96
|
}
|
|
83
|
-
function relinks(path, moved, before, moves) {
|
|
97
|
+
function relinks(path, moved, before, moves, definition) {
|
|
84
98
|
const changes = [];
|
|
85
99
|
for (const link of links(before, path)) {
|
|
86
100
|
const destination = moves.get(link.target) ?? link.target;
|
|
@@ -93,18 +107,9 @@ function relinks(path, moved, before, moves) {
|
|
|
93
107
|
}
|
|
94
108
|
if (changes.length === 0)
|
|
95
109
|
return { kind: 'changes', changes: [] };
|
|
96
|
-
const applied = apply(before, changes);
|
|
97
|
-
if (applied.outcome === 'refuse')
|
|
98
|
-
return
|
|
99
|
-
kind: 'refused',
|
|
100
|
-
refusal: {
|
|
101
|
-
code: applied.refusal.rule,
|
|
102
|
-
retryable: false,
|
|
103
|
-
file: path,
|
|
104
|
-
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
110
|
+
const applied = apply(before, changes, definition);
|
|
111
|
+
if (applied.outcome === 'refuse')
|
|
112
|
+
return refusedBy(applied.refusal, path);
|
|
108
113
|
const after = applied.document;
|
|
109
114
|
if (after === before)
|
|
110
115
|
return { kind: 'changes', changes: [] };
|
|
@@ -114,7 +119,18 @@ function relinks(path, moved, before, moves) {
|
|
|
114
119
|
return { kind: 'changes', changes: bodies(path, before, after) };
|
|
115
120
|
}
|
|
116
121
|
function partKey(change) {
|
|
117
|
-
return change.kind === 'section'
|
|
122
|
+
return change.kind === 'section' || change.kind === 'field'
|
|
123
|
+
? `${change.kind} ${change.name}`
|
|
124
|
+
: change.kind;
|
|
125
|
+
}
|
|
126
|
+
const NAMES_A_SKIP_TESTS = 64;
|
|
127
|
+
const ENCODED = /[%&\\]/u;
|
|
128
|
+
function movedNames(moves) {
|
|
129
|
+
const names = new Set([...moves.keys()].map((path) => path.slice(path.lastIndexOf('/') + 1)));
|
|
130
|
+
return names.size > NAMES_A_SKIP_TESTS ? null : [...names];
|
|
131
|
+
}
|
|
132
|
+
function cannotLink(text, names) {
|
|
133
|
+
return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
|
|
118
134
|
}
|
|
119
135
|
function* rewrites(changes, visible) {
|
|
120
136
|
const moves = new Map();
|
|
@@ -141,16 +157,20 @@ function* rewrites(changes, visible) {
|
|
|
141
157
|
}
|
|
142
158
|
if (moves.size === 0)
|
|
143
159
|
return { kind: 'changes', changes };
|
|
160
|
+
const names = movedNames(moves);
|
|
144
161
|
const merged = [...changes];
|
|
145
162
|
const added = [];
|
|
146
|
-
for (const [path, document] of documentsOf(visible.collection)) {
|
|
163
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
147
164
|
if (settled.has(path))
|
|
148
165
|
continue;
|
|
149
166
|
const moved = moves.get(path) ?? path;
|
|
150
|
-
const definition =
|
|
151
|
-
|
|
167
|
+
const definition = definitionsAt(visible.collection.composition, path)(document.type);
|
|
168
|
+
const repointing = repointed(document, definition, path, moved, moves, settled);
|
|
169
|
+
added.push(...repointing);
|
|
152
170
|
const own = parts.get(path) ?? [];
|
|
153
|
-
const relinked =
|
|
171
|
+
const relinked = moved === path && own.length === 0 && cannotLink(document.content, names)
|
|
172
|
+
? { kind: 'changes', changes: [] }
|
|
173
|
+
: relinks(path, moved, edited(document, own.map((index) => merged[index]), definition), moves, definition);
|
|
154
174
|
if (relinked.kind === 'refused')
|
|
155
175
|
return relinked;
|
|
156
176
|
const keyed = new Map();
|
|
@@ -163,7 +183,7 @@ function* rewrites(changes, visible) {
|
|
|
163
183
|
if (change.kind === 'replace') {
|
|
164
184
|
for (const index of own) {
|
|
165
185
|
const kind = merged[index]?.kind;
|
|
166
|
-
if (kind === 'section' || kind === 'preamble')
|
|
186
|
+
if (kind === 'field' || kind === 'section' || kind === 'preamble')
|
|
167
187
|
merged[index] = null;
|
|
168
188
|
}
|
|
169
189
|
added.push(change);
|
|
@@ -175,7 +195,9 @@ function* rewrites(changes, visible) {
|
|
|
175
195
|
else
|
|
176
196
|
merged[index] = change;
|
|
177
197
|
}
|
|
178
|
-
|
|
198
|
+
const text = visible.collection.text[path].length;
|
|
199
|
+
const rewritten = repointing.length > 0 || relinked.changes.length > 0;
|
|
200
|
+
yield ROW_WEIGHT + text + (rewritten ? text : 0);
|
|
179
201
|
}
|
|
180
202
|
return {
|
|
181
203
|
kind: 'changes',
|
|
@@ -199,6 +221,67 @@ export function* providerRewritten(batch, visible) {
|
|
|
199
221
|
...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
|
|
200
222
|
};
|
|
201
223
|
}
|
|
224
|
+
function crossed(document, definition, path, to) {
|
|
225
|
+
const changes = [];
|
|
226
|
+
for (const link of links(document, path)) {
|
|
227
|
+
const written = to(link.target, link.href);
|
|
228
|
+
if (written !== undefined)
|
|
229
|
+
changes.push({ kind: 'link', href: link.href, to: written });
|
|
230
|
+
}
|
|
231
|
+
for (const { pointer, value } of definition === undefined
|
|
232
|
+
? []
|
|
233
|
+
: references(document, definition)) {
|
|
234
|
+
const named = target(path, value);
|
|
235
|
+
const written = named === null ? undefined : to(named, value);
|
|
236
|
+
if (written !== undefined) {
|
|
237
|
+
changes.push({ kind: 'reference', pointer, value: retarget(value, written), prior: value });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return changes;
|
|
241
|
+
}
|
|
242
|
+
function rewritten(path, document, changes, definition) {
|
|
243
|
+
if (changes.length === 0)
|
|
244
|
+
return [];
|
|
245
|
+
const applied = apply(document, changes, definition);
|
|
246
|
+
if (applied.outcome !== 'applied')
|
|
247
|
+
return [];
|
|
248
|
+
const after = applied.document;
|
|
249
|
+
if (document.type === undefined)
|
|
250
|
+
return [{ kind: 'replace', path, content: serialize(after) }];
|
|
251
|
+
return bodies(path, document, after);
|
|
252
|
+
}
|
|
253
|
+
function roomPathOf(handle, path) {
|
|
254
|
+
return `/rooms/${handle}/${path}`;
|
|
255
|
+
}
|
|
256
|
+
function* leavingRewritten(batch, visible) {
|
|
257
|
+
const between = batch.between;
|
|
258
|
+
if (between === undefined)
|
|
259
|
+
return batch;
|
|
260
|
+
const moved = new Map(between.moves.map(({ path, to }) => [path, roomPathOf(between.destination.handle, to)]));
|
|
261
|
+
const derived = [];
|
|
262
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
263
|
+
if (moved.has(path))
|
|
264
|
+
continue;
|
|
265
|
+
const definition = definitionsAt(visible.collection.composition, path)(document.type);
|
|
266
|
+
derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named)), definition));
|
|
267
|
+
yield ROW_WEIGHT + visible.collection.text[path].length;
|
|
268
|
+
}
|
|
269
|
+
if (derived.length === 0)
|
|
270
|
+
return batch;
|
|
271
|
+
return {
|
|
272
|
+
...batch,
|
|
273
|
+
changes: [...derived, ...batch.changes],
|
|
274
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...batch.preconditions],
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
export function rewriting(batch, locator) {
|
|
278
|
+
return leaving(batch, locator) || batch.changes.some((change) => change.kind === 'move');
|
|
279
|
+
}
|
|
280
|
+
export function* effectiveIn(batch, locator, visible) {
|
|
281
|
+
return leaving(batch, locator)
|
|
282
|
+
? yield* leavingRewritten(batch, visible)
|
|
283
|
+
: yield* providerRewritten(batch, visible);
|
|
284
|
+
}
|
|
202
285
|
function* incoming(collection, removed, candidates) {
|
|
203
286
|
const found = new Map();
|
|
204
287
|
const note = (target, file) => {
|
|
@@ -209,14 +292,20 @@ function* incoming(collection, removed, candidates) {
|
|
|
209
292
|
found.set(target, referrers);
|
|
210
293
|
};
|
|
211
294
|
const read = function* (paths) {
|
|
212
|
-
|
|
295
|
+
const seen = new Set();
|
|
296
|
+
for (const path of paths) {
|
|
297
|
+
if (seen.has(path))
|
|
298
|
+
continue;
|
|
299
|
+
seen.add(path);
|
|
213
300
|
const document = collection.document(path);
|
|
214
301
|
if (document !== undefined)
|
|
215
302
|
yield [path, document];
|
|
216
303
|
}
|
|
217
304
|
};
|
|
218
|
-
for (const [path, document] of candidates === null
|
|
219
|
-
|
|
305
|
+
for (const [path, document] of candidates === null
|
|
306
|
+
? yield* documentsOf(collection)
|
|
307
|
+
: read(candidates)) {
|
|
308
|
+
const definition = definitionsAt(collection.composition, path)(document.type);
|
|
220
309
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
221
310
|
if (to.includes(ATTACHMENT))
|
|
222
311
|
continue;
|
|
@@ -226,7 +315,7 @@ function* incoming(collection, removed, candidates) {
|
|
|
226
315
|
}
|
|
227
316
|
yield ROW_WEIGHT + collection.text[path].length;
|
|
228
317
|
}
|
|
229
|
-
const [first] =
|
|
318
|
+
const [first] = yield* sorting(found.keys());
|
|
230
319
|
if (first === undefined)
|
|
231
320
|
return null;
|
|
232
321
|
const every = new Set();
|
|
@@ -239,14 +328,16 @@ function* incoming(collection, removed, candidates) {
|
|
|
239
328
|
code: 'referenced-missing-file',
|
|
240
329
|
retryable: false,
|
|
241
330
|
file: first,
|
|
242
|
-
referrers:
|
|
331
|
+
referrers: yield* sorting(every),
|
|
243
332
|
},
|
|
244
333
|
};
|
|
245
334
|
}
|
|
246
335
|
function affected(change) {
|
|
247
336
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
248
337
|
}
|
|
249
|
-
|
|
338
|
+
const WRITE = ['write'];
|
|
339
|
+
const MOVE_OUT = ['write', 'erase'];
|
|
340
|
+
function guard(changes, input, removing) {
|
|
250
341
|
const overridden = new Map();
|
|
251
342
|
const existing = {
|
|
252
343
|
has: (path) => overridden.get(path) ??
|
|
@@ -254,21 +345,17 @@ function guard(changes, input) {
|
|
|
254
345
|
add: (path) => void overridden.set(path, true),
|
|
255
346
|
delete: (path) => void overridden.set(path, false),
|
|
256
347
|
};
|
|
348
|
+
const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
|
|
257
349
|
for (const [index, change] of changes.entries()) {
|
|
258
350
|
for (const path of affected(change)) {
|
|
259
|
-
const
|
|
260
|
-
if (
|
|
261
|
-
return refuse(
|
|
262
|
-
if (covered(input.providerOwnedPaths, normalized)) {
|
|
263
|
-
return refuse('provider-owned-path', normalized);
|
|
264
|
-
}
|
|
265
|
-
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
266
|
-
if (!input.local && !covered(input.operations[operation], normalized))
|
|
267
|
-
return refuse(`${operation}-grant`, normalized);
|
|
351
|
+
const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
|
|
352
|
+
if (refused !== null)
|
|
353
|
+
return refuse(refused.code, refused.path);
|
|
268
354
|
}
|
|
269
|
-
if (change.kind === 'create') {
|
|
270
|
-
|
|
271
|
-
|
|
355
|
+
if (change.kind === 'create' || change.kind === 'arrival') {
|
|
356
|
+
const taken = existing.has(change.path) ? change.path : occupied(change.path);
|
|
357
|
+
if (taken !== null)
|
|
358
|
+
return refuse('path-collision', taken);
|
|
272
359
|
existing.add(change.path);
|
|
273
360
|
continue;
|
|
274
361
|
}
|
|
@@ -278,6 +365,9 @@ function guard(changes, input) {
|
|
|
278
365
|
input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
|
|
279
366
|
return refuse('path-collision', change.path);
|
|
280
367
|
}
|
|
368
|
+
const taken = occupied(change.path);
|
|
369
|
+
if (taken !== null)
|
|
370
|
+
return refuse('path-collision', taken);
|
|
281
371
|
if (!input.local &&
|
|
282
372
|
existing.has(change.path) &&
|
|
283
373
|
input.visible.revisions[departure(changes, index)] === undefined) {
|
|
@@ -289,8 +379,9 @@ function guard(changes, input) {
|
|
|
289
379
|
if (change.kind === 'move') {
|
|
290
380
|
if (!existing.has(change.path))
|
|
291
381
|
return refuse('path-absent', change.path);
|
|
292
|
-
|
|
293
|
-
|
|
382
|
+
const taken = existing.has(change.to) ? change.to : occupied(change.to);
|
|
383
|
+
if (taken !== null)
|
|
384
|
+
return refuse('path-collision', taken);
|
|
294
385
|
if (unrevised(input, change.path)) {
|
|
295
386
|
return refuse('unknown-revision', change.path);
|
|
296
387
|
}
|
|
@@ -320,13 +411,13 @@ function guard(changes, input) {
|
|
|
320
411
|
return null;
|
|
321
412
|
}
|
|
322
413
|
function unrevised(input, path) {
|
|
323
|
-
return
|
|
414
|
+
return !input.local && !input.queued.has(path) && input.visible.revisions[path] === undefined;
|
|
324
415
|
}
|
|
325
|
-
|
|
416
|
+
function queuedDocuments(queue) {
|
|
326
417
|
const paths = new Set();
|
|
327
418
|
for (const batch of queue) {
|
|
328
419
|
for (const change of batch.changes) {
|
|
329
|
-
if (change.kind === 'binary')
|
|
420
|
+
if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
|
|
330
421
|
continue;
|
|
331
422
|
if (change.kind === 'remove')
|
|
332
423
|
paths.delete(change.path);
|
|
@@ -417,7 +508,10 @@ function depth(path) {
|
|
|
417
508
|
function* exceeding(limits, changes, effective, resulting) {
|
|
418
509
|
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
419
510
|
const created = changes
|
|
420
|
-
.filter((change) => change.kind === 'create' ||
|
|
511
|
+
.filter((change) => change.kind === 'create' ||
|
|
512
|
+
change.kind === 'move' ||
|
|
513
|
+
change.kind === 'binary' ||
|
|
514
|
+
change.kind === 'arrival')
|
|
421
515
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
422
516
|
for (const limit of limits) {
|
|
423
517
|
const value = limit.value;
|
|
@@ -455,68 +549,69 @@ function* exceeding(limits, changes, effective, resulting) {
|
|
|
455
549
|
function shellRelative(text, path, handle, definition) {
|
|
456
550
|
if (!text.includes(`/rooms/${handle}/`))
|
|
457
551
|
return null;
|
|
458
|
-
const parsed = parse(text, path);
|
|
552
|
+
const parsed = parse(text, path, definition);
|
|
459
553
|
if (!parsed.ok)
|
|
460
554
|
return null;
|
|
461
555
|
const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
|
|
462
556
|
return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
|
|
463
557
|
}
|
|
464
558
|
function relativeField(path, name, value, handle, definition) {
|
|
465
|
-
|
|
466
|
-
if (definition === undefined ||
|
|
467
|
-
!opened.ok ||
|
|
468
|
-
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
559
|
+
if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
469
560
|
return value;
|
|
470
|
-
const held = apply(
|
|
561
|
+
const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
|
|
471
562
|
if (held.outcome !== 'applied')
|
|
472
563
|
return value;
|
|
473
564
|
const result = relativize(held.document, path, handle, definition);
|
|
474
565
|
return result.outcome === 'applied' ? result.document.fields[name] : value;
|
|
475
566
|
}
|
|
476
567
|
function relativeChanges(input) {
|
|
568
|
+
const changes = [];
|
|
569
|
+
for (const change of input.changes) {
|
|
570
|
+
const next = relativeChange(change, input);
|
|
571
|
+
if ('refusal' in next)
|
|
572
|
+
return next;
|
|
573
|
+
changes.push(next.change);
|
|
574
|
+
}
|
|
575
|
+
return { kind: 'changes', changes };
|
|
576
|
+
}
|
|
577
|
+
function relativeChange(change, input) {
|
|
477
578
|
const { handle, composition, visible } = input;
|
|
478
|
-
const typed = (path) => (type) => definitionOf(governing(composition, path), type);
|
|
479
579
|
const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
return value === change.value ? change : { ...change, value };
|
|
486
|
-
}
|
|
487
|
-
if (change.kind === 'section' || change.kind === 'preamble') {
|
|
488
|
-
const value = body(change.path, change.value);
|
|
489
|
-
return value === change.value ? change : { ...change, value };
|
|
490
|
-
}
|
|
491
|
-
if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
|
|
492
|
-
const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
|
|
493
|
-
return content === change.content ? change : { ...change, content };
|
|
494
|
-
}
|
|
495
|
-
if (change.kind !== 'create')
|
|
496
|
-
return change;
|
|
497
|
-
const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
|
|
498
|
-
const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
|
|
499
|
-
name,
|
|
500
|
-
relativeField(change.path, name, value, handle, definition(type)),
|
|
501
|
-
]));
|
|
502
|
-
const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
|
|
503
|
-
return { ...change, fields, sections };
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
function* unmodeledType(collection, composition) {
|
|
507
|
-
if (available(composition))
|
|
508
|
-
return false;
|
|
509
|
-
for (const [path, document] of documentsOf(collection)) {
|
|
510
|
-
if (document.type !== undefined &&
|
|
511
|
-
document.type !== 'any' &&
|
|
512
|
-
governing(composition, path).status === 'unavailable')
|
|
513
|
-
return true;
|
|
514
|
-
yield ROW_WEIGHT + collection.text[path].length;
|
|
580
|
+
const definition = definitionsAt(composition, change.path);
|
|
581
|
+
if (change.kind === 'field') {
|
|
582
|
+
const type = visible.collection.document(change.path)?.type;
|
|
583
|
+
const value = relativeField(change.path, change.name, change.value, handle, definition(type));
|
|
584
|
+
return { change: value === change.value ? change : { ...change, value } };
|
|
515
585
|
}
|
|
516
|
-
|
|
586
|
+
if (change.kind === 'section' || change.kind === 'preamble') {
|
|
587
|
+
const value = body(change.path, change.value);
|
|
588
|
+
return { change: value === change.value ? change : { ...change, value } };
|
|
589
|
+
}
|
|
590
|
+
if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
|
|
591
|
+
const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
|
|
592
|
+
return { change: content === change.content ? change : { ...change, content } };
|
|
593
|
+
}
|
|
594
|
+
if (change.kind !== 'create')
|
|
595
|
+
return { change };
|
|
596
|
+
const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
|
|
597
|
+
const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
|
|
598
|
+
name,
|
|
599
|
+
relativeField(change.path, name, value, handle, definition(type)),
|
|
600
|
+
]));
|
|
601
|
+
const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
|
|
602
|
+
const written = composed(change.path, fields, sections, composition);
|
|
603
|
+
if (written.outcome === 'refuse')
|
|
604
|
+
return refusedBy(written.refusal, change.path);
|
|
605
|
+
if (written.outcome === 'stale')
|
|
606
|
+
throw new Error(`the creation of ${change.path} is stale`);
|
|
607
|
+
if (Object.keys(sections).length === 0)
|
|
608
|
+
return { change: { ...change, fields, sections } };
|
|
609
|
+
return {
|
|
610
|
+
change: { kind: 'create', path: change.path, content: serialize(written.document) },
|
|
611
|
+
};
|
|
517
612
|
}
|
|
518
613
|
function unqueueable(file) {
|
|
519
|
-
return
|
|
614
|
+
return refuse('unknown-revision', file);
|
|
520
615
|
}
|
|
521
616
|
function isPart(change) {
|
|
522
617
|
return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
|
|
@@ -524,8 +619,29 @@ function isPart(change) {
|
|
|
524
619
|
function touched(change) {
|
|
525
620
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
526
621
|
}
|
|
527
|
-
function partsApplied(content, path, changes) {
|
|
528
|
-
const
|
|
622
|
+
function partsApplied(content, path, changes, composition) {
|
|
623
|
+
const applied = partsResult(content, path, changes, composition);
|
|
624
|
+
return applied?.outcome === 'applied' ? serialize(applied.document) : null;
|
|
625
|
+
}
|
|
626
|
+
function unapplied(changes, visible, composition) {
|
|
627
|
+
const parted = new Map();
|
|
628
|
+
for (const change of changes) {
|
|
629
|
+
if (isPart(change))
|
|
630
|
+
parted.set(change.path, [...(parted.get(change.path) ?? []), change]);
|
|
631
|
+
}
|
|
632
|
+
for (const [path, parts] of parted) {
|
|
633
|
+
const content = visible.documents[path];
|
|
634
|
+
if (content === undefined)
|
|
635
|
+
continue;
|
|
636
|
+
const applied = partsResult(content, path, parts, composition);
|
|
637
|
+
if (applied?.outcome === 'refuse')
|
|
638
|
+
return refusedBy(applied.refusal, path);
|
|
639
|
+
}
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
function partsResult(content, path, changes, composition) {
|
|
643
|
+
const definitions = definitionsAt(composition, path);
|
|
644
|
+
const parsed = parse(content, path, definitions);
|
|
529
645
|
if (!parsed.ok)
|
|
530
646
|
return null;
|
|
531
647
|
const document = parsed.document;
|
|
@@ -548,12 +664,11 @@ function partsApplied(content, path, changes) {
|
|
|
548
664
|
else
|
|
549
665
|
parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
550
666
|
}
|
|
551
|
-
|
|
552
|
-
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
667
|
+
return apply(document, [...parts.values()], definitions(document.type));
|
|
553
668
|
}
|
|
554
|
-
function settledParts(changes, deferred) {
|
|
669
|
+
function settledParts(changes, deferred, composition) {
|
|
555
670
|
for (const [held, parts] of deferred) {
|
|
556
|
-
const content = partsApplied(held.content, held.path, parts);
|
|
671
|
+
const content = partsApplied(held.content, held.path, parts, composition);
|
|
557
672
|
if (content === null)
|
|
558
673
|
return held.path;
|
|
559
674
|
changes[changes.indexOf(held)] = { ...held, content };
|
|
@@ -617,8 +732,8 @@ function folded(changes, change, appending, deferred) {
|
|
|
617
732
|
changes.push(change);
|
|
618
733
|
return 'appended';
|
|
619
734
|
}
|
|
620
|
-
function relinked(batch, move, before, after, appending) {
|
|
621
|
-
const derived =
|
|
735
|
+
function* relinked(batch, move, before, after, appending) {
|
|
736
|
+
const derived = yield* rewrites([move], after);
|
|
622
737
|
if (derived.kind === 'refused')
|
|
623
738
|
return derived;
|
|
624
739
|
const changes = [...batch.changes];
|
|
@@ -628,7 +743,9 @@ function relinked(batch, move, before, after, appending) {
|
|
|
628
743
|
for (const change of derived.changes) {
|
|
629
744
|
if (change === move)
|
|
630
745
|
continue;
|
|
631
|
-
const unsettled = isPart(change)
|
|
746
|
+
const unsettled = isPart(change)
|
|
747
|
+
? null
|
|
748
|
+
: settledParts(changes, deferred, after.collection.composition);
|
|
632
749
|
if (unsettled !== null)
|
|
633
750
|
return unqueueable(unsettled);
|
|
634
751
|
const outcome = folded(changes, change, appending, deferred);
|
|
@@ -639,12 +756,13 @@ function relinked(batch, move, before, after, appending) {
|
|
|
639
756
|
if (outcome !== 'skipped')
|
|
640
757
|
changed = true;
|
|
641
758
|
}
|
|
642
|
-
const unsettled = settledParts(changes, deferred);
|
|
759
|
+
const unsettled = settledParts(changes, deferred, after.collection.composition);
|
|
643
760
|
if (unsettled !== null)
|
|
644
761
|
return unqueueable(unsettled);
|
|
645
762
|
if (!changed)
|
|
646
763
|
return { kind: 'relinked', batch };
|
|
647
764
|
const held = new Set(batch.preconditions.map(identity));
|
|
765
|
+
const added = yield* preconditionsOf(appended, before, []);
|
|
648
766
|
return {
|
|
649
767
|
kind: 'relinked',
|
|
650
768
|
batch: immutable({
|
|
@@ -653,7 +771,7 @@ function relinked(batch, move, before, after, appending) {
|
|
|
653
771
|
changes,
|
|
654
772
|
preconditions: [
|
|
655
773
|
...batch.preconditions,
|
|
656
|
-
...
|
|
774
|
+
...added.filter((precondition) => !held.has(identity(precondition))),
|
|
657
775
|
],
|
|
658
776
|
}),
|
|
659
777
|
};
|
|
@@ -699,11 +817,10 @@ function withdrawnFile(batch, change, at) {
|
|
|
699
817
|
];
|
|
700
818
|
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
701
819
|
}
|
|
702
|
-
|
|
820
|
+
function* requeuing(queue, changes, visible, changeable, state) {
|
|
703
821
|
const next = [...queue];
|
|
704
822
|
const origins = new Map();
|
|
705
823
|
const withdrawn = new Map();
|
|
706
|
-
const owners = new Set();
|
|
707
824
|
const queued = new Set();
|
|
708
825
|
const kept = [];
|
|
709
826
|
const originOf = (batch) => origins.get(batch) ?? batch.id;
|
|
@@ -722,19 +839,19 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
722
839
|
return unqueueable(change.path);
|
|
723
840
|
const following = next.slice(index);
|
|
724
841
|
const rebuilt = [];
|
|
725
|
-
let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
|
|
842
|
+
let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
|
|
726
843
|
for (const [offset, batch] of following.entries()) {
|
|
727
844
|
let current = batch;
|
|
728
845
|
if (change.kind === 'move') {
|
|
729
|
-
const after = state(next.slice(0, index + offset + 1));
|
|
846
|
+
const after = yield* state(next.slice(0, index + offset + 1));
|
|
730
847
|
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
731
|
-
const relinking = relinked(batch, change, before, after, appending);
|
|
848
|
+
const relinking = yield* relinked(batch, change, before, after, appending);
|
|
732
849
|
if (relinking.kind === 'refused')
|
|
733
850
|
return relinking;
|
|
734
851
|
current = relinking.batch;
|
|
735
852
|
before = after;
|
|
736
853
|
}
|
|
737
|
-
if (current !== batch && !open(batch))
|
|
854
|
+
if (current !== batch && (!open(batch) || batch.between !== undefined))
|
|
738
855
|
return unqueueable(change.path);
|
|
739
856
|
rebuilt.push(offset === 0
|
|
740
857
|
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
@@ -749,7 +866,6 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
749
866
|
if (replaced !== null)
|
|
750
867
|
origins.set(replaced, origin);
|
|
751
868
|
}
|
|
752
|
-
owners.add(originOf(owner));
|
|
753
869
|
next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
|
|
754
870
|
queued.add(change.path);
|
|
755
871
|
}
|
|
@@ -759,112 +875,68 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
759
875
|
changes: kept,
|
|
760
876
|
queued,
|
|
761
877
|
withdrawn,
|
|
762
|
-
owners,
|
|
763
878
|
};
|
|
764
879
|
}
|
|
765
|
-
function
|
|
766
|
-
return precondition.kind === 'field' ||
|
|
767
|
-
precondition.kind === 'section' ||
|
|
768
|
-
precondition.kind === 'preamble'
|
|
769
|
-
? identity(precondition)
|
|
770
|
-
: `path ${precondition.path}`;
|
|
771
|
-
}
|
|
772
|
-
export function joined(queue, owner, extra) {
|
|
773
|
-
const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
|
|
774
|
-
const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
|
|
775
|
-
if (clash !== undefined)
|
|
776
|
-
return unqueueable(clash);
|
|
777
|
-
const changes = [...owner.changes];
|
|
778
|
-
const deferred = new Map();
|
|
779
|
-
for (const change of extra.changes) {
|
|
780
|
-
const unsettled = isPart(change) ? null : settledParts(changes, deferred);
|
|
781
|
-
if (unsettled !== null)
|
|
782
|
-
return unqueueable(unsettled);
|
|
783
|
-
if (folded(changes, change, true, deferred) === 'refused')
|
|
784
|
-
return unqueueable(change.path);
|
|
785
|
-
}
|
|
786
|
-
const unsettled = settledParts(changes, deferred);
|
|
787
|
-
if (unsettled !== null)
|
|
788
|
-
return unqueueable(unsettled);
|
|
789
|
-
const held = new Set(owner.preconditions.map(covering));
|
|
790
|
-
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
791
|
-
const preconditions = [
|
|
792
|
-
...owner.preconditions,
|
|
793
|
-
...extra.preconditions.filter((precondition) => {
|
|
794
|
-
const key = covering(precondition);
|
|
795
|
-
return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
|
|
796
|
-
}),
|
|
797
|
-
];
|
|
798
|
-
const batch = immutable({
|
|
799
|
-
...owner,
|
|
800
|
-
...(extra.validation === undefined ? {} : { validation: extra.validation }),
|
|
801
|
-
id: changeBatchIdentity(),
|
|
802
|
-
changes,
|
|
803
|
-
preconditions,
|
|
804
|
-
});
|
|
805
|
-
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
806
|
-
}
|
|
807
|
-
export function limitedIn(queue, batch, limits, state) {
|
|
880
|
+
function* limiting(queue, batch, limits, state) {
|
|
808
881
|
const at = queue.indexOf(batch);
|
|
809
|
-
const
|
|
810
|
-
|
|
882
|
+
const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
|
|
883
|
+
const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
|
|
884
|
+
return limited?.refusal ?? null;
|
|
811
885
|
}
|
|
812
|
-
function
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
const
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
886
|
+
function typedText(content) {
|
|
887
|
+
if (content === undefined)
|
|
888
|
+
return false;
|
|
889
|
+
const parsed = parse(content);
|
|
890
|
+
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
891
|
+
}
|
|
892
|
+
function touchedLibrary(resulting, touched, composition) {
|
|
893
|
+
const entries = touched
|
|
894
|
+
.flatMap((path) => {
|
|
895
|
+
const content = resulting.documents[path];
|
|
896
|
+
const binary = resulting.binaries[path] !== undefined;
|
|
897
|
+
return content === undefined && !binary ? [] : [{ path, content: content ?? null }];
|
|
898
|
+
})
|
|
899
|
+
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
900
|
+
const contents = new Map(entries.map(({ path, content }) => [path, content]));
|
|
901
|
+
const binaries = new Set(Array.from(entriesIn(resulting.binaries), ([path]) => path));
|
|
902
|
+
return validatedCollection(() => Promise.resolve({ entries, next: null }), async (paths) => {
|
|
903
|
+
const known = new Map();
|
|
904
|
+
const pace = paced();
|
|
905
|
+
for (const path of paths) {
|
|
906
|
+
const content = contents.get(path);
|
|
907
|
+
if (binaries.has(path))
|
|
908
|
+
known.set(path, { type: undefined, binary: true });
|
|
909
|
+
else if (content !== undefined && content !== null) {
|
|
910
|
+
const read = parse(content, path, definitionsAt(composition, path));
|
|
911
|
+
if (read.ok)
|
|
912
|
+
known.set(path, { type: read.document.type ?? 'any', binary: false });
|
|
913
|
+
await pace(content.length + ROW_WEIGHT);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return known;
|
|
917
|
+
}, composition);
|
|
918
|
+
}
|
|
919
|
+
async function judging(visible, resulting, composition, validate, removed, touched) {
|
|
833
920
|
if (removed.size > 0) {
|
|
834
|
-
const
|
|
835
|
-
const referenced = yield* incoming(collectionOf(resulting.documents, input.composition), removed, clean === null || referrers === null
|
|
836
|
-
? null
|
|
837
|
-
: [...referrers, ...(yield* differences(clean.text, resulting.documents))]);
|
|
921
|
+
const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
|
|
838
922
|
if (referenced !== null)
|
|
839
923
|
return referenced;
|
|
840
924
|
}
|
|
841
|
-
const typed = (content) => {
|
|
842
|
-
if (content === undefined)
|
|
843
|
-
return false;
|
|
844
|
-
const parsed = parse(content);
|
|
845
|
-
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
846
|
-
};
|
|
847
|
-
const touched = [...new Set(predicted.changes.flatMap(affected))];
|
|
848
925
|
const typedPaths = [];
|
|
926
|
+
const pace = paced();
|
|
849
927
|
for (const path of touched) {
|
|
850
|
-
const before =
|
|
928
|
+
const before = visible.documents[path];
|
|
851
929
|
const after = resulting.documents[path];
|
|
852
|
-
if (
|
|
930
|
+
if (typedText(before) || typedText(after))
|
|
853
931
|
typedPaths.push(path);
|
|
854
|
-
|
|
932
|
+
await pace((before?.length ?? 0) + (after?.length ?? 0) + ROW_WEIGHT);
|
|
855
933
|
}
|
|
856
|
-
const
|
|
857
|
-
const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
|
|
934
|
+
const withoutModel = typedPaths.find((path) => governing(composition, path).status === 'unavailable');
|
|
858
935
|
if (withoutModel !== undefined)
|
|
859
936
|
return refuse('model-unavailable', withoutModel);
|
|
860
|
-
const verdict =
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
: yield* validateCollection(needsModel
|
|
864
|
-
? resulting.documents
|
|
865
|
-
: Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
|
|
866
|
-
? []
|
|
867
|
-
: [[path, resulting.documents[path]]])), Object.keys(resulting.binaries), input.composition);
|
|
937
|
+
const verdict = typedPaths.length > 0
|
|
938
|
+
? await validate(resulting, touched)
|
|
939
|
+
: await touchedLibrary(resulting, touched, composition);
|
|
868
940
|
if (verdict.verdict === 'refuse') {
|
|
869
941
|
return {
|
|
870
942
|
kind: 'refused',
|
|
@@ -873,16 +945,99 @@ function* batching(input) {
|
|
|
873
945
|
retryable: false,
|
|
874
946
|
file: verdict.file,
|
|
875
947
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
948
|
+
...(verdict.line === undefined ? {} : { line: verdict.line }),
|
|
876
949
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
877
950
|
},
|
|
878
951
|
};
|
|
879
952
|
}
|
|
880
|
-
|
|
953
|
+
return {
|
|
954
|
+
kind: 'judged',
|
|
955
|
+
validated: typedPaths
|
|
956
|
+
.map((path) => governing(composition, path))
|
|
957
|
+
.find((model) => model.status !== 'unavailable'),
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function placed(reference) {
|
|
961
|
+
const key = roomPathOf(reference.handle, reference.path);
|
|
962
|
+
if (reference.kind === 'link')
|
|
963
|
+
return `link ${key}`;
|
|
964
|
+
const pointer = reference.pointer
|
|
965
|
+
.split('/')
|
|
966
|
+
.map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
|
|
967
|
+
.join('/');
|
|
968
|
+
return `field ${pointer} ${key}`;
|
|
969
|
+
}
|
|
970
|
+
function roomReferencesIn(path, content, composition) {
|
|
971
|
+
if (content === undefined)
|
|
972
|
+
return [];
|
|
973
|
+
const definitions = definitionsAt(composition, path);
|
|
974
|
+
const parsed = parse(content, path, definitions);
|
|
975
|
+
if (!parsed.ok)
|
|
976
|
+
return [];
|
|
977
|
+
return roomReferences(parsed.document, definitions(parsed.document.type));
|
|
978
|
+
}
|
|
979
|
+
function* admittedRooms(changes, visible, resulting, composition) {
|
|
980
|
+
const from = new Map();
|
|
981
|
+
for (const [index, change] of changes.entries()) {
|
|
982
|
+
if (change.kind === 'move')
|
|
983
|
+
from.set(change.to, departure(changes, index));
|
|
984
|
+
}
|
|
985
|
+
for (const path of [...new Set(changes.flatMap(affected))].sort()) {
|
|
986
|
+
const now = resulting.documents[path];
|
|
987
|
+
if (now === undefined)
|
|
988
|
+
continue;
|
|
989
|
+
const held = new Set(roomReferencesIn(path, visible.documents[from.get(path) ?? path], composition).map(placed));
|
|
990
|
+
for (const reference of roomReferencesIn(path, now, composition)) {
|
|
991
|
+
if (reference.kind === 'link' || reference.crossRoom === true)
|
|
992
|
+
continue;
|
|
993
|
+
if (held.has(placed(reference)))
|
|
994
|
+
continue;
|
|
995
|
+
return refuse('unresolved-reference', path, reference.pointer);
|
|
996
|
+
}
|
|
997
|
+
yield ROW_WEIGHT + now.length;
|
|
998
|
+
}
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
function validationOf(validated) {
|
|
1002
|
+
return validated === undefined
|
|
1003
|
+
? {}
|
|
1004
|
+
: {
|
|
1005
|
+
validation: {
|
|
1006
|
+
identity: validated.snapshot.identity,
|
|
1007
|
+
version: validated.snapshot.version,
|
|
1008
|
+
source: validated.status,
|
|
1009
|
+
},
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
async function batching(input) {
|
|
1013
|
+
const relative = relativeChanges(input);
|
|
1014
|
+
if (relative.kind === 'refused')
|
|
1015
|
+
return relative;
|
|
1016
|
+
const { changes } = relative;
|
|
1017
|
+
const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
|
|
1018
|
+
if (structural !== undefined && input.unmodeled) {
|
|
1019
|
+
return refuse('model-unavailable', structural.path);
|
|
1020
|
+
}
|
|
1021
|
+
if (changes.length === 0)
|
|
1022
|
+
return refuse('no-change');
|
|
1023
|
+
const rewritten = await stepped(rewrites(changes, input.visible));
|
|
1024
|
+
if (rewritten.kind === 'refused')
|
|
1025
|
+
return rewritten;
|
|
1026
|
+
const refusal = guard(changes, input, WRITE) ?? unapplied(changes, input.visible, input.composition);
|
|
1027
|
+
if (refusal !== null)
|
|
1028
|
+
return refusal;
|
|
1029
|
+
const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
|
|
1030
|
+
const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
|
|
1031
|
+
const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }], input.composition));
|
|
1032
|
+
const judged = await judging(input.visible, resulting, input.composition, input.validate, new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path)), [...new Set(predicted.changes.flatMap(affected))]);
|
|
1033
|
+
if (judged.kind === 'refused')
|
|
1034
|
+
return judged;
|
|
1035
|
+
const crossing = await stepped(admittedRooms(changes, input.visible, resulting, input.composition));
|
|
1036
|
+
if (crossing !== null)
|
|
1037
|
+
return crossing;
|
|
1038
|
+
const limited = await stepped(exceeding(input.providerLimits, changes, predicted.changes, resulting));
|
|
881
1039
|
if (limited !== null)
|
|
882
1040
|
return limited;
|
|
883
|
-
const validated = typedPaths
|
|
884
|
-
.map((path) => governing(input.composition, path))
|
|
885
|
-
.find((model) => model.status !== 'unavailable');
|
|
886
1041
|
return {
|
|
887
1042
|
kind: 'batch',
|
|
888
1043
|
batch: immutable({
|
|
@@ -891,18 +1046,296 @@ function* batching(input) {
|
|
|
891
1046
|
changes,
|
|
892
1047
|
preconditions,
|
|
893
1048
|
...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
|
|
894
|
-
...(validated
|
|
895
|
-
? {}
|
|
896
|
-
: {
|
|
897
|
-
validation: {
|
|
898
|
-
identity: validated.snapshot.identity,
|
|
899
|
-
version: validated.snapshot.version,
|
|
900
|
-
source: validated.status,
|
|
901
|
-
},
|
|
902
|
-
}),
|
|
1049
|
+
...validationOf(judged.validated),
|
|
903
1050
|
}),
|
|
904
1051
|
};
|
|
905
1052
|
}
|
|
906
|
-
|
|
907
|
-
|
|
1053
|
+
const MOVE_DOCUMENTS = 'move-documents';
|
|
1054
|
+
function heldAt(visible, path) {
|
|
1055
|
+
return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
|
|
1056
|
+
}
|
|
1057
|
+
function arrivingText(path, to, source, moved) {
|
|
1058
|
+
const content = source.visible.documents[path];
|
|
1059
|
+
const document = source.visible.collection.document(path);
|
|
1060
|
+
if (document === undefined)
|
|
1061
|
+
return content;
|
|
1062
|
+
const definition = definitionsAt(source.composition, path)(document.type);
|
|
1063
|
+
const changes = crossed(document, definition, path, (named, href) => {
|
|
1064
|
+
const at = moved.get(named);
|
|
1065
|
+
if (at !== undefined)
|
|
1066
|
+
return target(to, href) === at ? undefined : relative(to, at);
|
|
1067
|
+
return heldAt(source.visible, named) ? roomPathOf(source.handle, named) : undefined;
|
|
1068
|
+
});
|
|
1069
|
+
if (changes.length === 0)
|
|
1070
|
+
return content;
|
|
1071
|
+
const applied = apply(document, changes, definition);
|
|
1072
|
+
return applied.outcome === 'applied' ? serialize(applied.document) : content;
|
|
1073
|
+
}
|
|
1074
|
+
function arrivalOf({ path, to }, source, moved) {
|
|
1075
|
+
const { documents, revisions, fileIds, created, changes, binaries } = source.visible;
|
|
1076
|
+
const kept = {
|
|
1077
|
+
...(fileIds[path] === undefined ? {} : { fileId: fileIds[path] }),
|
|
1078
|
+
...(created[path] === undefined ? {} : { created: created[path] }),
|
|
1079
|
+
...(changes[path] === undefined ? {} : { change: changes[path] }),
|
|
1080
|
+
};
|
|
1081
|
+
const heads = {
|
|
1082
|
+
...(revisions[path] === undefined ? {} : { revision: revisions[path] }),
|
|
1083
|
+
...kept,
|
|
1084
|
+
};
|
|
1085
|
+
const binary = binaries[path];
|
|
1086
|
+
if (binary?.state === 'accepted') {
|
|
1087
|
+
const { byteLength, mediaType, sha256 } = binary;
|
|
1088
|
+
return {
|
|
1089
|
+
kind: 'arrival',
|
|
1090
|
+
path: to,
|
|
1091
|
+
binary: { byteLength, mediaType, sha256 },
|
|
1092
|
+
...heads,
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
const content = arrivingText(path, to, source, moved);
|
|
1096
|
+
return {
|
|
1097
|
+
kind: 'arrival',
|
|
1098
|
+
path: to,
|
|
1099
|
+
content,
|
|
1100
|
+
...(content === documents[path] ? heads : kept),
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
async function moving(input) {
|
|
1104
|
+
const { source, destination, moves } = input;
|
|
1105
|
+
const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
|
|
1106
|
+
const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
|
|
1107
|
+
if (leaves !== null)
|
|
1108
|
+
return leaves;
|
|
1109
|
+
const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
|
|
1110
|
+
if (uploading !== undefined)
|
|
1111
|
+
return refuse('unknown-revision', uploading.path);
|
|
1112
|
+
const moved = new Map(moves.map(({ path, to }) => [path, to]));
|
|
1113
|
+
const arrivals = moves.map((move) => arrivalOf(move, source, moved));
|
|
1114
|
+
const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, WRITE);
|
|
1115
|
+
if (arrives !== null)
|
|
1116
|
+
return arrives;
|
|
1117
|
+
const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
|
|
1118
|
+
if (typeof bound === 'number' && moves.length > bound) {
|
|
1119
|
+
return refuse('provider-limit', undefined, MOVE_DOCUMENTS);
|
|
1120
|
+
}
|
|
1121
|
+
if (source.unmodeled)
|
|
1122
|
+
return refuse('model-unavailable', moves[0].path);
|
|
1123
|
+
const id = changeBatchIdentity();
|
|
1124
|
+
const between = {
|
|
1125
|
+
source: { room: source.room, baseRoomVersion: source.baseRoomVersion },
|
|
1126
|
+
destination: {
|
|
1127
|
+
room: destination.room,
|
|
1128
|
+
baseRoomVersion: destination.baseRoomVersion,
|
|
1129
|
+
handle: destination.handle,
|
|
1130
|
+
},
|
|
1131
|
+
moves,
|
|
1132
|
+
};
|
|
1133
|
+
const written = input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy };
|
|
1134
|
+
const leavingBatch = {
|
|
1135
|
+
id,
|
|
1136
|
+
baseRoomVersion: source.baseRoomVersion,
|
|
1137
|
+
changes: removals,
|
|
1138
|
+
preconditions: moves.map(({ path }) => existence(source.visible, path)),
|
|
1139
|
+
...written,
|
|
1140
|
+
between,
|
|
1141
|
+
};
|
|
1142
|
+
const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
|
|
1143
|
+
const left = await stepped(overlaying(source.visible, [effective], source.composition));
|
|
1144
|
+
const sourceJudged = await judging(source.visible, left, source.composition, source.validate, new Set(moves.map(({ path }) => path)), [...new Set(effective.changes.map((change) => change.path))]);
|
|
1145
|
+
if (sourceJudged.kind === 'refused')
|
|
1146
|
+
return sourceJudged;
|
|
1147
|
+
const arrivingBatch = {
|
|
1148
|
+
id,
|
|
1149
|
+
baseRoomVersion: destination.baseRoomVersion,
|
|
1150
|
+
changes: arrivals,
|
|
1151
|
+
preconditions: moves.map(({ to }) => ({ kind: 'absent', path: to })),
|
|
1152
|
+
...written,
|
|
1153
|
+
between,
|
|
1154
|
+
};
|
|
1155
|
+
const arrived = await stepped(overlaying(destination.visible, [arrivingBatch], destination.composition));
|
|
1156
|
+
const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
|
|
1157
|
+
if (destinationJudged.kind === 'refused')
|
|
1158
|
+
return destinationJudged;
|
|
1159
|
+
const limited = (await stepped(exceeding(input.providerLimits, removals, effective.changes, left))) ??
|
|
1160
|
+
(await stepped(exceeding(input.providerLimits, arrivals, arrivals, arrived)));
|
|
1161
|
+
if (limited !== null)
|
|
1162
|
+
return limited;
|
|
1163
|
+
return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
|
|
1164
|
+
}
|
|
1165
|
+
function unreadable(saved, visible, composition) {
|
|
1166
|
+
const path = saved.find(({ path: at, parts }) => (parts.fields !== undefined ||
|
|
1167
|
+
parts.sections !== undefined ||
|
|
1168
|
+
parts.preamble !== undefined) &&
|
|
1169
|
+
typedText(visible.documents[at]) &&
|
|
1170
|
+
governing(composition, at).status === 'unavailable')?.path;
|
|
1171
|
+
return path === undefined ? null : refuse('model-unavailable', path);
|
|
1172
|
+
}
|
|
1173
|
+
function changedUnderneath(saved, visible) {
|
|
1174
|
+
const parts = saved.flatMap(({ path, parts: held }) => changedParts(path, held, visible));
|
|
1175
|
+
return parts.length === 0
|
|
1176
|
+
? null
|
|
1177
|
+
: { kind: 'refused', refusal: { code: 'changed-underneath', retryable: false, parts } };
|
|
1178
|
+
}
|
|
1179
|
+
function sideOf(room, visible) {
|
|
1180
|
+
const { record } = room;
|
|
1181
|
+
return {
|
|
1182
|
+
room: record.locator,
|
|
1183
|
+
handle: record.declaration.handle,
|
|
1184
|
+
baseRoomVersion: record.roomVersion,
|
|
1185
|
+
composition: room.composition,
|
|
1186
|
+
rule: room.rule,
|
|
1187
|
+
visible,
|
|
1188
|
+
folders: room.folders,
|
|
1189
|
+
queued: queuedDocuments(room.queue),
|
|
1190
|
+
unmodeled: !room.modelled,
|
|
1191
|
+
validate: room.validate,
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
function carriedDrafts(ask, from, into) {
|
|
1195
|
+
return new Map(ask.saved.flatMap(({ path, parts }) => {
|
|
1196
|
+
const record = from.drafts.get(path);
|
|
1197
|
+
if (parts.move === undefined || record === undefined || ask.consumed.has(path))
|
|
1198
|
+
return [];
|
|
1199
|
+
const to = moveTarget(parts.move);
|
|
1200
|
+
if (into.drafts.has(to))
|
|
1201
|
+
return [];
|
|
1202
|
+
const locator = into.record.locator;
|
|
1203
|
+
return [[path, { ...record, locator, path: to, revision: roomDraftRevision() }]];
|
|
1204
|
+
}));
|
|
1205
|
+
}
|
|
1206
|
+
async function plannedMove(ask, source, into) {
|
|
1207
|
+
const destination = into.record.locator;
|
|
1208
|
+
const mixed = ask.saved.find(({ parts }) => Object.keys(parts).length !== 1 || parts.move?.room !== destination);
|
|
1209
|
+
if (mixed !== undefined)
|
|
1210
|
+
return refuse('mixed-room-move', mixed.path);
|
|
1211
|
+
const leavingVisible = await stepped(source.replay(source.queue));
|
|
1212
|
+
const changed = changedUnderneath(ask.saved, leavingVisible);
|
|
1213
|
+
if (changed !== null)
|
|
1214
|
+
return changed;
|
|
1215
|
+
const moves = ask.saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
|
|
1216
|
+
const built = await moving({
|
|
1217
|
+
source: sideOf(source, leavingVisible),
|
|
1218
|
+
destination: sideOf(into, await stepped(into.replay(into.queue))),
|
|
1219
|
+
moves,
|
|
1220
|
+
providerLimits: ask.providerLimits,
|
|
1221
|
+
local: ask.local,
|
|
1222
|
+
...(ask.writtenBy === undefined ? {} : { writtenBy: ask.writtenBy }),
|
|
1223
|
+
});
|
|
1224
|
+
if (built.kind === 'refused')
|
|
1225
|
+
return built;
|
|
1226
|
+
const { id } = built.leaving;
|
|
1227
|
+
const carried = carriedDrafts(ask, source, into);
|
|
1228
|
+
return {
|
|
1229
|
+
kind: 'planned',
|
|
1230
|
+
identifier: id,
|
|
1231
|
+
rooms: [
|
|
1232
|
+
{
|
|
1233
|
+
locator: source.record.locator,
|
|
1234
|
+
queue: [...source.queue, built.leaving],
|
|
1235
|
+
saved: new Map([[id, ask.saved]]),
|
|
1236
|
+
consumed: [...ask.consumed, ...carried.keys()],
|
|
1237
|
+
carried: [],
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
locator: destination,
|
|
1241
|
+
queue: [...into.queue, built.arriving],
|
|
1242
|
+
saved: new Map([
|
|
1243
|
+
[id, [...carried.values()].map(({ path }) => ({ path, parts: {} }))],
|
|
1244
|
+
]),
|
|
1245
|
+
consumed: [],
|
|
1246
|
+
carried: [...carried.values()],
|
|
1247
|
+
},
|
|
1248
|
+
],
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
async function plannedWithin(ask, room) {
|
|
1252
|
+
const { record } = room;
|
|
1253
|
+
const visible = await stepped(room.replay(room.queue));
|
|
1254
|
+
const changed = unreadable(ask.saved, visible, room.composition) ?? changedUnderneath(ask.saved, visible);
|
|
1255
|
+
if (changed !== null)
|
|
1256
|
+
return changed;
|
|
1257
|
+
const parts = ask.saved.map(({ path, parts: held }) => changesOf(path, held));
|
|
1258
|
+
const changes = parts.flatMap(({ changes: made }) => made);
|
|
1259
|
+
const requeue = await stepped(requeuing(room.queue, changes, visible, (batch) => room.changeable.has(batch.id), room.replay));
|
|
1260
|
+
if (requeue.kind === 'refused')
|
|
1261
|
+
return requeue;
|
|
1262
|
+
const foreign = [...requeue.withdrawn.keys()].some((origin) => {
|
|
1263
|
+
const writer = room.queue.find((batch) => batch.id === origin)?.writtenBy;
|
|
1264
|
+
return writer?.app !== ask.writtenBy?.app || writer?.release !== ask.writtenBy?.release;
|
|
1265
|
+
});
|
|
1266
|
+
if (foreign)
|
|
1267
|
+
return unqueueable([...requeue.queued].at(-1));
|
|
1268
|
+
const input = {
|
|
1269
|
+
local: ask.local,
|
|
1270
|
+
baseRoomVersion: record.roomVersion,
|
|
1271
|
+
composition: room.composition,
|
|
1272
|
+
rule: room.rule,
|
|
1273
|
+
providerLimits: ask.providerLimits,
|
|
1274
|
+
handle: record.declaration.handle,
|
|
1275
|
+
visible,
|
|
1276
|
+
folders: room.folders,
|
|
1277
|
+
changes,
|
|
1278
|
+
bases: parts.flatMap(({ bases }) => bases),
|
|
1279
|
+
queued: queuedDocuments(room.queue),
|
|
1280
|
+
writtenBy: ask.writtenBy,
|
|
1281
|
+
unmodeled: !room.modelled,
|
|
1282
|
+
validate: room.validate,
|
|
1283
|
+
};
|
|
1284
|
+
const rewritten = requeue.withdrawn.size > 0;
|
|
1285
|
+
if (rewritten) {
|
|
1286
|
+
const whole = await batching({
|
|
1287
|
+
...input,
|
|
1288
|
+
providerLimits: [],
|
|
1289
|
+
queued: new Set([...input.queued, ...requeue.queued]),
|
|
1290
|
+
});
|
|
1291
|
+
if (whole.kind === 'refused')
|
|
1292
|
+
return whole;
|
|
1293
|
+
}
|
|
1294
|
+
const own = !rewritten
|
|
1295
|
+
? await batching(input)
|
|
1296
|
+
: requeue.changes.length === 0
|
|
1297
|
+
? null
|
|
1298
|
+
: await batching({
|
|
1299
|
+
...input,
|
|
1300
|
+
visible: await stepped(room.replay(requeue.queue)),
|
|
1301
|
+
changes: requeue.changes,
|
|
1302
|
+
queued: queuedDocuments(requeue.queue),
|
|
1303
|
+
});
|
|
1304
|
+
if (own?.kind === 'refused')
|
|
1305
|
+
return own;
|
|
1306
|
+
const queue = own === null ? requeue.queue : [...requeue.queue, own.batch];
|
|
1307
|
+
const saved = new Map();
|
|
1308
|
+
for (const [origin, rebuilt] of requeue.withdrawn) {
|
|
1309
|
+
if (rebuilt === null)
|
|
1310
|
+
continue;
|
|
1311
|
+
const limited = await stepped(limiting(queue, rebuilt, ask.providerLimits, room.replay));
|
|
1312
|
+
if (limited !== null)
|
|
1313
|
+
return { kind: 'refused', refusal: limited };
|
|
1314
|
+
saved.set(rebuilt.id, withdrawnDrafts(room.saved.get(origin) ?? [], changes));
|
|
1315
|
+
}
|
|
1316
|
+
if (own !== null) {
|
|
1317
|
+
saved.set(own.batch.id, ask.saved.filter(({ path }) => !requeue.queued.has(path)));
|
|
1318
|
+
}
|
|
1319
|
+
const carried = carriedDrafts(ask, room, room);
|
|
1320
|
+
return {
|
|
1321
|
+
kind: 'planned',
|
|
1322
|
+
identifier: own?.batch.id ??
|
|
1323
|
+
[...requeue.withdrawn].map(([origin, rebuilt]) => rebuilt?.id ?? origin).at(-1),
|
|
1324
|
+
rooms: [
|
|
1325
|
+
{
|
|
1326
|
+
locator: record.locator,
|
|
1327
|
+
queue,
|
|
1328
|
+
saved,
|
|
1329
|
+
consumed: [...ask.consumed, ...carried.keys()],
|
|
1330
|
+
carried: [...carried.values()],
|
|
1331
|
+
},
|
|
1332
|
+
],
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
export function destination(locator, saved) {
|
|
1336
|
+
const into = saved.find(({ parts }) => parts.move?.room !== undefined && parts.move.room !== locator);
|
|
1337
|
+
return into?.parts.move?.room ?? null;
|
|
1338
|
+
}
|
|
1339
|
+
export function planSave(ask, room, into) {
|
|
1340
|
+
return into === undefined ? plannedWithin(ask, room) : plannedMove(ask, room, into);
|
|
908
1341
|
}
|