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