@agentshouse/mdruntime 0.1.0-cede5a74284be → 0.1.0-cef5833f6c689
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 +19 -16
- package/dist/adapter.d.ts +153 -56
- 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/attempt.d.ts.map +1 -1
- package/dist/attempt.js +14 -1
- 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 +27 -11
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +185 -81
- package/dist/chain.d.ts +12 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +9 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +11 -63
- 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 +29 -16
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +98 -12
- package/dist/collection.d.ts +11 -7
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +38 -79
- package/dist/compile.d.ts +11 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +589 -0
- package/dist/connection.d.ts +44 -16
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +3759 -991
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/{chat/context.js → context.js} +1 -4
- package/dist/conversation.d.ts +39 -17
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +691 -259
- 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 +4 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +20 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +160 -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 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +7 -122
- package/dist/host.d.ts +11 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +122 -19
- package/dist/index.d.ts +19 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/job.d.ts +14 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +28 -0
- package/dist/live.d.ts +23 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +127 -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 +17 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +214 -94
- package/dist/messages.d.ts +28 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +89 -0
- package/dist/model.d.ts +10 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +8 -5
- 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 +22 -4
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +120 -14
- 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 +49 -7
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +40 -0
- package/dist/overlay.d.ts +3 -4
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +100 -182
- 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 +41 -183
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -464
- package/dist/reach.d.ts +18 -6
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +221 -80
- package/dist/react.d.ts +10 -6
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +7 -17
- package/dist/readiness.d.ts +50 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +27 -0
- package/dist/recovery.d.ts +26 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +224 -0
- package/dist/refresh.d.ts +17 -14
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +155 -51
- package/dist/replica.d.ts +56 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +301 -154
- package/dist/room-draft.d.ts +37 -6
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +161 -74
- package/dist/rows.d.ts +21 -37
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +63 -122
- package/dist/save.d.ts +52 -9
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +539 -171
- package/dist/search.d.ts +15 -55
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -121
- 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 +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +421 -0
- package/dist/status.d.ts +5 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -1
- package/dist/store.d.ts +110 -76
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +341 -442
- package/dist/stored.d.ts +162 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +543 -0
- package/dist/tables.d.ts +352 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +586 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +75 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +104 -0
- package/dist/windows.d.ts +11 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +16 -0
- package/dist/wire.d.ts +165 -32
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +56 -31
- package/package.json +22 -19
- package/AGENTS.md +0 -318
- package/dist/chat/acts.d.ts +0 -34
- 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/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 -60
- 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 -20
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -59
- package/dist/chat/markdown.d.ts +0 -19
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -187
- 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 -21
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -151
- 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 -645
- package/dist/chat/question.d.ts +0 -9
- package/dist/chat/question.d.ts.map +0 -1
- package/dist/chat/question.js +0 -80
- 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 -33
- 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 -18
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -440
package/dist/save.js
CHANGED
|
@@ -1,10 +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 { collectionOf, documentsOf, governing,
|
|
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';
|
|
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';
|
|
8
11
|
function refuse(code, file, location) {
|
|
9
12
|
return {
|
|
10
13
|
kind: 'refused',
|
|
@@ -20,7 +23,7 @@ function repointed(document, definition, from, to, moves, settled) {
|
|
|
20
23
|
const parsed = parse(document.content, from);
|
|
21
24
|
if (!parsed.ok)
|
|
22
25
|
return [];
|
|
23
|
-
|
|
26
|
+
const changes = [];
|
|
24
27
|
const names = new Set();
|
|
25
28
|
for (const { pointer, value } of definition === undefined
|
|
26
29
|
? []
|
|
@@ -32,38 +35,39 @@ function repointed(document, definition, from, to, moves, settled) {
|
|
|
32
35
|
const destination = moves.get(named) ?? named;
|
|
33
36
|
if (target(to, value) === destination)
|
|
34
37
|
continue;
|
|
35
|
-
|
|
38
|
+
changes.push({
|
|
36
39
|
kind: 'reference',
|
|
37
40
|
pointer,
|
|
38
41
|
value: retarget(value, relative(to, destination)),
|
|
39
42
|
prior: value,
|
|
40
43
|
});
|
|
41
|
-
if (applied.outcome !== 'applied')
|
|
42
|
-
continue;
|
|
43
|
-
next = applied.document;
|
|
44
44
|
names.add(name);
|
|
45
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;
|
|
46
52
|
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
47
53
|
}
|
|
48
|
-
function edited(document,
|
|
49
|
-
|
|
54
|
+
function edited(document, changes) {
|
|
55
|
+
const parts = new Map();
|
|
50
56
|
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
|
-
: {
|
|
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}`, {
|
|
58
61
|
kind: 'section',
|
|
59
62
|
name: change.name,
|
|
60
63
|
value: change.value,
|
|
61
|
-
prior:
|
|
64
|
+
prior: document.sections[change.name] ?? '',
|
|
62
65
|
});
|
|
63
|
-
if (applied.outcome === 'applied')
|
|
64
|
-
next = applied.document;
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
+
if (parts.size === 0)
|
|
68
|
+
return document;
|
|
69
|
+
const applied = apply(document, [...parts.values()]);
|
|
70
|
+
return applied.outcome === 'applied' ? applied.document : document;
|
|
67
71
|
}
|
|
68
72
|
function bodies(path, before, after) {
|
|
69
73
|
const changes = [];
|
|
@@ -78,7 +82,7 @@ function bodies(path, before, after) {
|
|
|
78
82
|
return changes;
|
|
79
83
|
}
|
|
80
84
|
function relinks(path, moved, before, moves) {
|
|
81
|
-
|
|
85
|
+
const changes = [];
|
|
82
86
|
for (const link of links(before, path)) {
|
|
83
87
|
const destination = moves.get(link.target) ?? link.target;
|
|
84
88
|
if (destination === link.target && moved === path)
|
|
@@ -86,20 +90,23 @@ function relinks(path, moved, before, moves) {
|
|
|
86
90
|
const to = relative(moved, destination);
|
|
87
91
|
if (to === relative(path, link.target))
|
|
88
92
|
continue;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
};
|
|
102
108
|
}
|
|
109
|
+
const after = applied.document;
|
|
103
110
|
if (after === before)
|
|
104
111
|
return { kind: 'changes', changes: [] };
|
|
105
112
|
if (before.type === undefined) {
|
|
@@ -107,19 +114,14 @@ function relinks(path, moved, before, moves) {
|
|
|
107
114
|
}
|
|
108
115
|
return { kind: 'changes', changes: bodies(path, before, after) };
|
|
109
116
|
}
|
|
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
|
-
}
|
|
117
|
+
function partKey(change) {
|
|
118
|
+
return change.kind === 'section' ? `section ${change.name}` : change.kind;
|
|
118
119
|
}
|
|
119
|
-
function rewrites(changes, visible) {
|
|
120
|
+
function* rewrites(changes, visible) {
|
|
120
121
|
const moves = new Map();
|
|
121
122
|
const settled = new Set();
|
|
122
|
-
|
|
123
|
+
const parts = new Map();
|
|
124
|
+
for (const [index, change] of changes.entries()) {
|
|
123
125
|
if (change.kind === 'move')
|
|
124
126
|
moves.set(change.path, change.to);
|
|
125
127
|
if (change.kind === 'remove' ||
|
|
@@ -130,54 +132,143 @@ function rewrites(changes, visible) {
|
|
|
130
132
|
}
|
|
131
133
|
if (change.kind === 'field')
|
|
132
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
|
+
}
|
|
133
142
|
}
|
|
134
143
|
if (moves.size === 0)
|
|
135
144
|
return { kind: 'changes', changes };
|
|
136
145
|
const merged = [...changes];
|
|
137
146
|
const added = [];
|
|
138
|
-
for (const [path, document] of documentsOf(visible.collection)) {
|
|
147
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
139
148
|
if (settled.has(path))
|
|
140
149
|
continue;
|
|
141
150
|
const moved = moves.get(path) ?? path;
|
|
142
151
|
const definition = definitionOf(governing(visible.collection.composition, path), document.type);
|
|
143
|
-
|
|
144
|
-
|
|
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);
|
|
145
156
|
if (relinked.kind === 'refused')
|
|
146
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
|
+
}
|
|
147
164
|
for (const change of relinked.changes) {
|
|
148
165
|
if (change.kind === 'replace') {
|
|
149
|
-
|
|
166
|
+
for (const index of own) {
|
|
167
|
+
const kind = merged[index]?.kind;
|
|
168
|
+
if (kind === 'section' || kind === 'preamble')
|
|
169
|
+
merged[index] = null;
|
|
170
|
+
}
|
|
150
171
|
added.push(change);
|
|
151
172
|
continue;
|
|
152
173
|
}
|
|
153
|
-
const index =
|
|
154
|
-
|
|
155
|
-
(change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
|
|
156
|
-
if (index === -1)
|
|
174
|
+
const index = keyed.get(partKey(change));
|
|
175
|
+
if (index === undefined)
|
|
157
176
|
added.push(change);
|
|
158
177
|
else
|
|
159
178
|
merged[index] = change;
|
|
160
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);
|
|
161
183
|
}
|
|
162
|
-
return {
|
|
184
|
+
return {
|
|
185
|
+
kind: 'changes',
|
|
186
|
+
changes: [...added, ...merged.filter((change) => change !== null)],
|
|
187
|
+
};
|
|
163
188
|
}
|
|
164
|
-
function anticipated(changes, preconditions, rewritten, visible) {
|
|
165
|
-
const
|
|
189
|
+
function* anticipated(changes, preconditions, rewritten, visible) {
|
|
190
|
+
const given = new Set(changes);
|
|
191
|
+
const derived = rewritten.filter((change) => !given.has(change));
|
|
166
192
|
return {
|
|
167
193
|
changes: rewritten,
|
|
168
|
-
preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
|
|
194
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
|
|
169
195
|
};
|
|
170
196
|
}
|
|
171
|
-
export function providerRewritten(batch, visible) {
|
|
172
|
-
const rewritten = rewrites(batch.changes, visible);
|
|
197
|
+
export function* providerRewritten(batch, visible) {
|
|
198
|
+
const rewritten = yield* rewrites(batch.changes, visible);
|
|
173
199
|
if (rewritten.kind === 'refused')
|
|
174
200
|
return batch;
|
|
175
201
|
return {
|
|
176
202
|
...batch,
|
|
177
|
-
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
203
|
+
...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
|
|
178
204
|
};
|
|
179
205
|
}
|
|
180
|
-
function
|
|
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],
|
|
261
|
+
};
|
|
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
|
+
}
|
|
271
|
+
function* incoming(collection, removed, candidates) {
|
|
181
272
|
const found = new Map();
|
|
182
273
|
const note = (target, file) => {
|
|
183
274
|
if (!removed.has(target))
|
|
@@ -186,7 +277,20 @@ function incoming(collection, removed) {
|
|
|
186
277
|
referrers.add(file);
|
|
187
278
|
found.set(target, referrers);
|
|
188
279
|
};
|
|
189
|
-
|
|
280
|
+
const read = function* (paths) {
|
|
281
|
+
const seen = new Set();
|
|
282
|
+
for (const path of paths) {
|
|
283
|
+
if (seen.has(path))
|
|
284
|
+
continue;
|
|
285
|
+
seen.add(path);
|
|
286
|
+
const document = collection.document(path);
|
|
287
|
+
if (document !== undefined)
|
|
288
|
+
yield [path, document];
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
for (const [path, document] of candidates === null
|
|
292
|
+
? yield* documentsOf(collection)
|
|
293
|
+
: read(candidates)) {
|
|
190
294
|
const definition = definitionOf(governing(collection.composition, path), document.type);
|
|
191
295
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
192
296
|
if (to.includes(ATTACHMENT))
|
|
@@ -195,8 +299,9 @@ function incoming(collection, removed) {
|
|
|
195
299
|
if (named !== null)
|
|
196
300
|
note(named, path);
|
|
197
301
|
}
|
|
302
|
+
yield ROW_WEIGHT + collection.text[path].length;
|
|
198
303
|
}
|
|
199
|
-
const [first] =
|
|
304
|
+
const [first] = yield* sorting(found.keys());
|
|
200
305
|
if (first === undefined)
|
|
201
306
|
return null;
|
|
202
307
|
const every = new Set();
|
|
@@ -209,18 +314,22 @@ function incoming(collection, removed) {
|
|
|
209
314
|
code: 'referenced-missing-file',
|
|
210
315
|
retryable: false,
|
|
211
316
|
file: first,
|
|
212
|
-
referrers:
|
|
317
|
+
referrers: yield* sorting(every),
|
|
213
318
|
},
|
|
214
319
|
};
|
|
215
320
|
}
|
|
216
321
|
function affected(change) {
|
|
217
322
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
218
323
|
}
|
|
219
|
-
function guard(changes, input) {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
324
|
+
function guard(changes, input, removing) {
|
|
325
|
+
const overridden = new Map();
|
|
326
|
+
const existing = {
|
|
327
|
+
has: (path) => overridden.get(path) ??
|
|
328
|
+
(Object.hasOwn(input.visible.documents, path) || Object.hasOwn(input.visible.binaries, path)),
|
|
329
|
+
add: (path) => void overridden.set(path, true),
|
|
330
|
+
delete: (path) => void overridden.set(path, false),
|
|
331
|
+
};
|
|
332
|
+
const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
|
|
224
333
|
for (const [index, change] of changes.entries()) {
|
|
225
334
|
for (const path of affected(change)) {
|
|
226
335
|
const normalized = normalize(path);
|
|
@@ -229,13 +338,14 @@ function guard(changes, input) {
|
|
|
229
338
|
if (covered(input.providerOwnedPaths, normalized)) {
|
|
230
339
|
return refuse('provider-owned-path', normalized);
|
|
231
340
|
}
|
|
232
|
-
const operation = change.kind === 'remove' ?
|
|
341
|
+
const operation = change.kind === 'remove' ? removing : 'write';
|
|
233
342
|
if (!input.local && !covered(input.operations[operation], normalized))
|
|
234
343
|
return refuse(`${operation}-grant`, normalized);
|
|
235
344
|
}
|
|
236
|
-
if (change.kind === 'create') {
|
|
237
|
-
|
|
238
|
-
|
|
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);
|
|
239
349
|
existing.add(change.path);
|
|
240
350
|
continue;
|
|
241
351
|
}
|
|
@@ -245,6 +355,9 @@ function guard(changes, input) {
|
|
|
245
355
|
input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
|
|
246
356
|
return refuse('path-collision', change.path);
|
|
247
357
|
}
|
|
358
|
+
const taken = occupied(change.path);
|
|
359
|
+
if (taken !== null)
|
|
360
|
+
return refuse('path-collision', taken);
|
|
248
361
|
if (!input.local &&
|
|
249
362
|
existing.has(change.path) &&
|
|
250
363
|
input.visible.revisions[departure(changes, index)] === undefined) {
|
|
@@ -256,8 +369,9 @@ function guard(changes, input) {
|
|
|
256
369
|
if (change.kind === 'move') {
|
|
257
370
|
if (!existing.has(change.path))
|
|
258
371
|
return refuse('path-absent', change.path);
|
|
259
|
-
|
|
260
|
-
|
|
372
|
+
const taken = existing.has(change.to) ? change.to : occupied(change.to);
|
|
373
|
+
if (taken !== null)
|
|
374
|
+
return refuse('path-collision', taken);
|
|
261
375
|
if (unrevised(input, change.path)) {
|
|
262
376
|
return refuse('unknown-revision', change.path);
|
|
263
377
|
}
|
|
@@ -275,7 +389,7 @@ function guard(changes, input) {
|
|
|
275
389
|
continue;
|
|
276
390
|
}
|
|
277
391
|
if (change.kind === 'replace') {
|
|
278
|
-
if (
|
|
392
|
+
if (unrevised(input, change.path)) {
|
|
279
393
|
return refuse('unknown-revision', change.path);
|
|
280
394
|
}
|
|
281
395
|
continue;
|
|
@@ -289,6 +403,22 @@ function guard(changes, input) {
|
|
|
289
403
|
function unrevised(input, path) {
|
|
290
404
|
return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
|
|
291
405
|
}
|
|
406
|
+
export function queuedDocuments(queue) {
|
|
407
|
+
const paths = new Set();
|
|
408
|
+
for (const batch of queue) {
|
|
409
|
+
for (const change of batch.changes) {
|
|
410
|
+
if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
|
|
411
|
+
continue;
|
|
412
|
+
if (change.kind === 'remove')
|
|
413
|
+
paths.delete(change.path);
|
|
414
|
+
else if (change.kind !== 'move')
|
|
415
|
+
paths.add(change.path);
|
|
416
|
+
else if (paths.delete(change.path))
|
|
417
|
+
paths.add(change.to);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return paths;
|
|
421
|
+
}
|
|
292
422
|
function identity(precondition) {
|
|
293
423
|
const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
|
|
294
424
|
return `${precondition.kind} ${precondition.path} ${part}`;
|
|
@@ -306,7 +436,7 @@ function sameTarget(precondition, change) {
|
|
|
306
436
|
return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
|
|
307
437
|
((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
|
|
308
438
|
}
|
|
309
|
-
function preconditionsOf(changes, visible, bases) {
|
|
439
|
+
function* preconditionsOf(changes, visible, bases) {
|
|
310
440
|
const preconditions = [];
|
|
311
441
|
const seen = new Set();
|
|
312
442
|
const add = (precondition) => {
|
|
@@ -317,6 +447,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
317
447
|
preconditions.push(precondition);
|
|
318
448
|
};
|
|
319
449
|
for (const change of changes) {
|
|
450
|
+
yield ROW_WEIGHT;
|
|
320
451
|
if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
|
|
321
452
|
const based = bases.find((base) => sameTarget(base, change));
|
|
322
453
|
if (based !== undefined) {
|
|
@@ -364,10 +495,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
364
495
|
function depth(path) {
|
|
365
496
|
return path.split('/').length;
|
|
366
497
|
}
|
|
367
|
-
function
|
|
368
|
-
const written =
|
|
498
|
+
function* exceeding(limits, changes, effective, resulting) {
|
|
499
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
369
500
|
const created = changes
|
|
370
|
-
.filter((change) => change.kind === 'create' ||
|
|
501
|
+
.filter((change) => change.kind === 'create' ||
|
|
502
|
+
change.kind === 'move' ||
|
|
503
|
+
change.kind === 'binary' ||
|
|
504
|
+
change.kind === 'arrival')
|
|
371
505
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
372
506
|
for (const limit of limits) {
|
|
373
507
|
const value = limit.value;
|
|
@@ -381,9 +515,13 @@ function exceeded(limits, changes, resulting) {
|
|
|
381
515
|
return refuse('provider-limit', undefined, limit.name);
|
|
382
516
|
}
|
|
383
517
|
if (limit.name === 'document-size') {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
+
}
|
|
387
525
|
}
|
|
388
526
|
if (limit.name === 'path-length') {
|
|
389
527
|
const long = created.find((path) => path.length > value);
|
|
@@ -413,7 +551,7 @@ function relativeField(path, name, value, handle, definition) {
|
|
|
413
551
|
!opened.ok ||
|
|
414
552
|
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
415
553
|
return value;
|
|
416
|
-
const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
|
|
554
|
+
const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
|
|
417
555
|
if (held.outcome !== 'applied')
|
|
418
556
|
return value;
|
|
419
557
|
const result = relativize(held.document, path, handle, definition);
|
|
@@ -449,15 +587,6 @@ function relativeChanges(input) {
|
|
|
449
587
|
return { ...change, fields, sections };
|
|
450
588
|
});
|
|
451
589
|
}
|
|
452
|
-
function unmodeledType(collection, composition) {
|
|
453
|
-
for (const [path, document] of documentsOf(collection)) {
|
|
454
|
-
if (document.type !== undefined &&
|
|
455
|
-
document.type !== 'any' &&
|
|
456
|
-
governing(composition, path).status === 'unavailable')
|
|
457
|
-
return true;
|
|
458
|
-
}
|
|
459
|
-
return false;
|
|
460
|
-
}
|
|
461
590
|
function unqueueable(file) {
|
|
462
591
|
return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
|
|
463
592
|
}
|
|
@@ -467,29 +596,44 @@ function isPart(change) {
|
|
|
467
596
|
function touched(change) {
|
|
468
597
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
469
598
|
}
|
|
470
|
-
function
|
|
599
|
+
function partsApplied(content, path, changes) {
|
|
471
600
|
const parsed = parse(content, path);
|
|
472
601
|
if (!parsed.ok)
|
|
473
602
|
return null;
|
|
474
603
|
const document = parsed.document;
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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}`, {
|
|
484
615
|
kind: 'section',
|
|
485
616
|
name: change.name,
|
|
486
617
|
value: change.value,
|
|
487
|
-
prior: document.sections[change.name],
|
|
488
|
-
}
|
|
489
|
-
|
|
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()]);
|
|
490
624
|
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
491
625
|
}
|
|
492
|
-
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) {
|
|
493
637
|
const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
|
|
494
638
|
const whole = held === undefined ? -1 : changes.indexOf(held);
|
|
495
639
|
const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
|
|
@@ -510,10 +654,11 @@ function folded(changes, change, appending) {
|
|
|
510
654
|
return 'appended';
|
|
511
655
|
}
|
|
512
656
|
if ('content' in held) {
|
|
513
|
-
const
|
|
514
|
-
if (
|
|
515
|
-
|
|
516
|
-
|
|
657
|
+
const parts = deferred.get(held);
|
|
658
|
+
if (parts === undefined)
|
|
659
|
+
deferred.set(held, [change]);
|
|
660
|
+
else
|
|
661
|
+
parts.push(change);
|
|
517
662
|
return 'folded';
|
|
518
663
|
}
|
|
519
664
|
if (change.kind === 'preamble')
|
|
@@ -544,17 +689,21 @@ function folded(changes, change, appending) {
|
|
|
544
689
|
changes.push(change);
|
|
545
690
|
return 'appended';
|
|
546
691
|
}
|
|
547
|
-
function relinked(batch, move, before, after, appending) {
|
|
548
|
-
const derived = rewrites([move], after);
|
|
692
|
+
function* relinked(batch, move, before, after, appending) {
|
|
693
|
+
const derived = yield* rewrites([move], after);
|
|
549
694
|
if (derived.kind === 'refused')
|
|
550
695
|
return derived;
|
|
551
696
|
const changes = [...batch.changes];
|
|
552
697
|
const appended = [];
|
|
553
698
|
let changed = false;
|
|
699
|
+
const deferred = new Map();
|
|
554
700
|
for (const change of derived.changes) {
|
|
555
701
|
if (change === move)
|
|
556
702
|
continue;
|
|
557
|
-
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);
|
|
558
707
|
if (outcome === 'refused')
|
|
559
708
|
return unqueueable(change.path);
|
|
560
709
|
if (outcome === 'appended')
|
|
@@ -562,9 +711,13 @@ function relinked(batch, move, before, after, appending) {
|
|
|
562
711
|
if (outcome !== 'skipped')
|
|
563
712
|
changed = true;
|
|
564
713
|
}
|
|
714
|
+
const unsettled = settledParts(changes, deferred);
|
|
715
|
+
if (unsettled !== null)
|
|
716
|
+
return unqueueable(unsettled);
|
|
565
717
|
if (!changed)
|
|
566
718
|
return { kind: 'relinked', batch };
|
|
567
719
|
const held = new Set(batch.preconditions.map(identity));
|
|
720
|
+
const added = yield* preconditionsOf(appended, before, []);
|
|
568
721
|
return {
|
|
569
722
|
kind: 'relinked',
|
|
570
723
|
batch: immutable({
|
|
@@ -573,7 +726,7 @@ function relinked(batch, move, before, after, appending) {
|
|
|
573
726
|
changes,
|
|
574
727
|
preconditions: [
|
|
575
728
|
...batch.preconditions,
|
|
576
|
-
...
|
|
729
|
+
...added.filter((precondition) => !held.has(identity(precondition))),
|
|
577
730
|
],
|
|
578
731
|
}),
|
|
579
732
|
};
|
|
@@ -619,7 +772,7 @@ function withdrawnFile(batch, change, at) {
|
|
|
619
772
|
];
|
|
620
773
|
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
621
774
|
}
|
|
622
|
-
export function
|
|
775
|
+
export function* requeuing(queue, changes, visible, changeable, state) {
|
|
623
776
|
const next = [...queue];
|
|
624
777
|
const origins = new Map();
|
|
625
778
|
const withdrawn = new Map();
|
|
@@ -642,19 +795,19 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
642
795
|
return unqueueable(change.path);
|
|
643
796
|
const following = next.slice(index);
|
|
644
797
|
const rebuilt = [];
|
|
645
|
-
let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
|
|
798
|
+
let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
|
|
646
799
|
for (const [offset, batch] of following.entries()) {
|
|
647
800
|
let current = batch;
|
|
648
801
|
if (change.kind === 'move') {
|
|
649
|
-
const after = state(next.slice(0, index + offset + 1));
|
|
802
|
+
const after = yield* state(next.slice(0, index + offset + 1));
|
|
650
803
|
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
651
|
-
const relinking = relinked(batch, change, before, after, appending);
|
|
804
|
+
const relinking = yield* relinked(batch, change, before, after, appending);
|
|
652
805
|
if (relinking.kind === 'refused')
|
|
653
806
|
return relinking;
|
|
654
807
|
current = relinking.batch;
|
|
655
808
|
before = after;
|
|
656
809
|
}
|
|
657
|
-
if (current !== batch && !open(batch))
|
|
810
|
+
if (current !== batch && (!open(batch) || batch.between !== undefined))
|
|
658
811
|
return unqueueable(change.path);
|
|
659
812
|
rebuilt.push(offset === 0
|
|
660
813
|
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
@@ -695,10 +848,17 @@ export function joined(queue, owner, extra) {
|
|
|
695
848
|
if (clash !== undefined)
|
|
696
849
|
return unqueueable(clash);
|
|
697
850
|
const changes = [...owner.changes];
|
|
851
|
+
const deferred = new Map();
|
|
698
852
|
for (const change of extra.changes) {
|
|
699
|
-
|
|
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')
|
|
700
857
|
return unqueueable(change.path);
|
|
701
858
|
}
|
|
859
|
+
const unsettled = settledParts(changes, deferred);
|
|
860
|
+
if (unsettled !== null)
|
|
861
|
+
return unqueueable(unsettled);
|
|
702
862
|
const held = new Set(owner.preconditions.map(covering));
|
|
703
863
|
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
704
864
|
const preconditions = [
|
|
@@ -717,49 +877,66 @@ export function joined(queue, owner, extra) {
|
|
|
717
877
|
});
|
|
718
878
|
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
719
879
|
}
|
|
720
|
-
export function
|
|
721
|
-
const
|
|
880
|
+
export function* limiting(queue, batch, limits, state) {
|
|
881
|
+
const at = queue.indexOf(batch);
|
|
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)));
|
|
722
884
|
return limited?.kind === 'refused' ? limited.refusal : null;
|
|
723
885
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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) {
|
|
743
920
|
if (removed.size > 0) {
|
|
744
|
-
const referenced = incoming(collectionOf(resulting.documents,
|
|
921
|
+
const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
|
|
745
922
|
if (referenced !== null)
|
|
746
923
|
return referenced;
|
|
747
924
|
}
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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');
|
|
757
935
|
if (withoutModel !== undefined)
|
|
758
936
|
return refuse('model-unavailable', withoutModel);
|
|
759
|
-
const
|
|
760
|
-
? resulting
|
|
761
|
-
:
|
|
762
|
-
const verdict = validateCollection(validationText, Object.keys(resulting.binaries), input.composition);
|
|
937
|
+
const verdict = typedPaths.length > 0
|
|
938
|
+
? await validate(resulting, touched)
|
|
939
|
+
: await touchedLibrary(resulting, touched, composition);
|
|
763
940
|
if (verdict.verdict === 'refuse') {
|
|
764
941
|
return {
|
|
765
942
|
kind: 'refused',
|
|
@@ -772,12 +949,92 @@ export function buildBatch(input) {
|
|
|
772
949
|
},
|
|
773
950
|
};
|
|
774
951
|
}
|
|
775
|
-
|
|
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));
|
|
776
1036
|
if (limited !== null)
|
|
777
1037
|
return limited;
|
|
778
|
-
const validated = typedPaths
|
|
779
|
-
.map((path) => governing(input.composition, path))
|
|
780
|
-
.find((model) => model.status !== 'unavailable');
|
|
781
1038
|
return {
|
|
782
1039
|
kind: 'batch',
|
|
783
1040
|
batch: immutable({
|
|
@@ -786,15 +1043,126 @@ export function buildBatch(input) {
|
|
|
786
1043
|
changes,
|
|
787
1044
|
preconditions,
|
|
788
1045
|
...(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
|
-
}),
|
|
1046
|
+
...validationOf(judged.validated),
|
|
798
1047
|
}),
|
|
799
1048
|
};
|
|
800
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
|
+
}
|
|
1166
|
+
export function buildBatch(input) {
|
|
1167
|
+
return batching(input);
|
|
1168
|
+
}
|