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