@agentshouse/mdruntime 0.1.0-cd3486bd400ed → 0.1.0-cd35ccee27a19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -10
- package/dist/acceptance.d.ts +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 +195 -91
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +7 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +40 -41
- package/dist/attempt.d.ts.map +1 -1
- package/dist/attempt.js +14 -1
- package/dist/binary.d.ts +18 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +31 -11
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +311 -92
- package/dist/cell.d.ts +6 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +9 -0
- package/dist/chain.d.ts +12 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +13 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +17 -62
- package/dist/change.d.ts +42 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +36 -0
- package/dist/chat-types.d.ts +5 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +13 -0
- package/dist/client.d.ts +49 -21
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +131 -17
- package/dist/collection.d.ts +31 -9
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +49 -80
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +711 -0
- package/dist/connection.d.ts +8 -111
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +107 -2218
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +7 -0
- package/dist/conversation-live.d.ts +136 -0
- package/dist/conversation-live.d.ts.map +1 -0
- package/dist/conversation-live.js +249 -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 +71 -42
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1068 -586
- 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 +4 -2
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +33 -11
- package/dist/failure.d.ts +1 -0
- package/dist/failure.d.ts.map +1 -1
- package/dist/failure.js +3 -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 +163 -19
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +3 -0
- package/dist/index.d.ts +37 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/install.d.ts +54 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +538 -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 +24 -8
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +289 -96
- package/dist/messages.d.ts +49 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +150 -0
- package/dist/model.d.ts +11 -12
- 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/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 +56 -5
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +49 -0
- package/dist/overlay.d.ts +17 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +161 -187
- package/dist/page-metadata.d.ts +8 -0
- package/dist/page-metadata.d.ts.map +1 -0
- package/dist/page-metadata.js +19 -0
- package/dist/parts.d.ts +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 -183
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -464
- package/dist/{chat/questions.d.ts → questions.d.ts} +5 -2
- package/dist/questions.d.ts.map +1 -0
- package/dist/{chat/questions.js → questions.js} +13 -2
- package/dist/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 +313 -72
- package/dist/react.d.ts +20 -6
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +17 -18
- package/dist/{chat/read.d.ts → read.d.ts} +2 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/{chat/read.js → read.js} +4 -0
- package/dist/readiness.d.ts +49 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +27 -0
- package/dist/reads.d.ts +37 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +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 +17 -14
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +145 -58
- 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 +348 -166
- package/dist/room-draft.d.ts +55 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +262 -73
- package/dist/room-drafts.d.ts +24 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +217 -0
- package/dist/rows.d.ts +30 -35
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +60 -126
- 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 +44 -22
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +950 -169
- 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 -55
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -121
- package/dist/seed.d.ts +25 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +145 -0
- package/dist/shared.d.ts +10 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +339 -0
- package/dist/shown.d.ts +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 +5 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +34 -0
- package/dist/store/readiness.d.ts +10 -0
- package/dist/store/readiness.d.ts.map +1 -0
- package/dist/store/readiness.js +52 -0
- package/dist/store/records.d.ts +212 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +430 -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 -117
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +155 -494
- 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 +220 -34
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +73 -31
- package/package.json +27 -15
- package/AGENTS.md +0 -290
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
- package/dist/chat/acts.d.ts +0 -34
- package/dist/chat/acts.d.ts.map +0 -1
- package/dist/chat/acts.js +0 -36
- package/dist/chat/messages.d.ts +0 -21
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -151
- package/dist/chat/overlay.d.ts +0 -31
- package/dist/chat/overlay.d.ts.map +0 -1
- package/dist/chat/overlay.js +0 -97
- package/dist/chat/parts.d.ts +0 -57
- package/dist/chat/parts.d.ts.map +0 -1
- package/dist/chat/parts.js +0 -119
- package/dist/chat/process.d.ts +0 -20
- package/dist/chat/process.d.ts.map +0 -1
- package/dist/chat/process.js +0 -32
- package/dist/chat/provider.d.ts +0 -57
- package/dist/chat/provider.d.ts.map +0 -1
- package/dist/chat/provider.js +0 -637
- package/dist/chat/questions.d.ts.map +0 -1
- package/dist/chat/read.d.ts.map +0 -1
- package/dist/chat.d.ts +0 -7
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -2
- package/dist/read-side.d.ts +0 -18
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -433
package/dist/save.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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';
|
|
8
11
|
function refuse(code, file, location) {
|
|
9
12
|
return {
|
|
10
13
|
kind: 'refused',
|
|
@@ -17,10 +20,7 @@ function refuse(code, file, location) {
|
|
|
17
20
|
};
|
|
18
21
|
}
|
|
19
22
|
function repointed(document, definition, from, to, moves, settled) {
|
|
20
|
-
const
|
|
21
|
-
if (!parsed.ok)
|
|
22
|
-
return [];
|
|
23
|
-
let next = parsed.document;
|
|
23
|
+
const changes = [];
|
|
24
24
|
const names = new Set();
|
|
25
25
|
for (const { pointer, value } of definition === undefined
|
|
26
26
|
? []
|
|
@@ -32,38 +32,39 @@ function repointed(document, definition, from, to, moves, settled) {
|
|
|
32
32
|
const destination = moves.get(named) ?? named;
|
|
33
33
|
if (target(to, value) === destination)
|
|
34
34
|
continue;
|
|
35
|
-
|
|
35
|
+
changes.push({
|
|
36
36
|
kind: 'reference',
|
|
37
37
|
pointer,
|
|
38
38
|
value: retarget(value, relative(to, destination)),
|
|
39
39
|
prior: value,
|
|
40
40
|
});
|
|
41
|
-
if (applied.outcome !== 'applied')
|
|
42
|
-
continue;
|
|
43
|
-
next = applied.document;
|
|
44
41
|
names.add(name);
|
|
45
42
|
}
|
|
43
|
+
if (changes.length === 0)
|
|
44
|
+
return [];
|
|
45
|
+
const applied = apply(document, changes, definition);
|
|
46
|
+
if (applied.outcome !== 'applied')
|
|
47
|
+
return [];
|
|
48
|
+
const next = applied.document;
|
|
46
49
|
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
47
50
|
}
|
|
48
|
-
function edited(document,
|
|
49
|
-
|
|
51
|
+
function edited(document, changes, definition) {
|
|
52
|
+
const parts = new Map();
|
|
50
53
|
for (const change of changes) {
|
|
51
|
-
if (change.
|
|
52
|
-
|
|
53
|
-
if (change.kind
|
|
54
|
-
|
|
55
|
-
const applied = apply(next, change.kind === 'preamble'
|
|
56
|
-
? { kind: 'preamble', value: change.value, prior: next.preamble }
|
|
57
|
-
: {
|
|
54
|
+
if (change.kind === 'preamble')
|
|
55
|
+
parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
56
|
+
if (change.kind === 'section')
|
|
57
|
+
parts.set(`section ${change.name}`, {
|
|
58
58
|
kind: 'section',
|
|
59
59
|
name: change.name,
|
|
60
60
|
value: change.value,
|
|
61
|
-
prior:
|
|
61
|
+
prior: document.sections[change.name] ?? '',
|
|
62
62
|
});
|
|
63
|
-
if (applied.outcome === 'applied')
|
|
64
|
-
next = applied.document;
|
|
65
63
|
}
|
|
66
|
-
|
|
64
|
+
if (parts.size === 0)
|
|
65
|
+
return document;
|
|
66
|
+
const applied = apply(document, [...parts.values()], definition);
|
|
67
|
+
return applied.outcome === 'applied' ? applied.document : document;
|
|
67
68
|
}
|
|
68
69
|
function bodies(path, before, after) {
|
|
69
70
|
const changes = [];
|
|
@@ -77,8 +78,8 @@ function bodies(path, before, after) {
|
|
|
77
78
|
}
|
|
78
79
|
return changes;
|
|
79
80
|
}
|
|
80
|
-
function relinks(path, moved, before, moves) {
|
|
81
|
-
|
|
81
|
+
function relinks(path, moved, before, moves, definition) {
|
|
82
|
+
const changes = [];
|
|
82
83
|
for (const link of links(before, path)) {
|
|
83
84
|
const destination = moves.get(link.target) ?? link.target;
|
|
84
85
|
if (destination === link.target && moved === path)
|
|
@@ -86,20 +87,23 @@ function relinks(path, moved, before, moves) {
|
|
|
86
87
|
const to = relative(moved, destination);
|
|
87
88
|
if (to === relative(path, link.target))
|
|
88
89
|
continue;
|
|
89
|
-
|
|
90
|
-
if (applied.outcome === 'refuse') {
|
|
91
|
-
return {
|
|
92
|
-
kind: 'refused',
|
|
93
|
-
refusal: {
|
|
94
|
-
code: applied.refusal.rule,
|
|
95
|
-
retryable: false,
|
|
96
|
-
file: path,
|
|
97
|
-
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
after = applied.document;
|
|
90
|
+
changes.push({ kind: 'link', href: link.href, to });
|
|
102
91
|
}
|
|
92
|
+
if (changes.length === 0)
|
|
93
|
+
return { kind: 'changes', changes: [] };
|
|
94
|
+
const applied = apply(before, changes, 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
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const after = applied.document;
|
|
103
107
|
if (after === before)
|
|
104
108
|
return { kind: 'changes', changes: [] };
|
|
105
109
|
if (before.type === undefined) {
|
|
@@ -107,74 +111,173 @@ function relinks(path, moved, before, moves) {
|
|
|
107
111
|
}
|
|
108
112
|
return { kind: 'changes', changes: bodies(path, before, after) };
|
|
109
113
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
const item = merged[index];
|
|
113
|
-
if (item.path !== path)
|
|
114
|
-
continue;
|
|
115
|
-
if (item.kind === 'section' || item.kind === 'preamble')
|
|
116
|
-
merged.splice(index, 1);
|
|
117
|
-
}
|
|
114
|
+
function partKey(change) {
|
|
115
|
+
return change.kind === 'section' ? `section ${change.name}` : change.kind;
|
|
118
116
|
}
|
|
119
|
-
|
|
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];
|
|
122
|
+
}
|
|
123
|
+
function cannotLink(text, names) {
|
|
124
|
+
return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
|
|
125
|
+
}
|
|
126
|
+
function* rewrites(changes, visible) {
|
|
120
127
|
const moves = new Map();
|
|
121
128
|
const settled = new Set();
|
|
122
|
-
|
|
129
|
+
const parts = new Map();
|
|
130
|
+
for (const [index, change] of changes.entries()) {
|
|
123
131
|
if (change.kind === 'move')
|
|
124
132
|
moves.set(change.path, change.to);
|
|
125
|
-
if (change.kind === 'remove' ||
|
|
133
|
+
if (change.kind === 'remove' ||
|
|
134
|
+
change.kind === 'replace' ||
|
|
135
|
+
change.kind === 'create' ||
|
|
136
|
+
change.kind === 'binary') {
|
|
126
137
|
settled.add(change.path);
|
|
127
138
|
}
|
|
128
139
|
if (change.kind === 'field')
|
|
129
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
|
+
}
|
|
130
148
|
}
|
|
131
149
|
if (moves.size === 0)
|
|
132
150
|
return { kind: 'changes', changes };
|
|
151
|
+
const names = movedNames(moves);
|
|
133
152
|
const merged = [...changes];
|
|
134
153
|
const added = [];
|
|
135
|
-
for (const [path, document] of documentsOf(visible.collection)) {
|
|
154
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
136
155
|
if (settled.has(path))
|
|
137
156
|
continue;
|
|
138
157
|
const moved = moves.get(path) ?? path;
|
|
139
|
-
const definition =
|
|
140
|
-
|
|
141
|
-
|
|
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);
|
|
142
165
|
if (relinked.kind === 'refused')
|
|
143
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
|
+
}
|
|
144
173
|
for (const change of relinked.changes) {
|
|
145
174
|
if (change.kind === 'replace') {
|
|
146
|
-
|
|
175
|
+
for (const index of own) {
|
|
176
|
+
const kind = merged[index]?.kind;
|
|
177
|
+
if (kind === 'section' || kind === 'preamble')
|
|
178
|
+
merged[index] = null;
|
|
179
|
+
}
|
|
147
180
|
added.push(change);
|
|
148
181
|
continue;
|
|
149
182
|
}
|
|
150
|
-
const index =
|
|
151
|
-
|
|
152
|
-
(change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
|
|
153
|
-
if (index === -1)
|
|
183
|
+
const index = keyed.get(partKey(change));
|
|
184
|
+
if (index === undefined)
|
|
154
185
|
added.push(change);
|
|
155
186
|
else
|
|
156
187
|
merged[index] = change;
|
|
157
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);
|
|
158
192
|
}
|
|
159
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
kind: 'changes',
|
|
195
|
+
changes: [...added, ...merged.filter((change) => change !== null)],
|
|
196
|
+
};
|
|
160
197
|
}
|
|
161
|
-
function anticipated(changes, preconditions, rewritten, visible) {
|
|
162
|
-
const
|
|
198
|
+
function* anticipated(changes, preconditions, rewritten, visible) {
|
|
199
|
+
const given = new Set(changes);
|
|
200
|
+
const derived = rewritten.filter((change) => !given.has(change));
|
|
163
201
|
return {
|
|
164
202
|
changes: rewritten,
|
|
165
|
-
preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
|
|
203
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
|
|
166
204
|
};
|
|
167
205
|
}
|
|
168
|
-
export function providerRewritten(batch, visible) {
|
|
169
|
-
const rewritten = rewrites(batch.changes, visible);
|
|
206
|
+
export function* providerRewritten(batch, visible) {
|
|
207
|
+
const rewritten = yield* rewrites(batch.changes, visible);
|
|
170
208
|
if (rewritten.kind === 'refused')
|
|
171
209
|
return batch;
|
|
172
210
|
return {
|
|
173
211
|
...batch,
|
|
174
|
-
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
212
|
+
...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
|
|
175
213
|
};
|
|
176
214
|
}
|
|
177
|
-
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) {
|
|
178
281
|
const found = new Map();
|
|
179
282
|
const note = (target, file) => {
|
|
180
283
|
if (!removed.has(target))
|
|
@@ -183,8 +286,21 @@ function incoming(collection, removed) {
|
|
|
183
286
|
referrers.add(file);
|
|
184
287
|
found.set(target, referrers);
|
|
185
288
|
};
|
|
186
|
-
|
|
187
|
-
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);
|
|
188
304
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
189
305
|
if (to.includes(ATTACHMENT))
|
|
190
306
|
continue;
|
|
@@ -192,8 +308,9 @@ function incoming(collection, removed) {
|
|
|
192
308
|
if (named !== null)
|
|
193
309
|
note(named, path);
|
|
194
310
|
}
|
|
311
|
+
yield ROW_WEIGHT + collection.text[path].length;
|
|
195
312
|
}
|
|
196
|
-
const [first] =
|
|
313
|
+
const [first] = yield* sorting(found.keys());
|
|
197
314
|
if (first === undefined)
|
|
198
315
|
return null;
|
|
199
316
|
const every = new Set();
|
|
@@ -206,42 +323,61 @@ function incoming(collection, removed) {
|
|
|
206
323
|
code: 'referenced-missing-file',
|
|
207
324
|
retryable: false,
|
|
208
325
|
file: first,
|
|
209
|
-
referrers:
|
|
326
|
+
referrers: yield* sorting(every),
|
|
210
327
|
},
|
|
211
328
|
};
|
|
212
329
|
}
|
|
213
330
|
function affected(change) {
|
|
214
331
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
215
332
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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()) {
|
|
222
345
|
for (const path of affected(change)) {
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
return refuse(
|
|
226
|
-
if (covered(input.providerOwnedPaths, normalized)) {
|
|
227
|
-
return refuse('provider-owned-path', normalized);
|
|
228
|
-
}
|
|
229
|
-
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
230
|
-
if (!input.local && !covered(input.operations[operation], normalized))
|
|
231
|
-
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);
|
|
232
349
|
}
|
|
233
|
-
if (change.kind === 'create') {
|
|
234
|
-
|
|
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))) {
|
|
235
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
|
+
}
|
|
236
371
|
existing.add(change.path);
|
|
237
372
|
continue;
|
|
238
373
|
}
|
|
239
374
|
if (change.kind === 'move') {
|
|
240
375
|
if (!existing.has(change.path))
|
|
241
376
|
return refuse('path-absent', change.path);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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)) {
|
|
245
381
|
return refuse('unknown-revision', change.path);
|
|
246
382
|
}
|
|
247
383
|
existing.delete(change.path);
|
|
@@ -251,14 +387,14 @@ function guard(changes, input) {
|
|
|
251
387
|
if (!existing.has(change.path))
|
|
252
388
|
return refuse('path-absent', change.path);
|
|
253
389
|
if (change.kind === 'remove') {
|
|
254
|
-
if (
|
|
390
|
+
if (unrevised(input, change.path)) {
|
|
255
391
|
return refuse('unknown-revision', change.path);
|
|
256
392
|
}
|
|
257
393
|
existing.delete(change.path);
|
|
258
394
|
continue;
|
|
259
395
|
}
|
|
260
396
|
if (change.kind === 'replace') {
|
|
261
|
-
if (
|
|
397
|
+
if (unrevised(input, change.path)) {
|
|
262
398
|
return refuse('unknown-revision', change.path);
|
|
263
399
|
}
|
|
264
400
|
continue;
|
|
@@ -269,8 +405,28 @@ function guard(changes, input) {
|
|
|
269
405
|
}
|
|
270
406
|
return null;
|
|
271
407
|
}
|
|
272
|
-
function
|
|
273
|
-
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}`;
|
|
274
430
|
}
|
|
275
431
|
function existence(visible, path) {
|
|
276
432
|
const revision = visible.revisions[path];
|
|
@@ -285,18 +441,18 @@ function sameTarget(precondition, change) {
|
|
|
285
441
|
return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
|
|
286
442
|
((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
|
|
287
443
|
}
|
|
288
|
-
function preconditionsOf(changes, visible, bases) {
|
|
444
|
+
function* preconditionsOf(changes, visible, bases) {
|
|
289
445
|
const preconditions = [];
|
|
290
446
|
const seen = new Set();
|
|
291
447
|
const add = (precondition) => {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
if (seen.has(identity))
|
|
448
|
+
const held = identity(precondition);
|
|
449
|
+
if (seen.has(held))
|
|
295
450
|
return;
|
|
296
|
-
seen.add(
|
|
451
|
+
seen.add(held);
|
|
297
452
|
preconditions.push(precondition);
|
|
298
453
|
};
|
|
299
454
|
for (const change of changes) {
|
|
455
|
+
yield ROW_WEIGHT;
|
|
300
456
|
if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
|
|
301
457
|
const based = bases.find((base) => sameTarget(base, change));
|
|
302
458
|
if (based !== undefined) {
|
|
@@ -328,6 +484,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
328
484
|
add({ kind: 'absent', path: change.path });
|
|
329
485
|
continue;
|
|
330
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
|
+
}
|
|
331
494
|
add(existence(visible, change.path));
|
|
332
495
|
if (change.kind === 'move')
|
|
333
496
|
add({ kind: 'absent', path: change.to });
|
|
@@ -337,10 +500,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
337
500
|
function depth(path) {
|
|
338
501
|
return path.split('/').length;
|
|
339
502
|
}
|
|
340
|
-
function
|
|
341
|
-
const written =
|
|
503
|
+
function* exceeding(limits, changes, effective, resulting) {
|
|
504
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
342
505
|
const created = changes
|
|
343
|
-
.filter((change) => change.kind === 'create' ||
|
|
506
|
+
.filter((change) => change.kind === 'create' ||
|
|
507
|
+
change.kind === 'move' ||
|
|
508
|
+
change.kind === 'binary' ||
|
|
509
|
+
change.kind === 'arrival')
|
|
344
510
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
345
511
|
for (const limit of limits) {
|
|
346
512
|
const value = limit.value;
|
|
@@ -354,9 +520,13 @@ function exceeded(limits, changes, resulting) {
|
|
|
354
520
|
return refuse('provider-limit', undefined, limit.name);
|
|
355
521
|
}
|
|
356
522
|
if (limit.name === 'document-size') {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
+
}
|
|
360
530
|
}
|
|
361
531
|
if (limit.name === 'path-length') {
|
|
362
532
|
const long = created.find((path) => path.length > value);
|
|
@@ -374,19 +544,16 @@ function exceeded(limits, changes, resulting) {
|
|
|
374
544
|
function shellRelative(text, path, handle, definition) {
|
|
375
545
|
if (!text.includes(`/rooms/${handle}/`))
|
|
376
546
|
return null;
|
|
377
|
-
const parsed = parse(text, path);
|
|
547
|
+
const parsed = parse(text, path, definition);
|
|
378
548
|
if (!parsed.ok)
|
|
379
549
|
return null;
|
|
380
550
|
const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
|
|
381
551
|
return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
|
|
382
552
|
}
|
|
383
553
|
function relativeField(path, name, value, handle, definition) {
|
|
384
|
-
|
|
385
|
-
if (definition === undefined ||
|
|
386
|
-
!opened.ok ||
|
|
387
|
-
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
554
|
+
if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
388
555
|
return value;
|
|
389
|
-
const held = apply(
|
|
556
|
+
const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
|
|
390
557
|
if (held.outcome !== 'applied')
|
|
391
558
|
return value;
|
|
392
559
|
const result = relativize(held.document, path, handle, definition);
|
|
@@ -394,7 +561,7 @@ function relativeField(path, name, value, handle, definition) {
|
|
|
394
561
|
}
|
|
395
562
|
function relativeChanges(input) {
|
|
396
563
|
const { handle, composition, visible } = input;
|
|
397
|
-
const typed = (path) => (
|
|
564
|
+
const typed = (path) => definitionsAt(composition, path);
|
|
398
565
|
const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
|
|
399
566
|
return input.changes.map((change) => {
|
|
400
567
|
const definition = typed(change.path);
|
|
@@ -422,54 +589,314 @@ function relativeChanges(input) {
|
|
|
422
589
|
return { ...change, fields, sections };
|
|
423
590
|
});
|
|
424
591
|
}
|
|
425
|
-
function
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
592
|
+
function unqueueable(file) {
|
|
593
|
+
return 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 };
|
|
431
635
|
}
|
|
432
|
-
|
|
636
|
+
deferred.clear();
|
|
637
|
+
return null;
|
|
433
638
|
}
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
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]);
|
|
439
772
|
if (changes.length === 0)
|
|
440
|
-
return
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
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) {
|
|
453
880
|
if (removed.size > 0) {
|
|
454
|
-
const referenced = incoming(collectionOf(resulting.documents,
|
|
881
|
+
const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
|
|
455
882
|
if (referenced !== null)
|
|
456
883
|
return referenced;
|
|
457
884
|
}
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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');
|
|
467
895
|
if (withoutModel !== undefined)
|
|
468
896
|
return refuse('model-unavailable', withoutModel);
|
|
469
|
-
const
|
|
470
|
-
? resulting
|
|
471
|
-
:
|
|
472
|
-
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);
|
|
473
900
|
if (verdict.verdict === 'refuse') {
|
|
474
901
|
return {
|
|
475
902
|
kind: 'refused',
|
|
@@ -482,12 +909,93 @@ export function buildBatch(input) {
|
|
|
482
909
|
},
|
|
483
910
|
};
|
|
484
911
|
}
|
|
485
|
-
|
|
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));
|
|
486
997
|
if (limited !== null)
|
|
487
998
|
return limited;
|
|
488
|
-
const validated = typedPaths
|
|
489
|
-
.map((path) => governing(input.composition, path))
|
|
490
|
-
.find((model) => model.status !== 'unavailable');
|
|
491
999
|
return {
|
|
492
1000
|
kind: 'batch',
|
|
493
1001
|
batch: immutable({
|
|
@@ -496,15 +1004,288 @@ export function buildBatch(input) {
|
|
|
496
1004
|
changes,
|
|
497
1005
|
preconditions,
|
|
498
1006
|
...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
|
|
499
|
-
...(validated
|
|
500
|
-
? {}
|
|
501
|
-
: {
|
|
502
|
-
validation: {
|
|
503
|
-
identity: validated.snapshot.identity,
|
|
504
|
-
version: validated.snapshot.version,
|
|
505
|
-
source: validated.status,
|
|
506
|
-
},
|
|
507
|
-
}),
|
|
1007
|
+
...validationOf(judged.validated),
|
|
508
1008
|
}),
|
|
509
1009
|
};
|
|
510
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
|
+
}
|