@agentshouse/mdruntime 0.1.0-cff8ba8fa2542 → 0.1.0-cffcba9cd9c2f
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 +309 -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 +195 -88
- 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 -28
- 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 -51
- 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 -103
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +103 -2076
- 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 +177 -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 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +149 -26
- 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 +39 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/install.d.ts +55 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +543 -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 +271 -0
- package/dist/listing.d.ts +52 -0
- package/dist/listing.d.ts.map +1 -0
- package/dist/listing.js +168 -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 +9 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +62 -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 +25 -8
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +296 -93
- 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 +16 -9
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +9 -15
- package/dist/models.d.ts +10 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +170 -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 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +26 -16
- package/dist/preparation.d.ts +20 -0
- package/dist/preparation.d.ts.map +1 -0
- package/dist/preparation.js +233 -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 -181
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -431
- 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 +32 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +402 -0
- package/dist/reach.d.ts +21 -5
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +306 -96
- package/dist/react.d.ts +43 -19
- 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 +59 -12
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +299 -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 -34
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +67 -87
- package/dist/runtime.d.ts +32 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/save.d.ts +74 -5
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +914 -188
- package/dist/saves.d.ts +32 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +679 -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 +299 -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 +41 -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 +69 -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 +7 -4
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -15
- 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 +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 +106 -92
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +142 -385
- 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 +224 -0
- package/dist/sync.d.ts +19 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +530 -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 -231
- 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 -377
package/dist/save.js
CHANGED
|
@@ -1,52 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { governing, parseCollection, validateCollection, } from './collection.js';
|
|
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';
|
|
5
4
|
import { changeBatchIdentity } from './identity.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
5
|
+
import { overlaying } from './overlay.js';
|
|
6
|
+
import { ancestors } from './paths.js';
|
|
7
|
+
import { entriesIn, sorting } from './shared.js';
|
|
8
|
+
import { paced, ROW_WEIGHT, stepped } from './turn.js';
|
|
9
|
+
import { validatedCollection } from './validation.js';
|
|
10
|
+
function refuse(code, file, location) {
|
|
9
11
|
return {
|
|
10
12
|
kind: 'refused',
|
|
11
13
|
refusal: {
|
|
12
|
-
|
|
14
|
+
code,
|
|
15
|
+
retryable: false,
|
|
13
16
|
...(file === undefined ? {} : { file }),
|
|
14
17
|
...(location === undefined ? {} : { location }),
|
|
15
18
|
},
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
|
-
function repointed(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return undefined;
|
|
28
|
-
const destination = moves.get(named) ?? named;
|
|
29
|
-
return target(to, value) === destination ? undefined : retarget(value, relative(to, destination));
|
|
30
|
-
}
|
|
31
|
-
function edited(document, path, changes) {
|
|
32
|
-
let next = document;
|
|
33
|
-
for (const change of changes) {
|
|
34
|
-
if (change.path !== path)
|
|
21
|
+
function repointed(document, definition, from, to, moves, settled) {
|
|
22
|
+
const changes = [];
|
|
23
|
+
const names = new Set();
|
|
24
|
+
for (const { pointer, value } of definition === undefined
|
|
25
|
+
? []
|
|
26
|
+
: references(document, definition)) {
|
|
27
|
+
const [name = ''] = pointerSegments(pointer);
|
|
28
|
+
const named = target(from, value);
|
|
29
|
+
if (named === null || settled.has(`${from} ${name}`))
|
|
35
30
|
continue;
|
|
36
|
-
|
|
31
|
+
const destination = moves.get(named) ?? named;
|
|
32
|
+
if (target(to, value) === destination)
|
|
37
33
|
continue;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
changes.push({
|
|
35
|
+
kind: 'reference',
|
|
36
|
+
pointer,
|
|
37
|
+
value: retarget(value, relative(to, destination)),
|
|
38
|
+
prior: value,
|
|
39
|
+
});
|
|
40
|
+
names.add(name);
|
|
41
|
+
}
|
|
42
|
+
if (changes.length === 0)
|
|
43
|
+
return [];
|
|
44
|
+
const applied = apply(document, changes, definition);
|
|
45
|
+
if (applied.outcome !== 'applied')
|
|
46
|
+
return [];
|
|
47
|
+
const next = applied.document;
|
|
48
|
+
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
49
|
+
}
|
|
50
|
+
function edited(document, changes, definition) {
|
|
51
|
+
const parts = new Map();
|
|
52
|
+
for (const change of changes) {
|
|
53
|
+
if (change.kind === 'preamble')
|
|
54
|
+
parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
55
|
+
if (change.kind === 'section')
|
|
56
|
+
parts.set(`section ${change.name}`, {
|
|
41
57
|
kind: 'section',
|
|
42
58
|
name: change.name,
|
|
43
59
|
value: change.value,
|
|
44
|
-
prior:
|
|
60
|
+
prior: document.sections[change.name] ?? '',
|
|
45
61
|
});
|
|
46
|
-
if (applied.outcome === 'applied')
|
|
47
|
-
next = applied.document;
|
|
48
62
|
}
|
|
49
|
-
|
|
63
|
+
if (parts.size === 0)
|
|
64
|
+
return document;
|
|
65
|
+
const applied = apply(document, [...parts.values()], definition);
|
|
66
|
+
return applied.outcome === 'applied' ? applied.document : document;
|
|
50
67
|
}
|
|
51
68
|
function bodies(path, before, after) {
|
|
52
69
|
const changes = [];
|
|
@@ -60,8 +77,8 @@ function bodies(path, before, after) {
|
|
|
60
77
|
}
|
|
61
78
|
return changes;
|
|
62
79
|
}
|
|
63
|
-
function relinks(path, moved, before, moves) {
|
|
64
|
-
|
|
80
|
+
function relinks(path, moved, before, moves, definition) {
|
|
81
|
+
const changes = [];
|
|
65
82
|
for (const link of links(before, path)) {
|
|
66
83
|
const destination = moves.get(link.target) ?? link.target;
|
|
67
84
|
if (destination === link.target && moved === path)
|
|
@@ -69,19 +86,23 @@ function relinks(path, moved, before, moves) {
|
|
|
69
86
|
const to = relative(moved, destination);
|
|
70
87
|
if (to === relative(path, link.target))
|
|
71
88
|
continue;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
changes.push({ kind: 'link', href: link.href, to });
|
|
90
|
+
}
|
|
91
|
+
if (changes.length === 0)
|
|
92
|
+
return { kind: 'changes', changes: [] };
|
|
93
|
+
const applied = apply(before, changes, definition);
|
|
94
|
+
if (applied.outcome === 'refuse') {
|
|
95
|
+
return {
|
|
96
|
+
kind: 'refused',
|
|
97
|
+
refusal: {
|
|
98
|
+
code: applied.refusal.rule,
|
|
99
|
+
retryable: false,
|
|
100
|
+
file: path,
|
|
101
|
+
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
102
|
+
},
|
|
103
|
+
};
|
|
84
104
|
}
|
|
105
|
+
const after = applied.document;
|
|
85
106
|
if (after === before)
|
|
86
107
|
return { kind: 'changes', changes: [] };
|
|
87
108
|
if (before.type === undefined) {
|
|
@@ -89,93 +110,173 @@ function relinks(path, moved, before, moves) {
|
|
|
89
110
|
}
|
|
90
111
|
return { kind: 'changes', changes: bodies(path, before, after) };
|
|
91
112
|
}
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
const item = merged[index];
|
|
95
|
-
if (item.path !== path)
|
|
96
|
-
continue;
|
|
97
|
-
if (item.kind === 'section' || item.kind === 'preamble')
|
|
98
|
-
merged.splice(index, 1);
|
|
99
|
-
}
|
|
113
|
+
function partKey(change) {
|
|
114
|
+
return change.kind === 'section' ? `section ${change.name}` : change.kind;
|
|
100
115
|
}
|
|
101
|
-
|
|
116
|
+
const NAMES_A_SKIP_TESTS = 64;
|
|
117
|
+
const ENCODED = /[%&\\]/u;
|
|
118
|
+
function movedNames(moves) {
|
|
119
|
+
const names = new Set([...moves.keys()].map((path) => path.slice(path.lastIndexOf('/') + 1)));
|
|
120
|
+
return names.size > NAMES_A_SKIP_TESTS ? null : [...names];
|
|
121
|
+
}
|
|
122
|
+
function cannotLink(text, names) {
|
|
123
|
+
return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
|
|
124
|
+
}
|
|
125
|
+
function* rewrites(changes, visible) {
|
|
102
126
|
const moves = new Map();
|
|
103
127
|
const settled = new Set();
|
|
104
|
-
|
|
128
|
+
const parts = new Map();
|
|
129
|
+
for (const [index, change] of changes.entries()) {
|
|
105
130
|
if (change.kind === 'move')
|
|
106
131
|
moves.set(change.path, change.to);
|
|
107
|
-
if (change.kind === 'remove' ||
|
|
132
|
+
if (change.kind === 'remove' ||
|
|
133
|
+
change.kind === 'replace' ||
|
|
134
|
+
change.kind === 'create' ||
|
|
135
|
+
change.kind === 'binary') {
|
|
108
136
|
settled.add(change.path);
|
|
109
137
|
}
|
|
110
138
|
if (change.kind === 'field')
|
|
111
139
|
settled.add(`${change.path} ${change.name}`);
|
|
140
|
+
if (isPart(change)) {
|
|
141
|
+
const held = parts.get(change.path);
|
|
142
|
+
if (held === undefined)
|
|
143
|
+
parts.set(change.path, [index]);
|
|
144
|
+
else
|
|
145
|
+
held.push(index);
|
|
146
|
+
}
|
|
112
147
|
}
|
|
113
148
|
if (moves.size === 0)
|
|
114
149
|
return { kind: 'changes', changes };
|
|
150
|
+
const names = movedNames(moves);
|
|
115
151
|
const merged = [...changes];
|
|
116
152
|
const added = [];
|
|
117
|
-
for (const [path, document] of
|
|
153
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
118
154
|
if (settled.has(path))
|
|
119
155
|
continue;
|
|
120
156
|
const moved = moves.get(path) ?? path;
|
|
121
|
-
const definition =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
added.push({ kind: 'field', path, name, value });
|
|
129
|
-
}
|
|
130
|
-
const relinked = relinks(path, moved, edited(document, path, merged), moves);
|
|
157
|
+
const definition = definitionsAt(visible.collection.composition, path)(document.type);
|
|
158
|
+
const repointing = repointed(document, definition, path, moved, moves, settled);
|
|
159
|
+
added.push(...repointing);
|
|
160
|
+
const own = parts.get(path) ?? [];
|
|
161
|
+
const relinked = moved === path && own.length === 0 && cannotLink(document.content, names)
|
|
162
|
+
? { kind: 'changes', changes: [] }
|
|
163
|
+
: relinks(path, moved, edited(document, own.map((index) => merged[index]), definition), moves, definition);
|
|
131
164
|
if (relinked.kind === 'refused')
|
|
132
165
|
return relinked;
|
|
166
|
+
const keyed = new Map();
|
|
167
|
+
for (const index of own) {
|
|
168
|
+
const key = partKey(merged[index]);
|
|
169
|
+
if (!keyed.has(key))
|
|
170
|
+
keyed.set(key, index);
|
|
171
|
+
}
|
|
133
172
|
for (const change of relinked.changes) {
|
|
134
173
|
if (change.kind === 'replace') {
|
|
135
|
-
|
|
174
|
+
for (const index of own) {
|
|
175
|
+
const kind = merged[index]?.kind;
|
|
176
|
+
if (kind === 'section' || kind === 'preamble')
|
|
177
|
+
merged[index] = null;
|
|
178
|
+
}
|
|
136
179
|
added.push(change);
|
|
137
180
|
continue;
|
|
138
181
|
}
|
|
139
|
-
const index =
|
|
140
|
-
|
|
141
|
-
(change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
|
|
142
|
-
if (index === -1)
|
|
182
|
+
const index = keyed.get(partKey(change));
|
|
183
|
+
if (index === undefined)
|
|
143
184
|
added.push(change);
|
|
144
185
|
else
|
|
145
186
|
merged[index] = change;
|
|
146
187
|
}
|
|
188
|
+
const text = visible.collection.text[path].length;
|
|
189
|
+
const rewritten = repointing.length > 0 || relinked.changes.length > 0;
|
|
190
|
+
yield ROW_WEIGHT + text + (rewritten ? text : 0);
|
|
147
191
|
}
|
|
148
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
kind: 'changes',
|
|
194
|
+
changes: [...added, ...merged.filter((change) => change !== null)],
|
|
195
|
+
};
|
|
149
196
|
}
|
|
150
|
-
function anticipated(changes, preconditions, rewritten, visible) {
|
|
151
|
-
const
|
|
197
|
+
function* anticipated(changes, preconditions, rewritten, visible) {
|
|
198
|
+
const given = new Set(changes);
|
|
199
|
+
const derived = rewritten.filter((change) => !given.has(change));
|
|
152
200
|
return {
|
|
153
201
|
changes: rewritten,
|
|
154
|
-
preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
|
|
202
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
|
|
155
203
|
};
|
|
156
204
|
}
|
|
157
|
-
export function providerRewritten(batch, visible) {
|
|
158
|
-
const rewritten = rewrites(batch.changes, visible);
|
|
205
|
+
export function* providerRewritten(batch, visible) {
|
|
206
|
+
const rewritten = yield* rewrites(batch.changes, visible);
|
|
159
207
|
if (rewritten.kind === 'refused')
|
|
160
208
|
return batch;
|
|
161
209
|
return {
|
|
162
210
|
...batch,
|
|
163
|
-
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
211
|
+
...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
|
|
164
212
|
};
|
|
165
213
|
}
|
|
166
|
-
function
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
if (
|
|
214
|
+
function crossed(document, definition, path, to) {
|
|
215
|
+
const changes = [];
|
|
216
|
+
for (const link of links(document, path)) {
|
|
217
|
+
const written = to(link.target, link.href);
|
|
218
|
+
if (written !== undefined)
|
|
219
|
+
changes.push({ kind: 'link', href: link.href, to: written });
|
|
220
|
+
}
|
|
221
|
+
for (const { pointer, value } of definition === undefined
|
|
222
|
+
? []
|
|
223
|
+
: references(document, definition)) {
|
|
224
|
+
const named = target(path, value);
|
|
225
|
+
const written = named === null ? undefined : to(named, value);
|
|
226
|
+
if (written !== undefined) {
|
|
227
|
+
changes.push({ kind: 'reference', pointer, value: retarget(value, written), prior: value });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return changes;
|
|
231
|
+
}
|
|
232
|
+
function rewritten(path, document, changes, definition) {
|
|
233
|
+
if (changes.length === 0)
|
|
234
|
+
return [];
|
|
235
|
+
const applied = apply(document, changes, definition);
|
|
236
|
+
if (applied.outcome !== 'applied')
|
|
237
|
+
return [];
|
|
238
|
+
const after = applied.document;
|
|
239
|
+
if (document.type === undefined)
|
|
240
|
+
return [{ kind: 'replace', path, content: serialize(after) }];
|
|
241
|
+
const names = new Set(changes.flatMap((change) => change.kind === 'reference' ? [pointerSegments(change.pointer)[0] ?? ''] : []));
|
|
242
|
+
return [
|
|
243
|
+
...[...names].map((name) => ({ kind: 'field', path, name, value: after.fields[name] })),
|
|
244
|
+
...bodies(path, document, after),
|
|
245
|
+
];
|
|
246
|
+
}
|
|
247
|
+
function roomPathOf(handle, path) {
|
|
248
|
+
return `/rooms/${handle}/${path}`;
|
|
249
|
+
}
|
|
250
|
+
export function* leavingRewritten(batch, visible) {
|
|
251
|
+
const between = batch.between;
|
|
252
|
+
if (between === undefined)
|
|
253
|
+
return batch;
|
|
254
|
+
const moved = new Map(between.moves.map(({ path, to }) => [path, roomPathOf(between.destination.handle, to)]));
|
|
255
|
+
const derived = [];
|
|
256
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
257
|
+
if (moved.has(path))
|
|
171
258
|
continue;
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
259
|
+
const definition = definitionsAt(visible.collection.composition, path)(document.type);
|
|
260
|
+
derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named)), definition));
|
|
261
|
+
yield ROW_WEIGHT + visible.collection.text[path].length;
|
|
175
262
|
}
|
|
176
|
-
|
|
263
|
+
if (derived.length === 0)
|
|
264
|
+
return batch;
|
|
265
|
+
return {
|
|
266
|
+
...batch,
|
|
267
|
+
changes: [...derived, ...batch.changes],
|
|
268
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...batch.preconditions],
|
|
269
|
+
};
|
|
177
270
|
}
|
|
178
|
-
function
|
|
271
|
+
export function rewriting(batch, locator) {
|
|
272
|
+
return leaving(batch, locator) || batch.changes.some((change) => change.kind === 'move');
|
|
273
|
+
}
|
|
274
|
+
export function* effectiveIn(batch, locator, visible) {
|
|
275
|
+
return leaving(batch, locator)
|
|
276
|
+
? yield* leavingRewritten(batch, visible)
|
|
277
|
+
: yield* providerRewritten(batch, visible);
|
|
278
|
+
}
|
|
279
|
+
function* incoming(collection, removed, candidates) {
|
|
179
280
|
const found = new Map();
|
|
180
281
|
const note = (target, file) => {
|
|
181
282
|
if (!removed.has(target))
|
|
@@ -184,18 +285,31 @@ function incoming(collection, removed) {
|
|
|
184
285
|
referrers.add(file);
|
|
185
286
|
found.set(target, referrers);
|
|
186
287
|
};
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
for (const
|
|
190
|
-
if (
|
|
288
|
+
const read = function* (paths) {
|
|
289
|
+
const seen = new Set();
|
|
290
|
+
for (const path of paths) {
|
|
291
|
+
if (seen.has(path))
|
|
191
292
|
continue;
|
|
192
|
-
|
|
293
|
+
seen.add(path);
|
|
294
|
+
const document = collection.document(path);
|
|
295
|
+
if (document !== undefined)
|
|
296
|
+
yield [path, document];
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
for (const [path, document] of candidates === null
|
|
300
|
+
? yield* documentsOf(collection)
|
|
301
|
+
: read(candidates)) {
|
|
302
|
+
const definition = definitionsAt(collection.composition, path)(document.type);
|
|
303
|
+
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
304
|
+
if (to.includes(ATTACHMENT))
|
|
305
|
+
continue;
|
|
306
|
+
const named = target(path, value);
|
|
307
|
+
if (named !== null)
|
|
193
308
|
note(named, path);
|
|
194
309
|
}
|
|
195
|
-
|
|
196
|
-
note(link.target, path);
|
|
310
|
+
yield ROW_WEIGHT + collection.text[path].length;
|
|
197
311
|
}
|
|
198
|
-
const [first] =
|
|
312
|
+
const [first] = yield* sorting(found.keys());
|
|
199
313
|
if (first === undefined)
|
|
200
314
|
return null;
|
|
201
315
|
const every = new Set();
|
|
@@ -204,41 +318,65 @@ function incoming(collection, removed) {
|
|
|
204
318
|
every.add(file);
|
|
205
319
|
return {
|
|
206
320
|
kind: 'refused',
|
|
207
|
-
refusal: {
|
|
321
|
+
refusal: {
|
|
322
|
+
code: 'referenced-missing-file',
|
|
323
|
+
retryable: false,
|
|
324
|
+
file: first,
|
|
325
|
+
referrers: yield* sorting(every),
|
|
326
|
+
},
|
|
208
327
|
};
|
|
209
328
|
}
|
|
210
329
|
function affected(change) {
|
|
211
330
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
212
331
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
332
|
+
const WRITE = ['write'];
|
|
333
|
+
const MOVE_OUT = ['write', 'erase'];
|
|
334
|
+
function guard(changes, input, removing) {
|
|
335
|
+
const overridden = new Map();
|
|
336
|
+
const existing = {
|
|
337
|
+
has: (path) => overridden.get(path) ??
|
|
338
|
+
(Object.hasOwn(input.visible.documents, path) || Object.hasOwn(input.visible.binaries, path)),
|
|
339
|
+
add: (path) => void overridden.set(path, true),
|
|
340
|
+
delete: (path) => void overridden.set(path, false),
|
|
341
|
+
};
|
|
342
|
+
const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
|
|
343
|
+
for (const [index, change] of changes.entries()) {
|
|
219
344
|
for (const path of affected(change)) {
|
|
220
|
-
const
|
|
221
|
-
if (
|
|
222
|
-
return refuse(
|
|
223
|
-
if (covered(input.providerOwnedPaths, normalized)) {
|
|
224
|
-
return refuse('provider-owned-path', normalized);
|
|
225
|
-
}
|
|
226
|
-
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
227
|
-
if (!covered(input.operations[operation], normalized))
|
|
228
|
-
return refuse(`${operation}-grant`, normalized);
|
|
345
|
+
const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
|
|
346
|
+
if (refused !== null)
|
|
347
|
+
return refuse(refused.code, refused.path);
|
|
229
348
|
}
|
|
230
|
-
if (change.kind === 'create') {
|
|
231
|
-
|
|
349
|
+
if (change.kind === 'create' || change.kind === 'arrival') {
|
|
350
|
+
const taken = existing.has(change.path) ? change.path : occupied(change.path);
|
|
351
|
+
if (taken !== null)
|
|
352
|
+
return refuse('path-collision', taken);
|
|
353
|
+
existing.add(change.path);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (change.kind === 'binary') {
|
|
357
|
+
if (input.visible.documents[change.path] !== undefined ||
|
|
358
|
+
(existing.has(change.path) &&
|
|
359
|
+
input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
|
|
232
360
|
return refuse('path-collision', change.path);
|
|
361
|
+
}
|
|
362
|
+
const taken = occupied(change.path);
|
|
363
|
+
if (taken !== null)
|
|
364
|
+
return refuse('path-collision', taken);
|
|
365
|
+
if (!input.local &&
|
|
366
|
+
existing.has(change.path) &&
|
|
367
|
+
input.visible.revisions[departure(changes, index)] === undefined) {
|
|
368
|
+
return refuse('unknown-revision', change.path);
|
|
369
|
+
}
|
|
233
370
|
existing.add(change.path);
|
|
234
371
|
continue;
|
|
235
372
|
}
|
|
236
373
|
if (change.kind === 'move') {
|
|
237
374
|
if (!existing.has(change.path))
|
|
238
375
|
return refuse('path-absent', change.path);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
376
|
+
const taken = existing.has(change.to) ? change.to : occupied(change.to);
|
|
377
|
+
if (taken !== null)
|
|
378
|
+
return refuse('path-collision', taken);
|
|
379
|
+
if (unrevised(input, change.path)) {
|
|
242
380
|
return refuse('unknown-revision', change.path);
|
|
243
381
|
}
|
|
244
382
|
existing.delete(change.path);
|
|
@@ -248,30 +386,53 @@ function guard(changes, input) {
|
|
|
248
386
|
if (!existing.has(change.path))
|
|
249
387
|
return refuse('path-absent', change.path);
|
|
250
388
|
if (change.kind === 'remove') {
|
|
251
|
-
if (
|
|
389
|
+
if (unrevised(input, change.path)) {
|
|
252
390
|
return refuse('unknown-revision', change.path);
|
|
253
391
|
}
|
|
254
392
|
existing.delete(change.path);
|
|
255
393
|
continue;
|
|
256
394
|
}
|
|
257
395
|
if (change.kind === 'replace') {
|
|
258
|
-
if (input
|
|
396
|
+
if (unrevised(input, change.path)) {
|
|
259
397
|
return refuse('unknown-revision', change.path);
|
|
260
398
|
}
|
|
261
399
|
continue;
|
|
262
400
|
}
|
|
263
|
-
if (input.visible.collection.
|
|
401
|
+
if (input.visible.collection.document(change.path) === undefined) {
|
|
264
402
|
return refuse('path-absent', change.path);
|
|
265
403
|
}
|
|
266
404
|
}
|
|
267
405
|
return null;
|
|
268
406
|
}
|
|
269
|
-
function
|
|
270
|
-
return visible.revisions[path] === undefined
|
|
407
|
+
function unrevised(input, path) {
|
|
408
|
+
return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
|
|
409
|
+
}
|
|
410
|
+
export function queuedDocuments(queue) {
|
|
411
|
+
const paths = new Set();
|
|
412
|
+
for (const batch of queue) {
|
|
413
|
+
for (const change of batch.changes) {
|
|
414
|
+
if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
|
|
415
|
+
continue;
|
|
416
|
+
if (change.kind === 'remove')
|
|
417
|
+
paths.delete(change.path);
|
|
418
|
+
else if (change.kind !== 'move')
|
|
419
|
+
paths.add(change.path);
|
|
420
|
+
else if (paths.delete(change.path))
|
|
421
|
+
paths.add(change.to);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return paths;
|
|
425
|
+
}
|
|
426
|
+
function identity(precondition) {
|
|
427
|
+
const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
|
|
428
|
+
return `${precondition.kind} ${precondition.path} ${part}`;
|
|
271
429
|
}
|
|
272
430
|
function existence(visible, path) {
|
|
273
431
|
const revision = visible.revisions[path];
|
|
274
|
-
|
|
432
|
+
if (revision !== undefined)
|
|
433
|
+
return { kind: 'revision', path, revision };
|
|
434
|
+
const content = visible.documents[path];
|
|
435
|
+
return content === undefined ? { kind: 'present', path } : { kind: 'content', path, content };
|
|
275
436
|
}
|
|
276
437
|
function sameTarget(precondition, change) {
|
|
277
438
|
if (precondition.kind !== change.kind || precondition.path !== change.path)
|
|
@@ -279,25 +440,25 @@ function sameTarget(precondition, change) {
|
|
|
279
440
|
return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
|
|
280
441
|
((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
|
|
281
442
|
}
|
|
282
|
-
function preconditionsOf(changes, visible, bases) {
|
|
443
|
+
function* preconditionsOf(changes, visible, bases) {
|
|
283
444
|
const preconditions = [];
|
|
284
445
|
const seen = new Set();
|
|
285
446
|
const add = (precondition) => {
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
if (seen.has(identity))
|
|
447
|
+
const held = identity(precondition);
|
|
448
|
+
if (seen.has(held))
|
|
289
449
|
return;
|
|
290
|
-
seen.add(
|
|
450
|
+
seen.add(held);
|
|
291
451
|
preconditions.push(precondition);
|
|
292
452
|
};
|
|
293
453
|
for (const change of changes) {
|
|
454
|
+
yield ROW_WEIGHT;
|
|
294
455
|
if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
|
|
295
456
|
const based = bases.find((base) => sameTarget(base, change));
|
|
296
457
|
if (based !== undefined) {
|
|
297
458
|
add(based);
|
|
298
459
|
continue;
|
|
299
460
|
}
|
|
300
|
-
const document = visible.collection.
|
|
461
|
+
const document = visible.collection.document(change.path);
|
|
301
462
|
if (change.kind === 'field') {
|
|
302
463
|
add({
|
|
303
464
|
kind: 'field',
|
|
@@ -322,6 +483,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
322
483
|
add({ kind: 'absent', path: change.path });
|
|
323
484
|
continue;
|
|
324
485
|
}
|
|
486
|
+
if (change.kind === 'binary') {
|
|
487
|
+
const revision = visible.revisions[change.path];
|
|
488
|
+
add(visible.binaries[change.path] === undefined || revision === undefined
|
|
489
|
+
? { kind: 'absent', path: change.path }
|
|
490
|
+
: { kind: 'revision', path: change.path, revision });
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
325
493
|
add(existence(visible, change.path));
|
|
326
494
|
if (change.kind === 'move')
|
|
327
495
|
add({ kind: 'absent', path: change.to });
|
|
@@ -331,10 +499,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
331
499
|
function depth(path) {
|
|
332
500
|
return path.split('/').length;
|
|
333
501
|
}
|
|
334
|
-
function
|
|
335
|
-
const written =
|
|
502
|
+
function* exceeding(limits, changes, effective, resulting) {
|
|
503
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
336
504
|
const created = changes
|
|
337
|
-
.filter((change) => change.kind === 'create' ||
|
|
505
|
+
.filter((change) => change.kind === 'create' ||
|
|
506
|
+
change.kind === 'move' ||
|
|
507
|
+
change.kind === 'binary' ||
|
|
508
|
+
change.kind === 'arrival')
|
|
338
509
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
339
510
|
for (const limit of limits) {
|
|
340
511
|
const value = limit.value;
|
|
@@ -348,9 +519,13 @@ function exceeded(limits, changes, resulting) {
|
|
|
348
519
|
return refuse('provider-limit', undefined, limit.name);
|
|
349
520
|
}
|
|
350
521
|
if (limit.name === 'document-size') {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
522
|
+
for (const path of written) {
|
|
523
|
+
const text = resulting.documents[path] ?? '';
|
|
524
|
+
if (new TextEncoder().encode(text).byteLength > value) {
|
|
525
|
+
return refuse('provider-limit', path, limit.name);
|
|
526
|
+
}
|
|
527
|
+
yield ROW_WEIGHT + text.length;
|
|
528
|
+
}
|
|
354
529
|
}
|
|
355
530
|
if (limit.name === 'path-length') {
|
|
356
531
|
const long = created.find((path) => path.length > value);
|
|
@@ -365,65 +540,506 @@ function exceeded(limits, changes, resulting) {
|
|
|
365
540
|
}
|
|
366
541
|
return null;
|
|
367
542
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (
|
|
379
|
-
return
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const
|
|
543
|
+
function shellRelative(text, path, handle, definition) {
|
|
544
|
+
if (!text.includes(`/rooms/${handle}/`))
|
|
545
|
+
return null;
|
|
546
|
+
const parsed = parse(text, path, definition);
|
|
547
|
+
if (!parsed.ok)
|
|
548
|
+
return null;
|
|
549
|
+
const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
|
|
550
|
+
return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
|
|
551
|
+
}
|
|
552
|
+
function relativeField(path, name, value, handle, definition) {
|
|
553
|
+
if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
554
|
+
return value;
|
|
555
|
+
const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
|
|
556
|
+
if (held.outcome !== 'applied')
|
|
557
|
+
return value;
|
|
558
|
+
const result = relativize(held.document, path, handle, definition);
|
|
559
|
+
return result.outcome === 'applied' ? result.document.fields[name] : value;
|
|
560
|
+
}
|
|
561
|
+
function relativeChanges(input) {
|
|
562
|
+
const { handle, composition, visible } = input;
|
|
563
|
+
const typed = (path) => definitionsAt(composition, path);
|
|
564
|
+
const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
|
|
565
|
+
return input.changes.map((change) => {
|
|
566
|
+
const definition = typed(change.path);
|
|
567
|
+
if (change.kind === 'field') {
|
|
568
|
+
const type = visible.collection.document(change.path)?.type;
|
|
569
|
+
const value = relativeField(change.path, change.name, change.value, handle, definition(type));
|
|
570
|
+
return value === change.value ? change : { ...change, value };
|
|
571
|
+
}
|
|
572
|
+
if (change.kind === 'section' || change.kind === 'preamble') {
|
|
573
|
+
const value = body(change.path, change.value);
|
|
574
|
+
return value === change.value ? change : { ...change, value };
|
|
575
|
+
}
|
|
576
|
+
if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
|
|
577
|
+
const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
|
|
578
|
+
return content === change.content ? change : { ...change, content };
|
|
579
|
+
}
|
|
580
|
+
if (change.kind !== 'create')
|
|
581
|
+
return change;
|
|
582
|
+
const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
|
|
583
|
+
const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
|
|
584
|
+
name,
|
|
585
|
+
relativeField(change.path, name, value, handle, definition(type)),
|
|
586
|
+
]));
|
|
587
|
+
const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
|
|
588
|
+
return { ...change, fields, sections };
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
function unqueueable(file) {
|
|
592
|
+
return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
|
|
593
|
+
}
|
|
594
|
+
function isPart(change) {
|
|
595
|
+
return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
|
|
596
|
+
}
|
|
597
|
+
function touched(change) {
|
|
598
|
+
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
599
|
+
}
|
|
600
|
+
function partsApplied(content, path, changes, composition) {
|
|
601
|
+
const definitions = definitionsAt(composition, path);
|
|
602
|
+
const parsed = parse(content, path, definitions);
|
|
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 });
|
|
624
|
+
}
|
|
625
|
+
const applied = apply(document, [...parts.values()], definitions(document.type));
|
|
626
|
+
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
627
|
+
}
|
|
628
|
+
function settledParts(changes, deferred, composition) {
|
|
629
|
+
for (const [held, parts] of deferred) {
|
|
630
|
+
const content = partsApplied(held.content, held.path, parts, composition);
|
|
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)
|
|
706
|
+
? null
|
|
707
|
+
: settledParts(changes, deferred, after.collection.composition);
|
|
708
|
+
if (unsettled !== null)
|
|
709
|
+
return unqueueable(unsettled);
|
|
710
|
+
const outcome = folded(changes, change, appending, deferred);
|
|
711
|
+
if (outcome === 'refused')
|
|
712
|
+
return unqueueable(change.path);
|
|
713
|
+
if (outcome === 'appended')
|
|
714
|
+
appended.push(change);
|
|
715
|
+
if (outcome !== 'skipped')
|
|
716
|
+
changed = true;
|
|
717
|
+
}
|
|
718
|
+
const unsettled = settledParts(changes, deferred, after.collection.composition);
|
|
719
|
+
if (unsettled !== null)
|
|
720
|
+
return unqueueable(unsettled);
|
|
721
|
+
if (!changed)
|
|
722
|
+
return { kind: 'relinked', batch };
|
|
723
|
+
const held = new Set(batch.preconditions.map(identity));
|
|
724
|
+
const added = yield* preconditionsOf(appended, before, []);
|
|
725
|
+
return {
|
|
726
|
+
kind: 'relinked',
|
|
727
|
+
batch: immutable({
|
|
728
|
+
...batch,
|
|
729
|
+
id: changeBatchIdentity(),
|
|
730
|
+
changes,
|
|
731
|
+
preconditions: [
|
|
732
|
+
...batch.preconditions,
|
|
733
|
+
...added.filter((precondition) => !held.has(identity(precondition))),
|
|
734
|
+
],
|
|
735
|
+
}),
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function leading(changes, at) {
|
|
739
|
+
const from = departure(changes, at);
|
|
740
|
+
return new Set(changes.filter((change, index) => index < at && change.kind === 'move' && departure(changes, index) === from));
|
|
741
|
+
}
|
|
742
|
+
function created(batch, at) {
|
|
743
|
+
const file = batch.changes[at];
|
|
744
|
+
return (leading(batch.changes, at).size === 0 &&
|
|
745
|
+
batch.preconditions.some((precondition) => precondition.kind === 'absent' && precondition.path === file.path));
|
|
746
|
+
}
|
|
747
|
+
function withdrawnFile(batch, change, at) {
|
|
748
|
+
const file = batch.changes[at];
|
|
749
|
+
const from = departure(batch.changes, at);
|
|
750
|
+
const moves = leading(batch.changes, at);
|
|
751
|
+
const fresh = created(batch, at);
|
|
752
|
+
const vacated = new Set([...moves].map((move) => move.to));
|
|
753
|
+
if (fresh)
|
|
754
|
+
vacated.add(file.path);
|
|
755
|
+
const replaced = fresh
|
|
756
|
+
? change.kind === 'remove'
|
|
757
|
+
? []
|
|
758
|
+
: [{ ...file, path: change.to }]
|
|
759
|
+
: change.kind === 'remove'
|
|
760
|
+
? [{ kind: 'remove', path: from }]
|
|
761
|
+
: change.to === from
|
|
762
|
+
? [{ ...file, path: from }]
|
|
763
|
+
: [
|
|
764
|
+
{ kind: 'move', path: from, to: change.to },
|
|
765
|
+
{ ...file, path: change.to },
|
|
766
|
+
];
|
|
767
|
+
const added = change.kind === 'move' && (fresh || change.to !== from)
|
|
768
|
+
? [{ kind: 'absent', path: change.to }]
|
|
769
|
+
: [];
|
|
770
|
+
const changes = batch.changes.flatMap((item) => item === file ? replaced : item.kind === 'move' && moves.has(item) ? [] : [item]);
|
|
771
|
+
if (changes.length === 0)
|
|
772
|
+
return null;
|
|
773
|
+
const preconditions = [
|
|
774
|
+
...added,
|
|
775
|
+
...batch.preconditions.filter((precondition) => precondition.kind !== 'absent' || !vacated.has(precondition.path)),
|
|
776
|
+
];
|
|
777
|
+
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
778
|
+
}
|
|
779
|
+
export function* requeuing(queue, changes, visible, changeable, state) {
|
|
780
|
+
const next = [...queue];
|
|
781
|
+
const origins = new Map();
|
|
782
|
+
const withdrawn = new Map();
|
|
783
|
+
const owners = new Set();
|
|
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
|
+
owners.add(originOf(owner));
|
|
830
|
+
next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
|
|
831
|
+
queued.add(change.path);
|
|
832
|
+
}
|
|
833
|
+
return {
|
|
834
|
+
kind: 'requeued',
|
|
835
|
+
queue: withdrawn.size === 0 ? queue : next,
|
|
836
|
+
changes: kept,
|
|
837
|
+
queued,
|
|
838
|
+
withdrawn,
|
|
839
|
+
owners,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function covering(precondition) {
|
|
843
|
+
return precondition.kind === 'field' ||
|
|
844
|
+
precondition.kind === 'section' ||
|
|
845
|
+
precondition.kind === 'preamble'
|
|
846
|
+
? identity(precondition)
|
|
847
|
+
: `path ${precondition.path}`;
|
|
848
|
+
}
|
|
849
|
+
export function joined(queue, owner, extra, composition) {
|
|
850
|
+
const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
|
|
851
|
+
const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
|
|
852
|
+
if (clash !== undefined)
|
|
853
|
+
return unqueueable(clash);
|
|
854
|
+
const changes = [...owner.changes];
|
|
855
|
+
const deferred = new Map();
|
|
856
|
+
for (const change of extra.changes) {
|
|
857
|
+
const unsettled = isPart(change) ? null : settledParts(changes, deferred, composition);
|
|
858
|
+
if (unsettled !== null)
|
|
859
|
+
return unqueueable(unsettled);
|
|
860
|
+
if (folded(changes, change, true, deferred) === 'refused')
|
|
861
|
+
return unqueueable(change.path);
|
|
862
|
+
}
|
|
863
|
+
const unsettled = settledParts(changes, deferred, composition);
|
|
864
|
+
if (unsettled !== null)
|
|
865
|
+
return unqueueable(unsettled);
|
|
866
|
+
const held = new Set(owner.preconditions.map(covering));
|
|
867
|
+
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
868
|
+
const preconditions = [
|
|
869
|
+
...owner.preconditions,
|
|
870
|
+
...extra.preconditions.filter((precondition) => {
|
|
871
|
+
const key = covering(precondition);
|
|
872
|
+
return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
|
|
873
|
+
}),
|
|
874
|
+
];
|
|
875
|
+
const batch = immutable({
|
|
876
|
+
...owner,
|
|
877
|
+
...(extra.validation === undefined ? {} : { validation: extra.validation }),
|
|
878
|
+
id: changeBatchIdentity(),
|
|
879
|
+
changes,
|
|
880
|
+
preconditions,
|
|
881
|
+
});
|
|
882
|
+
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
883
|
+
}
|
|
884
|
+
export function* limiting(queue, batch, limits, state) {
|
|
885
|
+
const at = queue.indexOf(batch);
|
|
886
|
+
const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
|
|
887
|
+
const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
|
|
888
|
+
return limited?.refusal ?? null;
|
|
889
|
+
}
|
|
890
|
+
function typedText(content) {
|
|
891
|
+
if (content === undefined)
|
|
892
|
+
return false;
|
|
893
|
+
const parsed = parse(content);
|
|
894
|
+
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
895
|
+
}
|
|
896
|
+
function touchedLibrary(resulting, touched, composition) {
|
|
897
|
+
const entries = touched
|
|
898
|
+
.flatMap((path) => {
|
|
899
|
+
const content = resulting.documents[path];
|
|
900
|
+
const binary = resulting.binaries[path] !== undefined;
|
|
901
|
+
return content === undefined && !binary ? [] : [{ path, content: content ?? null }];
|
|
902
|
+
})
|
|
903
|
+
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
904
|
+
const contents = new Map(entries.map(({ path, content }) => [path, content]));
|
|
905
|
+
const binaries = new Set(Array.from(entriesIn(resulting.binaries), ([path]) => path));
|
|
906
|
+
return validatedCollection(() => Promise.resolve({ entries, next: null }), async (paths) => {
|
|
907
|
+
const known = new Map();
|
|
908
|
+
const pace = paced();
|
|
909
|
+
for (const path of paths) {
|
|
910
|
+
const content = contents.get(path);
|
|
911
|
+
if (binaries.has(path))
|
|
912
|
+
known.set(path, { type: undefined, binary: true });
|
|
913
|
+
else if (content !== undefined && content !== null) {
|
|
914
|
+
const read = parse(content, path, definitionsAt(composition, path));
|
|
915
|
+
if (read.ok)
|
|
916
|
+
known.set(path, { type: read.document.type ?? 'any', binary: false });
|
|
917
|
+
await pace(content.length + ROW_WEIGHT);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return known;
|
|
921
|
+
}, composition);
|
|
922
|
+
}
|
|
923
|
+
async function judging(visible, resulting, composition, validate, removed, touched) {
|
|
390
924
|
if (removed.size > 0) {
|
|
391
|
-
const referenced = incoming(
|
|
925
|
+
const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
|
|
392
926
|
if (referenced !== null)
|
|
393
927
|
return referenced;
|
|
394
928
|
}
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
929
|
+
const typedPaths = [];
|
|
930
|
+
const pace = paced();
|
|
931
|
+
for (const path of touched) {
|
|
932
|
+
const before = visible.documents[path];
|
|
933
|
+
const after = resulting.documents[path];
|
|
934
|
+
if (typedText(before) || typedText(after))
|
|
935
|
+
typedPaths.push(path);
|
|
936
|
+
await pace((before?.length ?? 0) + (after?.length ?? 0) + ROW_WEIGHT);
|
|
937
|
+
}
|
|
938
|
+
const withoutModel = typedPaths.find((path) => governing(composition, path).status === 'unavailable');
|
|
404
939
|
if (withoutModel !== undefined)
|
|
405
940
|
return refuse('model-unavailable', withoutModel);
|
|
406
|
-
const
|
|
407
|
-
? resulting
|
|
408
|
-
:
|
|
409
|
-
const verdict = validateCollection(validationText, resulting.binaryTargets, input.composition);
|
|
941
|
+
const verdict = typedPaths.length > 0
|
|
942
|
+
? await validate(resulting, touched)
|
|
943
|
+
: await touchedLibrary(resulting, touched, composition);
|
|
410
944
|
if (verdict.verdict === 'refuse') {
|
|
411
945
|
return {
|
|
412
946
|
kind: 'refused',
|
|
413
947
|
refusal: {
|
|
414
|
-
|
|
948
|
+
code: verdict.rule,
|
|
949
|
+
retryable: false,
|
|
415
950
|
file: verdict.file,
|
|
416
951
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
417
952
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
418
953
|
},
|
|
419
954
|
};
|
|
420
955
|
}
|
|
421
|
-
|
|
956
|
+
return {
|
|
957
|
+
kind: 'judged',
|
|
958
|
+
validated: typedPaths
|
|
959
|
+
.map((path) => governing(composition, path))
|
|
960
|
+
.find((model) => model.status !== 'unavailable'),
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function placed(reference) {
|
|
964
|
+
const key = roomPathOf(reference.handle, reference.path);
|
|
965
|
+
if (reference.kind === 'link')
|
|
966
|
+
return `link ${key}`;
|
|
967
|
+
if (reference.kind === 'item')
|
|
968
|
+
return `section ${reference.section} ${key}`;
|
|
969
|
+
const pointer = reference.pointer
|
|
970
|
+
.split('/')
|
|
971
|
+
.map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
|
|
972
|
+
.join('/');
|
|
973
|
+
return `field ${pointer} ${key}`;
|
|
974
|
+
}
|
|
975
|
+
function roomReferencesIn(path, content, composition) {
|
|
976
|
+
if (content === undefined)
|
|
977
|
+
return [];
|
|
978
|
+
const definitions = definitionsAt(composition, path);
|
|
979
|
+
const parsed = parse(content, path, definitions);
|
|
980
|
+
if (!parsed.ok)
|
|
981
|
+
return [];
|
|
982
|
+
return roomReferences(parsed.document, definitions(parsed.document.type));
|
|
983
|
+
}
|
|
984
|
+
function* admittedRooms(changes, visible, resulting, composition) {
|
|
985
|
+
const from = new Map();
|
|
986
|
+
for (const [index, change] of changes.entries()) {
|
|
987
|
+
if (change.kind === 'move')
|
|
988
|
+
from.set(change.to, departure(changes, index));
|
|
989
|
+
}
|
|
990
|
+
for (const path of [...new Set(changes.flatMap(affected))].sort()) {
|
|
991
|
+
const now = resulting.documents[path];
|
|
992
|
+
if (now === undefined)
|
|
993
|
+
continue;
|
|
994
|
+
const held = new Set(roomReferencesIn(path, visible.documents[from.get(path) ?? path], composition).map(placed));
|
|
995
|
+
for (const reference of roomReferencesIn(path, now, composition)) {
|
|
996
|
+
if (reference.kind === 'link' || reference.crossRoom === true)
|
|
997
|
+
continue;
|
|
998
|
+
if (held.has(placed(reference)))
|
|
999
|
+
continue;
|
|
1000
|
+
return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
|
|
1001
|
+
}
|
|
1002
|
+
yield ROW_WEIGHT + now.length;
|
|
1003
|
+
}
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
function validationOf(validated) {
|
|
1007
|
+
return validated === undefined
|
|
1008
|
+
? {}
|
|
1009
|
+
: {
|
|
1010
|
+
validation: {
|
|
1011
|
+
identity: validated.snapshot.identity,
|
|
1012
|
+
version: validated.snapshot.version,
|
|
1013
|
+
source: validated.status,
|
|
1014
|
+
},
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
async function batching(input) {
|
|
1018
|
+
const changes = relativeChanges(input);
|
|
1019
|
+
const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
|
|
1020
|
+
if (structural !== undefined && input.unmodeled) {
|
|
1021
|
+
return refuse('model-unavailable', structural.path);
|
|
1022
|
+
}
|
|
1023
|
+
if (changes.length === 0)
|
|
1024
|
+
return refuse('no-change');
|
|
1025
|
+
const rewritten = await stepped(rewrites(changes, input.visible));
|
|
1026
|
+
if (rewritten.kind === 'refused')
|
|
1027
|
+
return rewritten;
|
|
1028
|
+
const refusal = guard(changes, input, WRITE);
|
|
1029
|
+
if (refusal !== null)
|
|
1030
|
+
return refusal;
|
|
1031
|
+
const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
|
|
1032
|
+
const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
|
|
1033
|
+
const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }], input.composition));
|
|
1034
|
+
const judged = await judging(input.visible, resulting, input.composition, input.validate, new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path)), [...new Set(predicted.changes.flatMap(affected))]);
|
|
1035
|
+
if (judged.kind === 'refused')
|
|
1036
|
+
return judged;
|
|
1037
|
+
const crossing = await stepped(admittedRooms(changes, input.visible, resulting, input.composition));
|
|
1038
|
+
if (crossing !== null)
|
|
1039
|
+
return crossing;
|
|
1040
|
+
const limited = await stepped(exceeding(input.providerLimits, changes, predicted.changes, resulting));
|
|
422
1041
|
if (limited !== null)
|
|
423
1042
|
return limited;
|
|
424
|
-
const validated = typedPaths
|
|
425
|
-
.map((path) => governing(input.composition, path))
|
|
426
|
-
.find((model) => model.status !== 'unavailable');
|
|
427
1043
|
return {
|
|
428
1044
|
kind: 'batch',
|
|
429
1045
|
batch: immutable({
|
|
@@ -432,15 +1048,125 @@ export function buildBatch(input) {
|
|
|
432
1048
|
changes,
|
|
433
1049
|
preconditions,
|
|
434
1050
|
...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
|
|
435
|
-
...(validated
|
|
436
|
-
? {}
|
|
437
|
-
: {
|
|
438
|
-
validation: {
|
|
439
|
-
identity: validated.snapshot.identity,
|
|
440
|
-
version: validated.snapshot.version,
|
|
441
|
-
source: validated.status,
|
|
442
|
-
},
|
|
443
|
-
}),
|
|
1051
|
+
...validationOf(judged.validated),
|
|
444
1052
|
}),
|
|
445
1053
|
};
|
|
446
1054
|
}
|
|
1055
|
+
export const MOVE_DOCUMENTS = 'move-documents';
|
|
1056
|
+
function heldAt(visible, path) {
|
|
1057
|
+
return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
|
|
1058
|
+
}
|
|
1059
|
+
function arrivingText(path, to, source, moved) {
|
|
1060
|
+
const content = source.visible.documents[path];
|
|
1061
|
+
const document = source.visible.collection.document(path);
|
|
1062
|
+
if (document === undefined)
|
|
1063
|
+
return content;
|
|
1064
|
+
const definition = definitionsAt(source.composition, path)(document.type);
|
|
1065
|
+
const changes = crossed(document, definition, path, (named, href) => {
|
|
1066
|
+
const at = moved.get(named);
|
|
1067
|
+
if (at !== undefined)
|
|
1068
|
+
return target(to, href) === at ? undefined : relative(to, at);
|
|
1069
|
+
return heldAt(source.visible, named) ? roomPathOf(source.handle, named) : undefined;
|
|
1070
|
+
});
|
|
1071
|
+
if (changes.length === 0)
|
|
1072
|
+
return content;
|
|
1073
|
+
const applied = apply(document, changes, definition);
|
|
1074
|
+
return applied.outcome === 'applied' ? serialize(applied.document) : content;
|
|
1075
|
+
}
|
|
1076
|
+
function arrivalOf({ path, to }, source, moved) {
|
|
1077
|
+
const { documents, revisions, fileIds, created, changes, binaries } = source.visible;
|
|
1078
|
+
const kept = {
|
|
1079
|
+
...(fileIds[path] === undefined ? {} : { fileId: fileIds[path] }),
|
|
1080
|
+
...(created[path] === undefined ? {} : { created: created[path] }),
|
|
1081
|
+
...(changes[path] === undefined ? {} : { change: changes[path] }),
|
|
1082
|
+
};
|
|
1083
|
+
const heads = {
|
|
1084
|
+
...(revisions[path] === undefined ? {} : { revision: revisions[path] }),
|
|
1085
|
+
...kept,
|
|
1086
|
+
};
|
|
1087
|
+
const binary = binaries[path];
|
|
1088
|
+
if (binary?.state === 'accepted') {
|
|
1089
|
+
const { byteLength, mediaType, sha256 } = binary;
|
|
1090
|
+
return {
|
|
1091
|
+
kind: 'arrival',
|
|
1092
|
+
path: to,
|
|
1093
|
+
binary: { byteLength, mediaType, sha256 },
|
|
1094
|
+
...heads,
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
const content = arrivingText(path, to, source, moved);
|
|
1098
|
+
return {
|
|
1099
|
+
kind: 'arrival',
|
|
1100
|
+
path: to,
|
|
1101
|
+
content,
|
|
1102
|
+
...(content === documents[path] ? heads : kept),
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
async function moving(input) {
|
|
1106
|
+
const { source, destination, moves } = input;
|
|
1107
|
+
const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
|
|
1108
|
+
const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
|
|
1109
|
+
if (leaves !== null)
|
|
1110
|
+
return leaves;
|
|
1111
|
+
const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
|
|
1112
|
+
if (uploading !== undefined)
|
|
1113
|
+
return refuse('unknown-revision', uploading.path);
|
|
1114
|
+
const moved = new Map(moves.map(({ path, to }) => [path, to]));
|
|
1115
|
+
const arrivals = moves.map((move) => arrivalOf(move, source, moved));
|
|
1116
|
+
const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, WRITE);
|
|
1117
|
+
if (arrives !== null)
|
|
1118
|
+
return arrives;
|
|
1119
|
+
const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
|
|
1120
|
+
if (typeof bound === 'number' && moves.length > bound) {
|
|
1121
|
+
return refuse('provider-limit', undefined, MOVE_DOCUMENTS);
|
|
1122
|
+
}
|
|
1123
|
+
if (source.unmodeled)
|
|
1124
|
+
return refuse('model-unavailable', moves[0].path);
|
|
1125
|
+
const id = changeBatchIdentity();
|
|
1126
|
+
const between = {
|
|
1127
|
+
source: { room: source.room, baseRoomVersion: source.baseRoomVersion },
|
|
1128
|
+
destination: {
|
|
1129
|
+
room: destination.room,
|
|
1130
|
+
baseRoomVersion: destination.baseRoomVersion,
|
|
1131
|
+
handle: destination.handle,
|
|
1132
|
+
},
|
|
1133
|
+
moves,
|
|
1134
|
+
};
|
|
1135
|
+
const written = input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy };
|
|
1136
|
+
const leavingBatch = {
|
|
1137
|
+
id,
|
|
1138
|
+
baseRoomVersion: source.baseRoomVersion,
|
|
1139
|
+
changes: removals,
|
|
1140
|
+
preconditions: moves.map(({ path }) => existence(source.visible, path)),
|
|
1141
|
+
...written,
|
|
1142
|
+
between,
|
|
1143
|
+
};
|
|
1144
|
+
const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
|
|
1145
|
+
const left = await stepped(overlaying(source.visible, [effective], source.composition));
|
|
1146
|
+
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))]);
|
|
1147
|
+
if (sourceJudged.kind === 'refused')
|
|
1148
|
+
return sourceJudged;
|
|
1149
|
+
const arrivingBatch = {
|
|
1150
|
+
id,
|
|
1151
|
+
baseRoomVersion: destination.baseRoomVersion,
|
|
1152
|
+
changes: arrivals,
|
|
1153
|
+
preconditions: moves.map(({ to }) => ({ kind: 'absent', path: to })),
|
|
1154
|
+
...written,
|
|
1155
|
+
between,
|
|
1156
|
+
};
|
|
1157
|
+
const arrived = await stepped(overlaying(destination.visible, [arrivingBatch], destination.composition));
|
|
1158
|
+
const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
|
|
1159
|
+
if (destinationJudged.kind === 'refused')
|
|
1160
|
+
return destinationJudged;
|
|
1161
|
+
const limited = (await stepped(exceeding(input.providerLimits, removals, effective.changes, left))) ??
|
|
1162
|
+
(await stepped(exceeding(input.providerLimits, arrivals, arrivals, arrived)));
|
|
1163
|
+
if (limited !== null)
|
|
1164
|
+
return limited;
|
|
1165
|
+
return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
|
|
1166
|
+
}
|
|
1167
|
+
export function buildMove(input) {
|
|
1168
|
+
return moving(input);
|
|
1169
|
+
}
|
|
1170
|
+
export function buildBatch(input) {
|
|
1171
|
+
return batching(input);
|
|
1172
|
+
}
|