@agentshouse/mdruntime 0.1.0-c4478f1a5ac2a → 0.1.0-c44d5cc00aee5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -19
- package/dist/acceptance.d.ts +3 -0
- package/dist/acceptance.d.ts.map +1 -0
- package/dist/acceptance.js +299 -0
- package/dist/act-lanes.d.ts +30 -0
- package/dist/act-lanes.d.ts.map +1 -0
- package/dist/act-lanes.js +225 -0
- package/dist/actions.d.ts +26 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +302 -0
- package/dist/adapter.d.ts +174 -119
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +6 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +35 -41
- package/dist/binary.d.ts +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 +3 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +11 -0
- package/dist/client.d.ts +42 -24
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +133 -27
- package/dist/collection.d.ts +29 -21
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +48 -290
- package/dist/compile.d.ts +18 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +724 -0
- package/dist/connection.d.ts +7 -118
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +109 -2967
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/{chat/context.js → context.js} +1 -4
- package/dist/conversation-live.d.ts +143 -0
- package/dist/conversation-live.d.ts.map +1 -0
- package/dist/conversation-live.js +254 -0
- package/dist/conversation-row.d.ts +33 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +53 -0
- package/dist/conversation.d.ts +78 -47
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1069 -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 +150 -0
- package/dist/durability.d.ts +23 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +9 -0
- package/dist/entry.d.ts +5 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +33 -28
- package/dist/expectation.d.ts +26 -13
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +72 -19
- package/dist/failure.d.ts +3 -0
- package/dist/failure.d.ts.map +1 -1
- package/dist/failure.js +6 -0
- package/dist/figures.d.ts +49 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +197 -0
- package/dist/file.d.ts +2 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +7 -122
- package/dist/holdings.d.ts +29 -0
- package/dist/holdings.d.ts.map +1 -0
- package/dist/holdings.js +95 -0
- package/dist/host.d.ts +27 -4
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +168 -27
- 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 +40 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -3
- package/dist/install.d.ts +54 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +530 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +277 -0
- package/dist/listing.d.ts +48 -0
- package/dist/listing.d.ts.map +1 -0
- package/dist/listing.js +139 -0
- package/dist/live.d.ts +23 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +165 -0
- package/dist/locks.d.ts +7 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +32 -0
- package/dist/lost.d.ts +5 -0
- package/dist/lost.d.ts.map +1 -0
- package/dist/lost.js +49 -0
- package/dist/media.d.ts +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +218 -0
- package/dist/memory.d.ts +23 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +282 -110
- package/dist/messages.d.ts +56 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +158 -0
- package/dist/model.d.ts +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 +153 -0
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +19 -3
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +116 -20
- package/dist/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/opening.d.ts +4 -0
- package/dist/opening.d.ts.map +1 -0
- package/dist/opening.js +53 -0
- package/dist/outcome.d.ts +52 -8
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +57 -0
- package/dist/overlay.d.ts +8 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +131 -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 +4 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +13 -0
- package/dist/paths.d.ts +2 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +26 -0
- package/dist/preparation.d.ts +20 -0
- package/dist/preparation.d.ts.map +1 -0
- package/dist/preparation.js +227 -0
- package/dist/presence.d.ts +52 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +220 -0
- package/dist/queries.d.ts +16 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +282 -0
- package/dist/query.d.ts +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 -7
- package/dist/questions.d.ts.map +1 -0
- package/dist/{chat/questions.js → questions.js} +11 -7
- package/dist/queue.d.ts +40 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +450 -0
- package/dist/reach.d.ts +21 -7
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +342 -97
- package/dist/react.d.ts +19 -4
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +40 -18
- package/dist/{chat/read.d.ts → read.d.ts} +2 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/{chat/read.js → read.js} +4 -0
- package/dist/readiness.d.ts +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 +55 -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 +194 -118
- package/dist/room-drafts.d.ts +24 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +217 -0
- package/dist/rows.d.ts +31 -29
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +60 -118
- package/dist/runtime.d.ts +34 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/save.d.ts +43 -48
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +791 -274
- package/dist/saves.d.ts +32 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +410 -0
- package/dist/search.d.ts +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 +33 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +302 -0
- package/dist/socket/admission.d.ts +7 -0
- package/dist/socket/admission.d.ts.map +1 -0
- package/dist/socket/admission.js +39 -0
- package/dist/socket/answers.d.ts +6 -0
- package/dist/socket/answers.d.ts.map +1 -0
- package/dist/socket/answers.js +20 -0
- package/dist/socket/lanes.d.ts +28 -0
- package/dist/socket/lanes.d.ts.map +1 -0
- package/dist/socket/lanes.js +103 -0
- package/dist/socket/reads.d.ts +10 -0
- package/dist/socket/reads.d.ts.map +1 -0
- package/dist/socket/reads.js +31 -0
- package/dist/socket/session.d.ts +38 -0
- package/dist/socket/session.d.ts.map +1 -0
- package/dist/socket/session.js +23 -0
- package/dist/socket.d.ts +67 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +164 -0
- package/dist/source.d.ts +54 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +82 -0
- package/dist/status.d.ts +5 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +34 -0
- package/dist/store/readiness.d.ts +10 -0
- package/dist/store/readiness.d.ts.map +1 -0
- package/dist/store/readiness.js +52 -0
- package/dist/store/records.d.ts +214 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +438 -0
- package/dist/store/room-held.d.ts +8 -0
- package/dist/store/room-held.d.ts.map +1 -0
- package/dist/store/room-held.js +17 -0
- package/dist/store/space.d.ts +11 -0
- package/dist/store/space.d.ts.map +1 -0
- package/dist/store/space.js +41 -0
- package/dist/store/watchers.d.ts +17 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +56 -0
- package/dist/store/writes.d.ts +24 -0
- package/dist/store/writes.d.ts.map +1 -0
- package/dist/store/writes.js +125 -0
- package/dist/store.d.ts +111 -142
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +159 -687
- package/dist/stored.d.ts +157 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +649 -0
- package/dist/surface.d.ts +88 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +220 -0
- package/dist/sync.d.ts +19 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +535 -0
- package/dist/tables.d.ts +415 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +761 -0
- package/dist/tallies.d.ts +18 -0
- package/dist/tallies.d.ts.map +1 -0
- package/dist/tallies.js +128 -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 +228 -36
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +75 -33
- package/package.json +19 -19
- package/AGENTS.md +0 -309
- 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/log.d.ts +0 -5
- package/dist/log.d.ts.map +0 -1
- package/dist/log.js +0 -14
- package/dist/read-side.d.ts +0 -25
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -639
package/dist/save.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
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) {
|
|
9
12
|
return {
|
|
10
13
|
kind: 'refused',
|
|
11
14
|
refusal: {
|
|
@@ -13,14 +16,15 @@ function refuse(code, file, location) {
|
|
|
13
16
|
retryable: false,
|
|
14
17
|
...(file === undefined ? {} : { file }),
|
|
15
18
|
...(location === undefined ? {} : { location }),
|
|
19
|
+
...(line === undefined ? {} : { line }),
|
|
16
20
|
},
|
|
17
21
|
};
|
|
18
22
|
}
|
|
23
|
+
function refusedBy(refusal, file) {
|
|
24
|
+
return refuse(refusal.rule, file, refusal.location, refusal.line);
|
|
25
|
+
}
|
|
19
26
|
function repointed(document, definition, from, to, moves, settled) {
|
|
20
|
-
const
|
|
21
|
-
if (!parsed.ok)
|
|
22
|
-
return [];
|
|
23
|
-
let next = parsed.document;
|
|
27
|
+
const changes = [];
|
|
24
28
|
const names = new Set();
|
|
25
29
|
for (const { pointer, value } of definition === undefined
|
|
26
30
|
? []
|
|
@@ -32,41 +36,54 @@ function repointed(document, definition, from, to, moves, settled) {
|
|
|
32
36
|
const destination = moves.get(named) ?? named;
|
|
33
37
|
if (target(to, value) === destination)
|
|
34
38
|
continue;
|
|
35
|
-
|
|
39
|
+
changes.push({
|
|
36
40
|
kind: 'reference',
|
|
37
41
|
pointer,
|
|
38
42
|
value: retarget(value, relative(to, destination)),
|
|
39
43
|
prior: value,
|
|
40
44
|
});
|
|
41
|
-
if (applied.outcome !== 'applied')
|
|
42
|
-
continue;
|
|
43
|
-
next = applied.document;
|
|
44
45
|
names.add(name);
|
|
45
46
|
}
|
|
47
|
+
if (changes.length === 0)
|
|
48
|
+
return [];
|
|
49
|
+
const applied = apply(document, changes, definition);
|
|
50
|
+
if (applied.outcome !== 'applied')
|
|
51
|
+
return [];
|
|
52
|
+
const next = applied.document;
|
|
46
53
|
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
47
54
|
}
|
|
48
|
-
function edited(document,
|
|
49
|
-
|
|
55
|
+
function edited(document, changes, definition) {
|
|
56
|
+
const parts = new Map();
|
|
50
57
|
for (const change of changes) {
|
|
51
|
-
if (change.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
+
});
|
|
65
|
+
if (change.kind === 'preamble')
|
|
66
|
+
parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
67
|
+
if (change.kind === 'section')
|
|
68
|
+
parts.set(`section ${change.name}`, {
|
|
58
69
|
kind: 'section',
|
|
59
70
|
name: change.name,
|
|
60
71
|
value: change.value,
|
|
61
|
-
prior:
|
|
72
|
+
prior: document.sections[change.name] ?? '',
|
|
62
73
|
});
|
|
63
|
-
if (applied.outcome === 'applied')
|
|
64
|
-
next = applied.document;
|
|
65
74
|
}
|
|
66
|
-
|
|
75
|
+
if (parts.size === 0)
|
|
76
|
+
return document;
|
|
77
|
+
const applied = apply(document, [...parts.values()], definition);
|
|
78
|
+
return applied.outcome === 'applied' ? applied.document : document;
|
|
67
79
|
}
|
|
68
80
|
function bodies(path, before, after) {
|
|
69
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
|
+
}
|
|
70
87
|
if (after.preamble !== before.preamble) {
|
|
71
88
|
changes.push({ kind: 'preamble', path, value: after.preamble });
|
|
72
89
|
}
|
|
@@ -77,8 +94,8 @@ function bodies(path, before, after) {
|
|
|
77
94
|
}
|
|
78
95
|
return changes;
|
|
79
96
|
}
|
|
80
|
-
function relinks(path, moved, before, moves) {
|
|
81
|
-
|
|
97
|
+
function relinks(path, moved, before, moves, definition) {
|
|
98
|
+
const changes = [];
|
|
82
99
|
for (const link of links(before, path)) {
|
|
83
100
|
const destination = moves.get(link.target) ?? link.target;
|
|
84
101
|
if (destination === link.target && moved === path)
|
|
@@ -86,20 +103,14 @@ function relinks(path, moved, before, moves) {
|
|
|
86
103
|
const to = relative(moved, destination);
|
|
87
104
|
if (to === relative(path, link.target))
|
|
88
105
|
continue;
|
|
89
|
-
|
|
90
|
-
if (applied.outcome === 'refuse') {
|
|
91
|
-
return {
|
|
92
|
-
kind: 'refused',
|
|
93
|
-
refusal: {
|
|
94
|
-
code: applied.refusal.rule,
|
|
95
|
-
retryable: false,
|
|
96
|
-
file: path,
|
|
97
|
-
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
after = applied.document;
|
|
106
|
+
changes.push({ kind: 'link', href: link.href, to });
|
|
102
107
|
}
|
|
108
|
+
if (changes.length === 0)
|
|
109
|
+
return { kind: 'changes', changes: [] };
|
|
110
|
+
const applied = apply(before, changes, definition);
|
|
111
|
+
if (applied.outcome === 'refuse')
|
|
112
|
+
return refusedBy(applied.refusal, path);
|
|
113
|
+
const after = applied.document;
|
|
103
114
|
if (after === before)
|
|
104
115
|
return { kind: 'changes', changes: [] };
|
|
105
116
|
if (before.type === undefined) {
|
|
@@ -107,19 +118,25 @@ function relinks(path, moved, before, moves) {
|
|
|
107
118
|
}
|
|
108
119
|
return { kind: 'changes', changes: bodies(path, before, after) };
|
|
109
120
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
continue;
|
|
115
|
-
if (item.kind === 'section' || item.kind === 'preamble')
|
|
116
|
-
merged.splice(index, 1);
|
|
117
|
-
}
|
|
121
|
+
function partKey(change) {
|
|
122
|
+
return change.kind === 'section' || change.kind === 'field'
|
|
123
|
+
? `${change.kind} ${change.name}`
|
|
124
|
+
: change.kind;
|
|
118
125
|
}
|
|
119
|
-
|
|
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));
|
|
134
|
+
}
|
|
135
|
+
function* rewrites(changes, visible) {
|
|
120
136
|
const moves = new Map();
|
|
121
137
|
const settled = new Set();
|
|
122
|
-
|
|
138
|
+
const parts = new Map();
|
|
139
|
+
for (const [index, change] of changes.entries()) {
|
|
123
140
|
if (change.kind === 'move')
|
|
124
141
|
moves.set(change.path, change.to);
|
|
125
142
|
if (change.kind === 'remove' ||
|
|
@@ -130,54 +147,142 @@ function rewrites(changes, visible) {
|
|
|
130
147
|
}
|
|
131
148
|
if (change.kind === 'field')
|
|
132
149
|
settled.add(`${change.path} ${change.name}`);
|
|
150
|
+
if (isPart(change)) {
|
|
151
|
+
const held = parts.get(change.path);
|
|
152
|
+
if (held === undefined)
|
|
153
|
+
parts.set(change.path, [index]);
|
|
154
|
+
else
|
|
155
|
+
held.push(index);
|
|
156
|
+
}
|
|
133
157
|
}
|
|
134
158
|
if (moves.size === 0)
|
|
135
159
|
return { kind: 'changes', changes };
|
|
160
|
+
const names = movedNames(moves);
|
|
136
161
|
const merged = [...changes];
|
|
137
162
|
const added = [];
|
|
138
|
-
for (const [path, document] of documentsOf(visible.collection)) {
|
|
163
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
139
164
|
if (settled.has(path))
|
|
140
165
|
continue;
|
|
141
166
|
const moved = moves.get(path) ?? path;
|
|
142
|
-
const definition =
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
const definition = definitionsAt(visible.collection.composition, path)(document.type);
|
|
168
|
+
const repointing = repointed(document, definition, path, moved, moves, settled);
|
|
169
|
+
added.push(...repointing);
|
|
170
|
+
const own = parts.get(path) ?? [];
|
|
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);
|
|
145
174
|
if (relinked.kind === 'refused')
|
|
146
175
|
return relinked;
|
|
176
|
+
const keyed = new Map();
|
|
177
|
+
for (const index of own) {
|
|
178
|
+
const key = partKey(merged[index]);
|
|
179
|
+
if (!keyed.has(key))
|
|
180
|
+
keyed.set(key, index);
|
|
181
|
+
}
|
|
147
182
|
for (const change of relinked.changes) {
|
|
148
183
|
if (change.kind === 'replace') {
|
|
149
|
-
|
|
184
|
+
for (const index of own) {
|
|
185
|
+
const kind = merged[index]?.kind;
|
|
186
|
+
if (kind === 'field' || kind === 'section' || kind === 'preamble')
|
|
187
|
+
merged[index] = null;
|
|
188
|
+
}
|
|
150
189
|
added.push(change);
|
|
151
190
|
continue;
|
|
152
191
|
}
|
|
153
|
-
const index =
|
|
154
|
-
|
|
155
|
-
(change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
|
|
156
|
-
if (index === -1)
|
|
192
|
+
const index = keyed.get(partKey(change));
|
|
193
|
+
if (index === undefined)
|
|
157
194
|
added.push(change);
|
|
158
195
|
else
|
|
159
196
|
merged[index] = change;
|
|
160
197
|
}
|
|
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);
|
|
161
201
|
}
|
|
162
|
-
return {
|
|
202
|
+
return {
|
|
203
|
+
kind: 'changes',
|
|
204
|
+
changes: [...added, ...merged.filter((change) => change !== null)],
|
|
205
|
+
};
|
|
163
206
|
}
|
|
164
|
-
function anticipated(changes, preconditions, rewritten, visible) {
|
|
165
|
-
const
|
|
207
|
+
function* anticipated(changes, preconditions, rewritten, visible) {
|
|
208
|
+
const given = new Set(changes);
|
|
209
|
+
const derived = rewritten.filter((change) => !given.has(change));
|
|
166
210
|
return {
|
|
167
211
|
changes: rewritten,
|
|
168
|
-
preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
|
|
212
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
|
|
169
213
|
};
|
|
170
214
|
}
|
|
171
|
-
export function providerRewritten(batch, visible) {
|
|
172
|
-
const rewritten = rewrites(batch.changes, visible);
|
|
215
|
+
export function* providerRewritten(batch, visible) {
|
|
216
|
+
const rewritten = yield* rewrites(batch.changes, visible);
|
|
173
217
|
if (rewritten.kind === 'refused')
|
|
174
218
|
return batch;
|
|
175
219
|
return {
|
|
176
220
|
...batch,
|
|
177
|
-
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
221
|
+
...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
|
|
222
|
+
};
|
|
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],
|
|
178
275
|
};
|
|
179
276
|
}
|
|
180
|
-
function
|
|
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
|
+
}
|
|
285
|
+
function* incoming(collection, removed, candidates) {
|
|
181
286
|
const found = new Map();
|
|
182
287
|
const note = (target, file) => {
|
|
183
288
|
if (!removed.has(target))
|
|
@@ -186,14 +291,21 @@ function incoming(collection, removed, candidates) {
|
|
|
186
291
|
referrers.add(file);
|
|
187
292
|
found.set(target, referrers);
|
|
188
293
|
};
|
|
189
|
-
const read =
|
|
190
|
-
|
|
191
|
-
|
|
294
|
+
const read = function* (paths) {
|
|
295
|
+
const seen = new Set();
|
|
296
|
+
for (const path of paths) {
|
|
297
|
+
if (seen.has(path))
|
|
298
|
+
continue;
|
|
299
|
+
seen.add(path);
|
|
192
300
|
const document = collection.document(path);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
301
|
+
if (document !== undefined)
|
|
302
|
+
yield [path, document];
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
for (const [path, document] of candidates === null
|
|
306
|
+
? yield* documentsOf(collection)
|
|
307
|
+
: read(candidates)) {
|
|
308
|
+
const definition = definitionsAt(collection.composition, path)(document.type);
|
|
197
309
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
198
310
|
if (to.includes(ATTACHMENT))
|
|
199
311
|
continue;
|
|
@@ -201,8 +313,9 @@ function incoming(collection, removed, candidates) {
|
|
|
201
313
|
if (named !== null)
|
|
202
314
|
note(named, path);
|
|
203
315
|
}
|
|
316
|
+
yield ROW_WEIGHT + collection.text[path].length;
|
|
204
317
|
}
|
|
205
|
-
const [first] =
|
|
318
|
+
const [first] = yield* sorting(found.keys());
|
|
206
319
|
if (first === undefined)
|
|
207
320
|
return null;
|
|
208
321
|
const every = new Set();
|
|
@@ -215,14 +328,16 @@ function incoming(collection, removed, candidates) {
|
|
|
215
328
|
code: 'referenced-missing-file',
|
|
216
329
|
retryable: false,
|
|
217
330
|
file: first,
|
|
218
|
-
referrers:
|
|
331
|
+
referrers: yield* sorting(every),
|
|
219
332
|
},
|
|
220
333
|
};
|
|
221
334
|
}
|
|
222
335
|
function affected(change) {
|
|
223
336
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
224
337
|
}
|
|
225
|
-
|
|
338
|
+
const WRITE = ['write'];
|
|
339
|
+
const MOVE_OUT = ['write', 'erase'];
|
|
340
|
+
function guard(changes, input, removing) {
|
|
226
341
|
const overridden = new Map();
|
|
227
342
|
const existing = {
|
|
228
343
|
has: (path) => overridden.get(path) ??
|
|
@@ -230,21 +345,17 @@ function guard(changes, input) {
|
|
|
230
345
|
add: (path) => void overridden.set(path, true),
|
|
231
346
|
delete: (path) => void overridden.set(path, false),
|
|
232
347
|
};
|
|
348
|
+
const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
|
|
233
349
|
for (const [index, change] of changes.entries()) {
|
|
234
350
|
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);
|
|
351
|
+
const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
|
|
352
|
+
if (refused !== null)
|
|
353
|
+
return refuse(refused.code, refused.path);
|
|
244
354
|
}
|
|
245
|
-
if (change.kind === 'create') {
|
|
246
|
-
|
|
247
|
-
|
|
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);
|
|
248
359
|
existing.add(change.path);
|
|
249
360
|
continue;
|
|
250
361
|
}
|
|
@@ -254,6 +365,9 @@ function guard(changes, input) {
|
|
|
254
365
|
input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
|
|
255
366
|
return refuse('path-collision', change.path);
|
|
256
367
|
}
|
|
368
|
+
const taken = occupied(change.path);
|
|
369
|
+
if (taken !== null)
|
|
370
|
+
return refuse('path-collision', taken);
|
|
257
371
|
if (!input.local &&
|
|
258
372
|
existing.has(change.path) &&
|
|
259
373
|
input.visible.revisions[departure(changes, index)] === undefined) {
|
|
@@ -265,8 +379,9 @@ function guard(changes, input) {
|
|
|
265
379
|
if (change.kind === 'move') {
|
|
266
380
|
if (!existing.has(change.path))
|
|
267
381
|
return refuse('path-absent', change.path);
|
|
268
|
-
|
|
269
|
-
|
|
382
|
+
const taken = existing.has(change.to) ? change.to : occupied(change.to);
|
|
383
|
+
if (taken !== null)
|
|
384
|
+
return refuse('path-collision', taken);
|
|
270
385
|
if (unrevised(input, change.path)) {
|
|
271
386
|
return refuse('unknown-revision', change.path);
|
|
272
387
|
}
|
|
@@ -284,7 +399,7 @@ function guard(changes, input) {
|
|
|
284
399
|
continue;
|
|
285
400
|
}
|
|
286
401
|
if (change.kind === 'replace') {
|
|
287
|
-
if (
|
|
402
|
+
if (unrevised(input, change.path)) {
|
|
288
403
|
return refuse('unknown-revision', change.path);
|
|
289
404
|
}
|
|
290
405
|
continue;
|
|
@@ -296,7 +411,23 @@ function guard(changes, input) {
|
|
|
296
411
|
return null;
|
|
297
412
|
}
|
|
298
413
|
function unrevised(input, path) {
|
|
299
|
-
return
|
|
414
|
+
return !input.local && !input.queued.has(path) && input.visible.revisions[path] === undefined;
|
|
415
|
+
}
|
|
416
|
+
function queuedDocuments(queue) {
|
|
417
|
+
const paths = new Set();
|
|
418
|
+
for (const batch of queue) {
|
|
419
|
+
for (const change of batch.changes) {
|
|
420
|
+
if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
|
|
421
|
+
continue;
|
|
422
|
+
if (change.kind === 'remove')
|
|
423
|
+
paths.delete(change.path);
|
|
424
|
+
else if (change.kind !== 'move')
|
|
425
|
+
paths.add(change.path);
|
|
426
|
+
else if (paths.delete(change.path))
|
|
427
|
+
paths.add(change.to);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return paths;
|
|
300
431
|
}
|
|
301
432
|
function identity(precondition) {
|
|
302
433
|
const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
|
|
@@ -315,7 +446,7 @@ function sameTarget(precondition, change) {
|
|
|
315
446
|
return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
|
|
316
447
|
((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
|
|
317
448
|
}
|
|
318
|
-
function preconditionsOf(changes, visible, bases) {
|
|
449
|
+
function* preconditionsOf(changes, visible, bases) {
|
|
319
450
|
const preconditions = [];
|
|
320
451
|
const seen = new Set();
|
|
321
452
|
const add = (precondition) => {
|
|
@@ -326,6 +457,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
326
457
|
preconditions.push(precondition);
|
|
327
458
|
};
|
|
328
459
|
for (const change of changes) {
|
|
460
|
+
yield ROW_WEIGHT;
|
|
329
461
|
if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
|
|
330
462
|
const based = bases.find((base) => sameTarget(base, change));
|
|
331
463
|
if (based !== undefined) {
|
|
@@ -373,10 +505,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
373
505
|
function depth(path) {
|
|
374
506
|
return path.split('/').length;
|
|
375
507
|
}
|
|
376
|
-
function
|
|
508
|
+
function* exceeding(limits, changes, effective, resulting) {
|
|
377
509
|
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
378
510
|
const created = changes
|
|
379
|
-
.filter((change) => change.kind === 'create' ||
|
|
511
|
+
.filter((change) => change.kind === 'create' ||
|
|
512
|
+
change.kind === 'move' ||
|
|
513
|
+
change.kind === 'binary' ||
|
|
514
|
+
change.kind === 'arrival')
|
|
380
515
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
381
516
|
for (const limit of limits) {
|
|
382
517
|
const value = limit.value;
|
|
@@ -390,9 +525,13 @@ function exceeded(limits, changes, effective, resulting) {
|
|
|
390
525
|
return refuse('provider-limit', undefined, limit.name);
|
|
391
526
|
}
|
|
392
527
|
if (limit.name === 'document-size') {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
528
|
+
for (const path of written) {
|
|
529
|
+
const text = resulting.documents[path] ?? '';
|
|
530
|
+
if (new TextEncoder().encode(text).byteLength > value) {
|
|
531
|
+
return refuse('provider-limit', path, limit.name);
|
|
532
|
+
}
|
|
533
|
+
yield ROW_WEIGHT + text.length;
|
|
534
|
+
}
|
|
396
535
|
}
|
|
397
536
|
if (limit.name === 'path-length') {
|
|
398
537
|
const long = created.find((path) => path.length > value);
|
|
@@ -410,67 +549,69 @@ function exceeded(limits, changes, effective, resulting) {
|
|
|
410
549
|
function shellRelative(text, path, handle, definition) {
|
|
411
550
|
if (!text.includes(`/rooms/${handle}/`))
|
|
412
551
|
return null;
|
|
413
|
-
const parsed = parse(text, path);
|
|
552
|
+
const parsed = parse(text, path, definition);
|
|
414
553
|
if (!parsed.ok)
|
|
415
554
|
return null;
|
|
416
555
|
const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
|
|
417
556
|
return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
|
|
418
557
|
}
|
|
419
558
|
function relativeField(path, name, value, handle, definition) {
|
|
420
|
-
|
|
421
|
-
if (definition === undefined ||
|
|
422
|
-
!opened.ok ||
|
|
423
|
-
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
559
|
+
if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
424
560
|
return value;
|
|
425
|
-
const held = apply(
|
|
561
|
+
const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
|
|
426
562
|
if (held.outcome !== 'applied')
|
|
427
563
|
return value;
|
|
428
564
|
const result = relativize(held.document, path, handle, definition);
|
|
429
565
|
return result.outcome === 'applied' ? result.document.fields[name] : value;
|
|
430
566
|
}
|
|
431
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) {
|
|
432
578
|
const { handle, composition, visible } = input;
|
|
433
|
-
const typed = (path) => (type) => definitionOf(governing(composition, path), type);
|
|
434
579
|
const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
return value === change.value ? change : { ...change, value };
|
|
445
|
-
}
|
|
446
|
-
if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
|
|
447
|
-
const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
|
|
448
|
-
return content === change.content ? change : { ...change, content };
|
|
449
|
-
}
|
|
450
|
-
if (change.kind !== 'create')
|
|
451
|
-
return change;
|
|
452
|
-
const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
|
|
453
|
-
const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
|
|
454
|
-
name,
|
|
455
|
-
relativeField(change.path, name, value, handle, definition(type)),
|
|
456
|
-
]));
|
|
457
|
-
const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
|
|
458
|
-
return { ...change, fields, sections };
|
|
459
|
-
});
|
|
460
|
-
}
|
|
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;
|
|
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 } };
|
|
585
|
+
}
|
|
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 } };
|
|
469
589
|
}
|
|
470
|
-
|
|
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
|
+
};
|
|
471
612
|
}
|
|
472
613
|
function unqueueable(file) {
|
|
473
|
-
return
|
|
614
|
+
return refuse('unknown-revision', file);
|
|
474
615
|
}
|
|
475
616
|
function isPart(change) {
|
|
476
617
|
return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
|
|
@@ -478,29 +619,64 @@ function isPart(change) {
|
|
|
478
619
|
function touched(change) {
|
|
479
620
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
480
621
|
}
|
|
481
|
-
function
|
|
482
|
-
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);
|
|
483
645
|
if (!parsed.ok)
|
|
484
646
|
return null;
|
|
485
647
|
const document = parsed.document;
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
648
|
+
const parts = new Map();
|
|
649
|
+
for (const change of changes) {
|
|
650
|
+
if (change.kind === 'field')
|
|
651
|
+
parts.set(`field ${change.name}`, {
|
|
652
|
+
kind: 'field',
|
|
653
|
+
name: change.name,
|
|
654
|
+
value: change.value,
|
|
655
|
+
prior: document.fields[change.name],
|
|
656
|
+
});
|
|
657
|
+
else if (change.kind === 'section')
|
|
658
|
+
parts.set(`section ${change.name}`, {
|
|
495
659
|
kind: 'section',
|
|
496
660
|
name: change.name,
|
|
497
661
|
value: change.value,
|
|
498
|
-
prior: document.sections[change.name],
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
|
|
662
|
+
prior: document.sections[change.name] ?? '',
|
|
663
|
+
});
|
|
664
|
+
else
|
|
665
|
+
parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
666
|
+
}
|
|
667
|
+
return apply(document, [...parts.values()], definitions(document.type));
|
|
502
668
|
}
|
|
503
|
-
function
|
|
669
|
+
function settledParts(changes, deferred, composition) {
|
|
670
|
+
for (const [held, parts] of deferred) {
|
|
671
|
+
const content = partsApplied(held.content, held.path, parts, composition);
|
|
672
|
+
if (content === null)
|
|
673
|
+
return held.path;
|
|
674
|
+
changes[changes.indexOf(held)] = { ...held, content };
|
|
675
|
+
}
|
|
676
|
+
deferred.clear();
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
function folded(changes, change, appending, deferred) {
|
|
504
680
|
const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
|
|
505
681
|
const whole = held === undefined ? -1 : changes.indexOf(held);
|
|
506
682
|
const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
|
|
@@ -521,10 +697,11 @@ function folded(changes, change, appending) {
|
|
|
521
697
|
return 'appended';
|
|
522
698
|
}
|
|
523
699
|
if ('content' in held) {
|
|
524
|
-
const
|
|
525
|
-
if (
|
|
526
|
-
|
|
527
|
-
|
|
700
|
+
const parts = deferred.get(held);
|
|
701
|
+
if (parts === undefined)
|
|
702
|
+
deferred.set(held, [change]);
|
|
703
|
+
else
|
|
704
|
+
parts.push(change);
|
|
528
705
|
return 'folded';
|
|
529
706
|
}
|
|
530
707
|
if (change.kind === 'preamble')
|
|
@@ -555,17 +732,23 @@ function folded(changes, change, appending) {
|
|
|
555
732
|
changes.push(change);
|
|
556
733
|
return 'appended';
|
|
557
734
|
}
|
|
558
|
-
function relinked(batch, move, before, after, appending) {
|
|
559
|
-
const derived = rewrites([move], after);
|
|
735
|
+
function* relinked(batch, move, before, after, appending) {
|
|
736
|
+
const derived = yield* rewrites([move], after);
|
|
560
737
|
if (derived.kind === 'refused')
|
|
561
738
|
return derived;
|
|
562
739
|
const changes = [...batch.changes];
|
|
563
740
|
const appended = [];
|
|
564
741
|
let changed = false;
|
|
742
|
+
const deferred = new Map();
|
|
565
743
|
for (const change of derived.changes) {
|
|
566
744
|
if (change === move)
|
|
567
745
|
continue;
|
|
568
|
-
const
|
|
746
|
+
const unsettled = isPart(change)
|
|
747
|
+
? null
|
|
748
|
+
: settledParts(changes, deferred, after.collection.composition);
|
|
749
|
+
if (unsettled !== null)
|
|
750
|
+
return unqueueable(unsettled);
|
|
751
|
+
const outcome = folded(changes, change, appending, deferred);
|
|
569
752
|
if (outcome === 'refused')
|
|
570
753
|
return unqueueable(change.path);
|
|
571
754
|
if (outcome === 'appended')
|
|
@@ -573,9 +756,13 @@ function relinked(batch, move, before, after, appending) {
|
|
|
573
756
|
if (outcome !== 'skipped')
|
|
574
757
|
changed = true;
|
|
575
758
|
}
|
|
759
|
+
const unsettled = settledParts(changes, deferred, after.collection.composition);
|
|
760
|
+
if (unsettled !== null)
|
|
761
|
+
return unqueueable(unsettled);
|
|
576
762
|
if (!changed)
|
|
577
763
|
return { kind: 'relinked', batch };
|
|
578
764
|
const held = new Set(batch.preconditions.map(identity));
|
|
765
|
+
const added = yield* preconditionsOf(appended, before, []);
|
|
579
766
|
return {
|
|
580
767
|
kind: 'relinked',
|
|
581
768
|
batch: immutable({
|
|
@@ -584,7 +771,7 @@ function relinked(batch, move, before, after, appending) {
|
|
|
584
771
|
changes,
|
|
585
772
|
preconditions: [
|
|
586
773
|
...batch.preconditions,
|
|
587
|
-
...
|
|
774
|
+
...added.filter((precondition) => !held.has(identity(precondition))),
|
|
588
775
|
],
|
|
589
776
|
}),
|
|
590
777
|
};
|
|
@@ -630,11 +817,10 @@ function withdrawnFile(batch, change, at) {
|
|
|
630
817
|
];
|
|
631
818
|
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
632
819
|
}
|
|
633
|
-
|
|
820
|
+
function* requeuing(queue, changes, visible, changeable, state) {
|
|
634
821
|
const next = [...queue];
|
|
635
822
|
const origins = new Map();
|
|
636
823
|
const withdrawn = new Map();
|
|
637
|
-
const owners = new Set();
|
|
638
824
|
const queued = new Set();
|
|
639
825
|
const kept = [];
|
|
640
826
|
const originOf = (batch) => origins.get(batch) ?? batch.id;
|
|
@@ -653,19 +839,19 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
653
839
|
return unqueueable(change.path);
|
|
654
840
|
const following = next.slice(index);
|
|
655
841
|
const rebuilt = [];
|
|
656
|
-
let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
|
|
842
|
+
let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
|
|
657
843
|
for (const [offset, batch] of following.entries()) {
|
|
658
844
|
let current = batch;
|
|
659
845
|
if (change.kind === 'move') {
|
|
660
|
-
const after = state(next.slice(0, index + offset + 1));
|
|
846
|
+
const after = yield* state(next.slice(0, index + offset + 1));
|
|
661
847
|
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
662
|
-
const relinking = relinked(batch, change, before, after, appending);
|
|
848
|
+
const relinking = yield* relinked(batch, change, before, after, appending);
|
|
663
849
|
if (relinking.kind === 'refused')
|
|
664
850
|
return relinking;
|
|
665
851
|
current = relinking.batch;
|
|
666
852
|
before = after;
|
|
667
853
|
}
|
|
668
|
-
if (current !== batch && !open(batch))
|
|
854
|
+
if (current !== batch && (!open(batch) || batch.between !== undefined))
|
|
669
855
|
return unqueueable(change.path);
|
|
670
856
|
rebuilt.push(offset === 0
|
|
671
857
|
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
@@ -680,7 +866,6 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
680
866
|
if (replaced !== null)
|
|
681
867
|
origins.set(replaced, origin);
|
|
682
868
|
}
|
|
683
|
-
owners.add(originOf(owner));
|
|
684
869
|
next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
|
|
685
870
|
queued.add(change.path);
|
|
686
871
|
}
|
|
@@ -690,100 +875,68 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
690
875
|
changes: kept,
|
|
691
876
|
queued,
|
|
692
877
|
withdrawn,
|
|
693
|
-
owners,
|
|
694
878
|
};
|
|
695
879
|
}
|
|
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) {
|
|
880
|
+
function* limiting(queue, batch, limits, state) {
|
|
732
881
|
const at = queue.indexOf(batch);
|
|
733
|
-
const
|
|
734
|
-
|
|
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;
|
|
735
885
|
}
|
|
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
|
-
|
|
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) {
|
|
761
920
|
if (removed.size > 0) {
|
|
762
|
-
const
|
|
763
|
-
const referenced = incoming(collectionOf(resulting.documents, input.composition), removed, referrers === null ? null : [...referrers, ...predicted.changes.flatMap(affected)]);
|
|
921
|
+
const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
|
|
764
922
|
if (referenced !== null)
|
|
765
923
|
return referenced;
|
|
766
924
|
}
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
const withoutModel = typedPaths.find((path) => governing(
|
|
925
|
+
const typedPaths = [];
|
|
926
|
+
const pace = paced();
|
|
927
|
+
for (const path of touched) {
|
|
928
|
+
const before = visible.documents[path];
|
|
929
|
+
const after = resulting.documents[path];
|
|
930
|
+
if (typedText(before) || typedText(after))
|
|
931
|
+
typedPaths.push(path);
|
|
932
|
+
await pace((before?.length ?? 0) + (after?.length ?? 0) + ROW_WEIGHT);
|
|
933
|
+
}
|
|
934
|
+
const withoutModel = typedPaths.find((path) => governing(composition, path).status === 'unavailable');
|
|
777
935
|
if (withoutModel !== undefined)
|
|
778
936
|
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);
|
|
937
|
+
const verdict = typedPaths.length > 0
|
|
938
|
+
? await validate(resulting, touched)
|
|
939
|
+
: await touchedLibrary(resulting, touched, composition);
|
|
787
940
|
if (verdict.verdict === 'refuse') {
|
|
788
941
|
return {
|
|
789
942
|
kind: 'refused',
|
|
@@ -792,16 +945,99 @@ export function buildBatch(input) {
|
|
|
792
945
|
retryable: false,
|
|
793
946
|
file: verdict.file,
|
|
794
947
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
948
|
+
...(verdict.line === undefined ? {} : { line: verdict.line }),
|
|
795
949
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
796
950
|
},
|
|
797
951
|
};
|
|
798
952
|
}
|
|
799
|
-
|
|
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));
|
|
800
1039
|
if (limited !== null)
|
|
801
1040
|
return limited;
|
|
802
|
-
const validated = typedPaths
|
|
803
|
-
.map((path) => governing(input.composition, path))
|
|
804
|
-
.find((model) => model.status !== 'unavailable');
|
|
805
1041
|
return {
|
|
806
1042
|
kind: 'batch',
|
|
807
1043
|
batch: immutable({
|
|
@@ -810,15 +1046,296 @@ export function buildBatch(input) {
|
|
|
810
1046
|
changes,
|
|
811
1047
|
preconditions,
|
|
812
1048
|
...(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
|
-
}),
|
|
1049
|
+
...validationOf(judged.validated),
|
|
822
1050
|
}),
|
|
823
1051
|
};
|
|
824
1052
|
}
|
|
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);
|
|
1341
|
+
}
|