@agentshouse/mdruntime 0.1.0-alpha.9 → 0.1.0-c0105dbd22806
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 +299 -0
- package/README.md +54 -0
- package/dist/adapter.d.ts +246 -40
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -10
- 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 +17 -7
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +97 -41
- package/dist/change-set.d.ts +33 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/change-set.js +90 -0
- package/dist/change.d.ts +24 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +31 -5
- 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 +49 -33
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +164 -183
- package/dist/collection.d.ts +9 -17
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +83 -89
- package/dist/connection.d.ts +66 -48
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1964 -1173
- package/dist/conversation.d.ts +65 -24
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +675 -334
- package/dist/durability.js +6 -6
- 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/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/file.d.ts +2 -2
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +48 -71
- package/dist/host.d.ts +15 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +109 -127
- package/dist/identity.d.ts +4 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +22 -9
- package/dist/index.d.ts +17 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/log.js +5 -6
- package/dist/memory.d.ts +19 -8
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +304 -216
- package/dist/model.d.ts +16 -30
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +21 -21
- package/dist/observation.d.ts +27 -0
- package/dist/observation.d.ts.map +1 -0
- package/dist/observation.js +95 -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 +37 -31
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +36 -18
- package/dist/overlay.d.ts +22 -4
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +236 -54
- package/dist/paths.d.ts +0 -2
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +6 -29
- package/dist/query.d.ts +219 -10
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +462 -15
- 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 +18 -0
- package/dist/read-side.d.ts.map +1 -0
- package/dist/read-side.js +440 -0
- package/dist/refresh.d.ts +15 -5
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +64 -41
- package/dist/replica.d.ts +28 -5
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +249 -40
- package/dist/room-draft.d.ts +144 -0
- package/dist/room-draft.d.ts.map +1 -0
- package/dist/room-draft.js +438 -0
- package/dist/rows.d.ts +46 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +145 -0
- package/dist/save.d.ts +33 -5
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +630 -366
- package/dist/search.d.ts +62 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +125 -0
- package/dist/status.d.ts +6 -8
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +8 -20
- package/dist/store.d.ts +89 -34
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +513 -132
- package/dist/transfer.d.ts +5 -0
- package/dist/transfer.d.ts.map +1 -1
- package/dist/transfer.js +1 -5
- package/dist/wire.d.ts +59 -19
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +88 -105
- package/package.json +34 -16
- package/dist/adapter.js.map +0 -1
- package/dist/bootstrap.js.map +0 -1
- package/dist/change.js.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/collection.js.map +0 -1
- package/dist/connection.js.map +0 -1
- package/dist/conversation.js.map +0 -1
- package/dist/durability.js.map +0 -1
- package/dist/file.js.map +0 -1
- package/dist/host.js.map +0 -1
- package/dist/identity.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/log.js.map +0 -1
- package/dist/memory.js.map +0 -1
- package/dist/model.js.map +0 -1
- package/dist/outcome.js.map +0 -1
- package/dist/overlay.js.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/query.js.map +0 -1
- package/dist/refresh.js.map +0 -1
- package/dist/replica.js.map +0 -1
- package/dist/save.js.map +0 -1
- package/dist/status.js.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/transfer.js.map +0 -1
- package/dist/wire.js.map +0 -1
package/dist/save.js
CHANGED
|
@@ -1,521 +1,784 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
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';
|
|
4
5
|
import { changeBatchIdentity } from './identity.js';
|
|
5
6
|
import { overlay } from './overlay.js';
|
|
6
|
-
import {
|
|
7
|
-
function refuse(
|
|
7
|
+
import { covered, normalize } from './paths.js';
|
|
8
|
+
function refuse(code, file, location) {
|
|
8
9
|
return {
|
|
9
10
|
kind: 'refused',
|
|
10
11
|
refusal: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
|
|
16
|
-
location
|
|
17
|
-
}
|
|
18
|
-
}
|
|
12
|
+
code,
|
|
13
|
+
retryable: false,
|
|
14
|
+
...(file === undefined ? {} : { file }),
|
|
15
|
+
...(location === undefined ? {} : { location }),
|
|
16
|
+
},
|
|
19
17
|
};
|
|
20
18
|
}
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
kind: 'move',
|
|
34
|
-
path: member,
|
|
35
|
-
to: `${change.to}${member.slice(path.length)}`
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
function expand(changes, visible) {
|
|
39
|
-
const expanded = [];
|
|
40
|
-
for (const change of changes){
|
|
41
|
-
if (change.kind !== 'move' && change.kind !== 'remove') {
|
|
42
|
-
expanded.push(change);
|
|
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}`))
|
|
43
31
|
continue;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const to = change.kind === 'move' ? normalize(change.to) : '';
|
|
47
|
-
if (path === null || path === '' || to === null || change.kind === 'move' && to === '') {
|
|
48
|
-
return {
|
|
49
|
-
kind: 'refused',
|
|
50
|
-
refusal: {
|
|
51
|
-
rule: 'room-root',
|
|
52
|
-
file: change.path
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
const structural = change.kind === 'move' ? {
|
|
57
|
-
...change,
|
|
58
|
-
path,
|
|
59
|
-
to
|
|
60
|
-
} : {
|
|
61
|
-
...change,
|
|
62
|
-
path
|
|
63
|
-
};
|
|
64
|
-
const present = visible.documents[path] !== undefined || visible.binaryTargets.includes(path);
|
|
65
|
-
if (present) {
|
|
66
|
-
expanded.push(structural);
|
|
32
|
+
const destination = moves.get(named) ?? named;
|
|
33
|
+
if (target(to, value) === destination)
|
|
67
34
|
continue;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
for (const member of beneathIt)expanded.push(spread(structural, path, member));
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
kind: 'changes',
|
|
83
|
-
changes: expanded
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
function referenceField(spec) {
|
|
87
|
-
if (typeof spec !== 'object' || spec === null) return false;
|
|
88
|
-
const declared = spec;
|
|
89
|
-
return declared.kind === 'reference' || declared.kind === 'list' && declared.of === 'reference';
|
|
90
|
-
}
|
|
91
|
-
function definitionOf(types, type) {
|
|
92
|
-
return types.find((declared)=>declared.name === type);
|
|
93
|
-
}
|
|
94
|
-
function retarget(value, from, to, moves) {
|
|
95
|
-
if (Array.isArray(value)) {
|
|
96
|
-
const next = value.map((item)=>retarget(item, from, to, moves) ?? item);
|
|
97
|
-
return next.some((item, index)=>item !== value[index]) ? next : undefined;
|
|
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);
|
|
98
45
|
}
|
|
99
|
-
|
|
100
|
-
const target = resolve(from, value);
|
|
101
|
-
if (target === null) return undefined;
|
|
102
|
-
const next = relative(to, moves.get(target) ?? target);
|
|
103
|
-
return next === value ? undefined : next;
|
|
46
|
+
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
104
47
|
}
|
|
105
48
|
function edited(document, path, changes) {
|
|
106
49
|
let next = document;
|
|
107
|
-
for (const change of changes){
|
|
108
|
-
if (change.path !== path)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
prior: next.preamble
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (applied.outcome === 'applied')
|
|
50
|
+
for (const change of changes) {
|
|
51
|
+
if (change.path !== path)
|
|
52
|
+
continue;
|
|
53
|
+
if (change.kind !== 'section' && change.kind !== 'preamble')
|
|
54
|
+
continue;
|
|
55
|
+
const applied = apply(next, change.kind === 'preamble'
|
|
56
|
+
? { kind: 'preamble', value: change.value, prior: next.preamble }
|
|
57
|
+
: {
|
|
58
|
+
kind: 'section',
|
|
59
|
+
name: change.name,
|
|
60
|
+
value: change.value,
|
|
61
|
+
prior: next.sections[change.name],
|
|
62
|
+
});
|
|
63
|
+
if (applied.outcome === 'applied')
|
|
64
|
+
next = applied.document;
|
|
121
65
|
}
|
|
122
66
|
return next;
|
|
123
67
|
}
|
|
124
68
|
function bodies(path, before, after) {
|
|
125
69
|
const changes = [];
|
|
126
70
|
if (after.preamble !== before.preamble) {
|
|
127
|
-
changes.push({
|
|
128
|
-
kind: 'preamble',
|
|
129
|
-
path,
|
|
130
|
-
value: after.preamble
|
|
131
|
-
});
|
|
71
|
+
changes.push({ kind: 'preamble', path, value: after.preamble });
|
|
132
72
|
}
|
|
133
|
-
for (const [name, value] of Object.entries(after.sections)){
|
|
134
|
-
if (before.sections[name] === value)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
path,
|
|
138
|
-
name,
|
|
139
|
-
value
|
|
140
|
-
});
|
|
73
|
+
for (const [name, value] of Object.entries(after.sections)) {
|
|
74
|
+
if (before.sections[name] === value)
|
|
75
|
+
continue;
|
|
76
|
+
changes.push({ kind: 'section', path, name, value });
|
|
141
77
|
}
|
|
142
78
|
return changes;
|
|
143
79
|
}
|
|
144
80
|
function relinks(path, moved, before, moves) {
|
|
145
81
|
let after = before;
|
|
146
|
-
for (const link of links(before, path)){
|
|
82
|
+
for (const link of links(before, path)) {
|
|
147
83
|
const destination = moves.get(link.target) ?? link.target;
|
|
148
|
-
if (destination === link.target && moved === path)
|
|
84
|
+
if (destination === link.target && moved === path)
|
|
85
|
+
continue;
|
|
149
86
|
const to = relative(moved, destination);
|
|
150
|
-
if (to === relative(path, link.target))
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
href: link.href,
|
|
154
|
-
to
|
|
155
|
-
});
|
|
87
|
+
if (to === relative(path, link.target))
|
|
88
|
+
continue;
|
|
89
|
+
const applied = apply(after, { kind: 'link', href: link.href, to });
|
|
156
90
|
if (applied.outcome === 'refuse') {
|
|
157
91
|
return {
|
|
158
92
|
kind: 'refused',
|
|
159
93
|
refusal: {
|
|
160
|
-
|
|
94
|
+
code: applied.refusal.rule,
|
|
95
|
+
retryable: false,
|
|
161
96
|
file: path,
|
|
162
|
-
...applied.refusal.location === undefined ? {} : {
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
}
|
|
97
|
+
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
98
|
+
},
|
|
166
99
|
};
|
|
167
100
|
}
|
|
168
101
|
after = applied.document;
|
|
169
102
|
}
|
|
170
|
-
if (after === before)
|
|
171
|
-
kind: 'changes',
|
|
172
|
-
changes: []
|
|
173
|
-
};
|
|
103
|
+
if (after === before)
|
|
104
|
+
return { kind: 'changes', changes: [] };
|
|
174
105
|
if (before.type === undefined) {
|
|
175
|
-
return {
|
|
176
|
-
kind: 'changes',
|
|
177
|
-
changes: [
|
|
178
|
-
{
|
|
179
|
-
kind: 'replace',
|
|
180
|
-
path,
|
|
181
|
-
content: serialize(after)
|
|
182
|
-
}
|
|
183
|
-
]
|
|
184
|
-
};
|
|
106
|
+
return { kind: 'changes', changes: [{ kind: 'replace', path, content: serialize(after) }] };
|
|
185
107
|
}
|
|
186
|
-
return {
|
|
187
|
-
kind: 'changes',
|
|
188
|
-
changes: bodies(path, before, after)
|
|
189
|
-
};
|
|
108
|
+
return { kind: 'changes', changes: bodies(path, before, after) };
|
|
190
109
|
}
|
|
191
110
|
function subsumed(merged, path) {
|
|
192
|
-
for(let index = merged.length - 1; index >= 0; index -= 1){
|
|
111
|
+
for (let index = merged.length - 1; index >= 0; index -= 1) {
|
|
193
112
|
const item = merged[index];
|
|
194
|
-
if (item.path !== path)
|
|
195
|
-
|
|
113
|
+
if (item.path !== path)
|
|
114
|
+
continue;
|
|
115
|
+
if (item.kind === 'section' || item.kind === 'preamble')
|
|
116
|
+
merged.splice(index, 1);
|
|
196
117
|
}
|
|
197
118
|
}
|
|
198
119
|
function rewrites(changes, visible) {
|
|
199
120
|
const moves = new Map();
|
|
200
121
|
const settled = new Set();
|
|
201
|
-
for (const change of changes){
|
|
202
|
-
if (change.kind === 'move')
|
|
203
|
-
|
|
122
|
+
for (const change of changes) {
|
|
123
|
+
if (change.kind === 'move')
|
|
124
|
+
moves.set(change.path, change.to);
|
|
125
|
+
if (change.kind === 'remove' ||
|
|
126
|
+
change.kind === 'replace' ||
|
|
127
|
+
change.kind === 'create' ||
|
|
128
|
+
change.kind === 'binary') {
|
|
204
129
|
settled.add(change.path);
|
|
205
130
|
}
|
|
206
|
-
if (change.kind === 'field')
|
|
131
|
+
if (change.kind === 'field')
|
|
132
|
+
settled.add(`${change.path} ${change.name}`);
|
|
207
133
|
}
|
|
208
|
-
if (moves.size === 0)
|
|
209
|
-
kind: 'changes',
|
|
210
|
-
|
|
211
|
-
};
|
|
212
|
-
const merged = [
|
|
213
|
-
...changes
|
|
214
|
-
];
|
|
134
|
+
if (moves.size === 0)
|
|
135
|
+
return { kind: 'changes', changes };
|
|
136
|
+
const merged = [...changes];
|
|
215
137
|
const added = [];
|
|
216
|
-
for (const [path, document] of
|
|
217
|
-
if (settled.has(path))
|
|
138
|
+
for (const [path, document] of documentsOf(visible.collection)) {
|
|
139
|
+
if (settled.has(path))
|
|
140
|
+
continue;
|
|
218
141
|
const moved = moves.get(path) ?? path;
|
|
219
|
-
const definition = definitionOf(
|
|
220
|
-
|
|
221
|
-
if (!referenceField(spec) || settled.has(`${path} ${name}`)) continue;
|
|
222
|
-
const value = retarget(document.fields[name], path, moved, moves);
|
|
223
|
-
if (value === undefined) continue;
|
|
224
|
-
added.push({
|
|
225
|
-
kind: 'field',
|
|
226
|
-
path,
|
|
227
|
-
name,
|
|
228
|
-
value
|
|
229
|
-
});
|
|
230
|
-
}
|
|
142
|
+
const definition = definitionOf(governing(visible.collection.composition, path), document.type);
|
|
143
|
+
added.push(...repointed(document, definition, path, moved, moves, settled));
|
|
231
144
|
const relinked = relinks(path, moved, edited(document, path, merged), moves);
|
|
232
|
-
if (relinked.kind === 'refused')
|
|
233
|
-
|
|
145
|
+
if (relinked.kind === 'refused')
|
|
146
|
+
return relinked;
|
|
147
|
+
for (const change of relinked.changes) {
|
|
234
148
|
if (change.kind === 'replace') {
|
|
235
149
|
subsumed(merged, path);
|
|
236
150
|
added.push(change);
|
|
237
151
|
continue;
|
|
238
152
|
}
|
|
239
|
-
const index = merged.findIndex((item)=>item.kind === change.kind &&
|
|
240
|
-
|
|
241
|
-
|
|
153
|
+
const index = merged.findIndex((item) => item.kind === change.kind &&
|
|
154
|
+
item.path === path &&
|
|
155
|
+
(change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
|
|
156
|
+
if (index === -1)
|
|
157
|
+
added.push(change);
|
|
158
|
+
else
|
|
159
|
+
merged[index] = change;
|
|
242
160
|
}
|
|
243
161
|
}
|
|
162
|
+
return { kind: 'changes', changes: [...added, ...merged] };
|
|
163
|
+
}
|
|
164
|
+
function anticipated(changes, preconditions, rewritten, visible) {
|
|
165
|
+
const derived = rewritten.filter((change) => !changes.includes(change));
|
|
244
166
|
return {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
...added,
|
|
248
|
-
...merged
|
|
249
|
-
]
|
|
167
|
+
changes: rewritten,
|
|
168
|
+
preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
|
|
250
169
|
};
|
|
251
170
|
}
|
|
252
|
-
function
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (target !== null) resolved.push(target);
|
|
261
|
-
}
|
|
262
|
-
return resolved;
|
|
171
|
+
export function providerRewritten(batch, visible) {
|
|
172
|
+
const rewritten = rewrites(batch.changes, visible);
|
|
173
|
+
if (rewritten.kind === 'refused')
|
|
174
|
+
return batch;
|
|
175
|
+
return {
|
|
176
|
+
...batch,
|
|
177
|
+
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
178
|
+
};
|
|
263
179
|
}
|
|
264
180
|
function incoming(collection, removed) {
|
|
265
181
|
const found = new Map();
|
|
266
|
-
const note = (target, file)=>{
|
|
267
|
-
if (!removed.has(target))
|
|
182
|
+
const note = (target, file) => {
|
|
183
|
+
if (!removed.has(target))
|
|
184
|
+
return;
|
|
268
185
|
const referrers = found.get(target) ?? new Set();
|
|
269
186
|
referrers.add(file);
|
|
270
187
|
found.set(target, referrers);
|
|
271
188
|
};
|
|
272
|
-
for (const [path, document] of
|
|
273
|
-
const definition = definitionOf(
|
|
274
|
-
for (const
|
|
275
|
-
if (
|
|
276
|
-
|
|
189
|
+
for (const [path, document] of documentsOf(collection)) {
|
|
190
|
+
const definition = definitionOf(governing(collection.composition, path), document.type);
|
|
191
|
+
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
192
|
+
if (to.includes(ATTACHMENT))
|
|
193
|
+
continue;
|
|
194
|
+
const named = target(path, value);
|
|
195
|
+
if (named !== null)
|
|
196
|
+
note(named, path);
|
|
277
197
|
}
|
|
278
|
-
for (const link of links(document, path))note(link.target, path);
|
|
279
198
|
}
|
|
280
|
-
const [first] = [
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (first === undefined) return null;
|
|
199
|
+
const [first] = [...found.keys()].sort();
|
|
200
|
+
if (first === undefined)
|
|
201
|
+
return null;
|
|
284
202
|
const every = new Set();
|
|
285
|
-
for (const referrers of found.values())
|
|
203
|
+
for (const referrers of found.values())
|
|
204
|
+
for (const file of referrers)
|
|
205
|
+
every.add(file);
|
|
286
206
|
return {
|
|
287
207
|
kind: 'refused',
|
|
288
208
|
refusal: {
|
|
289
|
-
|
|
209
|
+
code: 'referenced-missing-file',
|
|
210
|
+
retryable: false,
|
|
290
211
|
file: first,
|
|
291
|
-
referrers: [
|
|
292
|
-
|
|
293
|
-
].sort()
|
|
294
|
-
}
|
|
212
|
+
referrers: [...every].sort(),
|
|
213
|
+
},
|
|
295
214
|
};
|
|
296
215
|
}
|
|
297
216
|
function affected(change) {
|
|
298
|
-
return change.kind === 'move' ? [
|
|
299
|
-
change.path,
|
|
300
|
-
change.to
|
|
301
|
-
] : [
|
|
302
|
-
change.path
|
|
303
|
-
];
|
|
304
|
-
}
|
|
305
|
-
function definitionAddresses(composition) {
|
|
306
|
-
const addresses = new Set();
|
|
307
|
-
if (composition.model.status !== 'unavailable') {
|
|
308
|
-
for (const path of Object.keys(composition.model.snapshot.definitions))addresses.add(path);
|
|
309
|
-
}
|
|
310
|
-
if (composition.selection !== null && composition.selection.model.status !== 'unavailable') {
|
|
311
|
-
for (const path of Object.keys(composition.selection.model.snapshot.definitions)){
|
|
312
|
-
addresses.add(path);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return addresses;
|
|
217
|
+
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
316
218
|
}
|
|
317
219
|
function guard(changes, input) {
|
|
318
220
|
const existing = new Set([
|
|
319
221
|
...Object.keys(input.visible.documents),
|
|
320
|
-
...input.visible.
|
|
222
|
+
...Object.keys(input.visible.binaries),
|
|
321
223
|
]);
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
for (const path of affected(change)){
|
|
224
|
+
for (const [index, change] of changes.entries()) {
|
|
225
|
+
for (const path of affected(change)) {
|
|
325
226
|
const normalized = normalize(path);
|
|
326
|
-
if (normalized === null || normalized === '')
|
|
327
|
-
|
|
227
|
+
if (normalized === null || normalized === '')
|
|
228
|
+
return refuse('room-root', path);
|
|
328
229
|
if (covered(input.providerOwnedPaths, normalized)) {
|
|
329
230
|
return refuse('provider-owned-path', normalized);
|
|
330
231
|
}
|
|
331
232
|
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
332
|
-
if (!covered(input.operations[operation], normalized))
|
|
233
|
+
if (!input.local && !covered(input.operations[operation], normalized))
|
|
234
|
+
return refuse(`${operation}-grant`, normalized);
|
|
333
235
|
}
|
|
334
236
|
if (change.kind === 'create') {
|
|
335
|
-
if (existing.has(change.path))
|
|
237
|
+
if (existing.has(change.path))
|
|
238
|
+
return refuse('path-collision', change.path);
|
|
239
|
+
existing.add(change.path);
|
|
240
|
+
continue;
|
|
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
|
+
}
|
|
336
253
|
existing.add(change.path);
|
|
337
254
|
continue;
|
|
338
255
|
}
|
|
339
256
|
if (change.kind === 'move') {
|
|
340
|
-
if (!existing.has(change.path))
|
|
341
|
-
|
|
257
|
+
if (!existing.has(change.path))
|
|
258
|
+
return refuse('path-absent', change.path);
|
|
259
|
+
if (existing.has(change.to))
|
|
260
|
+
return refuse('path-collision', change.to);
|
|
261
|
+
if (unrevised(input, change.path)) {
|
|
262
|
+
return refuse('unknown-revision', change.path);
|
|
263
|
+
}
|
|
342
264
|
existing.delete(change.path);
|
|
343
265
|
existing.add(change.to);
|
|
344
266
|
continue;
|
|
345
267
|
}
|
|
346
|
-
if (!existing.has(change.path))
|
|
268
|
+
if (!existing.has(change.path))
|
|
269
|
+
return refuse('path-absent', change.path);
|
|
347
270
|
if (change.kind === 'remove') {
|
|
271
|
+
if (unrevised(input, change.path)) {
|
|
272
|
+
return refuse('unknown-revision', change.path);
|
|
273
|
+
}
|
|
348
274
|
existing.delete(change.path);
|
|
349
275
|
continue;
|
|
350
276
|
}
|
|
351
277
|
if (change.kind === 'replace') {
|
|
352
|
-
if (input.visible.revisions[change.path] === undefined) {
|
|
278
|
+
if (!input.local && input.visible.revisions[change.path] === undefined) {
|
|
353
279
|
return refuse('unknown-revision', change.path);
|
|
354
280
|
}
|
|
355
281
|
continue;
|
|
356
282
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (change.kind === 'section' && document.sections[change.name] === undefined) {
|
|
360
|
-
return refuse('required-section', change.path, change.name);
|
|
283
|
+
if (input.visible.collection.document(change.path) === undefined) {
|
|
284
|
+
return refuse('path-absent', change.path);
|
|
361
285
|
}
|
|
362
286
|
}
|
|
363
287
|
return null;
|
|
364
288
|
}
|
|
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}`;
|
|
295
|
+
}
|
|
365
296
|
function existence(visible, path) {
|
|
366
297
|
const revision = visible.revisions[path];
|
|
367
|
-
|
|
368
|
-
kind: '
|
|
369
|
-
|
|
370
|
-
} : {
|
|
371
|
-
kind: 'revision',
|
|
372
|
-
path,
|
|
373
|
-
revision
|
|
374
|
-
};
|
|
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 };
|
|
375
302
|
}
|
|
376
|
-
function
|
|
303
|
+
function sameTarget(precondition, change) {
|
|
304
|
+
if (precondition.kind !== change.kind || precondition.path !== change.path)
|
|
305
|
+
return false;
|
|
306
|
+
return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
|
|
307
|
+
((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
|
|
308
|
+
}
|
|
309
|
+
function preconditionsOf(changes, visible, bases) {
|
|
377
310
|
const preconditions = [];
|
|
378
311
|
const seen = new Set();
|
|
379
|
-
const add = (precondition)=>{
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
seen.add(
|
|
312
|
+
const add = (precondition) => {
|
|
313
|
+
const held = identity(precondition);
|
|
314
|
+
if (seen.has(held))
|
|
315
|
+
return;
|
|
316
|
+
seen.add(held);
|
|
384
317
|
preconditions.push(precondition);
|
|
385
318
|
};
|
|
386
|
-
for (const change of changes){
|
|
387
|
-
if (change.kind === 'field') {
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
319
|
+
for (const change of changes) {
|
|
320
|
+
if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
|
|
321
|
+
const based = bases.find((base) => sameTarget(base, change));
|
|
322
|
+
if (based !== undefined) {
|
|
323
|
+
add(based);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const document = visible.collection.document(change.path);
|
|
327
|
+
if (change.kind === 'field') {
|
|
328
|
+
add({
|
|
329
|
+
kind: 'field',
|
|
330
|
+
path: change.path,
|
|
331
|
+
name: change.name,
|
|
332
|
+
value: document.fields[change.name],
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
else if (change.kind === 'section') {
|
|
336
|
+
add({
|
|
337
|
+
kind: 'section',
|
|
338
|
+
path: change.path,
|
|
339
|
+
name: change.name,
|
|
340
|
+
value: document.sections[change.name],
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
else
|
|
344
|
+
add({ kind: 'preamble', path: change.path, value: document.preamble });
|
|
405
345
|
continue;
|
|
406
346
|
}
|
|
407
|
-
if (change.kind === '
|
|
408
|
-
|
|
409
|
-
add({
|
|
410
|
-
kind: 'preamble',
|
|
411
|
-
path: change.path,
|
|
412
|
-
value: document.preamble
|
|
413
|
-
});
|
|
347
|
+
if (change.kind === 'create') {
|
|
348
|
+
add({ kind: 'absent', path: change.path });
|
|
414
349
|
continue;
|
|
415
350
|
}
|
|
416
|
-
if (change.kind === '
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
path: change.path
|
|
420
|
-
|
|
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 });
|
|
421
356
|
continue;
|
|
422
357
|
}
|
|
423
358
|
add(existence(visible, change.path));
|
|
424
|
-
if (change.kind === 'move')
|
|
425
|
-
kind: 'absent',
|
|
426
|
-
path: change.to
|
|
427
|
-
});
|
|
359
|
+
if (change.kind === 'move')
|
|
360
|
+
add({ kind: 'absent', path: change.to });
|
|
428
361
|
}
|
|
429
362
|
return preconditions;
|
|
430
363
|
}
|
|
431
364
|
function depth(path) {
|
|
432
365
|
return path.split('/').length;
|
|
433
366
|
}
|
|
434
|
-
function exceeded(limits, changes, resulting) {
|
|
435
|
-
const written =
|
|
436
|
-
const created = changes
|
|
437
|
-
|
|
367
|
+
function exceeded(limits, changes, effective, resulting) {
|
|
368
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
369
|
+
const created = changes
|
|
370
|
+
.filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
|
|
371
|
+
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
372
|
+
for (const limit of limits) {
|
|
438
373
|
const value = limit.value;
|
|
439
|
-
if (typeof value !== 'number')
|
|
374
|
+
if (typeof value !== 'number')
|
|
375
|
+
continue;
|
|
440
376
|
if (limit.name === 'change-batch-operations' && changes.length > value) {
|
|
441
377
|
return refuse('provider-limit', undefined, limit.name);
|
|
442
378
|
}
|
|
379
|
+
if (limit.name === 'change-documents' &&
|
|
380
|
+
new Set(changes.map((change) => change.path)).size > value) {
|
|
381
|
+
return refuse('provider-limit', undefined, limit.name);
|
|
382
|
+
}
|
|
443
383
|
if (limit.name === 'document-size') {
|
|
444
|
-
const large = written.find((path)=>(resulting.documents[path] ?? '').
|
|
445
|
-
if (large !== undefined)
|
|
384
|
+
const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
|
|
385
|
+
if (large !== undefined)
|
|
386
|
+
return refuse('provider-limit', large, limit.name);
|
|
446
387
|
}
|
|
447
388
|
if (limit.name === 'path-length') {
|
|
448
|
-
const long = created.find((path)=>path.length > value);
|
|
449
|
-
if (long !== undefined)
|
|
389
|
+
const long = created.find((path) => path.length > value);
|
|
390
|
+
if (long !== undefined)
|
|
391
|
+
return refuse('provider-limit', long, limit.name);
|
|
450
392
|
}
|
|
451
393
|
if (limit.name === 'path-depth') {
|
|
452
|
-
const deep = created.find((path)=>depth(path) > value);
|
|
453
|
-
if (deep !== undefined)
|
|
394
|
+
const deep = created.find((path) => depth(path) > value);
|
|
395
|
+
if (deep !== undefined)
|
|
396
|
+
return refuse('provider-limit', deep, limit.name);
|
|
454
397
|
}
|
|
455
398
|
}
|
|
456
399
|
return null;
|
|
457
400
|
}
|
|
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 &&
|
|
455
|
+
document.type !== 'any' &&
|
|
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
|
+
}
|
|
458
725
|
export function buildBatch(input) {
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
if (
|
|
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))
|
|
729
|
+
return refuse('model-unavailable', structural.path);
|
|
730
|
+
if (changes.length === 0)
|
|
731
|
+
return refuse('no-change');
|
|
732
|
+
const rewritten = rewrites(changes, input.visible);
|
|
733
|
+
if (rewritten.kind === 'refused')
|
|
734
|
+
return rewritten;
|
|
467
735
|
const refusal = guard(changes, input);
|
|
468
|
-
if (refusal !== null)
|
|
469
|
-
|
|
736
|
+
if (refusal !== null)
|
|
737
|
+
return refusal;
|
|
738
|
+
const preconditions = preconditionsOf(changes, input.visible, input.bases);
|
|
739
|
+
const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
|
|
470
740
|
const resulting = overlay(input.visible, [
|
|
471
|
-
{
|
|
472
|
-
id: '',
|
|
473
|
-
baseRoomVersion: input.baseRoomVersion,
|
|
474
|
-
changes,
|
|
475
|
-
preconditions
|
|
476
|
-
}
|
|
741
|
+
{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
|
|
477
742
|
]);
|
|
478
|
-
const removed = new Set(changes.filter((change)=>change.kind === 'remove').map((change)=>change.path));
|
|
743
|
+
const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
|
|
479
744
|
if (removed.size > 0) {
|
|
480
|
-
const referenced = incoming(
|
|
481
|
-
if (referenced !== null)
|
|
745
|
+
const referenced = incoming(collectionOf(resulting.documents, input.composition), removed);
|
|
746
|
+
if (referenced !== null)
|
|
747
|
+
return referenced;
|
|
482
748
|
}
|
|
483
|
-
const typed = (content)=>{
|
|
484
|
-
if (content === undefined)
|
|
749
|
+
const typed = (content) => {
|
|
750
|
+
if (content === undefined)
|
|
751
|
+
return false;
|
|
485
752
|
const parsed = parse(content);
|
|
486
753
|
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
487
754
|
};
|
|
488
|
-
const typedPaths = [
|
|
489
|
-
...new Set(changes.flatMap(affected))
|
|
490
|
-
].filter((path)=>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]));
|
|
491
756
|
const needsModel = typedPaths.length > 0;
|
|
492
|
-
const withoutModel = typedPaths.find((path)=>governing(input.composition, path).status === 'unavailable');
|
|
493
|
-
if (
|
|
494
|
-
return refuse('model-unavailable', withoutModel
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
757
|
+
const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
|
|
758
|
+
if (withoutModel !== undefined)
|
|
759
|
+
return refuse('model-unavailable', withoutModel);
|
|
760
|
+
const validationText = needsModel
|
|
761
|
+
? resulting.documents
|
|
762
|
+
: Object.fromEntries(Object.entries(resulting.documents).filter(([path]) => predicted.changes.some((change) => affected(change).includes(path))));
|
|
763
|
+
const verdict = validateCollection(validationText, Object.keys(resulting.binaries), input.composition);
|
|
498
764
|
if (verdict.verdict === 'refuse') {
|
|
499
765
|
return {
|
|
500
766
|
kind: 'refused',
|
|
501
767
|
refusal: {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
} : {},
|
|
505
|
-
rule: verdict.rule,
|
|
768
|
+
code: verdict.rule,
|
|
769
|
+
retryable: false,
|
|
506
770
|
file: verdict.file,
|
|
507
|
-
...verdict.location === undefined ? {} : {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
...verdict.referrers === undefined ? {} : {
|
|
511
|
-
referrers: verdict.referrers
|
|
512
|
-
}
|
|
513
|
-
}
|
|
771
|
+
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
772
|
+
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
773
|
+
},
|
|
514
774
|
};
|
|
515
775
|
}
|
|
516
|
-
const limited = exceeded(input.providerLimits, changes, resulting);
|
|
517
|
-
if (limited !== null)
|
|
518
|
-
|
|
776
|
+
const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
|
|
777
|
+
if (limited !== null)
|
|
778
|
+
return limited;
|
|
779
|
+
const validated = typedPaths
|
|
780
|
+
.map((path) => governing(input.composition, path))
|
|
781
|
+
.find((model) => model.status !== 'unavailable');
|
|
519
782
|
return {
|
|
520
783
|
kind: 'batch',
|
|
521
784
|
batch: immutable({
|
|
@@ -523,15 +786,16 @@ export function buildBatch(input) {
|
|
|
523
786
|
baseRoomVersion: input.baseRoomVersion,
|
|
524
787
|
changes,
|
|
525
788
|
preconditions,
|
|
526
|
-
...
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
789
|
+
...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
|
|
790
|
+
...(validated === undefined
|
|
791
|
+
? {}
|
|
792
|
+
: {
|
|
793
|
+
validation: {
|
|
794
|
+
identity: validated.snapshot.identity,
|
|
795
|
+
version: validated.snapshot.version,
|
|
796
|
+
source: validated.status,
|
|
797
|
+
},
|
|
798
|
+
}),
|
|
799
|
+
}),
|
|
534
800
|
};
|
|
535
801
|
}
|
|
536
|
-
|
|
537
|
-
//# sourceMappingURL=save.js.map
|