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