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