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