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