@agentshouse/mdruntime 0.1.0-c3903a68dcbbf → 0.1.0-c56daf494ecb4
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 +184 -92
- package/README.md +22 -12
- package/dist/adapter.d.ts +125 -63
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -9
- package/dist/attached-model.d.ts +11 -0
- package/dist/attached-model.d.ts.map +1 -0
- package/dist/attached-model.js +47 -0
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -0
- package/dist/binary.d.ts +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +20 -6
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +93 -39
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +20 -17
- 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 +26 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +62 -0
- package/dist/chat/markdown.d.ts +20 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +188 -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 +17 -13
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +68 -31
- package/dist/collection.d.ts +6 -13
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +56 -29
- package/dist/connection.d.ts +28 -33
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +951 -745
- package/dist/conversation.d.ts +32 -13
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +293 -108
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +156 -0
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/host.d.ts +9 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +49 -68
- 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 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/memory.d.ts +14 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +137 -46
- package/dist/model.d.ts +13 -12
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +6 -3
- package/dist/observation.d.ts +2 -0
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +24 -14
- 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 +32 -23
- 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 +12 -3
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +78 -36
- package/dist/reach.d.ts +42 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +294 -0
- package/dist/react.d.ts +41 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +64 -0
- package/dist/read-side.d.ts +4 -0
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +125 -56
- package/dist/refresh.d.ts +12 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +44 -36
- package/dist/replica.d.ts +9 -6
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +83 -68
- package/dist/room-draft.d.ts +29 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +137 -26
- package/dist/rows.d.ts +7 -2
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +85 -40
- package/dist/save.d.ts +27 -3
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +435 -86
- 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 +41 -25
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +315 -191
- package/dist/wire.d.ts +58 -19
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +65 -67
- package/package.json +31 -7
- 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,35 +1,49 @@
|
|
|
1
1
|
import { definitionOf } from './model.js';
|
|
2
|
-
import { apply, links, parse, retarget, serialize, target, } from '@agentshouse/mdmodel';
|
|
3
|
-
import { immutable, } from './change.js';
|
|
4
|
-
import {
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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);
|
|
25
45
|
}
|
|
26
|
-
|
|
27
|
-
return undefined;
|
|
28
|
-
const named = target(from, value);
|
|
29
|
-
if (named === null)
|
|
30
|
-
return undefined;
|
|
31
|
-
const destination = moves.get(named) ?? named;
|
|
32
|
-
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] }));
|
|
33
47
|
}
|
|
34
48
|
function edited(document, path, changes) {
|
|
35
49
|
let next = document;
|
|
@@ -77,7 +91,8 @@ function relinks(path, moved, before, moves) {
|
|
|
77
91
|
return {
|
|
78
92
|
kind: 'refused',
|
|
79
93
|
refusal: {
|
|
80
|
-
|
|
94
|
+
code: applied.refusal.rule,
|
|
95
|
+
retryable: false,
|
|
81
96
|
file: path,
|
|
82
97
|
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
83
98
|
},
|
|
@@ -107,7 +122,10 @@ function rewrites(changes, visible) {
|
|
|
107
122
|
for (const change of changes) {
|
|
108
123
|
if (change.kind === 'move')
|
|
109
124
|
moves.set(change.path, change.to);
|
|
110
|
-
if (change.kind === 'remove' ||
|
|
125
|
+
if (change.kind === 'remove' ||
|
|
126
|
+
change.kind === 'replace' ||
|
|
127
|
+
change.kind === 'create' ||
|
|
128
|
+
change.kind === 'binary') {
|
|
111
129
|
settled.add(change.path);
|
|
112
130
|
}
|
|
113
131
|
if (change.kind === 'field')
|
|
@@ -117,19 +135,12 @@ function rewrites(changes, visible) {
|
|
|
117
135
|
return { kind: 'changes', changes };
|
|
118
136
|
const merged = [...changes];
|
|
119
137
|
const added = [];
|
|
120
|
-
for (const [path, document] of
|
|
138
|
+
for (const [path, document] of documentsOf(visible.collection)) {
|
|
121
139
|
if (settled.has(path))
|
|
122
140
|
continue;
|
|
123
141
|
const moved = moves.get(path) ?? path;
|
|
124
142
|
const definition = definitionOf(governing(visible.collection.composition, path), document.type);
|
|
125
|
-
|
|
126
|
-
if (!referenceField(spec) || settled.has(`${path} ${name}`))
|
|
127
|
-
continue;
|
|
128
|
-
const value = repointed(document.fields[name], path, moved, moves);
|
|
129
|
-
if (value === undefined)
|
|
130
|
-
continue;
|
|
131
|
-
added.push({ kind: 'field', path, name, value });
|
|
132
|
-
}
|
|
143
|
+
added.push(...repointed(document, definition, path, moved, moves, settled));
|
|
133
144
|
const relinked = relinks(path, moved, edited(document, path, merged), moves);
|
|
134
145
|
if (relinked.kind === 'refused')
|
|
135
146
|
return relinked;
|
|
@@ -166,18 +177,6 @@ export function providerRewritten(batch, visible) {
|
|
|
166
177
|
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
167
178
|
};
|
|
168
179
|
}
|
|
169
|
-
function targets(value, from) {
|
|
170
|
-
const values = Array.isArray(value) ? value : [value];
|
|
171
|
-
const resolved = [];
|
|
172
|
-
for (const item of values) {
|
|
173
|
-
if (typeof item !== 'string')
|
|
174
|
-
continue;
|
|
175
|
-
const named = target(from, item);
|
|
176
|
-
if (named !== null)
|
|
177
|
-
resolved.push(named);
|
|
178
|
-
}
|
|
179
|
-
return resolved;
|
|
180
|
-
}
|
|
181
180
|
function incoming(collection, removed) {
|
|
182
181
|
const found = new Map();
|
|
183
182
|
const note = (target, file) => {
|
|
@@ -187,16 +186,15 @@ function incoming(collection, removed) {
|
|
|
187
186
|
referrers.add(file);
|
|
188
187
|
found.set(target, referrers);
|
|
189
188
|
};
|
|
190
|
-
for (const [path, document] of
|
|
189
|
+
for (const [path, document] of documentsOf(collection)) {
|
|
191
190
|
const definition = definitionOf(governing(collection.composition, path), document.type);
|
|
192
|
-
for (const
|
|
193
|
-
if (
|
|
191
|
+
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
192
|
+
if (to.includes(ATTACHMENT))
|
|
194
193
|
continue;
|
|
195
|
-
|
|
194
|
+
const named = target(path, value);
|
|
195
|
+
if (named !== null)
|
|
196
196
|
note(named, path);
|
|
197
197
|
}
|
|
198
|
-
for (const link of links(document, path))
|
|
199
|
-
note(link.target, path);
|
|
200
198
|
}
|
|
201
199
|
const [first] = [...found.keys()].sort();
|
|
202
200
|
if (first === undefined)
|
|
@@ -207,7 +205,12 @@ function incoming(collection, removed) {
|
|
|
207
205
|
every.add(file);
|
|
208
206
|
return {
|
|
209
207
|
kind: 'refused',
|
|
210
|
-
refusal: {
|
|
208
|
+
refusal: {
|
|
209
|
+
code: 'referenced-missing-file',
|
|
210
|
+
retryable: false,
|
|
211
|
+
file: first,
|
|
212
|
+
referrers: [...every].sort(),
|
|
213
|
+
},
|
|
211
214
|
};
|
|
212
215
|
}
|
|
213
216
|
function affected(change) {
|
|
@@ -216,9 +219,9 @@ function affected(change) {
|
|
|
216
219
|
function guard(changes, input) {
|
|
217
220
|
const existing = new Set([
|
|
218
221
|
...Object.keys(input.visible.documents),
|
|
219
|
-
...input.visible.
|
|
222
|
+
...Object.keys(input.visible.binaries),
|
|
220
223
|
]);
|
|
221
|
-
for (const change of changes) {
|
|
224
|
+
for (const [index, change] of changes.entries()) {
|
|
222
225
|
for (const path of affected(change)) {
|
|
223
226
|
const normalized = normalize(path);
|
|
224
227
|
if (normalized === null || normalized === '')
|
|
@@ -227,7 +230,7 @@ function guard(changes, input) {
|
|
|
227
230
|
return refuse('provider-owned-path', normalized);
|
|
228
231
|
}
|
|
229
232
|
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
230
|
-
if (!covered(input.operations[operation], normalized))
|
|
233
|
+
if (!input.local && !covered(input.operations[operation], normalized))
|
|
231
234
|
return refuse(`${operation}-grant`, normalized);
|
|
232
235
|
}
|
|
233
236
|
if (change.kind === 'create') {
|
|
@@ -236,12 +239,26 @@ function guard(changes, input) {
|
|
|
236
239
|
existing.add(change.path);
|
|
237
240
|
continue;
|
|
238
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
|
+
}
|
|
239
256
|
if (change.kind === 'move') {
|
|
240
257
|
if (!existing.has(change.path))
|
|
241
258
|
return refuse('path-absent', change.path);
|
|
242
259
|
if (existing.has(change.to))
|
|
243
260
|
return refuse('path-collision', change.to);
|
|
244
|
-
if (
|
|
261
|
+
if (unrevised(input, change.path)) {
|
|
245
262
|
return refuse('unknown-revision', change.path);
|
|
246
263
|
}
|
|
247
264
|
existing.delete(change.path);
|
|
@@ -251,30 +268,37 @@ function guard(changes, input) {
|
|
|
251
268
|
if (!existing.has(change.path))
|
|
252
269
|
return refuse('path-absent', change.path);
|
|
253
270
|
if (change.kind === 'remove') {
|
|
254
|
-
if (
|
|
271
|
+
if (unrevised(input, change.path)) {
|
|
255
272
|
return refuse('unknown-revision', change.path);
|
|
256
273
|
}
|
|
257
274
|
existing.delete(change.path);
|
|
258
275
|
continue;
|
|
259
276
|
}
|
|
260
277
|
if (change.kind === 'replace') {
|
|
261
|
-
if (input.visible.revisions[change.path] === undefined) {
|
|
278
|
+
if (!input.local && input.visible.revisions[change.path] === undefined) {
|
|
262
279
|
return refuse('unknown-revision', change.path);
|
|
263
280
|
}
|
|
264
281
|
continue;
|
|
265
282
|
}
|
|
266
|
-
if (input.visible.collection.
|
|
283
|
+
if (input.visible.collection.document(change.path) === undefined) {
|
|
267
284
|
return refuse('path-absent', change.path);
|
|
268
285
|
}
|
|
269
286
|
}
|
|
270
287
|
return null;
|
|
271
288
|
}
|
|
272
|
-
function
|
|
273
|
-
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}`;
|
|
274
295
|
}
|
|
275
296
|
function existence(visible, path) {
|
|
276
297
|
const revision = visible.revisions[path];
|
|
277
|
-
|
|
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 };
|
|
278
302
|
}
|
|
279
303
|
function sameTarget(precondition, change) {
|
|
280
304
|
if (precondition.kind !== change.kind || precondition.path !== change.path)
|
|
@@ -286,11 +310,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
286
310
|
const preconditions = [];
|
|
287
311
|
const seen = new Set();
|
|
288
312
|
const add = (precondition) => {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
if (seen.has(identity))
|
|
313
|
+
const held = identity(precondition);
|
|
314
|
+
if (seen.has(held))
|
|
292
315
|
return;
|
|
293
|
-
seen.add(
|
|
316
|
+
seen.add(held);
|
|
294
317
|
preconditions.push(precondition);
|
|
295
318
|
};
|
|
296
319
|
for (const change of changes) {
|
|
@@ -300,7 +323,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
300
323
|
add(based);
|
|
301
324
|
continue;
|
|
302
325
|
}
|
|
303
|
-
const document = visible.collection.
|
|
326
|
+
const document = visible.collection.document(change.path);
|
|
304
327
|
if (change.kind === 'field') {
|
|
305
328
|
add({
|
|
306
329
|
kind: 'field',
|
|
@@ -325,6 +348,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
325
348
|
add({ kind: 'absent', path: change.path });
|
|
326
349
|
continue;
|
|
327
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
|
+
}
|
|
328
358
|
add(existence(visible, change.path));
|
|
329
359
|
if (change.kind === 'move')
|
|
330
360
|
add({ kind: 'absent', path: change.to });
|
|
@@ -334,10 +364,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
334
364
|
function depth(path) {
|
|
335
365
|
return path.split('/').length;
|
|
336
366
|
}
|
|
337
|
-
function exceeded(limits, changes, resulting) {
|
|
338
|
-
const written =
|
|
367
|
+
function exceeded(limits, changes, effective, resulting) {
|
|
368
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
339
369
|
const created = changes
|
|
340
|
-
.filter((change) => change.kind === 'create' || change.kind === 'move')
|
|
370
|
+
.filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
|
|
341
371
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
342
372
|
for (const limit of limits) {
|
|
343
373
|
const value = limit.value;
|
|
@@ -351,7 +381,7 @@ function exceeded(limits, changes, resulting) {
|
|
|
351
381
|
return refuse('provider-limit', undefined, limit.name);
|
|
352
382
|
}
|
|
353
383
|
if (limit.name === 'document-size') {
|
|
354
|
-
const large = written.find((path) => (resulting.documents[path] ?? '').
|
|
384
|
+
const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
|
|
355
385
|
if (large !== undefined)
|
|
356
386
|
return refuse('provider-limit', large, limit.name);
|
|
357
387
|
}
|
|
@@ -368,32 +398,351 @@ function exceeded(limits, changes, resulting) {
|
|
|
368
398
|
}
|
|
369
399
|
return null;
|
|
370
400
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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 &&
|
|
375
455
|
document.type !== 'any' &&
|
|
376
|
-
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 at = queue.indexOf(batch);
|
|
722
|
+
const limited = exceeded(limits, batch.changes, providerRewritten(batch, state(queue.slice(0, at))).changes, state(queue.slice(0, at + 1)));
|
|
723
|
+
return limited?.kind === 'refused' ? limited.refusal : null;
|
|
724
|
+
}
|
|
725
|
+
export function buildBatch(input) {
|
|
726
|
+
const changes = relativeChanges(input);
|
|
727
|
+
const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
|
|
728
|
+
if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
|
|
377
729
|
return refuse('model-unavailable', structural.path);
|
|
378
|
-
if (
|
|
730
|
+
if (changes.length === 0)
|
|
379
731
|
return refuse('no-change');
|
|
380
|
-
const rewritten = rewrites(
|
|
732
|
+
const rewritten = rewrites(changes, input.visible);
|
|
381
733
|
if (rewritten.kind === 'refused')
|
|
382
734
|
return rewritten;
|
|
383
|
-
const changes = input.rewriting === 'provider' ? input.changes : rewritten.changes;
|
|
384
735
|
const refusal = guard(changes, input);
|
|
385
736
|
if (refusal !== null)
|
|
386
737
|
return refusal;
|
|
387
738
|
const preconditions = preconditionsOf(changes, input.visible, input.bases);
|
|
388
|
-
const predicted = input.
|
|
389
|
-
? anticipated(changes, preconditions, rewritten.changes, input.visible)
|
|
390
|
-
: { changes, preconditions };
|
|
739
|
+
const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
|
|
391
740
|
const resulting = overlay(input.visible, [
|
|
392
741
|
{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
|
|
393
742
|
]);
|
|
394
743
|
const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
|
|
395
744
|
if (removed.size > 0) {
|
|
396
|
-
const referenced = incoming(
|
|
745
|
+
const referenced = incoming(collectionOf(resulting.documents, input.composition), removed);
|
|
397
746
|
if (referenced !== null)
|
|
398
747
|
return referenced;
|
|
399
748
|
}
|
|
@@ -403,8 +752,7 @@ export function buildBatch(input) {
|
|
|
403
752
|
const parsed = parse(content);
|
|
404
753
|
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
405
754
|
};
|
|
406
|
-
const typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) =>
|
|
407
|
-
(typed(input.visible.documents[path]) || typed(resulting.documents[path])));
|
|
755
|
+
const typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
|
|
408
756
|
const needsModel = typedPaths.length > 0;
|
|
409
757
|
const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
|
|
410
758
|
if (withoutModel !== undefined)
|
|
@@ -412,19 +760,20 @@ export function buildBatch(input) {
|
|
|
412
760
|
const validationText = needsModel
|
|
413
761
|
? resulting.documents
|
|
414
762
|
: Object.fromEntries(Object.entries(resulting.documents).filter(([path]) => predicted.changes.some((change) => affected(change).includes(path))));
|
|
415
|
-
const verdict = validateCollection(validationText, resulting.
|
|
763
|
+
const verdict = validateCollection(validationText, Object.keys(resulting.binaries), input.composition);
|
|
416
764
|
if (verdict.verdict === 'refuse') {
|
|
417
765
|
return {
|
|
418
766
|
kind: 'refused',
|
|
419
767
|
refusal: {
|
|
420
|
-
|
|
768
|
+
code: verdict.rule,
|
|
769
|
+
retryable: false,
|
|
421
770
|
file: verdict.file,
|
|
422
771
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
423
772
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
424
773
|
},
|
|
425
774
|
};
|
|
426
775
|
}
|
|
427
|
-
const limited = exceeded(input.providerLimits, changes, resulting);
|
|
776
|
+
const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
|
|
428
777
|
if (limited !== null)
|
|
429
778
|
return limited;
|
|
430
779
|
const validated = typedPaths
|