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