@agentshouse/mdruntime 0.1.0-cf6041a06849c → 0.1.0-cf96c797c5fa8
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 +135 -48
- package/README.md +20 -10
- package/dist/adapter.d.ts +49 -14
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +15 -2
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +57 -0
- package/dist/binary.d.ts +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +17 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +89 -38
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +19 -12
- package/dist/change.d.ts +15 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +26 -1
- package/dist/chat/acts.d.ts +34 -0
- package/dist/chat/acts.d.ts.map +1 -0
- package/dist/chat/acts.js +36 -0
- package/dist/chat/context.d.ts +3 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +10 -0
- package/dist/chat/input.d.ts +9 -0
- package/dist/chat/input.d.ts.map +1 -0
- package/dist/chat/input.js +99 -0
- package/dist/chat/kit.d.ts +60 -0
- package/dist/chat/kit.d.ts.map +1 -0
- package/dist/chat/kit.js +123 -0
- package/dist/chat/lasting.d.ts +6 -0
- package/dist/chat/lasting.d.ts.map +1 -0
- package/dist/chat/lasting.js +19 -0
- package/dist/chat/list.d.ts +20 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +59 -0
- package/dist/chat/markdown.d.ts +19 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +187 -0
- package/dist/chat/message.d.ts +6 -0
- package/dist/chat/message.d.ts.map +1 -0
- package/dist/chat/message.js +192 -0
- package/dist/chat/messages.d.ts +21 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +151 -0
- package/dist/chat/name.d.ts +6 -0
- package/dist/chat/name.d.ts.map +1 -0
- package/dist/chat/name.js +37 -0
- package/dist/chat/opened.d.ts +11 -0
- package/dist/chat/opened.d.ts.map +1 -0
- package/dist/chat/opened.js +17 -0
- package/dist/chat/overlay.d.ts +31 -0
- package/dist/chat/overlay.d.ts.map +1 -0
- package/dist/chat/overlay.js +97 -0
- package/dist/chat/parts.d.ts +57 -0
- package/dist/chat/parts.d.ts.map +1 -0
- package/dist/chat/parts.js +119 -0
- package/dist/chat/process.d.ts +20 -0
- package/dist/chat/process.d.ts.map +1 -0
- package/dist/chat/process.js +32 -0
- package/dist/chat/provider.d.ts +63 -0
- package/dist/chat/provider.d.ts.map +1 -0
- package/dist/chat/provider.js +645 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +80 -0
- package/dist/chat/questions.d.ts +31 -0
- package/dist/chat/questions.d.ts.map +1 -0
- package/dist/chat/questions.js +111 -0
- package/dist/chat/read.d.ts +5 -0
- package/dist/chat/read.d.ts.map +1 -0
- package/dist/chat/read.js +13 -0
- package/dist/chat/ready.d.ts +33 -0
- package/dist/chat/ready.d.ts.map +1 -0
- package/dist/chat/ready.js +91 -0
- package/dist/chat/style.d.ts +4 -0
- package/dist/chat/style.d.ts.map +1 -0
- package/dist/chat/style.js +302 -0
- package/dist/chat.d.ts +14 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +8 -0
- package/dist/client.d.ts +14 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +65 -15
- package/dist/collection.d.ts +6 -11
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +56 -19
- package/dist/connection.d.ts +22 -9
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +472 -142
- package/dist/conversation.d.ts +11 -3
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +47 -7
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/host.d.ts +1 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +9 -8
- package/dist/identity.d.ts +3 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +13 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/memory.d.ts +4 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +61 -20
- package/dist/model.d.ts +10 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +2 -0
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/outcome.d.ts +28 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +41 -1
- package/dist/overlay.d.ts +14 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +75 -37
- package/dist/paths.d.ts +0 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +0 -16
- package/dist/query.d.ts +5 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +52 -19
- package/dist/reach.d.ts +2 -0
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +44 -35
- package/dist/react.d.ts +26 -16
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +10 -8
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +111 -48
- package/dist/refresh.d.ts +10 -0
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +42 -38
- package/dist/replica.d.ts +8 -5
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +76 -56
- package/dist/room-draft.d.ts +26 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +132 -23
- package/dist/rows.d.ts +7 -2
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +87 -39
- package/dist/save.d.ts +28 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +429 -75
- package/dist/search.d.ts +3 -2
- package/dist/search.d.ts.map +1 -1
- package/dist/status.d.ts +2 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +34 -9
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +273 -164
- package/dist/wire.d.ts +7 -3
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +15 -2
- package/package.json +17 -9
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
package/dist/save.js
CHANGED
|
@@ -1,32 +1,49 @@
|
|
|
1
|
-
import { definitionOf
|
|
2
|
-
import { apply, links, parse, retarget, serialize, target, } from '@agentshouse/mdmodel';
|
|
3
|
-
import { immutable, } from './change.js';
|
|
4
|
-
import {
|
|
1
|
+
import { definitionOf } from './model.js';
|
|
2
|
+
import { apply, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, serialize, target, } from '@agentshouse/mdmodel';
|
|
3
|
+
import { immutable, queuedFile, departure, } from './change.js';
|
|
4
|
+
import { collectionOf, documentsOf, governing, validateCollection, } from './collection.js';
|
|
5
5
|
import { changeBatchIdentity } from './identity.js';
|
|
6
6
|
import { overlay } from './overlay.js';
|
|
7
|
-
import { covered, normalize
|
|
8
|
-
function refuse(
|
|
7
|
+
import { covered, normalize } from './paths.js';
|
|
8
|
+
function refuse(code, file, location) {
|
|
9
9
|
return {
|
|
10
10
|
kind: 'refused',
|
|
11
11
|
refusal: {
|
|
12
|
-
|
|
12
|
+
code,
|
|
13
|
+
retryable: false,
|
|
13
14
|
...(file === undefined ? {} : { file }),
|
|
14
15
|
...(location === undefined ? {} : { location }),
|
|
15
16
|
},
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
|
-
function repointed(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return
|
|
19
|
+
function repointed(document, definition, from, to, moves, settled) {
|
|
20
|
+
const parsed = parse(document.content, from);
|
|
21
|
+
if (!parsed.ok)
|
|
22
|
+
return [];
|
|
23
|
+
let next = parsed.document;
|
|
24
|
+
const names = new Set();
|
|
25
|
+
for (const { pointer, value } of definition === undefined
|
|
26
|
+
? []
|
|
27
|
+
: references(document, definition)) {
|
|
28
|
+
const [name = ''] = pointerSegments(pointer);
|
|
29
|
+
const named = target(from, value);
|
|
30
|
+
if (named === null || settled.has(`${from} ${name}`))
|
|
31
|
+
continue;
|
|
32
|
+
const destination = moves.get(named) ?? named;
|
|
33
|
+
if (target(to, value) === destination)
|
|
34
|
+
continue;
|
|
35
|
+
const applied = apply(next, {
|
|
36
|
+
kind: 'reference',
|
|
37
|
+
pointer,
|
|
38
|
+
value: retarget(value, relative(to, destination)),
|
|
39
|
+
prior: value,
|
|
40
|
+
});
|
|
41
|
+
if (applied.outcome !== 'applied')
|
|
42
|
+
continue;
|
|
43
|
+
next = applied.document;
|
|
44
|
+
names.add(name);
|
|
22
45
|
}
|
|
23
|
-
|
|
24
|
-
return undefined;
|
|
25
|
-
const named = target(from, value);
|
|
26
|
-
if (named === null)
|
|
27
|
-
return undefined;
|
|
28
|
-
const destination = moves.get(named) ?? named;
|
|
29
|
-
return target(to, value) === destination ? undefined : retarget(value, relative(to, destination));
|
|
46
|
+
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
30
47
|
}
|
|
31
48
|
function edited(document, path, changes) {
|
|
32
49
|
let next = document;
|
|
@@ -74,7 +91,8 @@ function relinks(path, moved, before, moves) {
|
|
|
74
91
|
return {
|
|
75
92
|
kind: 'refused',
|
|
76
93
|
refusal: {
|
|
77
|
-
|
|
94
|
+
code: applied.refusal.rule,
|
|
95
|
+
retryable: false,
|
|
78
96
|
file: path,
|
|
79
97
|
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
80
98
|
},
|
|
@@ -104,7 +122,10 @@ function rewrites(changes, visible) {
|
|
|
104
122
|
for (const change of changes) {
|
|
105
123
|
if (change.kind === 'move')
|
|
106
124
|
moves.set(change.path, change.to);
|
|
107
|
-
if (change.kind === 'remove' ||
|
|
125
|
+
if (change.kind === 'remove' ||
|
|
126
|
+
change.kind === 'replace' ||
|
|
127
|
+
change.kind === 'create' ||
|
|
128
|
+
change.kind === 'binary') {
|
|
108
129
|
settled.add(change.path);
|
|
109
130
|
}
|
|
110
131
|
if (change.kind === 'field')
|
|
@@ -114,19 +135,12 @@ function rewrites(changes, visible) {
|
|
|
114
135
|
return { kind: 'changes', changes };
|
|
115
136
|
const merged = [...changes];
|
|
116
137
|
const added = [];
|
|
117
|
-
for (const [path, document] of
|
|
138
|
+
for (const [path, document] of documentsOf(visible.collection)) {
|
|
118
139
|
if (settled.has(path))
|
|
119
140
|
continue;
|
|
120
141
|
const moved = moves.get(path) ?? path;
|
|
121
142
|
const definition = definitionOf(governing(visible.collection.composition, path), document.type);
|
|
122
|
-
|
|
123
|
-
if (!referenceField(spec) || settled.has(`${path} ${name}`))
|
|
124
|
-
continue;
|
|
125
|
-
const value = repointed(document.fields[name], path, moved, moves);
|
|
126
|
-
if (value === undefined)
|
|
127
|
-
continue;
|
|
128
|
-
added.push({ kind: 'field', path, name, value });
|
|
129
|
-
}
|
|
143
|
+
added.push(...repointed(document, definition, path, moved, moves, settled));
|
|
130
144
|
const relinked = relinks(path, moved, edited(document, path, merged), moves);
|
|
131
145
|
if (relinked.kind === 'refused')
|
|
132
146
|
return relinked;
|
|
@@ -163,18 +177,6 @@ export function providerRewritten(batch, visible) {
|
|
|
163
177
|
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
164
178
|
};
|
|
165
179
|
}
|
|
166
|
-
function targets(value, from) {
|
|
167
|
-
const values = Array.isArray(value) ? value : [value];
|
|
168
|
-
const resolved = [];
|
|
169
|
-
for (const item of values) {
|
|
170
|
-
if (typeof item !== 'string')
|
|
171
|
-
continue;
|
|
172
|
-
const named = target(from, item);
|
|
173
|
-
if (named !== null)
|
|
174
|
-
resolved.push(named);
|
|
175
|
-
}
|
|
176
|
-
return resolved;
|
|
177
|
-
}
|
|
178
180
|
function incoming(collection, removed) {
|
|
179
181
|
const found = new Map();
|
|
180
182
|
const note = (target, file) => {
|
|
@@ -184,16 +186,15 @@ function incoming(collection, removed) {
|
|
|
184
186
|
referrers.add(file);
|
|
185
187
|
found.set(target, referrers);
|
|
186
188
|
};
|
|
187
|
-
for (const [path, document] of
|
|
189
|
+
for (const [path, document] of documentsOf(collection)) {
|
|
188
190
|
const definition = definitionOf(governing(collection.composition, path), document.type);
|
|
189
|
-
for (const
|
|
190
|
-
if (
|
|
191
|
+
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
192
|
+
if (to.includes(ATTACHMENT))
|
|
191
193
|
continue;
|
|
192
|
-
|
|
194
|
+
const named = target(path, value);
|
|
195
|
+
if (named !== null)
|
|
193
196
|
note(named, path);
|
|
194
197
|
}
|
|
195
|
-
for (const link of links(document, path))
|
|
196
|
-
note(link.target, path);
|
|
197
198
|
}
|
|
198
199
|
const [first] = [...found.keys()].sort();
|
|
199
200
|
if (first === undefined)
|
|
@@ -204,7 +205,12 @@ function incoming(collection, removed) {
|
|
|
204
205
|
every.add(file);
|
|
205
206
|
return {
|
|
206
207
|
kind: 'refused',
|
|
207
|
-
refusal: {
|
|
208
|
+
refusal: {
|
|
209
|
+
code: 'referenced-missing-file',
|
|
210
|
+
retryable: false,
|
|
211
|
+
file: first,
|
|
212
|
+
referrers: [...every].sort(),
|
|
213
|
+
},
|
|
208
214
|
};
|
|
209
215
|
}
|
|
210
216
|
function affected(change) {
|
|
@@ -213,9 +219,9 @@ function affected(change) {
|
|
|
213
219
|
function guard(changes, input) {
|
|
214
220
|
const existing = new Set([
|
|
215
221
|
...Object.keys(input.visible.documents),
|
|
216
|
-
...input.visible.
|
|
222
|
+
...Object.keys(input.visible.binaries),
|
|
217
223
|
]);
|
|
218
|
-
for (const change of changes) {
|
|
224
|
+
for (const [index, change] of changes.entries()) {
|
|
219
225
|
for (const path of affected(change)) {
|
|
220
226
|
const normalized = normalize(path);
|
|
221
227
|
if (normalized === null || normalized === '')
|
|
@@ -224,7 +230,7 @@ function guard(changes, input) {
|
|
|
224
230
|
return refuse('provider-owned-path', normalized);
|
|
225
231
|
}
|
|
226
232
|
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
227
|
-
if (!covered(input.operations[operation], normalized))
|
|
233
|
+
if (!input.local && !covered(input.operations[operation], normalized))
|
|
228
234
|
return refuse(`${operation}-grant`, normalized);
|
|
229
235
|
}
|
|
230
236
|
if (change.kind === 'create') {
|
|
@@ -233,12 +239,26 @@ function guard(changes, input) {
|
|
|
233
239
|
existing.add(change.path);
|
|
234
240
|
continue;
|
|
235
241
|
}
|
|
242
|
+
if (change.kind === 'binary') {
|
|
243
|
+
if (input.visible.documents[change.path] !== undefined ||
|
|
244
|
+
(existing.has(change.path) &&
|
|
245
|
+
input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
|
|
246
|
+
return refuse('path-collision', change.path);
|
|
247
|
+
}
|
|
248
|
+
if (!input.local &&
|
|
249
|
+
existing.has(change.path) &&
|
|
250
|
+
input.visible.revisions[departure(changes, index)] === undefined) {
|
|
251
|
+
return refuse('unknown-revision', change.path);
|
|
252
|
+
}
|
|
253
|
+
existing.add(change.path);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
236
256
|
if (change.kind === 'move') {
|
|
237
257
|
if (!existing.has(change.path))
|
|
238
258
|
return refuse('path-absent', change.path);
|
|
239
259
|
if (existing.has(change.to))
|
|
240
260
|
return refuse('path-collision', change.to);
|
|
241
|
-
if (
|
|
261
|
+
if (unrevised(input, change.path)) {
|
|
242
262
|
return refuse('unknown-revision', change.path);
|
|
243
263
|
}
|
|
244
264
|
existing.delete(change.path);
|
|
@@ -248,30 +268,37 @@ function guard(changes, input) {
|
|
|
248
268
|
if (!existing.has(change.path))
|
|
249
269
|
return refuse('path-absent', change.path);
|
|
250
270
|
if (change.kind === 'remove') {
|
|
251
|
-
if (
|
|
271
|
+
if (unrevised(input, change.path)) {
|
|
252
272
|
return refuse('unknown-revision', change.path);
|
|
253
273
|
}
|
|
254
274
|
existing.delete(change.path);
|
|
255
275
|
continue;
|
|
256
276
|
}
|
|
257
277
|
if (change.kind === 'replace') {
|
|
258
|
-
if (input.visible.revisions[change.path] === undefined) {
|
|
278
|
+
if (!input.local && input.visible.revisions[change.path] === undefined) {
|
|
259
279
|
return refuse('unknown-revision', change.path);
|
|
260
280
|
}
|
|
261
281
|
continue;
|
|
262
282
|
}
|
|
263
|
-
if (input.visible.collection.
|
|
283
|
+
if (input.visible.collection.document(change.path) === undefined) {
|
|
264
284
|
return refuse('path-absent', change.path);
|
|
265
285
|
}
|
|
266
286
|
}
|
|
267
287
|
return null;
|
|
268
288
|
}
|
|
269
|
-
function
|
|
270
|
-
return visible.revisions[path] === undefined
|
|
289
|
+
function unrevised(input, path) {
|
|
290
|
+
return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
|
|
291
|
+
}
|
|
292
|
+
function identity(precondition) {
|
|
293
|
+
const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
|
|
294
|
+
return `${precondition.kind} ${precondition.path} ${part}`;
|
|
271
295
|
}
|
|
272
296
|
function existence(visible, path) {
|
|
273
297
|
const revision = visible.revisions[path];
|
|
274
|
-
|
|
298
|
+
if (revision !== undefined)
|
|
299
|
+
return { kind: 'revision', path, revision };
|
|
300
|
+
const content = visible.documents[path];
|
|
301
|
+
return content === undefined ? { kind: 'present', path } : { kind: 'content', path, content };
|
|
275
302
|
}
|
|
276
303
|
function sameTarget(precondition, change) {
|
|
277
304
|
if (precondition.kind !== change.kind || precondition.path !== change.path)
|
|
@@ -283,11 +310,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
283
310
|
const preconditions = [];
|
|
284
311
|
const seen = new Set();
|
|
285
312
|
const add = (precondition) => {
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
if (seen.has(identity))
|
|
313
|
+
const held = identity(precondition);
|
|
314
|
+
if (seen.has(held))
|
|
289
315
|
return;
|
|
290
|
-
seen.add(
|
|
316
|
+
seen.add(held);
|
|
291
317
|
preconditions.push(precondition);
|
|
292
318
|
};
|
|
293
319
|
for (const change of changes) {
|
|
@@ -297,7 +323,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
297
323
|
add(based);
|
|
298
324
|
continue;
|
|
299
325
|
}
|
|
300
|
-
const document = visible.collection.
|
|
326
|
+
const document = visible.collection.document(change.path);
|
|
301
327
|
if (change.kind === 'field') {
|
|
302
328
|
add({
|
|
303
329
|
kind: 'field',
|
|
@@ -322,6 +348,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
322
348
|
add({ kind: 'absent', path: change.path });
|
|
323
349
|
continue;
|
|
324
350
|
}
|
|
351
|
+
if (change.kind === 'binary') {
|
|
352
|
+
const revision = visible.revisions[change.path];
|
|
353
|
+
add(visible.binaries[change.path] === undefined || revision === undefined
|
|
354
|
+
? { kind: 'absent', path: change.path }
|
|
355
|
+
: { kind: 'revision', path: change.path, revision });
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
325
358
|
add(existence(visible, change.path));
|
|
326
359
|
if (change.kind === 'move')
|
|
327
360
|
add({ kind: 'absent', path: change.to });
|
|
@@ -334,7 +367,7 @@ function depth(path) {
|
|
|
334
367
|
function exceeded(limits, changes, resulting) {
|
|
335
368
|
const written = changes.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
336
369
|
const created = changes
|
|
337
|
-
.filter((change) => change.kind === 'create' || change.kind === 'move')
|
|
370
|
+
.filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
|
|
338
371
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
339
372
|
for (const limit of limits) {
|
|
340
373
|
const value = limit.value;
|
|
@@ -365,19 +398,339 @@ function exceeded(limits, changes, resulting) {
|
|
|
365
398
|
}
|
|
366
399
|
return null;
|
|
367
400
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
401
|
+
function shellRelative(text, path, handle, definition) {
|
|
402
|
+
if (!text.includes(`/rooms/${handle}/`))
|
|
403
|
+
return null;
|
|
404
|
+
const parsed = parse(text, path);
|
|
405
|
+
if (!parsed.ok)
|
|
406
|
+
return null;
|
|
407
|
+
const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
|
|
408
|
+
return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
|
|
409
|
+
}
|
|
410
|
+
function relativeField(path, name, value, handle, definition) {
|
|
411
|
+
const opened = parse('---\n---\n');
|
|
412
|
+
if (definition === undefined ||
|
|
413
|
+
!opened.ok ||
|
|
414
|
+
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
415
|
+
return value;
|
|
416
|
+
const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
|
|
417
|
+
if (held.outcome !== 'applied')
|
|
418
|
+
return value;
|
|
419
|
+
const result = relativize(held.document, path, handle, definition);
|
|
420
|
+
return result.outcome === 'applied' ? result.document.fields[name] : value;
|
|
421
|
+
}
|
|
422
|
+
function relativeChanges(input) {
|
|
423
|
+
const { handle, composition, visible } = input;
|
|
424
|
+
const typed = (path) => (type) => definitionOf(governing(composition, path), type);
|
|
425
|
+
const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
|
|
426
|
+
return input.changes.map((change) => {
|
|
427
|
+
const definition = typed(change.path);
|
|
428
|
+
if (change.kind === 'field') {
|
|
429
|
+
const type = visible.collection.document(change.path)?.type;
|
|
430
|
+
const value = relativeField(change.path, change.name, change.value, handle, definition(type));
|
|
431
|
+
return value === change.value ? change : { ...change, value };
|
|
432
|
+
}
|
|
433
|
+
if (change.kind === 'section' || change.kind === 'preamble') {
|
|
434
|
+
const value = body(change.path, change.value);
|
|
435
|
+
return value === change.value ? change : { ...change, value };
|
|
436
|
+
}
|
|
437
|
+
if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
|
|
438
|
+
const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
|
|
439
|
+
return content === change.content ? change : { ...change, content };
|
|
440
|
+
}
|
|
441
|
+
if (change.kind !== 'create')
|
|
442
|
+
return change;
|
|
443
|
+
const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
|
|
444
|
+
const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
|
|
445
|
+
name,
|
|
446
|
+
relativeField(change.path, name, value, handle, definition(type)),
|
|
447
|
+
]));
|
|
448
|
+
const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
|
|
449
|
+
return { ...change, fields, sections };
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
function unmodeledType(collection, composition) {
|
|
453
|
+
for (const [path, document] of documentsOf(collection)) {
|
|
454
|
+
if (document.type !== undefined &&
|
|
372
455
|
document.type !== 'any' &&
|
|
373
|
-
governing(
|
|
456
|
+
governing(composition, path).status === 'unavailable')
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
function unqueueable(file) {
|
|
462
|
+
return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
|
|
463
|
+
}
|
|
464
|
+
function isPart(change) {
|
|
465
|
+
return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
|
|
466
|
+
}
|
|
467
|
+
function touched(change) {
|
|
468
|
+
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
469
|
+
}
|
|
470
|
+
function partApplied(content, path, change) {
|
|
471
|
+
const parsed = parse(content, path);
|
|
472
|
+
if (!parsed.ok)
|
|
473
|
+
return null;
|
|
474
|
+
const document = parsed.document;
|
|
475
|
+
const applied = apply(document, change.kind === 'field'
|
|
476
|
+
? {
|
|
477
|
+
kind: 'field',
|
|
478
|
+
name: change.name,
|
|
479
|
+
value: change.value,
|
|
480
|
+
prior: document.fields[change.name],
|
|
481
|
+
}
|
|
482
|
+
: change.kind === 'section'
|
|
483
|
+
? {
|
|
484
|
+
kind: 'section',
|
|
485
|
+
name: change.name,
|
|
486
|
+
value: change.value,
|
|
487
|
+
prior: document.sections[change.name],
|
|
488
|
+
}
|
|
489
|
+
: { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
490
|
+
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
491
|
+
}
|
|
492
|
+
function folded(changes, change, appending) {
|
|
493
|
+
const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
|
|
494
|
+
const whole = held === undefined ? -1 : changes.indexOf(held);
|
|
495
|
+
const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
|
|
496
|
+
if (isPart(change)) {
|
|
497
|
+
const same = changes.findIndex((item) => item.kind === change.kind &&
|
|
498
|
+
item.path === change.path &&
|
|
499
|
+
(change.kind === 'preamble' || ('name' in item && item.name === change.name)));
|
|
500
|
+
if (same !== -1) {
|
|
501
|
+
changes[same] = change;
|
|
502
|
+
return 'folded';
|
|
503
|
+
}
|
|
504
|
+
if (held === undefined) {
|
|
505
|
+
if (!appending)
|
|
506
|
+
return 'skipped';
|
|
507
|
+
if (occupied)
|
|
508
|
+
return 'refused';
|
|
509
|
+
changes.push(change);
|
|
510
|
+
return 'appended';
|
|
511
|
+
}
|
|
512
|
+
if ('content' in held) {
|
|
513
|
+
const content = partApplied(held.content, held.path, change);
|
|
514
|
+
if (content === null)
|
|
515
|
+
return 'refused';
|
|
516
|
+
changes[whole] = { ...held, content };
|
|
517
|
+
return 'folded';
|
|
518
|
+
}
|
|
519
|
+
if (change.kind === 'preamble')
|
|
520
|
+
return 'refused';
|
|
521
|
+
changes[whole] =
|
|
522
|
+
change.kind === 'field'
|
|
523
|
+
? { ...held, fields: { ...held.fields, [change.name]: change.value } }
|
|
524
|
+
: { ...held, sections: { ...held.sections, [change.name]: change.value } };
|
|
525
|
+
return 'folded';
|
|
526
|
+
}
|
|
527
|
+
if (change.kind === 'replace') {
|
|
528
|
+
if (held !== undefined) {
|
|
529
|
+
if (!('content' in held))
|
|
530
|
+
return 'refused';
|
|
531
|
+
changes[whole] = { ...held, content: change.content };
|
|
532
|
+
return 'folded';
|
|
533
|
+
}
|
|
534
|
+
const parts = changes.filter((item) => isPart(item) && item.path === change.path);
|
|
535
|
+
if (parts.length === 0 && !appending)
|
|
536
|
+
return 'skipped';
|
|
537
|
+
if (occupied)
|
|
538
|
+
return 'refused';
|
|
539
|
+
changes.splice(0, changes.length, ...changes.filter((item) => !parts.includes(item)), change);
|
|
540
|
+
return 'appended';
|
|
541
|
+
}
|
|
542
|
+
if (changes.some((item) => touched(change).some((path) => touched(item).includes(path))))
|
|
543
|
+
return 'refused';
|
|
544
|
+
changes.push(change);
|
|
545
|
+
return 'appended';
|
|
546
|
+
}
|
|
547
|
+
function relinked(batch, move, before, after, appending) {
|
|
548
|
+
const derived = rewrites([move], after);
|
|
549
|
+
if (derived.kind === 'refused')
|
|
550
|
+
return derived;
|
|
551
|
+
const changes = [...batch.changes];
|
|
552
|
+
const appended = [];
|
|
553
|
+
let changed = false;
|
|
554
|
+
for (const change of derived.changes) {
|
|
555
|
+
if (change === move)
|
|
556
|
+
continue;
|
|
557
|
+
const outcome = folded(changes, change, appending);
|
|
558
|
+
if (outcome === 'refused')
|
|
559
|
+
return unqueueable(change.path);
|
|
560
|
+
if (outcome === 'appended')
|
|
561
|
+
appended.push(change);
|
|
562
|
+
if (outcome !== 'skipped')
|
|
563
|
+
changed = true;
|
|
564
|
+
}
|
|
565
|
+
if (!changed)
|
|
566
|
+
return { kind: 'relinked', batch };
|
|
567
|
+
const held = new Set(batch.preconditions.map(identity));
|
|
568
|
+
return {
|
|
569
|
+
kind: 'relinked',
|
|
570
|
+
batch: immutable({
|
|
571
|
+
...batch,
|
|
572
|
+
id: changeBatchIdentity(),
|
|
573
|
+
changes,
|
|
574
|
+
preconditions: [
|
|
575
|
+
...batch.preconditions,
|
|
576
|
+
...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
|
|
577
|
+
],
|
|
578
|
+
}),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function leading(changes, at) {
|
|
582
|
+
const from = departure(changes, at);
|
|
583
|
+
return new Set(changes.filter((change, index) => index < at && change.kind === 'move' && departure(changes, index) === from));
|
|
584
|
+
}
|
|
585
|
+
function created(batch, at) {
|
|
586
|
+
const file = batch.changes[at];
|
|
587
|
+
return (leading(batch.changes, at).size === 0 &&
|
|
588
|
+
batch.preconditions.some((precondition) => precondition.kind === 'absent' && precondition.path === file.path));
|
|
589
|
+
}
|
|
590
|
+
function withdrawnFile(batch, change, at) {
|
|
591
|
+
const file = batch.changes[at];
|
|
592
|
+
const from = departure(batch.changes, at);
|
|
593
|
+
const moves = leading(batch.changes, at);
|
|
594
|
+
const fresh = created(batch, at);
|
|
595
|
+
const vacated = new Set([...moves].map((move) => move.to));
|
|
596
|
+
if (fresh)
|
|
597
|
+
vacated.add(file.path);
|
|
598
|
+
const replaced = fresh
|
|
599
|
+
? change.kind === 'remove'
|
|
600
|
+
? []
|
|
601
|
+
: [{ ...file, path: change.to }]
|
|
602
|
+
: change.kind === 'remove'
|
|
603
|
+
? [{ kind: 'remove', path: from }]
|
|
604
|
+
: change.to === from
|
|
605
|
+
? [{ ...file, path: from }]
|
|
606
|
+
: [
|
|
607
|
+
{ kind: 'move', path: from, to: change.to },
|
|
608
|
+
{ ...file, path: change.to },
|
|
609
|
+
];
|
|
610
|
+
const added = change.kind === 'move' && (fresh || change.to !== from)
|
|
611
|
+
? [{ kind: 'absent', path: change.to }]
|
|
612
|
+
: [];
|
|
613
|
+
const changes = batch.changes.flatMap((item) => item === file ? replaced : item.kind === 'move' && moves.has(item) ? [] : [item]);
|
|
614
|
+
if (changes.length === 0)
|
|
615
|
+
return null;
|
|
616
|
+
const preconditions = [
|
|
617
|
+
...added,
|
|
618
|
+
...batch.preconditions.filter((precondition) => precondition.kind !== 'absent' || !vacated.has(precondition.path)),
|
|
619
|
+
];
|
|
620
|
+
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
621
|
+
}
|
|
622
|
+
export function requeued(queue, changes, visible, changeable, state) {
|
|
623
|
+
const next = [...queue];
|
|
624
|
+
const origins = new Map();
|
|
625
|
+
const withdrawn = new Map();
|
|
626
|
+
const owners = new Set();
|
|
627
|
+
const queued = new Set();
|
|
628
|
+
const kept = [];
|
|
629
|
+
const originOf = (batch) => origins.get(batch) ?? batch.id;
|
|
630
|
+
const open = (batch) => origins.has(batch) || changeable(batch);
|
|
631
|
+
for (const change of changes) {
|
|
632
|
+
const held = visible.binaries[change.path];
|
|
633
|
+
if ((change.kind !== 'move' && change.kind !== 'remove') || held?.state !== 'pending') {
|
|
634
|
+
kept.push(change);
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
let index = next.length - 1;
|
|
638
|
+
while (index >= 0 && queuedFile(next[index], change.path, held.localId) === -1)
|
|
639
|
+
index -= 1;
|
|
640
|
+
const owner = next[index];
|
|
641
|
+
if (owner === undefined || !open(owner))
|
|
642
|
+
return unqueueable(change.path);
|
|
643
|
+
const following = next.slice(index);
|
|
644
|
+
const rebuilt = [];
|
|
645
|
+
let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
|
|
646
|
+
for (const [offset, batch] of following.entries()) {
|
|
647
|
+
let current = batch;
|
|
648
|
+
if (change.kind === 'move') {
|
|
649
|
+
const after = state(next.slice(0, index + offset + 1));
|
|
650
|
+
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
651
|
+
const relinking = relinked(batch, change, before, after, appending);
|
|
652
|
+
if (relinking.kind === 'refused')
|
|
653
|
+
return relinking;
|
|
654
|
+
current = relinking.batch;
|
|
655
|
+
before = after;
|
|
656
|
+
}
|
|
657
|
+
if (current !== batch && !open(batch))
|
|
658
|
+
return unqueueable(change.path);
|
|
659
|
+
rebuilt.push(offset === 0
|
|
660
|
+
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
661
|
+
: current);
|
|
662
|
+
}
|
|
663
|
+
for (const [offset, batch] of following.entries()) {
|
|
664
|
+
const replaced = rebuilt[offset];
|
|
665
|
+
if (replaced === batch)
|
|
666
|
+
continue;
|
|
667
|
+
const origin = originOf(batch);
|
|
668
|
+
withdrawn.set(origin, replaced);
|
|
669
|
+
if (replaced !== null)
|
|
670
|
+
origins.set(replaced, origin);
|
|
671
|
+
}
|
|
672
|
+
owners.add(originOf(owner));
|
|
673
|
+
next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
|
|
674
|
+
queued.add(change.path);
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
kind: 'requeued',
|
|
678
|
+
queue: withdrawn.size === 0 ? queue : next,
|
|
679
|
+
changes: kept,
|
|
680
|
+
queued,
|
|
681
|
+
withdrawn,
|
|
682
|
+
owners,
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function covering(precondition) {
|
|
686
|
+
return precondition.kind === 'field' ||
|
|
687
|
+
precondition.kind === 'section' ||
|
|
688
|
+
precondition.kind === 'preamble'
|
|
689
|
+
? identity(precondition)
|
|
690
|
+
: `path ${precondition.path}`;
|
|
691
|
+
}
|
|
692
|
+
export function joined(queue, owner, extra) {
|
|
693
|
+
const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
|
|
694
|
+
const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
|
|
695
|
+
if (clash !== undefined)
|
|
696
|
+
return unqueueable(clash);
|
|
697
|
+
const changes = [...owner.changes];
|
|
698
|
+
for (const change of extra.changes) {
|
|
699
|
+
if (folded(changes, change, true) === 'refused')
|
|
700
|
+
return unqueueable(change.path);
|
|
701
|
+
}
|
|
702
|
+
const held = new Set(owner.preconditions.map(covering));
|
|
703
|
+
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
704
|
+
const preconditions = [
|
|
705
|
+
...owner.preconditions,
|
|
706
|
+
...extra.preconditions.filter((precondition) => {
|
|
707
|
+
const key = covering(precondition);
|
|
708
|
+
return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
|
|
709
|
+
}),
|
|
710
|
+
];
|
|
711
|
+
const batch = immutable({
|
|
712
|
+
...owner,
|
|
713
|
+
...(extra.validation === undefined ? {} : { validation: extra.validation }),
|
|
714
|
+
id: changeBatchIdentity(),
|
|
715
|
+
changes,
|
|
716
|
+
preconditions,
|
|
717
|
+
});
|
|
718
|
+
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
719
|
+
}
|
|
720
|
+
export function limitedIn(queue, batch, limits, state) {
|
|
721
|
+
const limited = exceeded(limits, batch.changes, state(queue.slice(0, queue.indexOf(batch) + 1)));
|
|
722
|
+
return limited?.kind === 'refused' ? limited.refusal : null;
|
|
723
|
+
}
|
|
724
|
+
export function buildBatch(input) {
|
|
725
|
+
const changes = relativeChanges(input);
|
|
726
|
+
const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
|
|
727
|
+
if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
|
|
374
728
|
return refuse('model-unavailable', structural.path);
|
|
375
|
-
if (
|
|
729
|
+
if (changes.length === 0)
|
|
376
730
|
return refuse('no-change');
|
|
377
|
-
const rewritten = rewrites(
|
|
731
|
+
const rewritten = rewrites(changes, input.visible);
|
|
378
732
|
if (rewritten.kind === 'refused')
|
|
379
733
|
return rewritten;
|
|
380
|
-
const changes = input.changes;
|
|
381
734
|
const refusal = guard(changes, input);
|
|
382
735
|
if (refusal !== null)
|
|
383
736
|
return refusal;
|
|
@@ -388,7 +741,7 @@ export function buildBatch(input) {
|
|
|
388
741
|
]);
|
|
389
742
|
const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
|
|
390
743
|
if (removed.size > 0) {
|
|
391
|
-
const referenced = incoming(
|
|
744
|
+
const referenced = incoming(collectionOf(resulting.documents, input.composition), removed);
|
|
392
745
|
if (referenced !== null)
|
|
393
746
|
return referenced;
|
|
394
747
|
}
|
|
@@ -406,12 +759,13 @@ export function buildBatch(input) {
|
|
|
406
759
|
const validationText = needsModel
|
|
407
760
|
? resulting.documents
|
|
408
761
|
: Object.fromEntries(Object.entries(resulting.documents).filter(([path]) => predicted.changes.some((change) => affected(change).includes(path))));
|
|
409
|
-
const verdict = validateCollection(validationText, resulting.
|
|
762
|
+
const verdict = validateCollection(validationText, Object.keys(resulting.binaries), input.composition);
|
|
410
763
|
if (verdict.verdict === 'refuse') {
|
|
411
764
|
return {
|
|
412
765
|
kind: 'refused',
|
|
413
766
|
refusal: {
|
|
414
|
-
|
|
767
|
+
code: verdict.rule,
|
|
768
|
+
retryable: false,
|
|
415
769
|
file: verdict.file,
|
|
416
770
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
417
771
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|