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