@agentshouse/mdruntime 0.1.0-cebcd51f70ed1 → 0.1.0-cedb03eaa48b6
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 +283 -222
- package/README.md +20 -10
- package/dist/adapter.d.ts +95 -20
- 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 +70 -0
- package/dist/binary.d.ts +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +18 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +113 -37
- package/dist/change-set.d.ts +2 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +49 -16
- 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 +33 -0
- package/dist/chat/acts.d.ts.map +1 -0
- package/dist/chat/acts.js +36 -0
- package/dist/chat/context.d.ts +3 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +10 -0
- package/dist/chat/earlier.d.ts +10 -0
- package/dist/chat/earlier.d.ts.map +1 -0
- package/dist/chat/earlier.js +54 -0
- package/dist/chat/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 +61 -0
- package/dist/chat/kit.d.ts.map +1 -0
- package/dist/chat/kit.js +123 -0
- package/dist/chat/lasting.d.ts +6 -0
- package/dist/chat/lasting.d.ts.map +1 -0
- package/dist/chat/lasting.js +19 -0
- package/dist/chat/list.d.ts +27 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +99 -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 +22 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +155 -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 +628 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +69 -0
- package/dist/chat/questions.d.ts +31 -0
- package/dist/chat/questions.d.ts.map +1 -0
- package/dist/chat/questions.js +111 -0
- package/dist/chat/read.d.ts +5 -0
- package/dist/chat/read.d.ts.map +1 -0
- package/dist/chat/read.js +13 -0
- package/dist/chat/ready.d.ts +34 -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 +30 -13
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +79 -16
- package/dist/collection.d.ts +21 -12
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +301 -23
- package/dist/connection.d.ts +50 -16
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1936 -415
- package/dist/conversation.d.ts +31 -10
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +521 -170
- package/dist/durability.d.ts +7 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +157 -0
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/host.d.ts +15 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +102 -19
- package/dist/identity.d.ts +5 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +19 -1
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/memory.d.ts +11 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +91 -24
- package/dist/model.d.ts +14 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +12 -1
- package/dist/observation.d.ts +7 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +29 -12
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/outcome.d.ts +38 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +45 -1
- package/dist/overlay.d.ts +16 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +118 -77
- package/dist/query.d.ts +60 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +41 -38
- package/dist/reach.d.ts +8 -3
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +73 -24
- package/dist/react.d.ts +31 -20
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +13 -21
- package/dist/read-side.d.ts +16 -7
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +493 -102
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/reads.d.ts +11 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +98 -0
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +197 -0
- package/dist/refresh.d.ts +13 -3
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +49 -38
- package/dist/replica.d.ts +13 -6
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +143 -70
- package/dist/room-draft.d.ts +38 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +134 -25
- package/dist/rows.d.ts +10 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +19 -14
- package/dist/save.d.ts +32 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +390 -45
- package/dist/search.d.ts +52 -8
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +192 -47
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +75 -0
- package/dist/shared.d.ts +11 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +368 -0
- package/dist/status.d.ts +6 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +89 -18
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +573 -200
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +51 -0
- package/dist/windows.d.ts +26 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +93 -0
- package/dist/wire.d.ts +157 -33
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +65 -31
- package/package.json +26 -14
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
package/dist/save.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { definitionOf } from './model.js';
|
|
2
2
|
import { apply, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, serialize, target, } from '@agentshouse/mdmodel';
|
|
3
|
-
import { immutable, } from './change.js';
|
|
4
|
-
import { governing,
|
|
3
|
+
import { immutable, queuedFile, departure, } from './change.js';
|
|
4
|
+
import { available, changedCleanly, collectionOf, documentsOf, governing, referring, validateCollection, } from './collection.js';
|
|
5
5
|
import { changeBatchIdentity } from './identity.js';
|
|
6
6
|
import { overlay } from './overlay.js';
|
|
7
7
|
import { covered, normalize } from './paths.js';
|
|
8
|
-
|
|
8
|
+
import { differing } from './shared.js';
|
|
9
|
+
function refuse(code, file, location) {
|
|
9
10
|
return {
|
|
10
11
|
kind: 'refused',
|
|
11
12
|
refusal: {
|
|
12
|
-
|
|
13
|
+
code,
|
|
14
|
+
retryable: false,
|
|
13
15
|
...(file === undefined ? {} : { file }),
|
|
14
16
|
...(location === undefined ? {} : { location }),
|
|
15
17
|
},
|
|
@@ -90,7 +92,8 @@ function relinks(path, moved, before, moves) {
|
|
|
90
92
|
return {
|
|
91
93
|
kind: 'refused',
|
|
92
94
|
refusal: {
|
|
93
|
-
|
|
95
|
+
code: applied.refusal.rule,
|
|
96
|
+
retryable: false,
|
|
94
97
|
file: path,
|
|
95
98
|
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
96
99
|
},
|
|
@@ -120,7 +123,10 @@ function rewrites(changes, visible) {
|
|
|
120
123
|
for (const change of changes) {
|
|
121
124
|
if (change.kind === 'move')
|
|
122
125
|
moves.set(change.path, change.to);
|
|
123
|
-
if (change.kind === 'remove' ||
|
|
126
|
+
if (change.kind === 'remove' ||
|
|
127
|
+
change.kind === 'replace' ||
|
|
128
|
+
change.kind === 'create' ||
|
|
129
|
+
change.kind === 'binary') {
|
|
124
130
|
settled.add(change.path);
|
|
125
131
|
}
|
|
126
132
|
if (change.kind === 'field')
|
|
@@ -130,7 +136,7 @@ function rewrites(changes, visible) {
|
|
|
130
136
|
return { kind: 'changes', changes };
|
|
131
137
|
const merged = [...changes];
|
|
132
138
|
const added = [];
|
|
133
|
-
for (const [path, document] of
|
|
139
|
+
for (const [path, document] of documentsOf(visible.collection)) {
|
|
134
140
|
if (settled.has(path))
|
|
135
141
|
continue;
|
|
136
142
|
const moved = moves.get(path) ?? path;
|
|
@@ -172,7 +178,7 @@ export function providerRewritten(batch, visible) {
|
|
|
172
178
|
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
173
179
|
};
|
|
174
180
|
}
|
|
175
|
-
function incoming(collection, removed) {
|
|
181
|
+
function incoming(collection, removed, candidates) {
|
|
176
182
|
const found = new Map();
|
|
177
183
|
const note = (target, file) => {
|
|
178
184
|
if (!removed.has(target))
|
|
@@ -181,7 +187,13 @@ function incoming(collection, removed) {
|
|
|
181
187
|
referrers.add(file);
|
|
182
188
|
found.set(target, referrers);
|
|
183
189
|
};
|
|
184
|
-
|
|
190
|
+
const read = candidates === null
|
|
191
|
+
? documentsOf(collection)
|
|
192
|
+
: [...new Set(candidates)].sort().flatMap((path) => {
|
|
193
|
+
const document = collection.document(path);
|
|
194
|
+
return document === undefined ? [] : [[path, document]];
|
|
195
|
+
});
|
|
196
|
+
for (const [path, document] of read) {
|
|
185
197
|
const definition = definitionOf(governing(collection.composition, path), document.type);
|
|
186
198
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
187
199
|
if (to.includes(ATTACHMENT))
|
|
@@ -200,18 +212,26 @@ function incoming(collection, removed) {
|
|
|
200
212
|
every.add(file);
|
|
201
213
|
return {
|
|
202
214
|
kind: 'refused',
|
|
203
|
-
refusal: {
|
|
215
|
+
refusal: {
|
|
216
|
+
code: 'referenced-missing-file',
|
|
217
|
+
retryable: false,
|
|
218
|
+
file: first,
|
|
219
|
+
referrers: [...every].sort(),
|
|
220
|
+
},
|
|
204
221
|
};
|
|
205
222
|
}
|
|
206
223
|
function affected(change) {
|
|
207
224
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
208
225
|
}
|
|
209
226
|
function guard(changes, input) {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
227
|
+
const overridden = new Map();
|
|
228
|
+
const existing = {
|
|
229
|
+
has: (path) => overridden.get(path) ??
|
|
230
|
+
(Object.hasOwn(input.visible.documents, path) || Object.hasOwn(input.visible.binaries, path)),
|
|
231
|
+
add: (path) => void overridden.set(path, true),
|
|
232
|
+
delete: (path) => void overridden.set(path, false),
|
|
233
|
+
};
|
|
234
|
+
for (const [index, change] of changes.entries()) {
|
|
215
235
|
for (const path of affected(change)) {
|
|
216
236
|
const normalized = normalize(path);
|
|
217
237
|
if (normalized === null || normalized === '')
|
|
@@ -220,7 +240,7 @@ function guard(changes, input) {
|
|
|
220
240
|
return refuse('provider-owned-path', normalized);
|
|
221
241
|
}
|
|
222
242
|
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
223
|
-
if (!covered(input.operations[operation], normalized))
|
|
243
|
+
if (!input.local && !covered(input.operations[operation], normalized))
|
|
224
244
|
return refuse(`${operation}-grant`, normalized);
|
|
225
245
|
}
|
|
226
246
|
if (change.kind === 'create') {
|
|
@@ -229,12 +249,26 @@ function guard(changes, input) {
|
|
|
229
249
|
existing.add(change.path);
|
|
230
250
|
continue;
|
|
231
251
|
}
|
|
252
|
+
if (change.kind === 'binary') {
|
|
253
|
+
if (input.visible.documents[change.path] !== undefined ||
|
|
254
|
+
(existing.has(change.path) &&
|
|
255
|
+
input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
|
|
256
|
+
return refuse('path-collision', change.path);
|
|
257
|
+
}
|
|
258
|
+
if (!input.local &&
|
|
259
|
+
existing.has(change.path) &&
|
|
260
|
+
input.visible.revisions[departure(changes, index)] === undefined) {
|
|
261
|
+
return refuse('unknown-revision', change.path);
|
|
262
|
+
}
|
|
263
|
+
existing.add(change.path);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
232
266
|
if (change.kind === 'move') {
|
|
233
267
|
if (!existing.has(change.path))
|
|
234
268
|
return refuse('path-absent', change.path);
|
|
235
269
|
if (existing.has(change.to))
|
|
236
270
|
return refuse('path-collision', change.to);
|
|
237
|
-
if (
|
|
271
|
+
if (unrevised(input, change.path)) {
|
|
238
272
|
return refuse('unknown-revision', change.path);
|
|
239
273
|
}
|
|
240
274
|
existing.delete(change.path);
|
|
@@ -244,30 +278,53 @@ function guard(changes, input) {
|
|
|
244
278
|
if (!existing.has(change.path))
|
|
245
279
|
return refuse('path-absent', change.path);
|
|
246
280
|
if (change.kind === 'remove') {
|
|
247
|
-
if (
|
|
281
|
+
if (unrevised(input, change.path)) {
|
|
248
282
|
return refuse('unknown-revision', change.path);
|
|
249
283
|
}
|
|
250
284
|
existing.delete(change.path);
|
|
251
285
|
continue;
|
|
252
286
|
}
|
|
253
287
|
if (change.kind === 'replace') {
|
|
254
|
-
if (input
|
|
288
|
+
if (unrevised(input, change.path)) {
|
|
255
289
|
return refuse('unknown-revision', change.path);
|
|
256
290
|
}
|
|
257
291
|
continue;
|
|
258
292
|
}
|
|
259
|
-
if (input.visible.collection.
|
|
293
|
+
if (input.visible.collection.document(change.path) === undefined) {
|
|
260
294
|
return refuse('path-absent', change.path);
|
|
261
295
|
}
|
|
262
296
|
}
|
|
263
297
|
return null;
|
|
264
298
|
}
|
|
265
|
-
function
|
|
266
|
-
return visible.revisions[path] === undefined
|
|
299
|
+
function unrevised(input, path) {
|
|
300
|
+
return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
|
|
301
|
+
}
|
|
302
|
+
export function queuedDocuments(queue) {
|
|
303
|
+
const paths = new Set();
|
|
304
|
+
for (const batch of queue) {
|
|
305
|
+
for (const change of batch.changes) {
|
|
306
|
+
if (change.kind === 'binary')
|
|
307
|
+
continue;
|
|
308
|
+
if (change.kind === 'remove')
|
|
309
|
+
paths.delete(change.path);
|
|
310
|
+
else if (change.kind !== 'move')
|
|
311
|
+
paths.add(change.path);
|
|
312
|
+
else if (paths.delete(change.path))
|
|
313
|
+
paths.add(change.to);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return paths;
|
|
317
|
+
}
|
|
318
|
+
function identity(precondition) {
|
|
319
|
+
const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
|
|
320
|
+
return `${precondition.kind} ${precondition.path} ${part}`;
|
|
267
321
|
}
|
|
268
322
|
function existence(visible, path) {
|
|
269
323
|
const revision = visible.revisions[path];
|
|
270
|
-
|
|
324
|
+
if (revision !== undefined)
|
|
325
|
+
return { kind: 'revision', path, revision };
|
|
326
|
+
const content = visible.documents[path];
|
|
327
|
+
return content === undefined ? { kind: 'present', path } : { kind: 'content', path, content };
|
|
271
328
|
}
|
|
272
329
|
function sameTarget(precondition, change) {
|
|
273
330
|
if (precondition.kind !== change.kind || precondition.path !== change.path)
|
|
@@ -279,11 +336,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
279
336
|
const preconditions = [];
|
|
280
337
|
const seen = new Set();
|
|
281
338
|
const add = (precondition) => {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
if (seen.has(identity))
|
|
339
|
+
const held = identity(precondition);
|
|
340
|
+
if (seen.has(held))
|
|
285
341
|
return;
|
|
286
|
-
seen.add(
|
|
342
|
+
seen.add(held);
|
|
287
343
|
preconditions.push(precondition);
|
|
288
344
|
};
|
|
289
345
|
for (const change of changes) {
|
|
@@ -293,7 +349,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
293
349
|
add(based);
|
|
294
350
|
continue;
|
|
295
351
|
}
|
|
296
|
-
const document = visible.collection.
|
|
352
|
+
const document = visible.collection.document(change.path);
|
|
297
353
|
if (change.kind === 'field') {
|
|
298
354
|
add({
|
|
299
355
|
kind: 'field',
|
|
@@ -318,6 +374,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
318
374
|
add({ kind: 'absent', path: change.path });
|
|
319
375
|
continue;
|
|
320
376
|
}
|
|
377
|
+
if (change.kind === 'binary') {
|
|
378
|
+
const revision = visible.revisions[change.path];
|
|
379
|
+
add(visible.binaries[change.path] === undefined || revision === undefined
|
|
380
|
+
? { kind: 'absent', path: change.path }
|
|
381
|
+
: { kind: 'revision', path: change.path, revision });
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
321
384
|
add(existence(visible, change.path));
|
|
322
385
|
if (change.kind === 'move')
|
|
323
386
|
add({ kind: 'absent', path: change.to });
|
|
@@ -327,10 +390,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
327
390
|
function depth(path) {
|
|
328
391
|
return path.split('/').length;
|
|
329
392
|
}
|
|
330
|
-
function exceeded(limits, changes, resulting) {
|
|
331
|
-
const written =
|
|
393
|
+
function exceeded(limits, changes, effective, resulting) {
|
|
394
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
332
395
|
const created = changes
|
|
333
|
-
.filter((change) => change.kind === 'create' || change.kind === 'move')
|
|
396
|
+
.filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
|
|
334
397
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
335
398
|
for (const limit of limits) {
|
|
336
399
|
const value = limit.value;
|
|
@@ -344,7 +407,7 @@ function exceeded(limits, changes, resulting) {
|
|
|
344
407
|
return refuse('provider-limit', undefined, limit.name);
|
|
345
408
|
}
|
|
346
409
|
if (limit.name === 'document-size') {
|
|
347
|
-
const large = written.find((path) => (resulting.documents[path] ?? '').
|
|
410
|
+
const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
|
|
348
411
|
if (large !== undefined)
|
|
349
412
|
return refuse('provider-limit', large, limit.name);
|
|
350
413
|
}
|
|
@@ -389,7 +452,7 @@ function relativeChanges(input) {
|
|
|
389
452
|
return input.changes.map((change) => {
|
|
390
453
|
const definition = typed(change.path);
|
|
391
454
|
if (change.kind === 'field') {
|
|
392
|
-
const type = visible.collection.
|
|
455
|
+
const type = visible.collection.document(change.path)?.type;
|
|
393
456
|
const value = relativeField(change.path, change.name, change.value, handle, definition(type));
|
|
394
457
|
return value === change.value ? change : { ...change, value };
|
|
395
458
|
}
|
|
@@ -412,13 +475,285 @@ function relativeChanges(input) {
|
|
|
412
475
|
return { ...change, fields, sections };
|
|
413
476
|
});
|
|
414
477
|
}
|
|
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
|
+
function unqueueable(file) {
|
|
490
|
+
return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
|
|
491
|
+
}
|
|
492
|
+
function isPart(change) {
|
|
493
|
+
return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
|
|
494
|
+
}
|
|
495
|
+
function touched(change) {
|
|
496
|
+
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
497
|
+
}
|
|
498
|
+
function partApplied(content, path, change) {
|
|
499
|
+
const parsed = parse(content, path);
|
|
500
|
+
if (!parsed.ok)
|
|
501
|
+
return null;
|
|
502
|
+
const document = parsed.document;
|
|
503
|
+
const applied = apply(document, change.kind === 'field'
|
|
504
|
+
? {
|
|
505
|
+
kind: 'field',
|
|
506
|
+
name: change.name,
|
|
507
|
+
value: change.value,
|
|
508
|
+
prior: document.fields[change.name],
|
|
509
|
+
}
|
|
510
|
+
: change.kind === 'section'
|
|
511
|
+
? {
|
|
512
|
+
kind: 'section',
|
|
513
|
+
name: change.name,
|
|
514
|
+
value: change.value,
|
|
515
|
+
prior: document.sections[change.name],
|
|
516
|
+
}
|
|
517
|
+
: { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
518
|
+
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
519
|
+
}
|
|
520
|
+
function folded(changes, change, appending) {
|
|
521
|
+
const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
|
|
522
|
+
const whole = held === undefined ? -1 : changes.indexOf(held);
|
|
523
|
+
const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
|
|
524
|
+
if (isPart(change)) {
|
|
525
|
+
const same = changes.findIndex((item) => item.kind === change.kind &&
|
|
526
|
+
item.path === change.path &&
|
|
527
|
+
(change.kind === 'preamble' || ('name' in item && item.name === change.name)));
|
|
528
|
+
if (same !== -1) {
|
|
529
|
+
changes[same] = change;
|
|
530
|
+
return 'folded';
|
|
531
|
+
}
|
|
532
|
+
if (held === undefined) {
|
|
533
|
+
if (!appending)
|
|
534
|
+
return 'skipped';
|
|
535
|
+
if (occupied)
|
|
536
|
+
return 'refused';
|
|
537
|
+
changes.push(change);
|
|
538
|
+
return 'appended';
|
|
539
|
+
}
|
|
540
|
+
if ('content' in held) {
|
|
541
|
+
const content = partApplied(held.content, held.path, change);
|
|
542
|
+
if (content === null)
|
|
543
|
+
return 'refused';
|
|
544
|
+
changes[whole] = { ...held, content };
|
|
545
|
+
return 'folded';
|
|
546
|
+
}
|
|
547
|
+
if (change.kind === 'preamble')
|
|
548
|
+
return 'refused';
|
|
549
|
+
changes[whole] =
|
|
550
|
+
change.kind === 'field'
|
|
551
|
+
? { ...held, fields: { ...held.fields, [change.name]: change.value } }
|
|
552
|
+
: { ...held, sections: { ...held.sections, [change.name]: change.value } };
|
|
553
|
+
return 'folded';
|
|
554
|
+
}
|
|
555
|
+
if (change.kind === 'replace') {
|
|
556
|
+
if (held !== undefined) {
|
|
557
|
+
if (!('content' in held))
|
|
558
|
+
return 'refused';
|
|
559
|
+
changes[whole] = { ...held, content: change.content };
|
|
560
|
+
return 'folded';
|
|
561
|
+
}
|
|
562
|
+
const parts = changes.filter((item) => isPart(item) && item.path === change.path);
|
|
563
|
+
if (parts.length === 0 && !appending)
|
|
564
|
+
return 'skipped';
|
|
565
|
+
if (occupied)
|
|
566
|
+
return 'refused';
|
|
567
|
+
changes.splice(0, changes.length, ...changes.filter((item) => !parts.includes(item)), change);
|
|
568
|
+
return 'appended';
|
|
569
|
+
}
|
|
570
|
+
if (changes.some((item) => touched(change).some((path) => touched(item).includes(path))))
|
|
571
|
+
return 'refused';
|
|
572
|
+
changes.push(change);
|
|
573
|
+
return 'appended';
|
|
574
|
+
}
|
|
575
|
+
function relinked(batch, move, before, after, appending) {
|
|
576
|
+
const derived = rewrites([move], after);
|
|
577
|
+
if (derived.kind === 'refused')
|
|
578
|
+
return derived;
|
|
579
|
+
const changes = [...batch.changes];
|
|
580
|
+
const appended = [];
|
|
581
|
+
let changed = false;
|
|
582
|
+
for (const change of derived.changes) {
|
|
583
|
+
if (change === move)
|
|
584
|
+
continue;
|
|
585
|
+
const outcome = folded(changes, change, appending);
|
|
586
|
+
if (outcome === 'refused')
|
|
587
|
+
return unqueueable(change.path);
|
|
588
|
+
if (outcome === 'appended')
|
|
589
|
+
appended.push(change);
|
|
590
|
+
if (outcome !== 'skipped')
|
|
591
|
+
changed = true;
|
|
592
|
+
}
|
|
593
|
+
if (!changed)
|
|
594
|
+
return { kind: 'relinked', batch };
|
|
595
|
+
const held = new Set(batch.preconditions.map(identity));
|
|
596
|
+
return {
|
|
597
|
+
kind: 'relinked',
|
|
598
|
+
batch: immutable({
|
|
599
|
+
...batch,
|
|
600
|
+
id: changeBatchIdentity(),
|
|
601
|
+
changes,
|
|
602
|
+
preconditions: [
|
|
603
|
+
...batch.preconditions,
|
|
604
|
+
...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
|
|
605
|
+
],
|
|
606
|
+
}),
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function leading(changes, at) {
|
|
610
|
+
const from = departure(changes, at);
|
|
611
|
+
return new Set(changes.filter((change, index) => index < at && change.kind === 'move' && departure(changes, index) === from));
|
|
612
|
+
}
|
|
613
|
+
function created(batch, at) {
|
|
614
|
+
const file = batch.changes[at];
|
|
615
|
+
return (leading(batch.changes, at).size === 0 &&
|
|
616
|
+
batch.preconditions.some((precondition) => precondition.kind === 'absent' && precondition.path === file.path));
|
|
617
|
+
}
|
|
618
|
+
function withdrawnFile(batch, change, at) {
|
|
619
|
+
const file = batch.changes[at];
|
|
620
|
+
const from = departure(batch.changes, at);
|
|
621
|
+
const moves = leading(batch.changes, at);
|
|
622
|
+
const fresh = created(batch, at);
|
|
623
|
+
const vacated = new Set([...moves].map((move) => move.to));
|
|
624
|
+
if (fresh)
|
|
625
|
+
vacated.add(file.path);
|
|
626
|
+
const replaced = fresh
|
|
627
|
+
? change.kind === 'remove'
|
|
628
|
+
? []
|
|
629
|
+
: [{ ...file, path: change.to }]
|
|
630
|
+
: change.kind === 'remove'
|
|
631
|
+
? [{ kind: 'remove', path: from }]
|
|
632
|
+
: change.to === from
|
|
633
|
+
? [{ ...file, path: from }]
|
|
634
|
+
: [
|
|
635
|
+
{ kind: 'move', path: from, to: change.to },
|
|
636
|
+
{ ...file, path: change.to },
|
|
637
|
+
];
|
|
638
|
+
const added = change.kind === 'move' && (fresh || change.to !== from)
|
|
639
|
+
? [{ kind: 'absent', path: change.to }]
|
|
640
|
+
: [];
|
|
641
|
+
const changes = batch.changes.flatMap((item) => item === file ? replaced : item.kind === 'move' && moves.has(item) ? [] : [item]);
|
|
642
|
+
if (changes.length === 0)
|
|
643
|
+
return null;
|
|
644
|
+
const preconditions = [
|
|
645
|
+
...added,
|
|
646
|
+
...batch.preconditions.filter((precondition) => precondition.kind !== 'absent' || !vacated.has(precondition.path)),
|
|
647
|
+
];
|
|
648
|
+
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
649
|
+
}
|
|
650
|
+
export function requeued(queue, changes, visible, changeable, state) {
|
|
651
|
+
const next = [...queue];
|
|
652
|
+
const origins = new Map();
|
|
653
|
+
const withdrawn = new Map();
|
|
654
|
+
const owners = new Set();
|
|
655
|
+
const queued = new Set();
|
|
656
|
+
const kept = [];
|
|
657
|
+
const originOf = (batch) => origins.get(batch) ?? batch.id;
|
|
658
|
+
const open = (batch) => origins.has(batch) || changeable(batch);
|
|
659
|
+
for (const change of changes) {
|
|
660
|
+
const held = visible.binaries[change.path];
|
|
661
|
+
if ((change.kind !== 'move' && change.kind !== 'remove') || held?.state !== 'pending') {
|
|
662
|
+
kept.push(change);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
let index = next.length - 1;
|
|
666
|
+
while (index >= 0 && queuedFile(next[index], change.path, held.localId) === -1)
|
|
667
|
+
index -= 1;
|
|
668
|
+
const owner = next[index];
|
|
669
|
+
if (owner === undefined || !open(owner))
|
|
670
|
+
return unqueueable(change.path);
|
|
671
|
+
const following = next.slice(index);
|
|
672
|
+
const rebuilt = [];
|
|
673
|
+
let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
|
|
674
|
+
for (const [offset, batch] of following.entries()) {
|
|
675
|
+
let current = batch;
|
|
676
|
+
if (change.kind === 'move') {
|
|
677
|
+
const after = state(next.slice(0, index + offset + 1));
|
|
678
|
+
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
679
|
+
const relinking = relinked(batch, change, before, after, appending);
|
|
680
|
+
if (relinking.kind === 'refused')
|
|
681
|
+
return relinking;
|
|
682
|
+
current = relinking.batch;
|
|
683
|
+
before = after;
|
|
684
|
+
}
|
|
685
|
+
if (current !== batch && !open(batch))
|
|
686
|
+
return unqueueable(change.path);
|
|
687
|
+
rebuilt.push(offset === 0
|
|
688
|
+
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
689
|
+
: current);
|
|
690
|
+
}
|
|
691
|
+
for (const [offset, batch] of following.entries()) {
|
|
692
|
+
const replaced = rebuilt[offset];
|
|
693
|
+
if (replaced === batch)
|
|
694
|
+
continue;
|
|
695
|
+
const origin = originOf(batch);
|
|
696
|
+
withdrawn.set(origin, replaced);
|
|
697
|
+
if (replaced !== null)
|
|
698
|
+
origins.set(replaced, origin);
|
|
699
|
+
}
|
|
700
|
+
owners.add(originOf(owner));
|
|
701
|
+
next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
|
|
702
|
+
queued.add(change.path);
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
kind: 'requeued',
|
|
706
|
+
queue: withdrawn.size === 0 ? queue : next,
|
|
707
|
+
changes: kept,
|
|
708
|
+
queued,
|
|
709
|
+
withdrawn,
|
|
710
|
+
owners,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function covering(precondition) {
|
|
714
|
+
return precondition.kind === 'field' ||
|
|
715
|
+
precondition.kind === 'section' ||
|
|
716
|
+
precondition.kind === 'preamble'
|
|
717
|
+
? identity(precondition)
|
|
718
|
+
: `path ${precondition.path}`;
|
|
719
|
+
}
|
|
720
|
+
export function joined(queue, owner, extra) {
|
|
721
|
+
const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
|
|
722
|
+
const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
|
|
723
|
+
if (clash !== undefined)
|
|
724
|
+
return unqueueable(clash);
|
|
725
|
+
const changes = [...owner.changes];
|
|
726
|
+
for (const change of extra.changes) {
|
|
727
|
+
if (folded(changes, change, true) === 'refused')
|
|
728
|
+
return unqueueable(change.path);
|
|
729
|
+
}
|
|
730
|
+
const held = new Set(owner.preconditions.map(covering));
|
|
731
|
+
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
732
|
+
const preconditions = [
|
|
733
|
+
...owner.preconditions,
|
|
734
|
+
...extra.preconditions.filter((precondition) => {
|
|
735
|
+
const key = covering(precondition);
|
|
736
|
+
return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
|
|
737
|
+
}),
|
|
738
|
+
];
|
|
739
|
+
const batch = immutable({
|
|
740
|
+
...owner,
|
|
741
|
+
...(extra.validation === undefined ? {} : { validation: extra.validation }),
|
|
742
|
+
id: changeBatchIdentity(),
|
|
743
|
+
changes,
|
|
744
|
+
preconditions,
|
|
745
|
+
});
|
|
746
|
+
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
747
|
+
}
|
|
748
|
+
export function limitedIn(queue, batch, limits, state) {
|
|
749
|
+
const at = queue.indexOf(batch);
|
|
750
|
+
const limited = exceeded(limits, batch.changes, providerRewritten(batch, state(queue.slice(0, at))).changes, state(queue.slice(0, at + 1)));
|
|
751
|
+
return limited?.kind === 'refused' ? limited.refusal : null;
|
|
752
|
+
}
|
|
415
753
|
export function buildBatch(input) {
|
|
416
754
|
const changes = relativeChanges(input);
|
|
417
755
|
const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
|
|
418
|
-
if (structural !== undefined &&
|
|
419
|
-
Object.entries(input.visible.collection.documents).some(([path, document]) => document.type !== undefined &&
|
|
420
|
-
document.type !== 'any' &&
|
|
421
|
-
governing(input.composition, path).status === 'unavailable'))
|
|
756
|
+
if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
|
|
422
757
|
return refuse('model-unavailable', structural.path);
|
|
423
758
|
if (changes.length === 0)
|
|
424
759
|
return refuse('no-change');
|
|
@@ -433,9 +768,13 @@ export function buildBatch(input) {
|
|
|
433
768
|
const resulting = overlay(input.visible, [
|
|
434
769
|
{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
|
|
435
770
|
]);
|
|
771
|
+
const clean = input.clean !== undefined && available(input.composition) ? input.clean : null;
|
|
436
772
|
const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
|
|
437
773
|
if (removed.size > 0) {
|
|
438
|
-
const
|
|
774
|
+
const referrers = clean === null ? null : referring(clean, input.composition, removed);
|
|
775
|
+
const referenced = incoming(collectionOf(resulting.documents, input.composition), removed, clean === null || referrers === null
|
|
776
|
+
? null
|
|
777
|
+
: [...referrers, ...differing(clean.text, resulting.documents)]);
|
|
439
778
|
if (referenced !== null)
|
|
440
779
|
return referenced;
|
|
441
780
|
}
|
|
@@ -445,27 +784,33 @@ export function buildBatch(input) {
|
|
|
445
784
|
const parsed = parse(content);
|
|
446
785
|
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
447
786
|
};
|
|
448
|
-
const
|
|
787
|
+
const touched = [...new Set(predicted.changes.flatMap(affected))];
|
|
788
|
+
const typedPaths = touched.filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
|
|
449
789
|
const needsModel = typedPaths.length > 0;
|
|
450
790
|
const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
|
|
451
791
|
if (withoutModel !== undefined)
|
|
452
792
|
return refuse('model-unavailable', withoutModel);
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
793
|
+
const verdict = clean !== null &&
|
|
794
|
+
changedCleanly({ text: resulting.documents, binaries: resulting.binaries }, clean, input.composition)
|
|
795
|
+
? { verdict: 'accept' }
|
|
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);
|
|
457
801
|
if (verdict.verdict === 'refuse') {
|
|
458
802
|
return {
|
|
459
803
|
kind: 'refused',
|
|
460
804
|
refusal: {
|
|
461
|
-
|
|
805
|
+
code: verdict.rule,
|
|
806
|
+
retryable: false,
|
|
462
807
|
file: verdict.file,
|
|
463
808
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
464
809
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
465
810
|
},
|
|
466
811
|
};
|
|
467
812
|
}
|
|
468
|
-
const limited = exceeded(input.providerLimits, changes, resulting);
|
|
813
|
+
const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
|
|
469
814
|
if (limited !== null)
|
|
470
815
|
return limited;
|
|
471
816
|
const validated = typedPaths
|