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