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