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