@agentshouse/mdruntime 0.1.0-c2aaa9c0e3ca4 → 0.1.0-c2c52158b7381
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 +285 -276
- package/README.md +9 -8
- package/dist/adapter.d.ts +80 -21
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +4 -1
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +13 -3
- package/dist/attempt.d.ts.map +1 -1
- package/dist/attempt.js +14 -1
- package/dist/bootstrap.d.ts +3 -2
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +83 -26
- package/dist/change-set.d.ts +7 -2
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +37 -10
- package/dist/change.d.ts +31 -1
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +10 -1
- package/dist/chat/acts.d.ts +3 -4
- package/dist/chat/acts.d.ts.map +1 -1
- package/dist/chat/acts.js +10 -10
- package/dist/chat/earlier.d.ts +10 -0
- package/dist/chat/earlier.d.ts.map +1 -0
- package/dist/chat/earlier.js +54 -0
- package/dist/chat/house.d.ts +25 -0
- package/dist/chat/house.d.ts.map +1 -0
- package/dist/chat/house.js +38 -0
- package/dist/chat/input.d.ts +3 -1
- package/dist/chat/input.d.ts.map +1 -1
- package/dist/chat/input.js +11 -18
- package/dist/chat/kit.d.ts +2 -1
- package/dist/chat/kit.d.ts.map +1 -1
- package/dist/chat/kit.js +3 -3
- package/dist/chat/list.d.ts +9 -1
- package/dist/chat/list.d.ts.map +1 -1
- package/dist/chat/list.js +53 -9
- package/dist/chat/markdown.d.ts +3 -1
- package/dist/chat/markdown.d.ts.map +1 -1
- package/dist/chat/markdown.js +85 -26
- package/dist/chat/message.d.ts +1 -0
- package/dist/chat/message.d.ts.map +1 -1
- package/dist/chat/message.js +57 -12
- package/dist/chat/messages.d.ts +9 -1
- package/dist/chat/messages.d.ts.map +1 -1
- package/dist/chat/messages.js +67 -28
- package/dist/chat/name.d.ts +1 -0
- package/dist/chat/name.d.ts.map +1 -1
- package/dist/chat/name.js +1 -1
- package/dist/chat/opened.d.ts +1 -0
- package/dist/chat/opened.d.ts.map +1 -1
- package/dist/chat/opened.js +2 -0
- package/dist/chat/overlay.d.ts +1 -0
- package/dist/chat/overlay.d.ts.map +1 -1
- package/dist/chat/overlay.js +11 -5
- package/dist/chat/parts.d.ts +1 -0
- package/dist/chat/parts.d.ts.map +1 -1
- package/dist/chat/parts.js +3 -2
- package/dist/chat/pieces.d.ts +62 -0
- package/dist/chat/pieces.d.ts.map +1 -0
- package/dist/chat/pieces.js +1110 -0
- package/dist/chat/provider.d.ts +7 -2
- package/dist/chat/provider.d.ts.map +1 -1
- package/dist/chat/provider.js +129 -155
- package/dist/chat/question.d.ts +1 -1
- package/dist/chat/question.d.ts.map +1 -1
- package/dist/chat/question.js +7 -18
- package/dist/chat/ready.d.ts +5 -1
- package/dist/chat/ready.d.ts.map +1 -1
- package/dist/chat/ready.js +9 -7
- package/dist/chat/room-file.d.ts +8 -0
- package/dist/chat/room-file.d.ts.map +1 -0
- package/dist/chat/room-file.js +72 -0
- package/dist/chat/steady.d.ts +3 -0
- package/dist/chat/steady.d.ts.map +1 -0
- package/dist/chat/steady.js +26 -0
- package/dist/chat/style.d.ts.map +1 -1
- package/dist/chat/style.js +8 -4
- package/dist/chat.d.ts +3 -2
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/client.d.ts +21 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +47 -8
- package/dist/collection.d.ts +21 -6
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +332 -40
- package/dist/connection.d.ts +39 -11
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +2294 -492
- package/dist/conversation.d.ts +28 -9
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +500 -165
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/durability.d.ts +8 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +18 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +157 -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 -10
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +3 -120
- package/dist/host.d.ts +15 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +103 -13
- package/dist/identity.d.ts +2 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +6 -0
- package/dist/index.d.ts +15 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/memory.d.ts +8 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +43 -12
- package/dist/model.d.ts +4 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -1
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +7 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +29 -12
- package/dist/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/outcome.d.ts +39 -4
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +29 -0
- package/dist/overlay.d.ts +6 -4
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +229 -161
- package/dist/query.d.ts +57 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +37 -7
- package/dist/reach.d.ts +11 -4
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +192 -41
- package/dist/react.d.ts +8 -6
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +7 -17
- package/dist/read-side.d.ts +18 -9
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +894 -155
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/reads.d.ts +11 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +98 -0
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +198 -0
- package/dist/refresh.d.ts +4 -4
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +46 -20
- package/dist/replica.d.ts +12 -7
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +151 -64
- package/dist/room-draft.d.ts +16 -2
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +32 -25
- package/dist/rows.d.ts +6 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +10 -13
- package/dist/save.d.ts +52 -10
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +519 -159
- package/dist/search.d.ts +49 -6
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +242 -46
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +75 -0
- package/dist/shared.d.ts +19 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +461 -0
- package/dist/status.d.ts +4 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/store.d.ts +61 -17
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +581 -310
- package/dist/tables.d.ts +242 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +406 -0
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +67 -0
- package/dist/windows.d.ts +21 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +84 -0
- package/dist/wire.d.ts +168 -32
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +56 -31
- package/package.json +10 -2
package/dist/save.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
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';
|
|
2
|
+
import { apply, ARRAY_INDEX, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
|
|
3
|
+
import { immutable, leaving, queuedFile, departure, } from './change.js';
|
|
4
|
+
import { available, changedCleanly, collectionOf, documentsOf, governing, referring, validateCollection, } from './collection.js';
|
|
5
5
|
import { changeBatchIdentity } from './identity.js';
|
|
6
|
-
import {
|
|
6
|
+
import { overlaying } from './overlay.js';
|
|
7
7
|
import { covered, normalize } from './paths.js';
|
|
8
|
+
import { differences, sorting } from './shared.js';
|
|
9
|
+
import { ROW_WEIGHT, stepped } from './turn.js';
|
|
8
10
|
function refuse(code, file, location) {
|
|
9
11
|
return {
|
|
10
12
|
kind: 'refused',
|
|
@@ -20,7 +22,7 @@ function repointed(document, definition, from, to, moves, settled) {
|
|
|
20
22
|
const parsed = parse(document.content, from);
|
|
21
23
|
if (!parsed.ok)
|
|
22
24
|
return [];
|
|
23
|
-
|
|
25
|
+
const changes = [];
|
|
24
26
|
const names = new Set();
|
|
25
27
|
for (const { pointer, value } of definition === undefined
|
|
26
28
|
? []
|
|
@@ -32,38 +34,39 @@ function repointed(document, definition, from, to, moves, settled) {
|
|
|
32
34
|
const destination = moves.get(named) ?? named;
|
|
33
35
|
if (target(to, value) === destination)
|
|
34
36
|
continue;
|
|
35
|
-
|
|
37
|
+
changes.push({
|
|
36
38
|
kind: 'reference',
|
|
37
39
|
pointer,
|
|
38
40
|
value: retarget(value, relative(to, destination)),
|
|
39
41
|
prior: value,
|
|
40
42
|
});
|
|
41
|
-
if (applied.outcome !== 'applied')
|
|
42
|
-
continue;
|
|
43
|
-
next = applied.document;
|
|
44
43
|
names.add(name);
|
|
45
44
|
}
|
|
45
|
+
if (changes.length === 0)
|
|
46
|
+
return [];
|
|
47
|
+
const applied = apply(parsed.document, changes);
|
|
48
|
+
if (applied.outcome !== 'applied')
|
|
49
|
+
return [];
|
|
50
|
+
const next = applied.document;
|
|
46
51
|
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
47
52
|
}
|
|
48
|
-
function edited(document,
|
|
49
|
-
|
|
53
|
+
function edited(document, changes) {
|
|
54
|
+
const parts = new Map();
|
|
50
55
|
for (const change of changes) {
|
|
51
|
-
if (change.
|
|
52
|
-
|
|
53
|
-
if (change.kind
|
|
54
|
-
|
|
55
|
-
const applied = apply(next, change.kind === 'preamble'
|
|
56
|
-
? { kind: 'preamble', value: change.value, prior: next.preamble }
|
|
57
|
-
: {
|
|
56
|
+
if (change.kind === 'preamble')
|
|
57
|
+
parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
58
|
+
if (change.kind === 'section')
|
|
59
|
+
parts.set(`section ${change.name}`, {
|
|
58
60
|
kind: 'section',
|
|
59
61
|
name: change.name,
|
|
60
62
|
value: change.value,
|
|
61
|
-
prior:
|
|
63
|
+
prior: document.sections[change.name] ?? '',
|
|
62
64
|
});
|
|
63
|
-
if (applied.outcome === 'applied')
|
|
64
|
-
next = applied.document;
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
if (parts.size === 0)
|
|
67
|
+
return document;
|
|
68
|
+
const applied = apply(document, [...parts.values()]);
|
|
69
|
+
return applied.outcome === 'applied' ? applied.document : document;
|
|
67
70
|
}
|
|
68
71
|
function bodies(path, before, after) {
|
|
69
72
|
const changes = [];
|
|
@@ -78,7 +81,7 @@ function bodies(path, before, after) {
|
|
|
78
81
|
return changes;
|
|
79
82
|
}
|
|
80
83
|
function relinks(path, moved, before, moves) {
|
|
81
|
-
|
|
84
|
+
const changes = [];
|
|
82
85
|
for (const link of links(before, path)) {
|
|
83
86
|
const destination = moves.get(link.target) ?? link.target;
|
|
84
87
|
if (destination === link.target && moved === path)
|
|
@@ -86,20 +89,23 @@ function relinks(path, moved, before, moves) {
|
|
|
86
89
|
const to = relative(moved, destination);
|
|
87
90
|
if (to === relative(path, link.target))
|
|
88
91
|
continue;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
changes.push({ kind: 'link', href: link.href, to });
|
|
93
|
+
}
|
|
94
|
+
if (changes.length === 0)
|
|
95
|
+
return { kind: 'changes', changes: [] };
|
|
96
|
+
const applied = apply(before, changes);
|
|
97
|
+
if (applied.outcome === 'refuse') {
|
|
98
|
+
return {
|
|
99
|
+
kind: 'refused',
|
|
100
|
+
refusal: {
|
|
101
|
+
code: applied.refusal.rule,
|
|
102
|
+
retryable: false,
|
|
103
|
+
file: path,
|
|
104
|
+
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
105
|
+
},
|
|
106
|
+
};
|
|
102
107
|
}
|
|
108
|
+
const after = applied.document;
|
|
103
109
|
if (after === before)
|
|
104
110
|
return { kind: 'changes', changes: [] };
|
|
105
111
|
if (before.type === undefined) {
|
|
@@ -107,19 +113,14 @@ function relinks(path, moved, before, moves) {
|
|
|
107
113
|
}
|
|
108
114
|
return { kind: 'changes', changes: bodies(path, before, after) };
|
|
109
115
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
const item = merged[index];
|
|
113
|
-
if (item.path !== path)
|
|
114
|
-
continue;
|
|
115
|
-
if (item.kind === 'section' || item.kind === 'preamble')
|
|
116
|
-
merged.splice(index, 1);
|
|
117
|
-
}
|
|
116
|
+
function partKey(change) {
|
|
117
|
+
return change.kind === 'section' ? `section ${change.name}` : change.kind;
|
|
118
118
|
}
|
|
119
|
-
function rewrites(changes, visible) {
|
|
119
|
+
function* rewrites(changes, visible) {
|
|
120
120
|
const moves = new Map();
|
|
121
121
|
const settled = new Set();
|
|
122
|
-
|
|
122
|
+
const parts = new Map();
|
|
123
|
+
for (const [index, change] of changes.entries()) {
|
|
123
124
|
if (change.kind === 'move')
|
|
124
125
|
moves.set(change.path, change.to);
|
|
125
126
|
if (change.kind === 'remove' ||
|
|
@@ -130,54 +131,147 @@ function rewrites(changes, visible) {
|
|
|
130
131
|
}
|
|
131
132
|
if (change.kind === 'field')
|
|
132
133
|
settled.add(`${change.path} ${change.name}`);
|
|
134
|
+
if (isPart(change)) {
|
|
135
|
+
const held = parts.get(change.path);
|
|
136
|
+
if (held === undefined)
|
|
137
|
+
parts.set(change.path, [index]);
|
|
138
|
+
else
|
|
139
|
+
held.push(index);
|
|
140
|
+
}
|
|
133
141
|
}
|
|
134
142
|
if (moves.size === 0)
|
|
135
143
|
return { kind: 'changes', changes };
|
|
136
144
|
const merged = [...changes];
|
|
137
145
|
const added = [];
|
|
138
|
-
for (const [path, document] of documentsOf(visible.collection)) {
|
|
146
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
139
147
|
if (settled.has(path))
|
|
140
148
|
continue;
|
|
141
149
|
const moved = moves.get(path) ?? path;
|
|
142
150
|
const definition = definitionOf(governing(visible.collection.composition, path), document.type);
|
|
143
|
-
|
|
144
|
-
|
|
151
|
+
const repointing = repointed(document, definition, path, moved, moves, settled);
|
|
152
|
+
added.push(...repointing);
|
|
153
|
+
const own = parts.get(path) ?? [];
|
|
154
|
+
const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
|
|
145
155
|
if (relinked.kind === 'refused')
|
|
146
156
|
return relinked;
|
|
157
|
+
const keyed = new Map();
|
|
158
|
+
for (const index of own) {
|
|
159
|
+
const key = partKey(merged[index]);
|
|
160
|
+
if (!keyed.has(key))
|
|
161
|
+
keyed.set(key, index);
|
|
162
|
+
}
|
|
147
163
|
for (const change of relinked.changes) {
|
|
148
164
|
if (change.kind === 'replace') {
|
|
149
|
-
|
|
165
|
+
for (const index of own) {
|
|
166
|
+
const kind = merged[index]?.kind;
|
|
167
|
+
if (kind === 'section' || kind === 'preamble')
|
|
168
|
+
merged[index] = null;
|
|
169
|
+
}
|
|
150
170
|
added.push(change);
|
|
151
171
|
continue;
|
|
152
172
|
}
|
|
153
|
-
const index =
|
|
154
|
-
|
|
155
|
-
(change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
|
|
156
|
-
if (index === -1)
|
|
173
|
+
const index = keyed.get(partKey(change));
|
|
174
|
+
if (index === undefined)
|
|
157
175
|
added.push(change);
|
|
158
176
|
else
|
|
159
177
|
merged[index] = change;
|
|
160
178
|
}
|
|
179
|
+
const text = visible.collection.text[path].length;
|
|
180
|
+
const rewritten = repointing.length > 0 || relinked.changes.length > 0;
|
|
181
|
+
yield ROW_WEIGHT + text + (rewritten ? text : 0);
|
|
161
182
|
}
|
|
162
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
kind: 'changes',
|
|
185
|
+
changes: [...added, ...merged.filter((change) => change !== null)],
|
|
186
|
+
};
|
|
163
187
|
}
|
|
164
|
-
function anticipated(changes, preconditions, rewritten, visible) {
|
|
165
|
-
const
|
|
188
|
+
function* anticipated(changes, preconditions, rewritten, visible) {
|
|
189
|
+
const given = new Set(changes);
|
|
190
|
+
const derived = rewritten.filter((change) => !given.has(change));
|
|
166
191
|
return {
|
|
167
192
|
changes: rewritten,
|
|
168
|
-
preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
|
|
193
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
|
|
169
194
|
};
|
|
170
195
|
}
|
|
171
|
-
export function providerRewritten(batch, visible) {
|
|
172
|
-
const rewritten = rewrites(batch.changes, visible);
|
|
196
|
+
export function* providerRewritten(batch, visible) {
|
|
197
|
+
const rewritten = yield* rewrites(batch.changes, visible);
|
|
173
198
|
if (rewritten.kind === 'refused')
|
|
174
199
|
return batch;
|
|
175
200
|
return {
|
|
176
201
|
...batch,
|
|
177
|
-
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
202
|
+
...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
|
|
178
203
|
};
|
|
179
204
|
}
|
|
180
|
-
function
|
|
205
|
+
function crossed(document, definition, path, to) {
|
|
206
|
+
const changes = [];
|
|
207
|
+
for (const link of links(document, path)) {
|
|
208
|
+
const written = to(link.target, link.href);
|
|
209
|
+
if (written !== undefined)
|
|
210
|
+
changes.push({ kind: 'link', href: link.href, to: written });
|
|
211
|
+
}
|
|
212
|
+
for (const { pointer, value } of definition === undefined
|
|
213
|
+
? []
|
|
214
|
+
: references(document, definition)) {
|
|
215
|
+
const named = target(path, value);
|
|
216
|
+
const written = named === null ? undefined : to(named, value);
|
|
217
|
+
if (written !== undefined) {
|
|
218
|
+
changes.push({ kind: 'reference', pointer, value: retarget(value, written), prior: value });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return changes;
|
|
222
|
+
}
|
|
223
|
+
function rewritten(path, document, changes) {
|
|
224
|
+
if (changes.length === 0)
|
|
225
|
+
return [];
|
|
226
|
+
const applied = apply(document, changes);
|
|
227
|
+
if (applied.outcome !== 'applied')
|
|
228
|
+
return [];
|
|
229
|
+
const after = applied.document;
|
|
230
|
+
if (document.type === undefined)
|
|
231
|
+
return [{ kind: 'replace', path, content: serialize(after) }];
|
|
232
|
+
const names = new Set(changes.flatMap((change) => change.kind === 'reference' ? [pointerSegments(change.pointer)[0] ?? ''] : []));
|
|
233
|
+
return [
|
|
234
|
+
...[...names].map((name) => ({ kind: 'field', path, name, value: after.fields[name] })),
|
|
235
|
+
...bodies(path, document, after),
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
function roomPathOf(handle, path) {
|
|
239
|
+
return `/rooms/${handle}/${path}`;
|
|
240
|
+
}
|
|
241
|
+
export function* leavingRewritten(batch, visible) {
|
|
242
|
+
const between = batch.between;
|
|
243
|
+
if (between === undefined)
|
|
244
|
+
return batch;
|
|
245
|
+
const moved = new Map(between.moves.map(({ path, to }) => [path, roomPathOf(between.destination.handle, to)]));
|
|
246
|
+
const derived = [];
|
|
247
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
248
|
+
if (moved.has(path))
|
|
249
|
+
continue;
|
|
250
|
+
const definition = definitionOf(governing(visible.collection.composition, path), document.type);
|
|
251
|
+
derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named))));
|
|
252
|
+
yield ROW_WEIGHT + visible.collection.text[path].length;
|
|
253
|
+
}
|
|
254
|
+
if (derived.length === 0)
|
|
255
|
+
return batch;
|
|
256
|
+
return {
|
|
257
|
+
...batch,
|
|
258
|
+
changes: [...derived, ...batch.changes],
|
|
259
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...batch.preconditions],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
export function rewriting(batch, locator) {
|
|
263
|
+
return leaving(batch, locator) || batch.changes.some((change) => change.kind === 'move');
|
|
264
|
+
}
|
|
265
|
+
export function* effectiveIn(batch, locator, visible) {
|
|
266
|
+
return leaving(batch, locator)
|
|
267
|
+
? yield* leavingRewritten(batch, visible)
|
|
268
|
+
: yield* providerRewritten(batch, visible);
|
|
269
|
+
}
|
|
270
|
+
function* chained(...sets) {
|
|
271
|
+
for (const set of sets)
|
|
272
|
+
yield* set;
|
|
273
|
+
}
|
|
274
|
+
function* incoming(collection, removed, candidates) {
|
|
181
275
|
const found = new Map();
|
|
182
276
|
const note = (target, file) => {
|
|
183
277
|
if (!removed.has(target))
|
|
@@ -186,7 +280,20 @@ function incoming(collection, removed) {
|
|
|
186
280
|
referrers.add(file);
|
|
187
281
|
found.set(target, referrers);
|
|
188
282
|
};
|
|
189
|
-
|
|
283
|
+
const read = function* (paths) {
|
|
284
|
+
const seen = new Set();
|
|
285
|
+
for (const path of paths) {
|
|
286
|
+
if (seen.has(path))
|
|
287
|
+
continue;
|
|
288
|
+
seen.add(path);
|
|
289
|
+
const document = collection.document(path);
|
|
290
|
+
if (document !== undefined)
|
|
291
|
+
yield [path, document];
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
for (const [path, document] of candidates === null
|
|
295
|
+
? yield* documentsOf(collection)
|
|
296
|
+
: read(candidates)) {
|
|
190
297
|
const definition = definitionOf(governing(collection.composition, path), document.type);
|
|
191
298
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
192
299
|
if (to.includes(ATTACHMENT))
|
|
@@ -195,8 +302,9 @@ function incoming(collection, removed) {
|
|
|
195
302
|
if (named !== null)
|
|
196
303
|
note(named, path);
|
|
197
304
|
}
|
|
305
|
+
yield ROW_WEIGHT + collection.text[path].length;
|
|
198
306
|
}
|
|
199
|
-
const [first] =
|
|
307
|
+
const [first] = yield* sorting(found.keys());
|
|
200
308
|
if (first === undefined)
|
|
201
309
|
return null;
|
|
202
310
|
const every = new Set();
|
|
@@ -209,18 +317,21 @@ function incoming(collection, removed) {
|
|
|
209
317
|
code: 'referenced-missing-file',
|
|
210
318
|
retryable: false,
|
|
211
319
|
file: first,
|
|
212
|
-
referrers:
|
|
320
|
+
referrers: yield* sorting(every),
|
|
213
321
|
},
|
|
214
322
|
};
|
|
215
323
|
}
|
|
216
324
|
function affected(change) {
|
|
217
325
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
218
326
|
}
|
|
219
|
-
function guard(changes, input) {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
327
|
+
function guard(changes, input, removing) {
|
|
328
|
+
const overridden = new Map();
|
|
329
|
+
const existing = {
|
|
330
|
+
has: (path) => overridden.get(path) ??
|
|
331
|
+
(Object.hasOwn(input.visible.documents, path) || Object.hasOwn(input.visible.binaries, path)),
|
|
332
|
+
add: (path) => void overridden.set(path, true),
|
|
333
|
+
delete: (path) => void overridden.set(path, false),
|
|
334
|
+
};
|
|
224
335
|
for (const [index, change] of changes.entries()) {
|
|
225
336
|
for (const path of affected(change)) {
|
|
226
337
|
const normalized = normalize(path);
|
|
@@ -229,11 +340,11 @@ function guard(changes, input) {
|
|
|
229
340
|
if (covered(input.providerOwnedPaths, normalized)) {
|
|
230
341
|
return refuse('provider-owned-path', normalized);
|
|
231
342
|
}
|
|
232
|
-
const operation = change.kind === 'remove' ?
|
|
343
|
+
const operation = change.kind === 'remove' ? removing : 'write';
|
|
233
344
|
if (!input.local && !covered(input.operations[operation], normalized))
|
|
234
345
|
return refuse(`${operation}-grant`, normalized);
|
|
235
346
|
}
|
|
236
|
-
if (change.kind === 'create') {
|
|
347
|
+
if (change.kind === 'create' || change.kind === 'arrival') {
|
|
237
348
|
if (existing.has(change.path))
|
|
238
349
|
return refuse('path-collision', change.path);
|
|
239
350
|
existing.add(change.path);
|
|
@@ -275,7 +386,7 @@ function guard(changes, input) {
|
|
|
275
386
|
continue;
|
|
276
387
|
}
|
|
277
388
|
if (change.kind === 'replace') {
|
|
278
|
-
if (
|
|
389
|
+
if (unrevised(input, change.path)) {
|
|
279
390
|
return refuse('unknown-revision', change.path);
|
|
280
391
|
}
|
|
281
392
|
continue;
|
|
@@ -289,6 +400,22 @@ function guard(changes, input) {
|
|
|
289
400
|
function unrevised(input, path) {
|
|
290
401
|
return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
|
|
291
402
|
}
|
|
403
|
+
export function queuedDocuments(queue) {
|
|
404
|
+
const paths = new Set();
|
|
405
|
+
for (const batch of queue) {
|
|
406
|
+
for (const change of batch.changes) {
|
|
407
|
+
if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
|
|
408
|
+
continue;
|
|
409
|
+
if (change.kind === 'remove')
|
|
410
|
+
paths.delete(change.path);
|
|
411
|
+
else if (change.kind !== 'move')
|
|
412
|
+
paths.add(change.path);
|
|
413
|
+
else if (paths.delete(change.path))
|
|
414
|
+
paths.add(change.to);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return paths;
|
|
418
|
+
}
|
|
292
419
|
function identity(precondition) {
|
|
293
420
|
const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
|
|
294
421
|
return `${precondition.kind} ${precondition.path} ${part}`;
|
|
@@ -306,7 +433,7 @@ function sameTarget(precondition, change) {
|
|
|
306
433
|
return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
|
|
307
434
|
((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
|
|
308
435
|
}
|
|
309
|
-
function preconditionsOf(changes, visible, bases) {
|
|
436
|
+
function* preconditionsOf(changes, visible, bases) {
|
|
310
437
|
const preconditions = [];
|
|
311
438
|
const seen = new Set();
|
|
312
439
|
const add = (precondition) => {
|
|
@@ -317,6 +444,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
317
444
|
preconditions.push(precondition);
|
|
318
445
|
};
|
|
319
446
|
for (const change of changes) {
|
|
447
|
+
yield ROW_WEIGHT;
|
|
320
448
|
if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
|
|
321
449
|
const based = bases.find((base) => sameTarget(base, change));
|
|
322
450
|
if (based !== undefined) {
|
|
@@ -364,10 +492,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
364
492
|
function depth(path) {
|
|
365
493
|
return path.split('/').length;
|
|
366
494
|
}
|
|
367
|
-
function
|
|
368
|
-
const written =
|
|
495
|
+
function* exceeding(limits, changes, effective, resulting) {
|
|
496
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
369
497
|
const created = changes
|
|
370
|
-
.filter((change) => change.kind === 'create' ||
|
|
498
|
+
.filter((change) => change.kind === 'create' ||
|
|
499
|
+
change.kind === 'move' ||
|
|
500
|
+
change.kind === 'binary' ||
|
|
501
|
+
change.kind === 'arrival')
|
|
371
502
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
372
503
|
for (const limit of limits) {
|
|
373
504
|
const value = limit.value;
|
|
@@ -381,9 +512,13 @@ function exceeded(limits, changes, resulting) {
|
|
|
381
512
|
return refuse('provider-limit', undefined, limit.name);
|
|
382
513
|
}
|
|
383
514
|
if (limit.name === 'document-size') {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
515
|
+
for (const path of written) {
|
|
516
|
+
const text = resulting.documents[path] ?? '';
|
|
517
|
+
if (new TextEncoder().encode(text).byteLength > value) {
|
|
518
|
+
return refuse('provider-limit', path, limit.name);
|
|
519
|
+
}
|
|
520
|
+
yield ROW_WEIGHT + text.length;
|
|
521
|
+
}
|
|
387
522
|
}
|
|
388
523
|
if (limit.name === 'path-length') {
|
|
389
524
|
const long = created.find((path) => path.length > value);
|
|
@@ -413,7 +548,7 @@ function relativeField(path, name, value, handle, definition) {
|
|
|
413
548
|
!opened.ok ||
|
|
414
549
|
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
415
550
|
return value;
|
|
416
|
-
const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
|
|
551
|
+
const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
|
|
417
552
|
if (held.outcome !== 'applied')
|
|
418
553
|
return value;
|
|
419
554
|
const result = relativize(held.document, path, handle, definition);
|
|
@@ -449,12 +584,15 @@ function relativeChanges(input) {
|
|
|
449
584
|
return { ...change, fields, sections };
|
|
450
585
|
});
|
|
451
586
|
}
|
|
452
|
-
function unmodeledType(collection, composition) {
|
|
453
|
-
|
|
587
|
+
function* unmodeledType(collection, composition) {
|
|
588
|
+
if (available(composition))
|
|
589
|
+
return false;
|
|
590
|
+
for (const [path, document] of yield* documentsOf(collection)) {
|
|
454
591
|
if (document.type !== undefined &&
|
|
455
592
|
document.type !== 'any' &&
|
|
456
593
|
governing(composition, path).status === 'unavailable')
|
|
457
594
|
return true;
|
|
595
|
+
yield ROW_WEIGHT + collection.text[path].length;
|
|
458
596
|
}
|
|
459
597
|
return false;
|
|
460
598
|
}
|
|
@@ -467,29 +605,44 @@ function isPart(change) {
|
|
|
467
605
|
function touched(change) {
|
|
468
606
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
469
607
|
}
|
|
470
|
-
function
|
|
608
|
+
function partsApplied(content, path, changes) {
|
|
471
609
|
const parsed = parse(content, path);
|
|
472
610
|
if (!parsed.ok)
|
|
473
611
|
return null;
|
|
474
612
|
const document = parsed.document;
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
613
|
+
const parts = new Map();
|
|
614
|
+
for (const change of changes) {
|
|
615
|
+
if (change.kind === 'field')
|
|
616
|
+
parts.set(`field ${change.name}`, {
|
|
617
|
+
kind: 'field',
|
|
618
|
+
name: change.name,
|
|
619
|
+
value: change.value,
|
|
620
|
+
prior: document.fields[change.name],
|
|
621
|
+
});
|
|
622
|
+
else if (change.kind === 'section')
|
|
623
|
+
parts.set(`section ${change.name}`, {
|
|
484
624
|
kind: 'section',
|
|
485
625
|
name: change.name,
|
|
486
626
|
value: change.value,
|
|
487
|
-
prior: document.sections[change.name],
|
|
488
|
-
}
|
|
489
|
-
|
|
627
|
+
prior: document.sections[change.name] ?? '',
|
|
628
|
+
});
|
|
629
|
+
else
|
|
630
|
+
parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
631
|
+
}
|
|
632
|
+
const applied = apply(document, [...parts.values()]);
|
|
490
633
|
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
491
634
|
}
|
|
492
|
-
function
|
|
635
|
+
function settledParts(changes, deferred) {
|
|
636
|
+
for (const [held, parts] of deferred) {
|
|
637
|
+
const content = partsApplied(held.content, held.path, parts);
|
|
638
|
+
if (content === null)
|
|
639
|
+
return held.path;
|
|
640
|
+
changes[changes.indexOf(held)] = { ...held, content };
|
|
641
|
+
}
|
|
642
|
+
deferred.clear();
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
function folded(changes, change, appending, deferred) {
|
|
493
646
|
const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
|
|
494
647
|
const whole = held === undefined ? -1 : changes.indexOf(held);
|
|
495
648
|
const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
|
|
@@ -510,10 +663,11 @@ function folded(changes, change, appending) {
|
|
|
510
663
|
return 'appended';
|
|
511
664
|
}
|
|
512
665
|
if ('content' in held) {
|
|
513
|
-
const
|
|
514
|
-
if (
|
|
515
|
-
|
|
516
|
-
|
|
666
|
+
const parts = deferred.get(held);
|
|
667
|
+
if (parts === undefined)
|
|
668
|
+
deferred.set(held, [change]);
|
|
669
|
+
else
|
|
670
|
+
parts.push(change);
|
|
517
671
|
return 'folded';
|
|
518
672
|
}
|
|
519
673
|
if (change.kind === 'preamble')
|
|
@@ -544,17 +698,21 @@ function folded(changes, change, appending) {
|
|
|
544
698
|
changes.push(change);
|
|
545
699
|
return 'appended';
|
|
546
700
|
}
|
|
547
|
-
function relinked(batch, move, before, after, appending) {
|
|
548
|
-
const derived = rewrites([move], after);
|
|
701
|
+
function* relinked(batch, move, before, after, appending) {
|
|
702
|
+
const derived = yield* rewrites([move], after);
|
|
549
703
|
if (derived.kind === 'refused')
|
|
550
704
|
return derived;
|
|
551
705
|
const changes = [...batch.changes];
|
|
552
706
|
const appended = [];
|
|
553
707
|
let changed = false;
|
|
708
|
+
const deferred = new Map();
|
|
554
709
|
for (const change of derived.changes) {
|
|
555
710
|
if (change === move)
|
|
556
711
|
continue;
|
|
557
|
-
const
|
|
712
|
+
const unsettled = isPart(change) ? null : settledParts(changes, deferred);
|
|
713
|
+
if (unsettled !== null)
|
|
714
|
+
return unqueueable(unsettled);
|
|
715
|
+
const outcome = folded(changes, change, appending, deferred);
|
|
558
716
|
if (outcome === 'refused')
|
|
559
717
|
return unqueueable(change.path);
|
|
560
718
|
if (outcome === 'appended')
|
|
@@ -562,9 +720,13 @@ function relinked(batch, move, before, after, appending) {
|
|
|
562
720
|
if (outcome !== 'skipped')
|
|
563
721
|
changed = true;
|
|
564
722
|
}
|
|
723
|
+
const unsettled = settledParts(changes, deferred);
|
|
724
|
+
if (unsettled !== null)
|
|
725
|
+
return unqueueable(unsettled);
|
|
565
726
|
if (!changed)
|
|
566
727
|
return { kind: 'relinked', batch };
|
|
567
728
|
const held = new Set(batch.preconditions.map(identity));
|
|
729
|
+
const added = yield* preconditionsOf(appended, before, []);
|
|
568
730
|
return {
|
|
569
731
|
kind: 'relinked',
|
|
570
732
|
batch: immutable({
|
|
@@ -573,7 +735,7 @@ function relinked(batch, move, before, after, appending) {
|
|
|
573
735
|
changes,
|
|
574
736
|
preconditions: [
|
|
575
737
|
...batch.preconditions,
|
|
576
|
-
...
|
|
738
|
+
...added.filter((precondition) => !held.has(identity(precondition))),
|
|
577
739
|
],
|
|
578
740
|
}),
|
|
579
741
|
};
|
|
@@ -619,7 +781,7 @@ function withdrawnFile(batch, change, at) {
|
|
|
619
781
|
];
|
|
620
782
|
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
621
783
|
}
|
|
622
|
-
export function
|
|
784
|
+
export function* requeuing(queue, changes, visible, changeable, state) {
|
|
623
785
|
const next = [...queue];
|
|
624
786
|
const origins = new Map();
|
|
625
787
|
const withdrawn = new Map();
|
|
@@ -642,19 +804,19 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
642
804
|
return unqueueable(change.path);
|
|
643
805
|
const following = next.slice(index);
|
|
644
806
|
const rebuilt = [];
|
|
645
|
-
let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
|
|
807
|
+
let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
|
|
646
808
|
for (const [offset, batch] of following.entries()) {
|
|
647
809
|
let current = batch;
|
|
648
810
|
if (change.kind === 'move') {
|
|
649
|
-
const after = state(next.slice(0, index + offset + 1));
|
|
811
|
+
const after = yield* state(next.slice(0, index + offset + 1));
|
|
650
812
|
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
651
|
-
const relinking = relinked(batch, change, before, after, appending);
|
|
813
|
+
const relinking = yield* relinked(batch, change, before, after, appending);
|
|
652
814
|
if (relinking.kind === 'refused')
|
|
653
815
|
return relinking;
|
|
654
816
|
current = relinking.batch;
|
|
655
817
|
before = after;
|
|
656
818
|
}
|
|
657
|
-
if (current !== batch && !open(batch))
|
|
819
|
+
if (current !== batch && (!open(batch) || batch.between !== undefined))
|
|
658
820
|
return unqueueable(change.path);
|
|
659
821
|
rebuilt.push(offset === 0
|
|
660
822
|
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
@@ -695,10 +857,17 @@ export function joined(queue, owner, extra) {
|
|
|
695
857
|
if (clash !== undefined)
|
|
696
858
|
return unqueueable(clash);
|
|
697
859
|
const changes = [...owner.changes];
|
|
860
|
+
const deferred = new Map();
|
|
698
861
|
for (const change of extra.changes) {
|
|
699
|
-
|
|
862
|
+
const unsettled = isPart(change) ? null : settledParts(changes, deferred);
|
|
863
|
+
if (unsettled !== null)
|
|
864
|
+
return unqueueable(unsettled);
|
|
865
|
+
if (folded(changes, change, true, deferred) === 'refused')
|
|
700
866
|
return unqueueable(change.path);
|
|
701
867
|
}
|
|
868
|
+
const unsettled = settledParts(changes, deferred);
|
|
869
|
+
if (unsettled !== null)
|
|
870
|
+
return unqueueable(unsettled);
|
|
702
871
|
const held = new Set(owner.preconditions.map(covering));
|
|
703
872
|
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
704
873
|
const preconditions = [
|
|
@@ -717,49 +886,46 @@ export function joined(queue, owner, extra) {
|
|
|
717
886
|
});
|
|
718
887
|
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
719
888
|
}
|
|
720
|
-
export function
|
|
721
|
-
const
|
|
889
|
+
export function* limiting(queue, batch, limits, state) {
|
|
890
|
+
const at = queue.indexOf(batch);
|
|
891
|
+
const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
|
|
892
|
+
const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
|
|
722
893
|
return limited?.kind === 'refused' ? limited.refusal : null;
|
|
723
894
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const rewritten = rewrites(changes, input.visible);
|
|
732
|
-
if (rewritten.kind === 'refused')
|
|
733
|
-
return rewritten;
|
|
734
|
-
const refusal = guard(changes, input);
|
|
735
|
-
if (refusal !== null)
|
|
736
|
-
return refusal;
|
|
737
|
-
const preconditions = preconditionsOf(changes, input.visible, input.bases);
|
|
738
|
-
const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
|
|
739
|
-
const resulting = overlay(input.visible, [
|
|
740
|
-
{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
|
|
741
|
-
]);
|
|
742
|
-
const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
|
|
895
|
+
function typedText(content) {
|
|
896
|
+
if (content === undefined)
|
|
897
|
+
return false;
|
|
898
|
+
const parsed = parse(content);
|
|
899
|
+
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
900
|
+
}
|
|
901
|
+
function* judging(visible, resulting, composition, clean, removed, touched) {
|
|
743
902
|
if (removed.size > 0) {
|
|
744
|
-
const
|
|
903
|
+
const referrers = clean === null ? null : yield* referring(clean, composition, removed);
|
|
904
|
+
const referenced = yield* incoming(collectionOf(resulting.documents, composition), removed, clean === null || referrers === null
|
|
905
|
+
? null
|
|
906
|
+
: chained(referrers, yield* differences(clean.text, resulting.documents)));
|
|
745
907
|
if (referenced !== null)
|
|
746
908
|
return referenced;
|
|
747
909
|
}
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const withoutModel = typedPaths.find((path) => governing(
|
|
910
|
+
const typedPaths = [];
|
|
911
|
+
for (const path of touched) {
|
|
912
|
+
const before = visible.documents[path];
|
|
913
|
+
const after = resulting.documents[path];
|
|
914
|
+
if (typedText(before) || typedText(after))
|
|
915
|
+
typedPaths.push(path);
|
|
916
|
+
yield ROW_WEIGHT + (before?.length ?? 0) + (after?.length ?? 0);
|
|
917
|
+
}
|
|
918
|
+
const withoutModel = typedPaths.find((path) => governing(composition, path).status === 'unavailable');
|
|
757
919
|
if (withoutModel !== undefined)
|
|
758
920
|
return refuse('model-unavailable', withoutModel);
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
921
|
+
const verdict = clean !== null &&
|
|
922
|
+
(yield* changedCleanly({ text: resulting.documents, binaries: resulting.binaries }, clean, composition))
|
|
923
|
+
? { verdict: 'accept' }
|
|
924
|
+
: yield* validateCollection(typedPaths.length > 0
|
|
925
|
+
? resulting.documents
|
|
926
|
+
: Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
|
|
927
|
+
? []
|
|
928
|
+
: [[path, resulting.documents[path]]])), Object.keys(resulting.binaries), composition);
|
|
763
929
|
if (verdict.verdict === 'refuse') {
|
|
764
930
|
return {
|
|
765
931
|
kind: 'refused',
|
|
@@ -772,12 +938,94 @@ export function buildBatch(input) {
|
|
|
772
938
|
},
|
|
773
939
|
};
|
|
774
940
|
}
|
|
775
|
-
|
|
941
|
+
return {
|
|
942
|
+
kind: 'judged',
|
|
943
|
+
validated: typedPaths
|
|
944
|
+
.map((path) => governing(composition, path))
|
|
945
|
+
.find((model) => model.status !== 'unavailable'),
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
function placed(reference) {
|
|
949
|
+
const key = roomPathOf(reference.handle, reference.path);
|
|
950
|
+
if (reference.kind === 'link')
|
|
951
|
+
return `link ${key}`;
|
|
952
|
+
if (reference.kind === 'item')
|
|
953
|
+
return `section ${reference.section} ${key}`;
|
|
954
|
+
const pointer = reference.pointer
|
|
955
|
+
.split('/')
|
|
956
|
+
.map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
|
|
957
|
+
.join('/');
|
|
958
|
+
return `field ${pointer} ${key}`;
|
|
959
|
+
}
|
|
960
|
+
function roomReferencesIn(path, content, composition) {
|
|
961
|
+
if (content === undefined)
|
|
962
|
+
return [];
|
|
963
|
+
const parsed = parse(content, path);
|
|
964
|
+
if (!parsed.ok)
|
|
965
|
+
return [];
|
|
966
|
+
return roomReferences(path, content, definitionOf(governing(composition, path), parsed.document.type));
|
|
967
|
+
}
|
|
968
|
+
function* admittedRooms(changes, visible, resulting, composition) {
|
|
969
|
+
const from = new Map();
|
|
970
|
+
for (const [index, change] of changes.entries()) {
|
|
971
|
+
if (change.kind === 'move')
|
|
972
|
+
from.set(change.to, departure(changes, index));
|
|
973
|
+
}
|
|
974
|
+
for (const path of [...new Set(changes.flatMap(affected))].sort()) {
|
|
975
|
+
const now = resulting.documents[path];
|
|
976
|
+
if (now === undefined)
|
|
977
|
+
continue;
|
|
978
|
+
const held = new Set(roomReferencesIn(path, visible.documents[from.get(path) ?? path], composition).map(placed));
|
|
979
|
+
for (const reference of roomReferencesIn(path, now, composition)) {
|
|
980
|
+
if (reference.kind === 'link' || reference.crossRoom === true)
|
|
981
|
+
continue;
|
|
982
|
+
if (held.has(placed(reference)))
|
|
983
|
+
continue;
|
|
984
|
+
return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
|
|
985
|
+
}
|
|
986
|
+
yield ROW_WEIGHT + now.length;
|
|
987
|
+
}
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
function validationOf(validated) {
|
|
991
|
+
return validated === undefined
|
|
992
|
+
? {}
|
|
993
|
+
: {
|
|
994
|
+
validation: {
|
|
995
|
+
identity: validated.snapshot.identity,
|
|
996
|
+
version: validated.snapshot.version,
|
|
997
|
+
source: validated.status,
|
|
998
|
+
},
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function* batching(input) {
|
|
1002
|
+
const changes = relativeChanges(input);
|
|
1003
|
+
const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
|
|
1004
|
+
if (structural !== undefined &&
|
|
1005
|
+
(yield* unmodeledType(input.visible.collection, input.composition)))
|
|
1006
|
+
return refuse('model-unavailable', structural.path);
|
|
1007
|
+
if (changes.length === 0)
|
|
1008
|
+
return refuse('no-change');
|
|
1009
|
+
const rewritten = yield* rewrites(changes, input.visible);
|
|
1010
|
+
if (rewritten.kind === 'refused')
|
|
1011
|
+
return rewritten;
|
|
1012
|
+
const refusal = guard(changes, input, 'delete');
|
|
1013
|
+
if (refusal !== null)
|
|
1014
|
+
return refusal;
|
|
1015
|
+
const preconditions = yield* preconditionsOf(changes, input.visible, input.bases);
|
|
1016
|
+
const predicted = yield* anticipated(changes, preconditions, rewritten.changes, input.visible);
|
|
1017
|
+
const resulting = yield* overlaying(input.visible, [
|
|
1018
|
+
{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
|
|
1019
|
+
]);
|
|
1020
|
+
const judged = yield* judging(input.visible, resulting, input.composition, input.clean !== undefined && available(input.composition) ? input.clean : null, new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path)), [...new Set(predicted.changes.flatMap(affected))]);
|
|
1021
|
+
if (judged.kind === 'refused')
|
|
1022
|
+
return judged;
|
|
1023
|
+
const crossing = yield* admittedRooms(changes, input.visible, resulting, input.composition);
|
|
1024
|
+
if (crossing !== null)
|
|
1025
|
+
return crossing;
|
|
1026
|
+
const limited = yield* exceeding(input.providerLimits, changes, predicted.changes, resulting);
|
|
776
1027
|
if (limited !== null)
|
|
777
1028
|
return limited;
|
|
778
|
-
const validated = typedPaths
|
|
779
|
-
.map((path) => governing(input.composition, path))
|
|
780
|
-
.find((model) => model.status !== 'unavailable');
|
|
781
1029
|
return {
|
|
782
1030
|
kind: 'batch',
|
|
783
1031
|
batch: immutable({
|
|
@@ -786,15 +1034,127 @@ export function buildBatch(input) {
|
|
|
786
1034
|
changes,
|
|
787
1035
|
preconditions,
|
|
788
1036
|
...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
|
|
789
|
-
...(validated
|
|
790
|
-
? {}
|
|
791
|
-
: {
|
|
792
|
-
validation: {
|
|
793
|
-
identity: validated.snapshot.identity,
|
|
794
|
-
version: validated.snapshot.version,
|
|
795
|
-
source: validated.status,
|
|
796
|
-
},
|
|
797
|
-
}),
|
|
1037
|
+
...validationOf(judged.validated),
|
|
798
1038
|
}),
|
|
799
1039
|
};
|
|
800
1040
|
}
|
|
1041
|
+
export const MOVE_DOCUMENTS = 'move-documents';
|
|
1042
|
+
function heldAt(visible, path) {
|
|
1043
|
+
return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
|
|
1044
|
+
}
|
|
1045
|
+
function arrivingText(path, to, source, moved) {
|
|
1046
|
+
const content = source.visible.documents[path];
|
|
1047
|
+
const document = source.visible.collection.document(path);
|
|
1048
|
+
if (document === undefined)
|
|
1049
|
+
return content;
|
|
1050
|
+
const definition = definitionOf(governing(source.composition, path), document.type);
|
|
1051
|
+
const changes = crossed(document, definition, path, (named, href) => {
|
|
1052
|
+
const at = moved.get(named);
|
|
1053
|
+
if (at !== undefined)
|
|
1054
|
+
return target(to, href) === at ? undefined : relative(to, at);
|
|
1055
|
+
return heldAt(source.visible, named) ? roomPathOf(source.handle, named) : undefined;
|
|
1056
|
+
});
|
|
1057
|
+
if (changes.length === 0)
|
|
1058
|
+
return content;
|
|
1059
|
+
const applied = apply(document, changes);
|
|
1060
|
+
return applied.outcome === 'applied' ? serialize(applied.document) : content;
|
|
1061
|
+
}
|
|
1062
|
+
function arrivalOf({ path, to }, source, moved) {
|
|
1063
|
+
const { documents, revisions, fileIds, changes, binaries } = source.visible;
|
|
1064
|
+
const kept = {
|
|
1065
|
+
...(fileIds[path] === undefined ? {} : { fileId: fileIds[path] }),
|
|
1066
|
+
...(changes[path] === undefined ? {} : { change: changes[path] }),
|
|
1067
|
+
};
|
|
1068
|
+
const heads = {
|
|
1069
|
+
...(revisions[path] === undefined ? {} : { revision: revisions[path] }),
|
|
1070
|
+
...kept,
|
|
1071
|
+
};
|
|
1072
|
+
const binary = binaries[path];
|
|
1073
|
+
if (binary?.state === 'accepted') {
|
|
1074
|
+
const { fileRef, byteLength, mediaType, sha256 } = binary;
|
|
1075
|
+
return {
|
|
1076
|
+
kind: 'arrival',
|
|
1077
|
+
path: to,
|
|
1078
|
+
binary: { fileRef, byteLength, mediaType, sha256 },
|
|
1079
|
+
...heads,
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
const content = arrivingText(path, to, source, moved);
|
|
1083
|
+
return {
|
|
1084
|
+
kind: 'arrival',
|
|
1085
|
+
path: to,
|
|
1086
|
+
content,
|
|
1087
|
+
...(content === documents[path] ? heads : kept),
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function* moving(input) {
|
|
1091
|
+
const { source, destination, moves } = input;
|
|
1092
|
+
if (moves.length === 0)
|
|
1093
|
+
return refuse('no-change');
|
|
1094
|
+
const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
|
|
1095
|
+
const leaves = guard(removals, { ...source, local: input.local, bases: [] }, 'write');
|
|
1096
|
+
if (leaves !== null)
|
|
1097
|
+
return leaves;
|
|
1098
|
+
const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
|
|
1099
|
+
if (uploading !== undefined)
|
|
1100
|
+
return refuse('unknown-revision', uploading.path);
|
|
1101
|
+
const moved = new Map(moves.map(({ path, to }) => [path, to]));
|
|
1102
|
+
const arrivals = moves.map((move) => arrivalOf(move, source, moved));
|
|
1103
|
+
const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, 'write');
|
|
1104
|
+
if (arrives !== null)
|
|
1105
|
+
return arrives;
|
|
1106
|
+
const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
|
|
1107
|
+
if (typeof bound === 'number' && moves.length > bound) {
|
|
1108
|
+
return refuse('provider-limit', undefined, MOVE_DOCUMENTS);
|
|
1109
|
+
}
|
|
1110
|
+
if (yield* unmodeledType(source.visible.collection, source.composition)) {
|
|
1111
|
+
return refuse('model-unavailable', moves[0].path);
|
|
1112
|
+
}
|
|
1113
|
+
const id = changeBatchIdentity();
|
|
1114
|
+
const between = {
|
|
1115
|
+
source: { room: source.room, baseRoomVersion: source.baseRoomVersion },
|
|
1116
|
+
destination: {
|
|
1117
|
+
room: destination.room,
|
|
1118
|
+
baseRoomVersion: destination.baseRoomVersion,
|
|
1119
|
+
handle: destination.handle,
|
|
1120
|
+
},
|
|
1121
|
+
moves,
|
|
1122
|
+
};
|
|
1123
|
+
const written = input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy };
|
|
1124
|
+
const leavingBatch = {
|
|
1125
|
+
id,
|
|
1126
|
+
baseRoomVersion: source.baseRoomVersion,
|
|
1127
|
+
changes: removals,
|
|
1128
|
+
preconditions: moves.map(({ path }) => existence(source.visible, path)),
|
|
1129
|
+
...written,
|
|
1130
|
+
between,
|
|
1131
|
+
};
|
|
1132
|
+
const effective = yield* leavingRewritten(leavingBatch, source.visible);
|
|
1133
|
+
const left = yield* overlaying(source.visible, [effective]);
|
|
1134
|
+
const sourceJudged = yield* judging(source.visible, left, source.composition, available(source.composition) ? source.clean : null, new Set(moves.map(({ path }) => path)), [...new Set(effective.changes.map((change) => change.path))]);
|
|
1135
|
+
if (sourceJudged.kind === 'refused')
|
|
1136
|
+
return sourceJudged;
|
|
1137
|
+
const arrivingBatch = {
|
|
1138
|
+
id,
|
|
1139
|
+
baseRoomVersion: destination.baseRoomVersion,
|
|
1140
|
+
changes: arrivals,
|
|
1141
|
+
preconditions: moves.map(({ to }) => ({ kind: 'absent', path: to })),
|
|
1142
|
+
...written,
|
|
1143
|
+
between,
|
|
1144
|
+
};
|
|
1145
|
+
const arrived = yield* overlaying(destination.visible, [arrivingBatch]);
|
|
1146
|
+
const destinationJudged = yield* judging(destination.visible, arrived, destination.composition, available(destination.composition) ? destination.clean : null, new Set(), moves.map(({ to }) => to));
|
|
1147
|
+
if (destinationJudged.kind === 'refused')
|
|
1148
|
+
return destinationJudged;
|
|
1149
|
+
const limited = (yield* exceeding(input.providerLimits, removals, effective.changes, left)) ??
|
|
1150
|
+
(yield* exceeding(input.providerLimits, arrivals, arrivals, arrived));
|
|
1151
|
+
if (limited !== null)
|
|
1152
|
+
return limited;
|
|
1153
|
+
return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
|
|
1154
|
+
}
|
|
1155
|
+
export function buildMove(input) {
|
|
1156
|
+
return stepped(moving(input));
|
|
1157
|
+
}
|
|
1158
|
+
export function buildBatch(input) {
|
|
1159
|
+
return stepped(batching(input));
|
|
1160
|
+
}
|