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