@agentshouse/mdruntime 0.1.0-cd3486bd400ed → 0.1.0-cd37f80239af0
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 +21 -9
- package/dist/adapter.d.ts +149 -46
- 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 +38 -4
- package/dist/attempt.d.ts.map +1 -1
- package/dist/attempt.js +14 -1
- 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 +27 -11
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +184 -84
- 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 -62
- package/dist/change.d.ts +41 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +34 -0
- package/dist/client.d.ts +31 -13
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +103 -7
- package/dist/collection.d.ts +10 -7
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +38 -79
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +786 -0
- package/dist/connection.d.ts +50 -16
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +3786 -931
- package/dist/conversation.d.ts +37 -16
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +700 -257
- 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 +4 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +20 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +151 -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 +370 -0
- package/dist/host.d.ts +13 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +130 -17
- package/dist/identity.d.ts +2 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +6 -0
- package/dist/index.d.ts +17 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/live.d.ts +22 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +123 -0
- package/dist/media.d.ts +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +218 -0
- package/dist/memory.d.ts +17 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +231 -91
- 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 +9 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +8 -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/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/outcome.d.ts +52 -4
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +46 -0
- package/dist/overlay.d.ts +15 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +129 -177
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -0
- package/dist/query.d.ts +68 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +48 -7
- package/dist/reach.d.ts +18 -4
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +224 -55
- package/dist/react.d.ts +9 -6
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +7 -17
- package/dist/readiness.d.ts +52 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +27 -0
- package/dist/recovery.d.ts +26 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +224 -0
- package/dist/refresh.d.ts +17 -14
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +136 -56
- package/dist/replica.d.ts +58 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +307 -159
- package/dist/room-draft.d.ts +51 -4
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +210 -18
- package/dist/rows.d.ts +37 -35
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +173 -102
- package/dist/save.d.ts +70 -3
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +805 -147
- package/dist/search.d.ts +39 -28
- 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 +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +421 -0
- package/dist/status.d.ts +5 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -1
- package/dist/store.d.ts +107 -80
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +325 -461
- package/dist/stored.d.ts +130 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +560 -0
- package/dist/tables.d.ts +325 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +578 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +75 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +101 -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 +165 -30
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +56 -29
- 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 -290
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
- package/dist/chat/acts.d.ts +0 -34
- package/dist/chat/acts.d.ts.map +0 -1
- package/dist/chat/acts.js +0 -36
- package/dist/chat/messages.d.ts +0 -21
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -151
- package/dist/chat/overlay.d.ts +0 -31
- package/dist/chat/overlay.d.ts.map +0 -1
- package/dist/chat/overlay.js +0 -97
- package/dist/chat/parts.d.ts +0 -57
- package/dist/chat/parts.d.ts.map +0 -1
- package/dist/chat/parts.js +0 -119
- package/dist/chat/process.d.ts +0 -20
- package/dist/chat/process.d.ts.map +0 -1
- package/dist/chat/process.js +0 -32
- package/dist/chat/provider.d.ts +0 -57
- package/dist/chat/provider.d.ts.map +0 -1
- package/dist/chat/provider.js +0 -637
- package/dist/chat/questions.d.ts +0 -31
- package/dist/chat/questions.d.ts.map +0 -1
- package/dist/chat/questions.js +0 -111
- package/dist/chat/read.d.ts +0 -5
- package/dist/chat/read.d.ts.map +0 -1
- package/dist/chat/read.js +0 -13
- package/dist/chat.d.ts +0 -7
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -2
- package/dist/read-side.d.ts +0 -18
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -433
package/dist/save.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
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 { collectionOf, documentsOf, 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 { collectionOf, documentsOf, governing, } from './collection.js';
|
|
5
5
|
import { changeBatchIdentity } from './identity.js';
|
|
6
|
-
import {
|
|
7
|
-
import { covered, normalize } from './paths.js';
|
|
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';
|
|
8
11
|
function refuse(code, file, location) {
|
|
9
12
|
return {
|
|
10
13
|
kind: 'refused',
|
|
@@ -20,7 +23,7 @@ function repointed(document, definition, from, to, moves, settled) {
|
|
|
20
23
|
const parsed = parse(document.content, from);
|
|
21
24
|
if (!parsed.ok)
|
|
22
25
|
return [];
|
|
23
|
-
|
|
26
|
+
const changes = [];
|
|
24
27
|
const names = new Set();
|
|
25
28
|
for (const { pointer, value } of definition === undefined
|
|
26
29
|
? []
|
|
@@ -32,38 +35,39 @@ function repointed(document, definition, from, to, moves, settled) {
|
|
|
32
35
|
const destination = moves.get(named) ?? named;
|
|
33
36
|
if (target(to, value) === destination)
|
|
34
37
|
continue;
|
|
35
|
-
|
|
38
|
+
changes.push({
|
|
36
39
|
kind: 'reference',
|
|
37
40
|
pointer,
|
|
38
41
|
value: retarget(value, relative(to, destination)),
|
|
39
42
|
prior: value,
|
|
40
43
|
});
|
|
41
|
-
if (applied.outcome !== 'applied')
|
|
42
|
-
continue;
|
|
43
|
-
next = applied.document;
|
|
44
44
|
names.add(name);
|
|
45
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;
|
|
46
52
|
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
47
53
|
}
|
|
48
|
-
function edited(document,
|
|
49
|
-
|
|
54
|
+
function edited(document, changes) {
|
|
55
|
+
const parts = new Map();
|
|
50
56
|
for (const change of changes) {
|
|
51
|
-
if (change.
|
|
52
|
-
|
|
53
|
-
if (change.kind
|
|
54
|
-
|
|
55
|
-
const applied = apply(next, change.kind === 'preamble'
|
|
56
|
-
? { kind: 'preamble', value: change.value, prior: next.preamble }
|
|
57
|
-
: {
|
|
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}`, {
|
|
58
61
|
kind: 'section',
|
|
59
62
|
name: change.name,
|
|
60
63
|
value: change.value,
|
|
61
|
-
prior:
|
|
64
|
+
prior: document.sections[change.name] ?? '',
|
|
62
65
|
});
|
|
63
|
-
if (applied.outcome === 'applied')
|
|
64
|
-
next = applied.document;
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
+
if (parts.size === 0)
|
|
68
|
+
return document;
|
|
69
|
+
const applied = apply(document, [...parts.values()]);
|
|
70
|
+
return applied.outcome === 'applied' ? applied.document : document;
|
|
67
71
|
}
|
|
68
72
|
function bodies(path, before, after) {
|
|
69
73
|
const changes = [];
|
|
@@ -78,7 +82,7 @@ function bodies(path, before, after) {
|
|
|
78
82
|
return changes;
|
|
79
83
|
}
|
|
80
84
|
function relinks(path, moved, before, moves) {
|
|
81
|
-
|
|
85
|
+
const changes = [];
|
|
82
86
|
for (const link of links(before, path)) {
|
|
83
87
|
const destination = moves.get(link.target) ?? link.target;
|
|
84
88
|
if (destination === link.target && moved === path)
|
|
@@ -86,20 +90,23 @@ function relinks(path, moved, before, moves) {
|
|
|
86
90
|
const to = relative(moved, destination);
|
|
87
91
|
if (to === relative(path, link.target))
|
|
88
92
|
continue;
|
|
89
|
-
|
|
90
|
-
if (applied.outcome === 'refuse') {
|
|
91
|
-
return {
|
|
92
|
-
kind: 'refused',
|
|
93
|
-
refusal: {
|
|
94
|
-
code: applied.refusal.rule,
|
|
95
|
-
retryable: false,
|
|
96
|
-
file: path,
|
|
97
|
-
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
after = applied.document;
|
|
93
|
+
changes.push({ kind: 'link', href: link.href, to });
|
|
102
94
|
}
|
|
95
|
+
if (changes.length === 0)
|
|
96
|
+
return { kind: 'changes', changes: [] };
|
|
97
|
+
const applied = apply(before, changes);
|
|
98
|
+
if (applied.outcome === 'refuse') {
|
|
99
|
+
return {
|
|
100
|
+
kind: 'refused',
|
|
101
|
+
refusal: {
|
|
102
|
+
code: applied.refusal.rule,
|
|
103
|
+
retryable: false,
|
|
104
|
+
file: path,
|
|
105
|
+
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const after = applied.document;
|
|
103
110
|
if (after === before)
|
|
104
111
|
return { kind: 'changes', changes: [] };
|
|
105
112
|
if (before.type === undefined) {
|
|
@@ -107,74 +114,161 @@ function relinks(path, moved, before, moves) {
|
|
|
107
114
|
}
|
|
108
115
|
return { kind: 'changes', changes: bodies(path, before, after) };
|
|
109
116
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
const item = merged[index];
|
|
113
|
-
if (item.path !== path)
|
|
114
|
-
continue;
|
|
115
|
-
if (item.kind === 'section' || item.kind === 'preamble')
|
|
116
|
-
merged.splice(index, 1);
|
|
117
|
-
}
|
|
117
|
+
function partKey(change) {
|
|
118
|
+
return change.kind === 'section' ? `section ${change.name}` : change.kind;
|
|
118
119
|
}
|
|
119
|
-
function rewrites(changes, visible) {
|
|
120
|
+
function* rewrites(changes, visible) {
|
|
120
121
|
const moves = new Map();
|
|
121
122
|
const settled = new Set();
|
|
122
|
-
|
|
123
|
+
const parts = new Map();
|
|
124
|
+
for (const [index, change] of changes.entries()) {
|
|
123
125
|
if (change.kind === 'move')
|
|
124
126
|
moves.set(change.path, change.to);
|
|
125
|
-
if (change.kind === 'remove' ||
|
|
127
|
+
if (change.kind === 'remove' ||
|
|
128
|
+
change.kind === 'replace' ||
|
|
129
|
+
change.kind === 'create' ||
|
|
130
|
+
change.kind === 'binary') {
|
|
126
131
|
settled.add(change.path);
|
|
127
132
|
}
|
|
128
133
|
if (change.kind === 'field')
|
|
129
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
|
+
}
|
|
130
142
|
}
|
|
131
143
|
if (moves.size === 0)
|
|
132
144
|
return { kind: 'changes', changes };
|
|
133
145
|
const merged = [...changes];
|
|
134
146
|
const added = [];
|
|
135
|
-
for (const [path, document] of documentsOf(visible.collection)) {
|
|
147
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
136
148
|
if (settled.has(path))
|
|
137
149
|
continue;
|
|
138
150
|
const moved = moves.get(path) ?? path;
|
|
139
151
|
const definition = definitionOf(governing(visible.collection.composition, path), document.type);
|
|
140
|
-
|
|
141
|
-
|
|
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);
|
|
142
156
|
if (relinked.kind === 'refused')
|
|
143
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
|
+
}
|
|
144
164
|
for (const change of relinked.changes) {
|
|
145
165
|
if (change.kind === 'replace') {
|
|
146
|
-
|
|
166
|
+
for (const index of own) {
|
|
167
|
+
const kind = merged[index]?.kind;
|
|
168
|
+
if (kind === 'section' || kind === 'preamble')
|
|
169
|
+
merged[index] = null;
|
|
170
|
+
}
|
|
147
171
|
added.push(change);
|
|
148
172
|
continue;
|
|
149
173
|
}
|
|
150
|
-
const index =
|
|
151
|
-
|
|
152
|
-
(change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
|
|
153
|
-
if (index === -1)
|
|
174
|
+
const index = keyed.get(partKey(change));
|
|
175
|
+
if (index === undefined)
|
|
154
176
|
added.push(change);
|
|
155
177
|
else
|
|
156
178
|
merged[index] = change;
|
|
157
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);
|
|
158
183
|
}
|
|
159
|
-
return {
|
|
184
|
+
return {
|
|
185
|
+
kind: 'changes',
|
|
186
|
+
changes: [...added, ...merged.filter((change) => change !== null)],
|
|
187
|
+
};
|
|
160
188
|
}
|
|
161
|
-
function anticipated(changes, preconditions, rewritten, visible) {
|
|
162
|
-
const
|
|
189
|
+
function* anticipated(changes, preconditions, rewritten, visible) {
|
|
190
|
+
const given = new Set(changes);
|
|
191
|
+
const derived = rewritten.filter((change) => !given.has(change));
|
|
163
192
|
return {
|
|
164
193
|
changes: rewritten,
|
|
165
|
-
preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
|
|
194
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
|
|
166
195
|
};
|
|
167
196
|
}
|
|
168
|
-
export function providerRewritten(batch, visible) {
|
|
169
|
-
const rewritten = rewrites(batch.changes, visible);
|
|
197
|
+
export function* providerRewritten(batch, visible) {
|
|
198
|
+
const rewritten = yield* rewrites(batch.changes, visible);
|
|
170
199
|
if (rewritten.kind === 'refused')
|
|
171
200
|
return batch;
|
|
172
201
|
return {
|
|
173
202
|
...batch,
|
|
174
|
-
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
203
|
+
...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
|
|
175
204
|
};
|
|
176
205
|
}
|
|
177
|
-
function
|
|
206
|
+
function crossed(document, definition, path, to) {
|
|
207
|
+
const changes = [];
|
|
208
|
+
for (const link of links(document, path)) {
|
|
209
|
+
const written = to(link.target, link.href);
|
|
210
|
+
if (written !== undefined)
|
|
211
|
+
changes.push({ kind: 'link', href: link.href, to: written });
|
|
212
|
+
}
|
|
213
|
+
for (const { pointer, value } of definition === undefined
|
|
214
|
+
? []
|
|
215
|
+
: references(document, definition)) {
|
|
216
|
+
const named = target(path, value);
|
|
217
|
+
const written = named === null ? undefined : to(named, value);
|
|
218
|
+
if (written !== undefined) {
|
|
219
|
+
changes.push({ kind: 'reference', pointer, value: retarget(value, written), prior: value });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return changes;
|
|
223
|
+
}
|
|
224
|
+
function rewritten(path, document, changes) {
|
|
225
|
+
if (changes.length === 0)
|
|
226
|
+
return [];
|
|
227
|
+
const applied = apply(document, changes);
|
|
228
|
+
if (applied.outcome !== 'applied')
|
|
229
|
+
return [];
|
|
230
|
+
const after = applied.document;
|
|
231
|
+
if (document.type === undefined)
|
|
232
|
+
return [{ kind: 'replace', path, content: serialize(after) }];
|
|
233
|
+
const names = new Set(changes.flatMap((change) => change.kind === 'reference' ? [pointerSegments(change.pointer)[0] ?? ''] : []));
|
|
234
|
+
return [
|
|
235
|
+
...[...names].map((name) => ({ kind: 'field', path, name, value: after.fields[name] })),
|
|
236
|
+
...bodies(path, document, after),
|
|
237
|
+
];
|
|
238
|
+
}
|
|
239
|
+
function roomPathOf(handle, path) {
|
|
240
|
+
return `/rooms/${handle}/${path}`;
|
|
241
|
+
}
|
|
242
|
+
export function* leavingRewritten(batch, visible) {
|
|
243
|
+
const between = batch.between;
|
|
244
|
+
if (between === undefined)
|
|
245
|
+
return batch;
|
|
246
|
+
const moved = new Map(between.moves.map(({ path, to }) => [path, roomPathOf(between.destination.handle, to)]));
|
|
247
|
+
const derived = [];
|
|
248
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
249
|
+
if (moved.has(path))
|
|
250
|
+
continue;
|
|
251
|
+
const definition = definitionOf(governing(visible.collection.composition, path), document.type);
|
|
252
|
+
derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named))));
|
|
253
|
+
yield ROW_WEIGHT + visible.collection.text[path].length;
|
|
254
|
+
}
|
|
255
|
+
if (derived.length === 0)
|
|
256
|
+
return batch;
|
|
257
|
+
return {
|
|
258
|
+
...batch,
|
|
259
|
+
changes: [...derived, ...batch.changes],
|
|
260
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...batch.preconditions],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export function rewriting(batch, locator) {
|
|
264
|
+
return leaving(batch, locator) || batch.changes.some((change) => change.kind === 'move');
|
|
265
|
+
}
|
|
266
|
+
export function* effectiveIn(batch, locator, visible) {
|
|
267
|
+
return leaving(batch, locator)
|
|
268
|
+
? yield* leavingRewritten(batch, visible)
|
|
269
|
+
: yield* providerRewritten(batch, visible);
|
|
270
|
+
}
|
|
271
|
+
function* incoming(collection, removed, candidates) {
|
|
178
272
|
const found = new Map();
|
|
179
273
|
const note = (target, file) => {
|
|
180
274
|
if (!removed.has(target))
|
|
@@ -183,7 +277,20 @@ function incoming(collection, removed) {
|
|
|
183
277
|
referrers.add(file);
|
|
184
278
|
found.set(target, referrers);
|
|
185
279
|
};
|
|
186
|
-
|
|
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)) {
|
|
187
294
|
const definition = definitionOf(governing(collection.composition, path), document.type);
|
|
188
295
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
189
296
|
if (to.includes(ATTACHMENT))
|
|
@@ -192,8 +299,9 @@ function incoming(collection, removed) {
|
|
|
192
299
|
if (named !== null)
|
|
193
300
|
note(named, path);
|
|
194
301
|
}
|
|
302
|
+
yield ROW_WEIGHT + collection.text[path].length;
|
|
195
303
|
}
|
|
196
|
-
const [first] =
|
|
304
|
+
const [first] = yield* sorting(found.keys());
|
|
197
305
|
if (first === undefined)
|
|
198
306
|
return null;
|
|
199
307
|
const every = new Set();
|
|
@@ -206,19 +314,23 @@ function incoming(collection, removed) {
|
|
|
206
314
|
code: 'referenced-missing-file',
|
|
207
315
|
retryable: false,
|
|
208
316
|
file: first,
|
|
209
|
-
referrers:
|
|
317
|
+
referrers: yield* sorting(every),
|
|
210
318
|
},
|
|
211
319
|
};
|
|
212
320
|
}
|
|
213
321
|
function affected(change) {
|
|
214
322
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
215
323
|
}
|
|
216
|
-
function guard(changes, input) {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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()) {
|
|
222
334
|
for (const path of affected(change)) {
|
|
223
335
|
const normalized = normalize(path);
|
|
224
336
|
if (normalized === null || normalized === '')
|
|
@@ -226,22 +338,41 @@ function guard(changes, input) {
|
|
|
226
338
|
if (covered(input.providerOwnedPaths, normalized)) {
|
|
227
339
|
return refuse('provider-owned-path', normalized);
|
|
228
340
|
}
|
|
229
|
-
const operation = change.kind === 'remove' ?
|
|
341
|
+
const operation = change.kind === 'remove' ? removing : 'write';
|
|
230
342
|
if (!input.local && !covered(input.operations[operation], normalized))
|
|
231
343
|
return refuse(`${operation}-grant`, normalized);
|
|
232
344
|
}
|
|
233
|
-
if (change.kind === 'create') {
|
|
234
|
-
|
|
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))) {
|
|
235
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
|
+
}
|
|
236
366
|
existing.add(change.path);
|
|
237
367
|
continue;
|
|
238
368
|
}
|
|
239
369
|
if (change.kind === 'move') {
|
|
240
370
|
if (!existing.has(change.path))
|
|
241
371
|
return refuse('path-absent', change.path);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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)) {
|
|
245
376
|
return refuse('unknown-revision', change.path);
|
|
246
377
|
}
|
|
247
378
|
existing.delete(change.path);
|
|
@@ -251,14 +382,14 @@ function guard(changes, input) {
|
|
|
251
382
|
if (!existing.has(change.path))
|
|
252
383
|
return refuse('path-absent', change.path);
|
|
253
384
|
if (change.kind === 'remove') {
|
|
254
|
-
if (
|
|
385
|
+
if (unrevised(input, change.path)) {
|
|
255
386
|
return refuse('unknown-revision', change.path);
|
|
256
387
|
}
|
|
257
388
|
existing.delete(change.path);
|
|
258
389
|
continue;
|
|
259
390
|
}
|
|
260
391
|
if (change.kind === 'replace') {
|
|
261
|
-
if (
|
|
392
|
+
if (unrevised(input, change.path)) {
|
|
262
393
|
return refuse('unknown-revision', change.path);
|
|
263
394
|
}
|
|
264
395
|
continue;
|
|
@@ -269,8 +400,28 @@ function guard(changes, input) {
|
|
|
269
400
|
}
|
|
270
401
|
return null;
|
|
271
402
|
}
|
|
272
|
-
function
|
|
273
|
-
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}`;
|
|
274
425
|
}
|
|
275
426
|
function existence(visible, path) {
|
|
276
427
|
const revision = visible.revisions[path];
|
|
@@ -285,18 +436,18 @@ function sameTarget(precondition, change) {
|
|
|
285
436
|
return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
|
|
286
437
|
((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
|
|
287
438
|
}
|
|
288
|
-
function preconditionsOf(changes, visible, bases) {
|
|
439
|
+
function* preconditionsOf(changes, visible, bases) {
|
|
289
440
|
const preconditions = [];
|
|
290
441
|
const seen = new Set();
|
|
291
442
|
const add = (precondition) => {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
if (seen.has(identity))
|
|
443
|
+
const held = identity(precondition);
|
|
444
|
+
if (seen.has(held))
|
|
295
445
|
return;
|
|
296
|
-
seen.add(
|
|
446
|
+
seen.add(held);
|
|
297
447
|
preconditions.push(precondition);
|
|
298
448
|
};
|
|
299
449
|
for (const change of changes) {
|
|
450
|
+
yield ROW_WEIGHT;
|
|
300
451
|
if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
|
|
301
452
|
const based = bases.find((base) => sameTarget(base, change));
|
|
302
453
|
if (based !== undefined) {
|
|
@@ -328,6 +479,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
328
479
|
add({ kind: 'absent', path: change.path });
|
|
329
480
|
continue;
|
|
330
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
|
+
}
|
|
331
489
|
add(existence(visible, change.path));
|
|
332
490
|
if (change.kind === 'move')
|
|
333
491
|
add({ kind: 'absent', path: change.to });
|
|
@@ -337,10 +495,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
337
495
|
function depth(path) {
|
|
338
496
|
return path.split('/').length;
|
|
339
497
|
}
|
|
340
|
-
function
|
|
341
|
-
const written =
|
|
498
|
+
function* exceeding(limits, changes, effective, resulting) {
|
|
499
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
342
500
|
const created = changes
|
|
343
|
-
.filter((change) => change.kind === 'create' ||
|
|
501
|
+
.filter((change) => change.kind === 'create' ||
|
|
502
|
+
change.kind === 'move' ||
|
|
503
|
+
change.kind === 'binary' ||
|
|
504
|
+
change.kind === 'arrival')
|
|
344
505
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
345
506
|
for (const limit of limits) {
|
|
346
507
|
const value = limit.value;
|
|
@@ -354,9 +515,13 @@ function exceeded(limits, changes, resulting) {
|
|
|
354
515
|
return refuse('provider-limit', undefined, limit.name);
|
|
355
516
|
}
|
|
356
517
|
if (limit.name === 'document-size') {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
+
}
|
|
360
525
|
}
|
|
361
526
|
if (limit.name === 'path-length') {
|
|
362
527
|
const long = created.find((path) => path.length > value);
|
|
@@ -386,7 +551,7 @@ function relativeField(path, name, value, handle, definition) {
|
|
|
386
551
|
!opened.ok ||
|
|
387
552
|
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
388
553
|
return value;
|
|
389
|
-
const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
|
|
554
|
+
const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
|
|
390
555
|
if (held.outcome !== 'applied')
|
|
391
556
|
return value;
|
|
392
557
|
const result = relativize(held.document, path, handle, definition);
|
|
@@ -422,54 +587,356 @@ function relativeChanges(input) {
|
|
|
422
587
|
return { ...change, fields, sections };
|
|
423
588
|
});
|
|
424
589
|
}
|
|
425
|
-
function
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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 });
|
|
431
622
|
}
|
|
432
|
-
|
|
623
|
+
const applied = apply(document, [...parts.values()]);
|
|
624
|
+
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
433
625
|
}
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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]);
|
|
439
767
|
if (changes.length === 0)
|
|
440
|
-
return
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const
|
|
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) {
|
|
453
920
|
if (removed.size > 0) {
|
|
454
|
-
const referenced = incoming(collectionOf(resulting.documents,
|
|
921
|
+
const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
|
|
455
922
|
if (referenced !== null)
|
|
456
923
|
return referenced;
|
|
457
924
|
}
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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');
|
|
467
935
|
if (withoutModel !== undefined)
|
|
468
936
|
return refuse('model-unavailable', withoutModel);
|
|
469
|
-
const
|
|
470
|
-
? resulting
|
|
471
|
-
:
|
|
472
|
-
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);
|
|
473
940
|
if (verdict.verdict === 'refuse') {
|
|
474
941
|
return {
|
|
475
942
|
kind: 'refused',
|
|
@@ -482,12 +949,92 @@ export function buildBatch(input) {
|
|
|
482
949
|
},
|
|
483
950
|
};
|
|
484
951
|
}
|
|
485
|
-
|
|
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));
|
|
486
1036
|
if (limited !== null)
|
|
487
1037
|
return limited;
|
|
488
|
-
const validated = typedPaths
|
|
489
|
-
.map((path) => governing(input.composition, path))
|
|
490
|
-
.find((model) => model.status !== 'unavailable');
|
|
491
1038
|
return {
|
|
492
1039
|
kind: 'batch',
|
|
493
1040
|
batch: immutable({
|
|
@@ -496,15 +1043,126 @@ export function buildBatch(input) {
|
|
|
496
1043
|
changes,
|
|
497
1044
|
preconditions,
|
|
498
1045
|
...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
|
|
499
|
-
...(validated
|
|
500
|
-
? {}
|
|
501
|
-
: {
|
|
502
|
-
validation: {
|
|
503
|
-
identity: validated.snapshot.identity,
|
|
504
|
-
version: validated.snapshot.version,
|
|
505
|
-
source: validated.status,
|
|
506
|
-
},
|
|
507
|
-
}),
|
|
1046
|
+
...validationOf(judged.validated),
|
|
508
1047
|
}),
|
|
509
1048
|
};
|
|
510
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
|
+
}
|