@agentshouse/mdruntime 0.1.0-cf04ede9fb796 → 0.1.0-cf0c43fc48aad
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 +12 -12
- package/dist/acceptance.d.ts +0 -3
- package/dist/acceptance.d.ts.map +1 -1
- package/dist/acceptance.js +74 -164
- package/dist/act-lanes.d.ts +30 -0
- package/dist/act-lanes.d.ts.map +1 -0
- package/dist/act-lanes.js +225 -0
- package/dist/actions.d.ts +8 -2
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +118 -67
- package/dist/adapter.d.ts +27 -48
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bootstrap.d.ts +1 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +6 -19
- package/dist/change-set.d.ts +4 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +6 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +4 -2
- package/dist/client.d.ts +9 -18
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +32 -15
- package/dist/collection.d.ts +22 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +12 -2
- package/dist/compile.d.ts +2 -0
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +31 -24
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +26 -21
- package/dist/conversation-live.d.ts +10 -6
- package/dist/conversation-live.d.ts.map +1 -1
- package/dist/conversation-live.js +8 -6
- package/dist/conversation.d.ts +28 -10
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +194 -141
- package/dist/declaration.d.ts +10 -0
- package/dist/declaration.d.ts.map +1 -0
- package/dist/declaration.js +24 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +19 -44
- package/dist/durability.d.ts +3 -0
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.js +1 -1
- package/dist/expectation.d.ts +26 -13
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +72 -19
- package/dist/failure.d.ts +3 -0
- package/dist/failure.d.ts.map +1 -1
- package/dist/failure.js +6 -0
- package/dist/host.d.ts +17 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +45 -11
- package/dist/index.d.ts +12 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -6
- package/dist/install.d.ts +2 -4
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +52 -78
- package/dist/lifecycle.d.ts +1 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +40 -31
- package/dist/listing.d.ts +6 -11
- package/dist/listing.d.ts.map +1 -1
- package/dist/listing.js +25 -62
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +3 -3
- package/dist/locks.d.ts +2 -4
- package/dist/locks.d.ts.map +1 -1
- package/dist/locks.js +10 -39
- package/dist/lost.d.ts.map +1 -1
- package/dist/lost.js +10 -22
- package/dist/media.d.ts +1 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +4 -4
- package/dist/memory.d.ts +4 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +44 -23
- package/dist/messages.d.ts +9 -5
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +25 -35
- package/dist/model.d.ts +0 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -5
- package/dist/models.d.ts +4 -8
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +95 -137
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +12 -17
- package/dist/opened.d.ts +1 -1
- package/dist/opened.d.ts.map +1 -1
- package/dist/opened.js +5 -5
- package/dist/opening.d.ts.map +1 -1
- package/dist/opening.js +5 -9
- package/dist/outcome.d.ts +3 -0
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +15 -0
- package/dist/overlay.d.ts +5 -1
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +36 -21
- package/dist/page-metadata.d.ts +8 -0
- package/dist/page-metadata.d.ts.map +1 -0
- package/dist/page-metadata.js +19 -0
- package/dist/parts.d.ts +3 -59
- package/dist/parts.d.ts.map +1 -1
- package/dist/parts.js +10 -123
- package/dist/preparation.d.ts.map +1 -1
- package/dist/preparation.js +25 -24
- package/dist/presence.d.ts +52 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +220 -0
- package/dist/queries.d.ts +1 -1
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +24 -14
- package/dist/query.d.ts +0 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/questions.d.ts +0 -5
- package/dist/questions.d.ts.map +1 -1
- package/dist/questions.js +0 -7
- package/dist/queue.d.ts +40 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +450 -0
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +67 -22
- package/dist/react.d.ts +5 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +33 -1
- package/dist/reads.d.ts +3 -1
- package/dist/reads.d.ts.map +1 -1
- package/dist/reads.js +9 -2
- package/dist/recovery.d.ts +3 -1
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +29 -7
- package/dist/refresh.js +2 -2
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +5 -6
- package/dist/replica.d.ts +5 -11
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +13 -15
- package/dist/retried.d.ts +30 -0
- package/dist/retried.d.ts.map +1 -0
- package/dist/retried.js +72 -0
- package/dist/room-draft.d.ts +4 -6
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +46 -57
- package/dist/room-drafts.d.ts.map +1 -1
- package/dist/room-drafts.js +17 -16
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +3 -4
- package/dist/runtime.d.ts +9 -18
- package/dist/runtime.d.ts.map +1 -1
- package/dist/save.d.ts +42 -90
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +334 -167
- package/dist/saves.d.ts +2 -40
- package/dist/saves.d.ts.map +1 -1
- package/dist/saves.js +107 -705
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +2 -3
- package/dist/shared.d.ts +0 -5
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +4 -90
- package/dist/shown.d.ts +4 -3
- package/dist/shown.d.ts.map +1 -1
- package/dist/shown.js +26 -31
- package/dist/socket/lanes.d.ts +12 -4
- package/dist/socket/lanes.d.ts.map +1 -1
- package/dist/socket/lanes.js +34 -8
- package/dist/socket/session.d.ts +0 -1
- package/dist/socket/session.d.ts.map +1 -1
- package/dist/socket.d.ts +3 -6
- package/dist/socket.d.ts.map +1 -1
- package/dist/socket.js +5 -16
- package/dist/source.d.ts +0 -7
- package/dist/source.d.ts.map +1 -1
- package/dist/source.js +5 -4
- package/dist/store/records.d.ts +8 -6
- package/dist/store/records.d.ts.map +1 -1
- package/dist/store/records.js +13 -6
- package/dist/store/space.d.ts +1 -1
- package/dist/store/space.d.ts.map +1 -1
- package/dist/store/space.js +0 -2
- package/dist/store/watchers.js +1 -2
- package/dist/store.d.ts +11 -6
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +38 -13
- package/dist/stored.d.ts +2 -1
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +11 -11
- package/dist/surface.d.ts +1 -1
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +13 -16
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +88 -85
- package/dist/tables.d.ts +6 -8
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +41 -20
- package/dist/tallies.js +5 -5
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +2 -2
- package/dist/wire.d.ts +51 -7
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +15 -4
- package/package.json +3 -2
- package/dist/attempt.d.ts +0 -16
- package/dist/attempt.d.ts.map +0 -1
- package/dist/attempt.js +0 -70
- package/dist/job.d.ts +0 -33
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -54
- package/dist/log.d.ts +0 -6
- package/dist/log.d.ts.map +0 -1
- package/dist/log.js +0 -14
- package/dist/socket/rooms.d.ts +0 -44
- package/dist/socket/rooms.d.ts.map +0 -1
- package/dist/socket/rooms.js +0 -90
- package/dist/store/fences.d.ts +0 -16
- package/dist/store/fences.d.ts.map +0 -1
- package/dist/store/fences.js +0 -26
package/dist/save.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { apply, ARRAY_INDEX, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
|
|
1
|
+
import { apply, ARRAY_INDEX, ATTACHMENT, create, equalValue, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
|
|
3
2
|
import { immutable, leaving, queuedFile, departure, } from './change.js';
|
|
4
|
-
import { collectionOf, documentsOf, governing, } from './collection.js';
|
|
5
|
-
import { changeBatchIdentity } from './identity.js';
|
|
6
|
-
import { overlaying } from './overlay.js';
|
|
7
|
-
import { ancestors
|
|
3
|
+
import { collectionOf, definitionsAt, documentsOf, governing, } from './collection.js';
|
|
4
|
+
import { changeBatchIdentity, roomDraftRevision } from './identity.js';
|
|
5
|
+
import { composed, overlaying } from './overlay.js';
|
|
6
|
+
import { ancestors } from './paths.js';
|
|
8
7
|
import { entriesIn, sorting } from './shared.js';
|
|
8
|
+
import { changedParts, changesOf, moveTarget, withdrawnDrafts, } from './room-draft.js';
|
|
9
9
|
import { paced, ROW_WEIGHT, stepped } from './turn.js';
|
|
10
10
|
import { validatedCollection } from './validation.js';
|
|
11
|
-
function refuse(code, file, location) {
|
|
11
|
+
function refuse(code, file, location, line) {
|
|
12
12
|
return {
|
|
13
13
|
kind: 'refused',
|
|
14
14
|
refusal: {
|
|
@@ -16,13 +16,14 @@ function refuse(code, file, location) {
|
|
|
16
16
|
retryable: false,
|
|
17
17
|
...(file === undefined ? {} : { file }),
|
|
18
18
|
...(location === undefined ? {} : { location }),
|
|
19
|
+
...(line === undefined ? {} : { line }),
|
|
19
20
|
},
|
|
20
21
|
};
|
|
21
22
|
}
|
|
23
|
+
function refusedBy(refusal, file) {
|
|
24
|
+
return refuse(refusal.rule, file, refusal.location, refusal.line);
|
|
25
|
+
}
|
|
22
26
|
function repointed(document, definition, from, to, moves, settled) {
|
|
23
|
-
const parsed = parse(document.content, from);
|
|
24
|
-
if (!parsed.ok)
|
|
25
|
-
return [];
|
|
26
27
|
const changes = [];
|
|
27
28
|
const names = new Set();
|
|
28
29
|
for (const { pointer, value } of definition === undefined
|
|
@@ -45,15 +46,22 @@ function repointed(document, definition, from, to, moves, settled) {
|
|
|
45
46
|
}
|
|
46
47
|
if (changes.length === 0)
|
|
47
48
|
return [];
|
|
48
|
-
const applied = apply(
|
|
49
|
+
const applied = apply(document, changes, definition);
|
|
49
50
|
if (applied.outcome !== 'applied')
|
|
50
51
|
return [];
|
|
51
52
|
const next = applied.document;
|
|
52
53
|
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
53
54
|
}
|
|
54
|
-
function edited(document, changes) {
|
|
55
|
+
function edited(document, changes, definition) {
|
|
55
56
|
const parts = new Map();
|
|
56
57
|
for (const change of changes) {
|
|
58
|
+
if (change.kind === 'field')
|
|
59
|
+
parts.set(`field ${change.name}`, {
|
|
60
|
+
kind: 'field',
|
|
61
|
+
name: change.name,
|
|
62
|
+
value: change.value,
|
|
63
|
+
prior: document.fields[change.name],
|
|
64
|
+
});
|
|
57
65
|
if (change.kind === 'preamble')
|
|
58
66
|
parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
59
67
|
if (change.kind === 'section')
|
|
@@ -66,11 +74,16 @@ function edited(document, changes) {
|
|
|
66
74
|
}
|
|
67
75
|
if (parts.size === 0)
|
|
68
76
|
return document;
|
|
69
|
-
const applied = apply(document, [...parts.values()]);
|
|
77
|
+
const applied = apply(document, [...parts.values()], definition);
|
|
70
78
|
return applied.outcome === 'applied' ? applied.document : document;
|
|
71
79
|
}
|
|
72
80
|
function bodies(path, before, after) {
|
|
73
81
|
const changes = [];
|
|
82
|
+
for (const [name, value] of Object.entries(after.fields)) {
|
|
83
|
+
if (equalValue(before.fields[name], value))
|
|
84
|
+
continue;
|
|
85
|
+
changes.push({ kind: 'field', path, name, value });
|
|
86
|
+
}
|
|
74
87
|
if (after.preamble !== before.preamble) {
|
|
75
88
|
changes.push({ kind: 'preamble', path, value: after.preamble });
|
|
76
89
|
}
|
|
@@ -81,7 +94,7 @@ function bodies(path, before, after) {
|
|
|
81
94
|
}
|
|
82
95
|
return changes;
|
|
83
96
|
}
|
|
84
|
-
function relinks(path, moved, before, moves) {
|
|
97
|
+
function relinks(path, moved, before, moves, definition) {
|
|
85
98
|
const changes = [];
|
|
86
99
|
for (const link of links(before, path)) {
|
|
87
100
|
const destination = moves.get(link.target) ?? link.target;
|
|
@@ -94,18 +107,9 @@ function relinks(path, moved, before, moves) {
|
|
|
94
107
|
}
|
|
95
108
|
if (changes.length === 0)
|
|
96
109
|
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
|
-
}
|
|
110
|
+
const applied = apply(before, changes, definition);
|
|
111
|
+
if (applied.outcome === 'refuse')
|
|
112
|
+
return refusedBy(applied.refusal, path);
|
|
109
113
|
const after = applied.document;
|
|
110
114
|
if (after === before)
|
|
111
115
|
return { kind: 'changes', changes: [] };
|
|
@@ -115,7 +119,18 @@ function relinks(path, moved, before, moves) {
|
|
|
115
119
|
return { kind: 'changes', changes: bodies(path, before, after) };
|
|
116
120
|
}
|
|
117
121
|
function partKey(change) {
|
|
118
|
-
return change.kind === 'section'
|
|
122
|
+
return change.kind === 'section' || change.kind === 'field'
|
|
123
|
+
? `${change.kind} ${change.name}`
|
|
124
|
+
: change.kind;
|
|
125
|
+
}
|
|
126
|
+
const NAMES_A_SKIP_TESTS = 64;
|
|
127
|
+
const ENCODED = /[%&\\]/u;
|
|
128
|
+
function movedNames(moves) {
|
|
129
|
+
const names = new Set([...moves.keys()].map((path) => path.slice(path.lastIndexOf('/') + 1)));
|
|
130
|
+
return names.size > NAMES_A_SKIP_TESTS ? null : [...names];
|
|
131
|
+
}
|
|
132
|
+
function cannotLink(text, names) {
|
|
133
|
+
return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
|
|
119
134
|
}
|
|
120
135
|
function* rewrites(changes, visible) {
|
|
121
136
|
const moves = new Map();
|
|
@@ -142,17 +157,20 @@ function* rewrites(changes, visible) {
|
|
|
142
157
|
}
|
|
143
158
|
if (moves.size === 0)
|
|
144
159
|
return { kind: 'changes', changes };
|
|
160
|
+
const names = movedNames(moves);
|
|
145
161
|
const merged = [...changes];
|
|
146
162
|
const added = [];
|
|
147
163
|
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
148
164
|
if (settled.has(path))
|
|
149
165
|
continue;
|
|
150
166
|
const moved = moves.get(path) ?? path;
|
|
151
|
-
const definition =
|
|
167
|
+
const definition = definitionsAt(visible.collection.composition, path)(document.type);
|
|
152
168
|
const repointing = repointed(document, definition, path, moved, moves, settled);
|
|
153
169
|
added.push(...repointing);
|
|
154
170
|
const own = parts.get(path) ?? [];
|
|
155
|
-
const relinked =
|
|
171
|
+
const relinked = moved === path && own.length === 0 && cannotLink(document.content, names)
|
|
172
|
+
? { kind: 'changes', changes: [] }
|
|
173
|
+
: relinks(path, moved, edited(document, own.map((index) => merged[index]), definition), moves, definition);
|
|
156
174
|
if (relinked.kind === 'refused')
|
|
157
175
|
return relinked;
|
|
158
176
|
const keyed = new Map();
|
|
@@ -165,7 +183,7 @@ function* rewrites(changes, visible) {
|
|
|
165
183
|
if (change.kind === 'replace') {
|
|
166
184
|
for (const index of own) {
|
|
167
185
|
const kind = merged[index]?.kind;
|
|
168
|
-
if (kind === 'section' || kind === 'preamble')
|
|
186
|
+
if (kind === 'field' || kind === 'section' || kind === 'preamble')
|
|
169
187
|
merged[index] = null;
|
|
170
188
|
}
|
|
171
189
|
added.push(change);
|
|
@@ -221,25 +239,21 @@ function crossed(document, definition, path, to) {
|
|
|
221
239
|
}
|
|
222
240
|
return changes;
|
|
223
241
|
}
|
|
224
|
-
function rewritten(path, document, changes) {
|
|
242
|
+
function rewritten(path, document, changes, definition) {
|
|
225
243
|
if (changes.length === 0)
|
|
226
244
|
return [];
|
|
227
|
-
const applied = apply(document, changes);
|
|
245
|
+
const applied = apply(document, changes, definition);
|
|
228
246
|
if (applied.outcome !== 'applied')
|
|
229
247
|
return [];
|
|
230
248
|
const after = applied.document;
|
|
231
249
|
if (document.type === undefined)
|
|
232
250
|
return [{ kind: 'replace', path, content: serialize(after) }];
|
|
233
|
-
|
|
234
|
-
return [
|
|
235
|
-
...[...names].map((name) => ({ kind: 'field', path, name, value: after.fields[name] })),
|
|
236
|
-
...bodies(path, document, after),
|
|
237
|
-
];
|
|
251
|
+
return bodies(path, document, after);
|
|
238
252
|
}
|
|
239
253
|
function roomPathOf(handle, path) {
|
|
240
254
|
return `/rooms/${handle}/${path}`;
|
|
241
255
|
}
|
|
242
|
-
|
|
256
|
+
function* leavingRewritten(batch, visible) {
|
|
243
257
|
const between = batch.between;
|
|
244
258
|
if (between === undefined)
|
|
245
259
|
return batch;
|
|
@@ -248,8 +262,8 @@ export function* leavingRewritten(batch, visible) {
|
|
|
248
262
|
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
249
263
|
if (moved.has(path))
|
|
250
264
|
continue;
|
|
251
|
-
const definition =
|
|
252
|
-
derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named))));
|
|
265
|
+
const definition = definitionsAt(visible.collection.composition, path)(document.type);
|
|
266
|
+
derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named)), definition));
|
|
253
267
|
yield ROW_WEIGHT + visible.collection.text[path].length;
|
|
254
268
|
}
|
|
255
269
|
if (derived.length === 0)
|
|
@@ -291,7 +305,7 @@ function* incoming(collection, removed, candidates) {
|
|
|
291
305
|
for (const [path, document] of candidates === null
|
|
292
306
|
? yield* documentsOf(collection)
|
|
293
307
|
: read(candidates)) {
|
|
294
|
-
const definition =
|
|
308
|
+
const definition = definitionsAt(collection.composition, path)(document.type);
|
|
295
309
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
296
310
|
if (to.includes(ATTACHMENT))
|
|
297
311
|
continue;
|
|
@@ -334,18 +348,9 @@ function guard(changes, input, removing) {
|
|
|
334
348
|
const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
|
|
335
349
|
for (const [index, change] of changes.entries()) {
|
|
336
350
|
for (const path of affected(change)) {
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
339
|
-
return refuse(
|
|
340
|
-
if (covered(input.providerOwnedPaths, normalized) ||
|
|
341
|
-
(input.authoredPaths !== undefined && !authoredAt(input.authoredPaths, normalized))) {
|
|
342
|
-
return refuse('provider-owned-path', normalized);
|
|
343
|
-
}
|
|
344
|
-
if (!input.pathRule(normalized))
|
|
345
|
-
return refuse('invalid-path', normalized);
|
|
346
|
-
for (const operation of change.kind === 'remove' ? removing : WRITE)
|
|
347
|
-
if (!input.local && !covered(input.operations[operation], normalized))
|
|
348
|
-
return refuse(`${operation}-grant`, normalized);
|
|
351
|
+
const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
|
|
352
|
+
if (refused !== null)
|
|
353
|
+
return refuse(refused.code, refused.path);
|
|
349
354
|
}
|
|
350
355
|
if (change.kind === 'create' || change.kind === 'arrival') {
|
|
351
356
|
const taken = existing.has(change.path) ? change.path : occupied(change.path);
|
|
@@ -406,9 +411,9 @@ function guard(changes, input, removing) {
|
|
|
406
411
|
return null;
|
|
407
412
|
}
|
|
408
413
|
function unrevised(input, path) {
|
|
409
|
-
return
|
|
414
|
+
return !input.local && !input.queued.has(path) && input.visible.revisions[path] === undefined;
|
|
410
415
|
}
|
|
411
|
-
|
|
416
|
+
function queuedDocuments(queue) {
|
|
412
417
|
const paths = new Set();
|
|
413
418
|
for (const batch of queue) {
|
|
414
419
|
for (const change of batch.changes) {
|
|
@@ -544,56 +549,69 @@ function* exceeding(limits, changes, effective, resulting) {
|
|
|
544
549
|
function shellRelative(text, path, handle, definition) {
|
|
545
550
|
if (!text.includes(`/rooms/${handle}/`))
|
|
546
551
|
return null;
|
|
547
|
-
const parsed = parse(text, path);
|
|
552
|
+
const parsed = parse(text, path, definition);
|
|
548
553
|
if (!parsed.ok)
|
|
549
554
|
return null;
|
|
550
555
|
const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
|
|
551
556
|
return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
|
|
552
557
|
}
|
|
553
558
|
function relativeField(path, name, value, handle, definition) {
|
|
554
|
-
|
|
555
|
-
if (definition === undefined ||
|
|
556
|
-
!opened.ok ||
|
|
557
|
-
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
559
|
+
if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
558
560
|
return value;
|
|
559
|
-
const held = apply(
|
|
561
|
+
const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
|
|
560
562
|
if (held.outcome !== 'applied')
|
|
561
563
|
return value;
|
|
562
564
|
const result = relativize(held.document, path, handle, definition);
|
|
563
565
|
return result.outcome === 'applied' ? result.document.fields[name] : value;
|
|
564
566
|
}
|
|
565
567
|
function relativeChanges(input) {
|
|
568
|
+
const changes = [];
|
|
569
|
+
for (const change of input.changes) {
|
|
570
|
+
const next = relativeChange(change, input);
|
|
571
|
+
if ('refusal' in next)
|
|
572
|
+
return next;
|
|
573
|
+
changes.push(next.change);
|
|
574
|
+
}
|
|
575
|
+
return { kind: 'changes', changes };
|
|
576
|
+
}
|
|
577
|
+
function relativeChange(change, input) {
|
|
566
578
|
const { handle, composition, visible } = input;
|
|
567
|
-
const typed = (path) => (type) => definitionOf(governing(composition, path), type);
|
|
568
579
|
const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
580
|
+
const definition = definitionsAt(composition, change.path);
|
|
581
|
+
if (change.kind === 'field') {
|
|
582
|
+
const type = visible.collection.document(change.path)?.type;
|
|
583
|
+
const value = relativeField(change.path, change.name, change.value, handle, definition(type));
|
|
584
|
+
return { change: value === change.value ? change : { ...change, value } };
|
|
585
|
+
}
|
|
586
|
+
if (change.kind === 'section' || change.kind === 'preamble') {
|
|
587
|
+
const value = body(change.path, change.value);
|
|
588
|
+
return { change: value === change.value ? change : { ...change, value } };
|
|
589
|
+
}
|
|
590
|
+
if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
|
|
591
|
+
const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
|
|
592
|
+
return { change: content === change.content ? change : { ...change, content } };
|
|
593
|
+
}
|
|
594
|
+
if (change.kind !== 'create')
|
|
595
|
+
return { change };
|
|
596
|
+
const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
|
|
597
|
+
const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
|
|
598
|
+
name,
|
|
599
|
+
relativeField(change.path, name, value, handle, definition(type)),
|
|
600
|
+
]));
|
|
601
|
+
const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
|
|
602
|
+
const written = composed(change.path, fields, sections, composition);
|
|
603
|
+
if (written.outcome === 'refuse')
|
|
604
|
+
return refusedBy(written.refusal, change.path);
|
|
605
|
+
if (written.outcome === 'stale')
|
|
606
|
+
throw new Error(`the creation of ${change.path} is stale`);
|
|
607
|
+
if (Object.keys(sections).length === 0)
|
|
608
|
+
return { change: { ...change, fields, sections } };
|
|
609
|
+
return {
|
|
610
|
+
change: { kind: 'create', path: change.path, content: serialize(written.document) },
|
|
611
|
+
};
|
|
594
612
|
}
|
|
595
613
|
function unqueueable(file) {
|
|
596
|
-
return
|
|
614
|
+
return refuse('unknown-revision', file);
|
|
597
615
|
}
|
|
598
616
|
function isPart(change) {
|
|
599
617
|
return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
|
|
@@ -601,8 +619,29 @@ function isPart(change) {
|
|
|
601
619
|
function touched(change) {
|
|
602
620
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
603
621
|
}
|
|
604
|
-
function partsApplied(content, path, changes) {
|
|
605
|
-
const
|
|
622
|
+
function partsApplied(content, path, changes, composition) {
|
|
623
|
+
const applied = partsResult(content, path, changes, composition);
|
|
624
|
+
return applied?.outcome === 'applied' ? serialize(applied.document) : null;
|
|
625
|
+
}
|
|
626
|
+
function unapplied(changes, visible, composition) {
|
|
627
|
+
const parted = new Map();
|
|
628
|
+
for (const change of changes) {
|
|
629
|
+
if (isPart(change))
|
|
630
|
+
parted.set(change.path, [...(parted.get(change.path) ?? []), change]);
|
|
631
|
+
}
|
|
632
|
+
for (const [path, parts] of parted) {
|
|
633
|
+
const content = visible.documents[path];
|
|
634
|
+
if (content === undefined)
|
|
635
|
+
continue;
|
|
636
|
+
const applied = partsResult(content, path, parts, composition);
|
|
637
|
+
if (applied?.outcome === 'refuse')
|
|
638
|
+
return refusedBy(applied.refusal, path);
|
|
639
|
+
}
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
function partsResult(content, path, changes, composition) {
|
|
643
|
+
const definitions = definitionsAt(composition, path);
|
|
644
|
+
const parsed = parse(content, path, definitions);
|
|
606
645
|
if (!parsed.ok)
|
|
607
646
|
return null;
|
|
608
647
|
const document = parsed.document;
|
|
@@ -625,12 +664,11 @@ function partsApplied(content, path, changes) {
|
|
|
625
664
|
else
|
|
626
665
|
parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
627
666
|
}
|
|
628
|
-
|
|
629
|
-
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
667
|
+
return apply(document, [...parts.values()], definitions(document.type));
|
|
630
668
|
}
|
|
631
|
-
function settledParts(changes, deferred) {
|
|
669
|
+
function settledParts(changes, deferred, composition) {
|
|
632
670
|
for (const [held, parts] of deferred) {
|
|
633
|
-
const content = partsApplied(held.content, held.path, parts);
|
|
671
|
+
const content = partsApplied(held.content, held.path, parts, composition);
|
|
634
672
|
if (content === null)
|
|
635
673
|
return held.path;
|
|
636
674
|
changes[changes.indexOf(held)] = { ...held, content };
|
|
@@ -705,7 +743,9 @@ function* relinked(batch, move, before, after, appending) {
|
|
|
705
743
|
for (const change of derived.changes) {
|
|
706
744
|
if (change === move)
|
|
707
745
|
continue;
|
|
708
|
-
const unsettled = isPart(change)
|
|
746
|
+
const unsettled = isPart(change)
|
|
747
|
+
? null
|
|
748
|
+
: settledParts(changes, deferred, after.collection.composition);
|
|
709
749
|
if (unsettled !== null)
|
|
710
750
|
return unqueueable(unsettled);
|
|
711
751
|
const outcome = folded(changes, change, appending, deferred);
|
|
@@ -716,7 +756,7 @@ function* relinked(batch, move, before, after, appending) {
|
|
|
716
756
|
if (outcome !== 'skipped')
|
|
717
757
|
changed = true;
|
|
718
758
|
}
|
|
719
|
-
const unsettled = settledParts(changes, deferred);
|
|
759
|
+
const unsettled = settledParts(changes, deferred, after.collection.composition);
|
|
720
760
|
if (unsettled !== null)
|
|
721
761
|
return unqueueable(unsettled);
|
|
722
762
|
if (!changed)
|
|
@@ -777,11 +817,10 @@ function withdrawnFile(batch, change, at) {
|
|
|
777
817
|
];
|
|
778
818
|
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
779
819
|
}
|
|
780
|
-
|
|
820
|
+
function* requeuing(queue, changes, visible, changeable, state) {
|
|
781
821
|
const next = [...queue];
|
|
782
822
|
const origins = new Map();
|
|
783
823
|
const withdrawn = new Map();
|
|
784
|
-
const owners = new Set();
|
|
785
824
|
const queued = new Set();
|
|
786
825
|
const kept = [];
|
|
787
826
|
const originOf = (batch) => origins.get(batch) ?? batch.id;
|
|
@@ -827,7 +866,6 @@ export function* requeuing(queue, changes, visible, changeable, state) {
|
|
|
827
866
|
if (replaced !== null)
|
|
828
867
|
origins.set(replaced, origin);
|
|
829
868
|
}
|
|
830
|
-
owners.add(originOf(owner));
|
|
831
869
|
next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
|
|
832
870
|
queued.add(change.path);
|
|
833
871
|
}
|
|
@@ -837,56 +875,13 @@ export function* requeuing(queue, changes, visible, changeable, state) {
|
|
|
837
875
|
changes: kept,
|
|
838
876
|
queued,
|
|
839
877
|
withdrawn,
|
|
840
|
-
owners,
|
|
841
878
|
};
|
|
842
879
|
}
|
|
843
|
-
function
|
|
844
|
-
return precondition.kind === 'field' ||
|
|
845
|
-
precondition.kind === 'section' ||
|
|
846
|
-
precondition.kind === 'preamble'
|
|
847
|
-
? identity(precondition)
|
|
848
|
-
: `path ${precondition.path}`;
|
|
849
|
-
}
|
|
850
|
-
export function joined(queue, owner, extra) {
|
|
851
|
-
const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
|
|
852
|
-
const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
|
|
853
|
-
if (clash !== undefined)
|
|
854
|
-
return unqueueable(clash);
|
|
855
|
-
const changes = [...owner.changes];
|
|
856
|
-
const deferred = new Map();
|
|
857
|
-
for (const change of extra.changes) {
|
|
858
|
-
const unsettled = isPart(change) ? null : settledParts(changes, deferred);
|
|
859
|
-
if (unsettled !== null)
|
|
860
|
-
return unqueueable(unsettled);
|
|
861
|
-
if (folded(changes, change, true, deferred) === 'refused')
|
|
862
|
-
return unqueueable(change.path);
|
|
863
|
-
}
|
|
864
|
-
const unsettled = settledParts(changes, deferred);
|
|
865
|
-
if (unsettled !== null)
|
|
866
|
-
return unqueueable(unsettled);
|
|
867
|
-
const held = new Set(owner.preconditions.map(covering));
|
|
868
|
-
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
869
|
-
const preconditions = [
|
|
870
|
-
...owner.preconditions,
|
|
871
|
-
...extra.preconditions.filter((precondition) => {
|
|
872
|
-
const key = covering(precondition);
|
|
873
|
-
return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
|
|
874
|
-
}),
|
|
875
|
-
];
|
|
876
|
-
const batch = immutable({
|
|
877
|
-
...owner,
|
|
878
|
-
...(extra.validation === undefined ? {} : { validation: extra.validation }),
|
|
879
|
-
id: changeBatchIdentity(),
|
|
880
|
-
changes,
|
|
881
|
-
preconditions,
|
|
882
|
-
});
|
|
883
|
-
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
884
|
-
}
|
|
885
|
-
export function* limiting(queue, batch, limits, state) {
|
|
880
|
+
function* limiting(queue, batch, limits, state) {
|
|
886
881
|
const at = queue.indexOf(batch);
|
|
887
882
|
const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
|
|
888
883
|
const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
|
|
889
|
-
return limited?.
|
|
884
|
+
return limited?.refusal ?? null;
|
|
890
885
|
}
|
|
891
886
|
function typedText(content) {
|
|
892
887
|
if (content === undefined)
|
|
@@ -912,7 +907,7 @@ function touchedLibrary(resulting, touched, composition) {
|
|
|
912
907
|
if (binaries.has(path))
|
|
913
908
|
known.set(path, { type: undefined, binary: true });
|
|
914
909
|
else if (content !== undefined && content !== null) {
|
|
915
|
-
const read = parse(content, path);
|
|
910
|
+
const read = parse(content, path, definitionsAt(composition, path));
|
|
916
911
|
if (read.ok)
|
|
917
912
|
known.set(path, { type: read.document.type ?? 'any', binary: false });
|
|
918
913
|
await pace(content.length + ROW_WEIGHT);
|
|
@@ -950,6 +945,7 @@ async function judging(visible, resulting, composition, validate, removed, touch
|
|
|
950
945
|
retryable: false,
|
|
951
946
|
file: verdict.file,
|
|
952
947
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
948
|
+
...(verdict.line === undefined ? {} : { line: verdict.line }),
|
|
953
949
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
954
950
|
},
|
|
955
951
|
};
|
|
@@ -965,8 +961,6 @@ function placed(reference) {
|
|
|
965
961
|
const key = roomPathOf(reference.handle, reference.path);
|
|
966
962
|
if (reference.kind === 'link')
|
|
967
963
|
return `link ${key}`;
|
|
968
|
-
if (reference.kind === 'item')
|
|
969
|
-
return `section ${reference.section} ${key}`;
|
|
970
964
|
const pointer = reference.pointer
|
|
971
965
|
.split('/')
|
|
972
966
|
.map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
|
|
@@ -976,10 +970,11 @@ function placed(reference) {
|
|
|
976
970
|
function roomReferencesIn(path, content, composition) {
|
|
977
971
|
if (content === undefined)
|
|
978
972
|
return [];
|
|
979
|
-
const
|
|
973
|
+
const definitions = definitionsAt(composition, path);
|
|
974
|
+
const parsed = parse(content, path, definitions);
|
|
980
975
|
if (!parsed.ok)
|
|
981
976
|
return [];
|
|
982
|
-
return roomReferences(parsed.document,
|
|
977
|
+
return roomReferences(parsed.document, definitions(parsed.document.type));
|
|
983
978
|
}
|
|
984
979
|
function* admittedRooms(changes, visible, resulting, composition) {
|
|
985
980
|
const from = new Map();
|
|
@@ -997,7 +992,7 @@ function* admittedRooms(changes, visible, resulting, composition) {
|
|
|
997
992
|
continue;
|
|
998
993
|
if (held.has(placed(reference)))
|
|
999
994
|
continue;
|
|
1000
|
-
return refuse('unresolved-reference', path, reference.
|
|
995
|
+
return refuse('unresolved-reference', path, reference.pointer);
|
|
1001
996
|
}
|
|
1002
997
|
yield ROW_WEIGHT + now.length;
|
|
1003
998
|
}
|
|
@@ -1015,7 +1010,10 @@ function validationOf(validated) {
|
|
|
1015
1010
|
};
|
|
1016
1011
|
}
|
|
1017
1012
|
async function batching(input) {
|
|
1018
|
-
const
|
|
1013
|
+
const relative = relativeChanges(input);
|
|
1014
|
+
if (relative.kind === 'refused')
|
|
1015
|
+
return relative;
|
|
1016
|
+
const { changes } = relative;
|
|
1019
1017
|
const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
|
|
1020
1018
|
if (structural !== undefined && input.unmodeled) {
|
|
1021
1019
|
return refuse('model-unavailable', structural.path);
|
|
@@ -1025,12 +1023,12 @@ async function batching(input) {
|
|
|
1025
1023
|
const rewritten = await stepped(rewrites(changes, input.visible));
|
|
1026
1024
|
if (rewritten.kind === 'refused')
|
|
1027
1025
|
return rewritten;
|
|
1028
|
-
const refusal = guard(changes, input, WRITE);
|
|
1026
|
+
const refusal = guard(changes, input, WRITE) ?? unapplied(changes, input.visible, input.composition);
|
|
1029
1027
|
if (refusal !== null)
|
|
1030
1028
|
return refusal;
|
|
1031
1029
|
const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
|
|
1032
1030
|
const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
|
|
1033
|
-
const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }]));
|
|
1031
|
+
const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }], input.composition));
|
|
1034
1032
|
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))]);
|
|
1035
1033
|
if (judged.kind === 'refused')
|
|
1036
1034
|
return judged;
|
|
@@ -1052,7 +1050,7 @@ async function batching(input) {
|
|
|
1052
1050
|
}),
|
|
1053
1051
|
};
|
|
1054
1052
|
}
|
|
1055
|
-
|
|
1053
|
+
const MOVE_DOCUMENTS = 'move-documents';
|
|
1056
1054
|
function heldAt(visible, path) {
|
|
1057
1055
|
return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
|
|
1058
1056
|
}
|
|
@@ -1061,7 +1059,7 @@ function arrivingText(path, to, source, moved) {
|
|
|
1061
1059
|
const document = source.visible.collection.document(path);
|
|
1062
1060
|
if (document === undefined)
|
|
1063
1061
|
return content;
|
|
1064
|
-
const definition =
|
|
1062
|
+
const definition = definitionsAt(source.composition, path)(document.type);
|
|
1065
1063
|
const changes = crossed(document, definition, path, (named, href) => {
|
|
1066
1064
|
const at = moved.get(named);
|
|
1067
1065
|
if (at !== undefined)
|
|
@@ -1070,7 +1068,7 @@ function arrivingText(path, to, source, moved) {
|
|
|
1070
1068
|
});
|
|
1071
1069
|
if (changes.length === 0)
|
|
1072
1070
|
return content;
|
|
1073
|
-
const applied = apply(document, changes);
|
|
1071
|
+
const applied = apply(document, changes, definition);
|
|
1074
1072
|
return applied.outcome === 'applied' ? serialize(applied.document) : content;
|
|
1075
1073
|
}
|
|
1076
1074
|
function arrivalOf({ path, to }, source, moved) {
|
|
@@ -1103,11 +1101,9 @@ function arrivalOf({ path, to }, source, moved) {
|
|
|
1103
1101
|
};
|
|
1104
1102
|
}
|
|
1105
1103
|
async function moving(input) {
|
|
1106
|
-
const { source, destination, moves
|
|
1107
|
-
if (moves.length === 0)
|
|
1108
|
-
return refuse('no-change');
|
|
1104
|
+
const { source, destination, moves } = input;
|
|
1109
1105
|
const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
|
|
1110
|
-
const leaves = guard(removals, { ...source, local: input.local, bases: []
|
|
1106
|
+
const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
|
|
1111
1107
|
if (leaves !== null)
|
|
1112
1108
|
return leaves;
|
|
1113
1109
|
const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
|
|
@@ -1115,7 +1111,7 @@ async function moving(input) {
|
|
|
1115
1111
|
return refuse('unknown-revision', uploading.path);
|
|
1116
1112
|
const moved = new Map(moves.map(({ path, to }) => [path, to]));
|
|
1117
1113
|
const arrivals = moves.map((move) => arrivalOf(move, source, moved));
|
|
1118
|
-
const arrives = guard(arrivals, { ...destination, local: input.local, bases: []
|
|
1114
|
+
const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, WRITE);
|
|
1119
1115
|
if (arrives !== null)
|
|
1120
1116
|
return arrives;
|
|
1121
1117
|
const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
|
|
@@ -1144,7 +1140,7 @@ async function moving(input) {
|
|
|
1144
1140
|
between,
|
|
1145
1141
|
};
|
|
1146
1142
|
const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
|
|
1147
|
-
const left = await stepped(overlaying(source.visible, [effective]));
|
|
1143
|
+
const left = await stepped(overlaying(source.visible, [effective], source.composition));
|
|
1148
1144
|
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))]);
|
|
1149
1145
|
if (sourceJudged.kind === 'refused')
|
|
1150
1146
|
return sourceJudged;
|
|
@@ -1156,7 +1152,7 @@ async function moving(input) {
|
|
|
1156
1152
|
...written,
|
|
1157
1153
|
between,
|
|
1158
1154
|
};
|
|
1159
|
-
const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
|
|
1155
|
+
const arrived = await stepped(overlaying(destination.visible, [arrivingBatch], destination.composition));
|
|
1160
1156
|
const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
|
|
1161
1157
|
if (destinationJudged.kind === 'refused')
|
|
1162
1158
|
return destinationJudged;
|
|
@@ -1166,9 +1162,180 @@ async function moving(input) {
|
|
|
1166
1162
|
return limited;
|
|
1167
1163
|
return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
|
|
1168
1164
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1165
|
+
function unreadable(saved, visible, composition) {
|
|
1166
|
+
const path = saved.find(({ path: at, parts }) => (parts.fields !== undefined ||
|
|
1167
|
+
parts.sections !== undefined ||
|
|
1168
|
+
parts.preamble !== undefined) &&
|
|
1169
|
+
typedText(visible.documents[at]) &&
|
|
1170
|
+
governing(composition, at).status === 'unavailable')?.path;
|
|
1171
|
+
return path === undefined ? null : refuse('model-unavailable', path);
|
|
1172
|
+
}
|
|
1173
|
+
function changedUnderneath(saved, visible) {
|
|
1174
|
+
const parts = saved.flatMap(({ path, parts: held }) => changedParts(path, held, visible));
|
|
1175
|
+
return parts.length === 0
|
|
1176
|
+
? null
|
|
1177
|
+
: { kind: 'refused', refusal: { code: 'changed-underneath', retryable: false, parts } };
|
|
1178
|
+
}
|
|
1179
|
+
function sideOf(room, visible) {
|
|
1180
|
+
const { record } = room;
|
|
1181
|
+
return {
|
|
1182
|
+
room: record.locator,
|
|
1183
|
+
handle: record.declaration.handle,
|
|
1184
|
+
baseRoomVersion: record.roomVersion,
|
|
1185
|
+
composition: room.composition,
|
|
1186
|
+
rule: room.rule,
|
|
1187
|
+
visible,
|
|
1188
|
+
folders: room.folders,
|
|
1189
|
+
queued: queuedDocuments(room.queue),
|
|
1190
|
+
unmodeled: !room.modelled,
|
|
1191
|
+
validate: room.validate,
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
function carriedDrafts(ask, from, into) {
|
|
1195
|
+
return new Map(ask.saved.flatMap(({ path, parts }) => {
|
|
1196
|
+
const record = from.drafts.get(path);
|
|
1197
|
+
if (parts.move === undefined || record === undefined || ask.consumed.has(path))
|
|
1198
|
+
return [];
|
|
1199
|
+
const to = moveTarget(parts.move);
|
|
1200
|
+
if (into.drafts.has(to))
|
|
1201
|
+
return [];
|
|
1202
|
+
const locator = into.record.locator;
|
|
1203
|
+
return [[path, { ...record, locator, path: to, revision: roomDraftRevision() }]];
|
|
1204
|
+
}));
|
|
1205
|
+
}
|
|
1206
|
+
async function plannedMove(ask, source, into) {
|
|
1207
|
+
const destination = into.record.locator;
|
|
1208
|
+
const mixed = ask.saved.find(({ parts }) => Object.keys(parts).length !== 1 || parts.move?.room !== destination);
|
|
1209
|
+
if (mixed !== undefined)
|
|
1210
|
+
return refuse('mixed-room-move', mixed.path);
|
|
1211
|
+
const leavingVisible = await stepped(source.replay(source.queue));
|
|
1212
|
+
const changed = changedUnderneath(ask.saved, leavingVisible);
|
|
1213
|
+
if (changed !== null)
|
|
1214
|
+
return changed;
|
|
1215
|
+
const moves = ask.saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
|
|
1216
|
+
const built = await moving({
|
|
1217
|
+
source: sideOf(source, leavingVisible),
|
|
1218
|
+
destination: sideOf(into, await stepped(into.replay(into.queue))),
|
|
1219
|
+
moves,
|
|
1220
|
+
providerLimits: ask.providerLimits,
|
|
1221
|
+
local: ask.local,
|
|
1222
|
+
...(ask.writtenBy === undefined ? {} : { writtenBy: ask.writtenBy }),
|
|
1223
|
+
});
|
|
1224
|
+
if (built.kind === 'refused')
|
|
1225
|
+
return built;
|
|
1226
|
+
const { id } = built.leaving;
|
|
1227
|
+
const carried = carriedDrafts(ask, source, into);
|
|
1228
|
+
return {
|
|
1229
|
+
kind: 'planned',
|
|
1230
|
+
identifier: id,
|
|
1231
|
+
rooms: [
|
|
1232
|
+
{
|
|
1233
|
+
locator: source.record.locator,
|
|
1234
|
+
queue: [...source.queue, built.leaving],
|
|
1235
|
+
saved: new Map([[id, ask.saved]]),
|
|
1236
|
+
consumed: [...ask.consumed, ...carried.keys()],
|
|
1237
|
+
carried: [],
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
locator: destination,
|
|
1241
|
+
queue: [...into.queue, built.arriving],
|
|
1242
|
+
saved: new Map([
|
|
1243
|
+
[id, [...carried.values()].map(({ path }) => ({ path, parts: {} }))],
|
|
1244
|
+
]),
|
|
1245
|
+
consumed: [],
|
|
1246
|
+
carried: [...carried.values()],
|
|
1247
|
+
},
|
|
1248
|
+
],
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
async function plannedWithin(ask, room) {
|
|
1252
|
+
const { record } = room;
|
|
1253
|
+
const visible = await stepped(room.replay(room.queue));
|
|
1254
|
+
const changed = unreadable(ask.saved, visible, room.composition) ?? changedUnderneath(ask.saved, visible);
|
|
1255
|
+
if (changed !== null)
|
|
1256
|
+
return changed;
|
|
1257
|
+
const parts = ask.saved.map(({ path, parts: held }) => changesOf(path, held));
|
|
1258
|
+
const changes = parts.flatMap(({ changes: made }) => made);
|
|
1259
|
+
const requeue = await stepped(requeuing(room.queue, changes, visible, (batch) => room.changeable.has(batch.id), room.replay));
|
|
1260
|
+
if (requeue.kind === 'refused')
|
|
1261
|
+
return requeue;
|
|
1262
|
+
const foreign = [...requeue.withdrawn.keys()].some((origin) => {
|
|
1263
|
+
const writer = room.queue.find((batch) => batch.id === origin)?.writtenBy;
|
|
1264
|
+
return writer?.app !== ask.writtenBy?.app || writer?.release !== ask.writtenBy?.release;
|
|
1265
|
+
});
|
|
1266
|
+
if (foreign)
|
|
1267
|
+
return unqueueable([...requeue.queued].at(-1));
|
|
1268
|
+
const input = {
|
|
1269
|
+
local: ask.local,
|
|
1270
|
+
baseRoomVersion: record.roomVersion,
|
|
1271
|
+
composition: room.composition,
|
|
1272
|
+
rule: room.rule,
|
|
1273
|
+
providerLimits: ask.providerLimits,
|
|
1274
|
+
handle: record.declaration.handle,
|
|
1275
|
+
visible,
|
|
1276
|
+
folders: room.folders,
|
|
1277
|
+
changes,
|
|
1278
|
+
bases: parts.flatMap(({ bases }) => bases),
|
|
1279
|
+
queued: queuedDocuments(room.queue),
|
|
1280
|
+
writtenBy: ask.writtenBy,
|
|
1281
|
+
unmodeled: !room.modelled,
|
|
1282
|
+
validate: room.validate,
|
|
1283
|
+
};
|
|
1284
|
+
const rewritten = requeue.withdrawn.size > 0;
|
|
1285
|
+
if (rewritten) {
|
|
1286
|
+
const whole = await batching({
|
|
1287
|
+
...input,
|
|
1288
|
+
providerLimits: [],
|
|
1289
|
+
queued: new Set([...input.queued, ...requeue.queued]),
|
|
1290
|
+
});
|
|
1291
|
+
if (whole.kind === 'refused')
|
|
1292
|
+
return whole;
|
|
1293
|
+
}
|
|
1294
|
+
const own = !rewritten
|
|
1295
|
+
? await batching(input)
|
|
1296
|
+
: requeue.changes.length === 0
|
|
1297
|
+
? null
|
|
1298
|
+
: await batching({
|
|
1299
|
+
...input,
|
|
1300
|
+
visible: await stepped(room.replay(requeue.queue)),
|
|
1301
|
+
changes: requeue.changes,
|
|
1302
|
+
queued: queuedDocuments(requeue.queue),
|
|
1303
|
+
});
|
|
1304
|
+
if (own?.kind === 'refused')
|
|
1305
|
+
return own;
|
|
1306
|
+
const queue = own === null ? requeue.queue : [...requeue.queue, own.batch];
|
|
1307
|
+
const saved = new Map();
|
|
1308
|
+
for (const [origin, rebuilt] of requeue.withdrawn) {
|
|
1309
|
+
if (rebuilt === null)
|
|
1310
|
+
continue;
|
|
1311
|
+
const limited = await stepped(limiting(queue, rebuilt, ask.providerLimits, room.replay));
|
|
1312
|
+
if (limited !== null)
|
|
1313
|
+
return { kind: 'refused', refusal: limited };
|
|
1314
|
+
saved.set(rebuilt.id, withdrawnDrafts(room.saved.get(origin) ?? [], changes));
|
|
1315
|
+
}
|
|
1316
|
+
if (own !== null) {
|
|
1317
|
+
saved.set(own.batch.id, ask.saved.filter(({ path }) => !requeue.queued.has(path)));
|
|
1318
|
+
}
|
|
1319
|
+
const carried = carriedDrafts(ask, room, room);
|
|
1320
|
+
return {
|
|
1321
|
+
kind: 'planned',
|
|
1322
|
+
identifier: own?.batch.id ??
|
|
1323
|
+
[...requeue.withdrawn].map(([origin, rebuilt]) => rebuilt?.id ?? origin).at(-1),
|
|
1324
|
+
rooms: [
|
|
1325
|
+
{
|
|
1326
|
+
locator: record.locator,
|
|
1327
|
+
queue,
|
|
1328
|
+
saved,
|
|
1329
|
+
consumed: [...ask.consumed, ...carried.keys()],
|
|
1330
|
+
carried: [...carried.values()],
|
|
1331
|
+
},
|
|
1332
|
+
],
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
export function destination(locator, saved) {
|
|
1336
|
+
const into = saved.find(({ parts }) => parts.move?.room !== undefined && parts.move.room !== locator);
|
|
1337
|
+
return into?.parts.move?.room ?? null;
|
|
1171
1338
|
}
|
|
1172
|
-
export function
|
|
1173
|
-
return
|
|
1339
|
+
export function planSave(ask, room, into) {
|
|
1340
|
+
return into === undefined ? plannedWithin(ask, room) : plannedMove(ask, room, into);
|
|
1174
1341
|
}
|