@agentshouse/mdruntime 0.1.0-c969b8dee6cc8 → 0.1.0-c96e80d38e5f0
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 +134 -76
- 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 +13 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +80 -7
- package/dist/collection.d.ts +10 -21
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +39 -291
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +789 -0
- package/dist/connection.d.ts +19 -15
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +3020 -1417
- package/dist/conversation.d.ts +23 -14
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +337 -179
- 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 +19 -23
- 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 +4 -10
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +49 -31
- 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 +11 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +198 -107
- 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 +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 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +87 -172
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -0
- package/dist/phrase.d.ts +3 -0
- package/dist/phrase.d.ts.map +1 -0
- package/dist/phrase.js +8 -0
- package/dist/query.d.ts +15 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +33 -2
- package/dist/reach.d.ts +13 -4
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +200 -81
- package/dist/react.d.ts +1 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/readiness.d.ts +14 -17
- 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 +155 -58
- package/dist/replica.d.ts +54 -36
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +282 -189
- package/dist/room-draft.d.ts +22 -4
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +154 -67
- package/dist/rows.d.ts +39 -31
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +178 -101
- package/dist/save.d.ts +52 -14
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +513 -185
- package/dist/search.d.ts +12 -95
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -266
- package/dist/shared.d.ts +7 -3
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +119 -66
- 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 +91 -103
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +330 -693
- package/dist/stored.d.ts +162 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +549 -0
- package/dist/tables.d.ts +353 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +585 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/turn.d.ts +5 -3
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +51 -21
- 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 +18 -5
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +6 -2
- package/package.json +18 -19
- package/AGENTS.md +0 -305
- package/dist/acquisition.d.ts +0 -14
- package/dist/acquisition.d.ts.map +0 -1
- package/dist/acquisition.js +0 -24
- 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 -25
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -767
- 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,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 {
|
|
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
|
-
if (applied.outcome === 'refuse') {
|
|
91
|
-
return {
|
|
92
|
-
kind: 'refused',
|
|
93
|
-
refusal: {
|
|
94
|
-
code: applied.refusal.rule,
|
|
95
|
-
retryable: false,
|
|
96
|
-
file: path,
|
|
97
|
-
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
after = applied.document;
|
|
93
|
+
changes.push({ kind: 'link', href: link.href, to });
|
|
102
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
|
+
};
|
|
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,13 +277,20 @@ function incoming(collection, removed, candidates) {
|
|
|
186
277
|
referrers.add(file);
|
|
187
278
|
found.set(target, referrers);
|
|
188
279
|
};
|
|
189
|
-
const read =
|
|
190
|
-
|
|
191
|
-
|
|
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);
|
|
192
286
|
const document = collection.document(path);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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)) {
|
|
196
294
|
const definition = definitionOf(governing(collection.composition, path), document.type);
|
|
197
295
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
198
296
|
if (to.includes(ATTACHMENT))
|
|
@@ -201,8 +299,9 @@ function incoming(collection, removed, candidates) {
|
|
|
201
299
|
if (named !== null)
|
|
202
300
|
note(named, path);
|
|
203
301
|
}
|
|
302
|
+
yield ROW_WEIGHT + collection.text[path].length;
|
|
204
303
|
}
|
|
205
|
-
const [first] =
|
|
304
|
+
const [first] = yield* sorting(found.keys());
|
|
206
305
|
if (first === undefined)
|
|
207
306
|
return null;
|
|
208
307
|
const every = new Set();
|
|
@@ -215,14 +314,14 @@ function incoming(collection, removed, candidates) {
|
|
|
215
314
|
code: 'referenced-missing-file',
|
|
216
315
|
retryable: false,
|
|
217
316
|
file: first,
|
|
218
|
-
referrers:
|
|
317
|
+
referrers: yield* sorting(every),
|
|
219
318
|
},
|
|
220
319
|
};
|
|
221
320
|
}
|
|
222
321
|
function affected(change) {
|
|
223
322
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
224
323
|
}
|
|
225
|
-
function guard(changes, input) {
|
|
324
|
+
function guard(changes, input, removing) {
|
|
226
325
|
const overridden = new Map();
|
|
227
326
|
const existing = {
|
|
228
327
|
has: (path) => overridden.get(path) ??
|
|
@@ -230,6 +329,7 @@ function guard(changes, input) {
|
|
|
230
329
|
add: (path) => void overridden.set(path, true),
|
|
231
330
|
delete: (path) => void overridden.set(path, false),
|
|
232
331
|
};
|
|
332
|
+
const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
|
|
233
333
|
for (const [index, change] of changes.entries()) {
|
|
234
334
|
for (const path of affected(change)) {
|
|
235
335
|
const normalized = normalize(path);
|
|
@@ -238,13 +338,14 @@ function guard(changes, input) {
|
|
|
238
338
|
if (covered(input.providerOwnedPaths, normalized)) {
|
|
239
339
|
return refuse('provider-owned-path', normalized);
|
|
240
340
|
}
|
|
241
|
-
const operation = change.kind === 'remove' ?
|
|
341
|
+
const operation = change.kind === 'remove' ? removing : 'write';
|
|
242
342
|
if (!input.local && !covered(input.operations[operation], normalized))
|
|
243
343
|
return refuse(`${operation}-grant`, normalized);
|
|
244
344
|
}
|
|
245
|
-
if (change.kind === 'create') {
|
|
246
|
-
|
|
247
|
-
|
|
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);
|
|
248
349
|
existing.add(change.path);
|
|
249
350
|
continue;
|
|
250
351
|
}
|
|
@@ -254,6 +355,9 @@ function guard(changes, input) {
|
|
|
254
355
|
input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
|
|
255
356
|
return refuse('path-collision', change.path);
|
|
256
357
|
}
|
|
358
|
+
const taken = occupied(change.path);
|
|
359
|
+
if (taken !== null)
|
|
360
|
+
return refuse('path-collision', taken);
|
|
257
361
|
if (!input.local &&
|
|
258
362
|
existing.has(change.path) &&
|
|
259
363
|
input.visible.revisions[departure(changes, index)] === undefined) {
|
|
@@ -265,8 +369,9 @@ function guard(changes, input) {
|
|
|
265
369
|
if (change.kind === 'move') {
|
|
266
370
|
if (!existing.has(change.path))
|
|
267
371
|
return refuse('path-absent', change.path);
|
|
268
|
-
|
|
269
|
-
|
|
372
|
+
const taken = existing.has(change.to) ? change.to : occupied(change.to);
|
|
373
|
+
if (taken !== null)
|
|
374
|
+
return refuse('path-collision', taken);
|
|
270
375
|
if (unrevised(input, change.path)) {
|
|
271
376
|
return refuse('unknown-revision', change.path);
|
|
272
377
|
}
|
|
@@ -302,7 +407,7 @@ export function queuedDocuments(queue) {
|
|
|
302
407
|
const paths = new Set();
|
|
303
408
|
for (const batch of queue) {
|
|
304
409
|
for (const change of batch.changes) {
|
|
305
|
-
if (change.kind === 'binary')
|
|
410
|
+
if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
|
|
306
411
|
continue;
|
|
307
412
|
if (change.kind === 'remove')
|
|
308
413
|
paths.delete(change.path);
|
|
@@ -331,7 +436,7 @@ function sameTarget(precondition, change) {
|
|
|
331
436
|
return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
|
|
332
437
|
((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
|
|
333
438
|
}
|
|
334
|
-
function preconditionsOf(changes, visible, bases) {
|
|
439
|
+
function* preconditionsOf(changes, visible, bases) {
|
|
335
440
|
const preconditions = [];
|
|
336
441
|
const seen = new Set();
|
|
337
442
|
const add = (precondition) => {
|
|
@@ -342,6 +447,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
342
447
|
preconditions.push(precondition);
|
|
343
448
|
};
|
|
344
449
|
for (const change of changes) {
|
|
450
|
+
yield ROW_WEIGHT;
|
|
345
451
|
if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
|
|
346
452
|
const based = bases.find((base) => sameTarget(base, change));
|
|
347
453
|
if (based !== undefined) {
|
|
@@ -389,10 +495,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
389
495
|
function depth(path) {
|
|
390
496
|
return path.split('/').length;
|
|
391
497
|
}
|
|
392
|
-
function
|
|
498
|
+
function* exceeding(limits, changes, effective, resulting) {
|
|
393
499
|
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
394
500
|
const created = changes
|
|
395
|
-
.filter((change) => change.kind === 'create' ||
|
|
501
|
+
.filter((change) => change.kind === 'create' ||
|
|
502
|
+
change.kind === 'move' ||
|
|
503
|
+
change.kind === 'binary' ||
|
|
504
|
+
change.kind === 'arrival')
|
|
396
505
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
397
506
|
for (const limit of limits) {
|
|
398
507
|
const value = limit.value;
|
|
@@ -406,9 +515,13 @@ function exceeded(limits, changes, effective, resulting) {
|
|
|
406
515
|
return refuse('provider-limit', undefined, limit.name);
|
|
407
516
|
}
|
|
408
517
|
if (limit.name === 'document-size') {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
+
}
|
|
412
525
|
}
|
|
413
526
|
if (limit.name === 'path-length') {
|
|
414
527
|
const long = created.find((path) => path.length > value);
|
|
@@ -438,7 +551,7 @@ function relativeField(path, name, value, handle, definition) {
|
|
|
438
551
|
!opened.ok ||
|
|
439
552
|
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
440
553
|
return value;
|
|
441
|
-
const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
|
|
554
|
+
const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
|
|
442
555
|
if (held.outcome !== 'applied')
|
|
443
556
|
return value;
|
|
444
557
|
const result = relativize(held.document, path, handle, definition);
|
|
@@ -474,17 +587,6 @@ function relativeChanges(input) {
|
|
|
474
587
|
return { ...change, fields, sections };
|
|
475
588
|
});
|
|
476
589
|
}
|
|
477
|
-
function unmodeledType(collection, composition) {
|
|
478
|
-
if (available(composition))
|
|
479
|
-
return false;
|
|
480
|
-
for (const [path, document] of documentsOf(collection)) {
|
|
481
|
-
if (document.type !== undefined &&
|
|
482
|
-
document.type !== 'any' &&
|
|
483
|
-
governing(composition, path).status === 'unavailable')
|
|
484
|
-
return true;
|
|
485
|
-
}
|
|
486
|
-
return false;
|
|
487
|
-
}
|
|
488
590
|
function unqueueable(file) {
|
|
489
591
|
return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
|
|
490
592
|
}
|
|
@@ -494,29 +596,44 @@ function isPart(change) {
|
|
|
494
596
|
function touched(change) {
|
|
495
597
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
496
598
|
}
|
|
497
|
-
function
|
|
599
|
+
function partsApplied(content, path, changes) {
|
|
498
600
|
const parsed = parse(content, path);
|
|
499
601
|
if (!parsed.ok)
|
|
500
602
|
return null;
|
|
501
603
|
const document = parsed.document;
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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}`, {
|
|
511
615
|
kind: 'section',
|
|
512
616
|
name: change.name,
|
|
513
617
|
value: change.value,
|
|
514
|
-
prior: document.sections[change.name],
|
|
515
|
-
}
|
|
516
|
-
|
|
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()]);
|
|
517
624
|
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
518
625
|
}
|
|
519
|
-
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) {
|
|
520
637
|
const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
|
|
521
638
|
const whole = held === undefined ? -1 : changes.indexOf(held);
|
|
522
639
|
const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
|
|
@@ -537,10 +654,11 @@ function folded(changes, change, appending) {
|
|
|
537
654
|
return 'appended';
|
|
538
655
|
}
|
|
539
656
|
if ('content' in held) {
|
|
540
|
-
const
|
|
541
|
-
if (
|
|
542
|
-
|
|
543
|
-
|
|
657
|
+
const parts = deferred.get(held);
|
|
658
|
+
if (parts === undefined)
|
|
659
|
+
deferred.set(held, [change]);
|
|
660
|
+
else
|
|
661
|
+
parts.push(change);
|
|
544
662
|
return 'folded';
|
|
545
663
|
}
|
|
546
664
|
if (change.kind === 'preamble')
|
|
@@ -571,17 +689,21 @@ function folded(changes, change, appending) {
|
|
|
571
689
|
changes.push(change);
|
|
572
690
|
return 'appended';
|
|
573
691
|
}
|
|
574
|
-
function relinked(batch, move, before, after, appending) {
|
|
575
|
-
const derived = rewrites([move], after);
|
|
692
|
+
function* relinked(batch, move, before, after, appending) {
|
|
693
|
+
const derived = yield* rewrites([move], after);
|
|
576
694
|
if (derived.kind === 'refused')
|
|
577
695
|
return derived;
|
|
578
696
|
const changes = [...batch.changes];
|
|
579
697
|
const appended = [];
|
|
580
698
|
let changed = false;
|
|
699
|
+
const deferred = new Map();
|
|
581
700
|
for (const change of derived.changes) {
|
|
582
701
|
if (change === move)
|
|
583
702
|
continue;
|
|
584
|
-
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);
|
|
585
707
|
if (outcome === 'refused')
|
|
586
708
|
return unqueueable(change.path);
|
|
587
709
|
if (outcome === 'appended')
|
|
@@ -589,9 +711,13 @@ function relinked(batch, move, before, after, appending) {
|
|
|
589
711
|
if (outcome !== 'skipped')
|
|
590
712
|
changed = true;
|
|
591
713
|
}
|
|
714
|
+
const unsettled = settledParts(changes, deferred);
|
|
715
|
+
if (unsettled !== null)
|
|
716
|
+
return unqueueable(unsettled);
|
|
592
717
|
if (!changed)
|
|
593
718
|
return { kind: 'relinked', batch };
|
|
594
719
|
const held = new Set(batch.preconditions.map(identity));
|
|
720
|
+
const added = yield* preconditionsOf(appended, before, []);
|
|
595
721
|
return {
|
|
596
722
|
kind: 'relinked',
|
|
597
723
|
batch: immutable({
|
|
@@ -600,7 +726,7 @@ function relinked(batch, move, before, after, appending) {
|
|
|
600
726
|
changes,
|
|
601
727
|
preconditions: [
|
|
602
728
|
...batch.preconditions,
|
|
603
|
-
...
|
|
729
|
+
...added.filter((precondition) => !held.has(identity(precondition))),
|
|
604
730
|
],
|
|
605
731
|
}),
|
|
606
732
|
};
|
|
@@ -646,7 +772,7 @@ function withdrawnFile(batch, change, at) {
|
|
|
646
772
|
];
|
|
647
773
|
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
648
774
|
}
|
|
649
|
-
export function
|
|
775
|
+
export function* requeuing(queue, changes, visible, changeable, state) {
|
|
650
776
|
const next = [...queue];
|
|
651
777
|
const origins = new Map();
|
|
652
778
|
const withdrawn = new Map();
|
|
@@ -669,19 +795,19 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
669
795
|
return unqueueable(change.path);
|
|
670
796
|
const following = next.slice(index);
|
|
671
797
|
const rebuilt = [];
|
|
672
|
-
let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
|
|
798
|
+
let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
|
|
673
799
|
for (const [offset, batch] of following.entries()) {
|
|
674
800
|
let current = batch;
|
|
675
801
|
if (change.kind === 'move') {
|
|
676
|
-
const after = state(next.slice(0, index + offset + 1));
|
|
802
|
+
const after = yield* state(next.slice(0, index + offset + 1));
|
|
677
803
|
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
678
|
-
const relinking = relinked(batch, change, before, after, appending);
|
|
804
|
+
const relinking = yield* relinked(batch, change, before, after, appending);
|
|
679
805
|
if (relinking.kind === 'refused')
|
|
680
806
|
return relinking;
|
|
681
807
|
current = relinking.batch;
|
|
682
808
|
before = after;
|
|
683
809
|
}
|
|
684
|
-
if (current !== batch && !open(batch))
|
|
810
|
+
if (current !== batch && (!open(batch) || batch.between !== undefined))
|
|
685
811
|
return unqueueable(change.path);
|
|
686
812
|
rebuilt.push(offset === 0
|
|
687
813
|
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
@@ -722,10 +848,17 @@ export function joined(queue, owner, extra) {
|
|
|
722
848
|
if (clash !== undefined)
|
|
723
849
|
return unqueueable(clash);
|
|
724
850
|
const changes = [...owner.changes];
|
|
851
|
+
const deferred = new Map();
|
|
725
852
|
for (const change of extra.changes) {
|
|
726
|
-
|
|
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')
|
|
727
857
|
return unqueueable(change.path);
|
|
728
858
|
}
|
|
859
|
+
const unsettled = settledParts(changes, deferred);
|
|
860
|
+
if (unsettled !== null)
|
|
861
|
+
return unqueueable(unsettled);
|
|
729
862
|
const held = new Set(owner.preconditions.map(covering));
|
|
730
863
|
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
731
864
|
const preconditions = [
|
|
@@ -744,62 +877,66 @@ export function joined(queue, owner, extra) {
|
|
|
744
877
|
});
|
|
745
878
|
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
746
879
|
}
|
|
747
|
-
export function
|
|
880
|
+
export function* limiting(queue, batch, limits, state) {
|
|
748
881
|
const at = queue.indexOf(batch);
|
|
749
|
-
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)));
|
|
750
884
|
return limited?.kind === 'refused' ? limited.refusal : null;
|
|
751
885
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
const
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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) {
|
|
777
920
|
if (removed.size > 0) {
|
|
778
|
-
const
|
|
779
|
-
const referenced = incoming(collectionOf(resulting.documents, input.composition), removed, referrers === null ? null : [...referrers, ...predicted.changes.flatMap(affected)]);
|
|
921
|
+
const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
|
|
780
922
|
if (referenced !== null)
|
|
781
923
|
return referenced;
|
|
782
924
|
}
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
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');
|
|
793
935
|
if (withoutModel !== undefined)
|
|
794
936
|
return refuse('model-unavailable', withoutModel);
|
|
795
|
-
const verdict =
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
: validateCollection(needsModel
|
|
799
|
-
? resulting.documents
|
|
800
|
-
: Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
|
|
801
|
-
? []
|
|
802
|
-
: [[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);
|
|
803
940
|
if (verdict.verdict === 'refuse') {
|
|
804
941
|
return {
|
|
805
942
|
kind: 'refused',
|
|
@@ -812,12 +949,92 @@ export function buildBatch(input) {
|
|
|
812
949
|
},
|
|
813
950
|
};
|
|
814
951
|
}
|
|
815
|
-
|
|
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));
|
|
816
1036
|
if (limited !== null)
|
|
817
1037
|
return limited;
|
|
818
|
-
const validated = typedPaths
|
|
819
|
-
.map((path) => governing(input.composition, path))
|
|
820
|
-
.find((model) => model.status !== 'unavailable');
|
|
821
1038
|
return {
|
|
822
1039
|
kind: 'batch',
|
|
823
1040
|
batch: immutable({
|
|
@@ -826,15 +1043,126 @@ export function buildBatch(input) {
|
|
|
826
1043
|
changes,
|
|
827
1044
|
preconditions,
|
|
828
1045
|
...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
|
|
829
|
-
...(validated
|
|
830
|
-
? {}
|
|
831
|
-
: {
|
|
832
|
-
validation: {
|
|
833
|
-
identity: validated.snapshot.identity,
|
|
834
|
-
version: validated.snapshot.version,
|
|
835
|
-
source: validated.status,
|
|
836
|
-
},
|
|
837
|
-
}),
|
|
1046
|
+
...validationOf(judged.validated),
|
|
838
1047
|
}),
|
|
839
1048
|
};
|
|
840
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
|
+
}
|