@agentshouse/mdruntime 0.1.0-cd50a4607cb7d → 0.1.0-cd574a610e9c8
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 +226 -117
- 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/authored.d.ts +88 -0
- package/dist/authored.d.ts.map +1 -0
- package/dist/authored.js +345 -0
- 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 +309 -109
- 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 -88
- 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 +45 -25
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +132 -27
- package/dist/collection.d.ts +32 -21
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +53 -288
- package/dist/column.d.ts +5 -0
- package/dist/column.d.ts.map +1 -0
- package/dist/column.js +9 -0
- 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 -118
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +111 -2975
- 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 +85 -48
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1095 -688
- 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 +5 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +33 -28
- 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 +27 -4
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +177 -27
- 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 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -3
- 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 +282 -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 +25 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +320 -110
- package/dist/messages.d.ts +58 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +160 -0
- package/dist/model.d.ts +11 -13
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +7 -24
- 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 +40 -10
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +233 -39
- 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 +62 -9
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +72 -0
- package/dist/overlay.d.ts +9 -6
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +129 -166
- 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 +27 -186
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -464
- package/dist/{chat/questions.d.ts → questions.d.ts} +6 -7
- package/dist/questions.d.ts.map +1 -0
- package/dist/{chat/questions.js → questions.js} +12 -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 +20 -7
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +360 -98
- package/dist/react.d.ts +22 -5
- 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 +55 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +25 -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 +5 -2
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +65 -8
- package/dist/refresh.d.ts +17 -14
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +145 -54
- 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 +340 -191
- 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 +66 -12
- 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/room-list.d.ts +10 -0
- package/dist/room-list.d.ts.map +1 -0
- package/dist/room-list.js +45 -0
- package/dist/rows.d.ts +32 -29
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +60 -118
- 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 -48
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +751 -303
- package/dist/saves.d.ts +34 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +437 -0
- package/dist/search.d.ts +15 -77
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -186
- 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 +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 +223 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +465 -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 -141
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +163 -687
- package/dist/stored.d.ts +167 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +700 -0
- package/dist/surface.d.ts +90 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +225 -0
- package/dist/sync.d.ts +19 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +536 -0
- package/dist/tables.d.ts +376 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +499 -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 +5 -3
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +51 -21
- 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 +11 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +16 -0
- package/dist/wire.d.ts +230 -36
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +77 -33
- package/package.json +19 -19
- package/AGENTS.md +0 -296
- 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/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 -620
- 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/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 -25
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -639
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, 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) {
|
|
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,53 +36,24 @@ 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
|
|
49
|
-
let next = document;
|
|
50
|
-
for (const change of changes) {
|
|
51
|
-
if (change.path !== path)
|
|
52
|
-
continue;
|
|
53
|
-
if (change.kind !== 'section' && change.kind !== 'preamble')
|
|
54
|
-
continue;
|
|
55
|
-
const applied = apply(next, change.kind === 'preamble'
|
|
56
|
-
? { kind: 'preamble', value: change.value, prior: next.preamble }
|
|
57
|
-
: {
|
|
58
|
-
kind: 'section',
|
|
59
|
-
name: change.name,
|
|
60
|
-
value: change.value,
|
|
61
|
-
prior: next.sections[change.name],
|
|
62
|
-
});
|
|
63
|
-
if (applied.outcome === 'applied')
|
|
64
|
-
next = applied.document;
|
|
65
|
-
}
|
|
66
|
-
return next;
|
|
67
|
-
}
|
|
68
|
-
function bodies(path, before, after) {
|
|
55
|
+
function relinks(path, moved, before, moves, definition) {
|
|
69
56
|
const changes = [];
|
|
70
|
-
if (after.preamble !== before.preamble) {
|
|
71
|
-
changes.push({ kind: 'preamble', path, value: after.preamble });
|
|
72
|
-
}
|
|
73
|
-
for (const [name, value] of Object.entries(after.sections)) {
|
|
74
|
-
if (before.sections[name] === value)
|
|
75
|
-
continue;
|
|
76
|
-
changes.push({ kind: 'section', path, name, value });
|
|
77
|
-
}
|
|
78
|
-
return changes;
|
|
79
|
-
}
|
|
80
|
-
function relinks(path, moved, before, moves) {
|
|
81
|
-
let after = before;
|
|
82
57
|
for (const link of links(before, path)) {
|
|
83
58
|
const destination = moves.get(link.target) ?? link.target;
|
|
84
59
|
if (destination === link.target && moved === path)
|
|
@@ -86,40 +61,40 @@ function relinks(path, moved, before, moves) {
|
|
|
86
61
|
const to = relative(moved, destination);
|
|
87
62
|
if (to === relative(path, link.target))
|
|
88
63
|
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;
|
|
64
|
+
changes.push({ kind: 'link', href: link.href, to });
|
|
102
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;
|
|
103
72
|
if (after === before)
|
|
104
73
|
return { kind: 'changes', changes: [] };
|
|
105
74
|
if (before.type === undefined) {
|
|
106
75
|
return { kind: 'changes', changes: [{ kind: 'replace', path, content: serialize(after) }] };
|
|
107
76
|
}
|
|
108
|
-
return { kind: 'changes', changes:
|
|
77
|
+
return { kind: 'changes', changes: partsBetween(path, before, after) };
|
|
109
78
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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));
|
|
118
92
|
}
|
|
119
|
-
function rewrites(changes, visible) {
|
|
93
|
+
function* rewrites(changes, visible) {
|
|
120
94
|
const moves = new Map();
|
|
121
95
|
const settled = new Set();
|
|
122
|
-
|
|
96
|
+
const parts = new Map();
|
|
97
|
+
for (const [index, change] of changes.entries()) {
|
|
123
98
|
if (change.kind === 'move')
|
|
124
99
|
moves.set(change.path, change.to);
|
|
125
100
|
if (change.kind === 'remove' ||
|
|
@@ -130,54 +105,143 @@ function rewrites(changes, visible) {
|
|
|
130
105
|
}
|
|
131
106
|
if (change.kind === 'field')
|
|
132
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
|
+
}
|
|
133
115
|
}
|
|
134
116
|
if (moves.size === 0)
|
|
135
117
|
return { kind: 'changes', changes };
|
|
118
|
+
const names = movedNames(moves);
|
|
136
119
|
const merged = [...changes];
|
|
137
120
|
const added = [];
|
|
138
|
-
for (const [path, document] of documentsOf(visible.collection)) {
|
|
121
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
139
122
|
if (settled.has(path))
|
|
140
123
|
continue;
|
|
141
124
|
const moved = moves.get(path) ?? path;
|
|
142
|
-
const definition =
|
|
143
|
-
|
|
144
|
-
|
|
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);
|
|
145
133
|
if (relinked.kind === 'refused')
|
|
146
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
|
+
}
|
|
147
141
|
for (const change of relinked.changes) {
|
|
148
142
|
if (change.kind === 'replace') {
|
|
149
|
-
|
|
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
|
+
}
|
|
150
148
|
added.push(change);
|
|
151
149
|
continue;
|
|
152
150
|
}
|
|
153
|
-
const index =
|
|
154
|
-
|
|
155
|
-
(change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
|
|
156
|
-
if (index === -1)
|
|
151
|
+
const index = keyed.get(partKey(change));
|
|
152
|
+
if (index === undefined)
|
|
157
153
|
added.push(change);
|
|
158
154
|
else
|
|
159
155
|
merged[index] = change;
|
|
160
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);
|
|
161
160
|
}
|
|
162
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
kind: 'changes',
|
|
163
|
+
changes: [...added, ...merged.filter((change) => change !== null)],
|
|
164
|
+
};
|
|
163
165
|
}
|
|
164
|
-
function anticipated(changes, preconditions, rewritten, visible) {
|
|
165
|
-
const
|
|
166
|
+
function* anticipated(changes, preconditions, rewritten, visible) {
|
|
167
|
+
const given = new Set(changes);
|
|
168
|
+
const derived = rewritten.filter((change) => !given.has(change));
|
|
166
169
|
return {
|
|
167
170
|
changes: rewritten,
|
|
168
|
-
preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
|
|
171
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
|
|
169
172
|
};
|
|
170
173
|
}
|
|
171
|
-
export function providerRewritten(batch, visible) {
|
|
172
|
-
const rewritten = rewrites(batch.changes, visible);
|
|
174
|
+
export function* providerRewritten(batch, visible) {
|
|
175
|
+
const rewritten = yield* rewrites(batch.changes, visible);
|
|
173
176
|
if (rewritten.kind === 'refused')
|
|
174
177
|
return batch;
|
|
175
178
|
return {
|
|
176
179
|
...batch,
|
|
177
|
-
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
180
|
+
...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
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],
|
|
178
234
|
};
|
|
179
235
|
}
|
|
180
|
-
function
|
|
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) {
|
|
181
245
|
const found = new Map();
|
|
182
246
|
const note = (target, file) => {
|
|
183
247
|
if (!removed.has(target))
|
|
@@ -186,14 +250,21 @@ function incoming(collection, removed, candidates) {
|
|
|
186
250
|
referrers.add(file);
|
|
187
251
|
found.set(target, referrers);
|
|
188
252
|
};
|
|
189
|
-
const read =
|
|
190
|
-
|
|
191
|
-
|
|
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);
|
|
192
259
|
const document = collection.document(path);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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);
|
|
197
268
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
198
269
|
if (to.includes(ATTACHMENT))
|
|
199
270
|
continue;
|
|
@@ -201,8 +272,9 @@ function incoming(collection, removed, candidates) {
|
|
|
201
272
|
if (named !== null)
|
|
202
273
|
note(named, path);
|
|
203
274
|
}
|
|
275
|
+
yield ROW_WEIGHT + collection.text[path].length;
|
|
204
276
|
}
|
|
205
|
-
const [first] =
|
|
277
|
+
const [first] = yield* sorting(found.keys());
|
|
206
278
|
if (first === undefined)
|
|
207
279
|
return null;
|
|
208
280
|
const every = new Set();
|
|
@@ -215,14 +287,16 @@ function incoming(collection, removed, candidates) {
|
|
|
215
287
|
code: 'referenced-missing-file',
|
|
216
288
|
retryable: false,
|
|
217
289
|
file: first,
|
|
218
|
-
referrers:
|
|
290
|
+
referrers: yield* sorting(every),
|
|
219
291
|
},
|
|
220
292
|
};
|
|
221
293
|
}
|
|
222
294
|
function affected(change) {
|
|
223
295
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
224
296
|
}
|
|
225
|
-
|
|
297
|
+
const WRITE = ['write'];
|
|
298
|
+
const MOVE_OUT = ['write', 'erase'];
|
|
299
|
+
function guard(changes, input, removing) {
|
|
226
300
|
const overridden = new Map();
|
|
227
301
|
const existing = {
|
|
228
302
|
has: (path) => overridden.get(path) ??
|
|
@@ -230,21 +304,17 @@ function guard(changes, input) {
|
|
|
230
304
|
add: (path) => void overridden.set(path, true),
|
|
231
305
|
delete: (path) => void overridden.set(path, false),
|
|
232
306
|
};
|
|
307
|
+
const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
|
|
233
308
|
for (const [index, change] of changes.entries()) {
|
|
234
309
|
for (const path of affected(change)) {
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
237
|
-
return refuse(
|
|
238
|
-
if (covered(input.providerOwnedPaths, normalized)) {
|
|
239
|
-
return refuse('provider-owned-path', normalized);
|
|
240
|
-
}
|
|
241
|
-
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
242
|
-
if (!input.local && !covered(input.operations[operation], normalized))
|
|
243
|
-
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);
|
|
244
313
|
}
|
|
245
|
-
if (change.kind === 'create') {
|
|
246
|
-
|
|
247
|
-
|
|
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);
|
|
248
318
|
existing.add(change.path);
|
|
249
319
|
continue;
|
|
250
320
|
}
|
|
@@ -254,6 +324,9 @@ function guard(changes, input) {
|
|
|
254
324
|
input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
|
|
255
325
|
return refuse('path-collision', change.path);
|
|
256
326
|
}
|
|
327
|
+
const taken = occupied(change.path);
|
|
328
|
+
if (taken !== null)
|
|
329
|
+
return refuse('path-collision', taken);
|
|
257
330
|
if (!input.local &&
|
|
258
331
|
existing.has(change.path) &&
|
|
259
332
|
input.visible.revisions[departure(changes, index)] === undefined) {
|
|
@@ -265,8 +338,9 @@ function guard(changes, input) {
|
|
|
265
338
|
if (change.kind === 'move') {
|
|
266
339
|
if (!existing.has(change.path))
|
|
267
340
|
return refuse('path-absent', change.path);
|
|
268
|
-
|
|
269
|
-
|
|
341
|
+
const taken = existing.has(change.to) ? change.to : occupied(change.to);
|
|
342
|
+
if (taken !== null)
|
|
343
|
+
return refuse('path-collision', taken);
|
|
270
344
|
if (unrevised(input, change.path)) {
|
|
271
345
|
return refuse('unknown-revision', change.path);
|
|
272
346
|
}
|
|
@@ -284,7 +358,7 @@ function guard(changes, input) {
|
|
|
284
358
|
continue;
|
|
285
359
|
}
|
|
286
360
|
if (change.kind === 'replace') {
|
|
287
|
-
if (
|
|
361
|
+
if (unrevised(input, change.path)) {
|
|
288
362
|
return refuse('unknown-revision', change.path);
|
|
289
363
|
}
|
|
290
364
|
continue;
|
|
@@ -296,7 +370,23 @@ function guard(changes, input) {
|
|
|
296
370
|
return null;
|
|
297
371
|
}
|
|
298
372
|
function unrevised(input, path) {
|
|
299
|
-
return
|
|
373
|
+
return !input.local && !input.queued.has(path) && input.visible.revisions[path] === undefined;
|
|
374
|
+
}
|
|
375
|
+
function queuedDocuments(queue) {
|
|
376
|
+
const paths = new Set();
|
|
377
|
+
for (const batch of queue) {
|
|
378
|
+
for (const change of batch.changes) {
|
|
379
|
+
if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
|
|
380
|
+
continue;
|
|
381
|
+
if (change.kind === 'remove')
|
|
382
|
+
paths.delete(change.path);
|
|
383
|
+
else if (change.kind !== 'move')
|
|
384
|
+
paths.add(change.path);
|
|
385
|
+
else if (paths.delete(change.path))
|
|
386
|
+
paths.add(change.to);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return paths;
|
|
300
390
|
}
|
|
301
391
|
function identity(precondition) {
|
|
302
392
|
const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
|
|
@@ -315,7 +405,7 @@ function sameTarget(precondition, change) {
|
|
|
315
405
|
return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
|
|
316
406
|
((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
|
|
317
407
|
}
|
|
318
|
-
function preconditionsOf(changes, visible, bases) {
|
|
408
|
+
function* preconditionsOf(changes, visible, bases) {
|
|
319
409
|
const preconditions = [];
|
|
320
410
|
const seen = new Set();
|
|
321
411
|
const add = (precondition) => {
|
|
@@ -326,7 +416,8 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
326
416
|
preconditions.push(precondition);
|
|
327
417
|
};
|
|
328
418
|
for (const change of changes) {
|
|
329
|
-
|
|
419
|
+
yield ROW_WEIGHT;
|
|
420
|
+
if (isPart(change)) {
|
|
330
421
|
const based = bases.find((base) => sameTarget(base, change));
|
|
331
422
|
if (based !== undefined) {
|
|
332
423
|
add(based);
|
|
@@ -373,10 +464,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
373
464
|
function depth(path) {
|
|
374
465
|
return path.split('/').length;
|
|
375
466
|
}
|
|
376
|
-
function
|
|
467
|
+
function* exceeding(limits, changes, effective, resulting) {
|
|
377
468
|
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
378
469
|
const created = changes
|
|
379
|
-
.filter((change) => change.kind === 'create' ||
|
|
470
|
+
.filter((change) => change.kind === 'create' ||
|
|
471
|
+
change.kind === 'move' ||
|
|
472
|
+
change.kind === 'binary' ||
|
|
473
|
+
change.kind === 'arrival')
|
|
380
474
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
381
475
|
for (const limit of limits) {
|
|
382
476
|
const value = limit.value;
|
|
@@ -390,9 +484,13 @@ function exceeded(limits, changes, effective, resulting) {
|
|
|
390
484
|
return refuse('provider-limit', undefined, limit.name);
|
|
391
485
|
}
|
|
392
486
|
if (limit.name === 'document-size') {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
+
}
|
|
396
494
|
}
|
|
397
495
|
if (limit.name === 'path-length') {
|
|
398
496
|
const long = created.find((path) => path.length > value);
|
|
@@ -410,97 +508,109 @@ function exceeded(limits, changes, effective, resulting) {
|
|
|
410
508
|
function shellRelative(text, path, handle, definition) {
|
|
411
509
|
if (!text.includes(`/rooms/${handle}/`))
|
|
412
510
|
return null;
|
|
413
|
-
const parsed = parse(text, path);
|
|
511
|
+
const parsed = parse(text, path, definition);
|
|
414
512
|
if (!parsed.ok)
|
|
415
513
|
return null;
|
|
416
514
|
const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
|
|
417
515
|
return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
|
|
418
516
|
}
|
|
419
517
|
function relativeField(path, name, value, handle, definition) {
|
|
420
|
-
|
|
421
|
-
if (definition === undefined ||
|
|
422
|
-
!opened.ok ||
|
|
423
|
-
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
518
|
+
if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
424
519
|
return value;
|
|
425
|
-
const held = apply(
|
|
520
|
+
const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
|
|
426
521
|
if (held.outcome !== 'applied')
|
|
427
522
|
return value;
|
|
428
523
|
const result = relativize(held.document, path, handle, definition);
|
|
429
524
|
return result.outcome === 'applied' ? result.document.fields[name] : value;
|
|
430
525
|
}
|
|
431
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) {
|
|
432
537
|
const { handle, composition, visible } = input;
|
|
433
|
-
const typed = (path) => (type) => definitionOf(governing(composition, path), type);
|
|
434
538
|
const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
return content === change.content ? change : { ...change, content };
|
|
449
|
-
}
|
|
450
|
-
if (change.kind !== 'create')
|
|
451
|
-
return change;
|
|
452
|
-
const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
|
|
453
|
-
const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
|
|
454
|
-
name,
|
|
455
|
-
relativeField(change.path, name, value, handle, definition(type)),
|
|
456
|
-
]));
|
|
457
|
-
const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
|
|
458
|
-
return { ...change, fields, sections };
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
function unmodeledType(collection, composition) {
|
|
462
|
-
if (available(composition))
|
|
463
|
-
return false;
|
|
464
|
-
for (const [path, document] of documentsOf(collection)) {
|
|
465
|
-
if (document.type !== undefined &&
|
|
466
|
-
document.type !== 'any' &&
|
|
467
|
-
governing(composition, path).status === 'unavailable')
|
|
468
|
-
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 } };
|
|
544
|
+
}
|
|
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 } };
|
|
469
552
|
}
|
|
470
|
-
|
|
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
|
+
};
|
|
471
571
|
}
|
|
472
572
|
function unqueueable(file) {
|
|
473
|
-
return
|
|
474
|
-
}
|
|
475
|
-
function isPart(change) {
|
|
476
|
-
return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
|
|
573
|
+
return refuse('unknown-revision', file);
|
|
477
574
|
}
|
|
478
575
|
function touched(change) {
|
|
479
576
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
480
577
|
}
|
|
481
|
-
function
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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;
|
|
597
|
+
}
|
|
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;
|
|
502
612
|
}
|
|
503
|
-
function folded(changes, change, appending) {
|
|
613
|
+
function folded(changes, change, appending, deferred) {
|
|
504
614
|
const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
|
|
505
615
|
const whole = held === undefined ? -1 : changes.indexOf(held);
|
|
506
616
|
const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
|
|
@@ -521,10 +631,11 @@ function folded(changes, change, appending) {
|
|
|
521
631
|
return 'appended';
|
|
522
632
|
}
|
|
523
633
|
if ('content' in held) {
|
|
524
|
-
const
|
|
525
|
-
if (
|
|
526
|
-
|
|
527
|
-
|
|
634
|
+
const parts = deferred.get(held);
|
|
635
|
+
if (parts === undefined)
|
|
636
|
+
deferred.set(held, [change]);
|
|
637
|
+
else
|
|
638
|
+
parts.push(change);
|
|
528
639
|
return 'folded';
|
|
529
640
|
}
|
|
530
641
|
if (change.kind === 'preamble')
|
|
@@ -555,17 +666,23 @@ function folded(changes, change, appending) {
|
|
|
555
666
|
changes.push(change);
|
|
556
667
|
return 'appended';
|
|
557
668
|
}
|
|
558
|
-
function relinked(batch, move, before, after, appending) {
|
|
559
|
-
const derived = rewrites([move], after);
|
|
669
|
+
function* relinked(batch, move, before, after, appending) {
|
|
670
|
+
const derived = yield* rewrites([move], after);
|
|
560
671
|
if (derived.kind === 'refused')
|
|
561
672
|
return derived;
|
|
562
673
|
const changes = [...batch.changes];
|
|
563
674
|
const appended = [];
|
|
564
675
|
let changed = false;
|
|
676
|
+
const deferred = new Map();
|
|
565
677
|
for (const change of derived.changes) {
|
|
566
678
|
if (change === move)
|
|
567
679
|
continue;
|
|
568
|
-
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);
|
|
569
686
|
if (outcome === 'refused')
|
|
570
687
|
return unqueueable(change.path);
|
|
571
688
|
if (outcome === 'appended')
|
|
@@ -573,9 +690,13 @@ function relinked(batch, move, before, after, appending) {
|
|
|
573
690
|
if (outcome !== 'skipped')
|
|
574
691
|
changed = true;
|
|
575
692
|
}
|
|
693
|
+
const unsettled = settledParts(changes, deferred, after.collection.composition);
|
|
694
|
+
if (unsettled !== null)
|
|
695
|
+
return unqueueable(unsettled);
|
|
576
696
|
if (!changed)
|
|
577
697
|
return { kind: 'relinked', batch };
|
|
578
698
|
const held = new Set(batch.preconditions.map(identity));
|
|
699
|
+
const added = yield* preconditionsOf(appended, before, []);
|
|
579
700
|
return {
|
|
580
701
|
kind: 'relinked',
|
|
581
702
|
batch: immutable({
|
|
@@ -584,7 +705,7 @@ function relinked(batch, move, before, after, appending) {
|
|
|
584
705
|
changes,
|
|
585
706
|
preconditions: [
|
|
586
707
|
...batch.preconditions,
|
|
587
|
-
...
|
|
708
|
+
...added.filter((precondition) => !held.has(identity(precondition))),
|
|
588
709
|
],
|
|
589
710
|
}),
|
|
590
711
|
};
|
|
@@ -630,11 +751,10 @@ function withdrawnFile(batch, change, at) {
|
|
|
630
751
|
];
|
|
631
752
|
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
632
753
|
}
|
|
633
|
-
|
|
754
|
+
function* requeuing(queue, changes, visible, changeable, state) {
|
|
634
755
|
const next = [...queue];
|
|
635
756
|
const origins = new Map();
|
|
636
757
|
const withdrawn = new Map();
|
|
637
|
-
const owners = new Set();
|
|
638
758
|
const queued = new Set();
|
|
639
759
|
const kept = [];
|
|
640
760
|
const originOf = (batch) => origins.get(batch) ?? batch.id;
|
|
@@ -653,19 +773,19 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
653
773
|
return unqueueable(change.path);
|
|
654
774
|
const following = next.slice(index);
|
|
655
775
|
const rebuilt = [];
|
|
656
|
-
let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
|
|
776
|
+
let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
|
|
657
777
|
for (const [offset, batch] of following.entries()) {
|
|
658
778
|
let current = batch;
|
|
659
779
|
if (change.kind === 'move') {
|
|
660
|
-
const after = state(next.slice(0, index + offset + 1));
|
|
780
|
+
const after = yield* state(next.slice(0, index + offset + 1));
|
|
661
781
|
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
662
|
-
const relinking = relinked(batch, change, before, after, appending);
|
|
782
|
+
const relinking = yield* relinked(batch, change, before, after, appending);
|
|
663
783
|
if (relinking.kind === 'refused')
|
|
664
784
|
return relinking;
|
|
665
785
|
current = relinking.batch;
|
|
666
786
|
before = after;
|
|
667
787
|
}
|
|
668
|
-
if (current !== batch && !open(batch))
|
|
788
|
+
if (current !== batch && (!open(batch) || batch.between !== undefined))
|
|
669
789
|
return unqueueable(change.path);
|
|
670
790
|
rebuilt.push(offset === 0
|
|
671
791
|
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
@@ -680,7 +800,6 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
680
800
|
if (replaced !== null)
|
|
681
801
|
origins.set(replaced, origin);
|
|
682
802
|
}
|
|
683
|
-
owners.add(originOf(owner));
|
|
684
803
|
next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
|
|
685
804
|
queued.add(change.path);
|
|
686
805
|
}
|
|
@@ -690,100 +809,65 @@ export function requeued(queue, changes, visible, changeable, state) {
|
|
|
690
809
|
changes: kept,
|
|
691
810
|
queued,
|
|
692
811
|
withdrawn,
|
|
693
|
-
owners,
|
|
694
812
|
};
|
|
695
813
|
}
|
|
696
|
-
function
|
|
697
|
-
return precondition.kind === 'field' ||
|
|
698
|
-
precondition.kind === 'section' ||
|
|
699
|
-
precondition.kind === 'preamble'
|
|
700
|
-
? identity(precondition)
|
|
701
|
-
: `path ${precondition.path}`;
|
|
702
|
-
}
|
|
703
|
-
export function joined(queue, owner, extra) {
|
|
704
|
-
const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
|
|
705
|
-
const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
|
|
706
|
-
if (clash !== undefined)
|
|
707
|
-
return unqueueable(clash);
|
|
708
|
-
const changes = [...owner.changes];
|
|
709
|
-
for (const change of extra.changes) {
|
|
710
|
-
if (folded(changes, change, true) === 'refused')
|
|
711
|
-
return unqueueable(change.path);
|
|
712
|
-
}
|
|
713
|
-
const held = new Set(owner.preconditions.map(covering));
|
|
714
|
-
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
715
|
-
const preconditions = [
|
|
716
|
-
...owner.preconditions,
|
|
717
|
-
...extra.preconditions.filter((precondition) => {
|
|
718
|
-
const key = covering(precondition);
|
|
719
|
-
return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
|
|
720
|
-
}),
|
|
721
|
-
];
|
|
722
|
-
const batch = immutable({
|
|
723
|
-
...owner,
|
|
724
|
-
...(extra.validation === undefined ? {} : { validation: extra.validation }),
|
|
725
|
-
id: changeBatchIdentity(),
|
|
726
|
-
changes,
|
|
727
|
-
preconditions,
|
|
728
|
-
});
|
|
729
|
-
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
730
|
-
}
|
|
731
|
-
export function limitedIn(queue, batch, limits, state) {
|
|
814
|
+
function* limiting(queue, batch, limits, state) {
|
|
732
815
|
const at = queue.indexOf(batch);
|
|
733
|
-
const
|
|
734
|
-
|
|
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;
|
|
735
819
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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) {
|
|
761
851
|
if (removed.size > 0) {
|
|
762
|
-
const
|
|
763
|
-
const referenced = incoming(collectionOf(resulting.documents, input.composition), removed, referrers === null ? null : [...referrers, ...predicted.changes.flatMap(affected)]);
|
|
852
|
+
const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
|
|
764
853
|
if (referenced !== null)
|
|
765
854
|
return referenced;
|
|
766
855
|
}
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
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');
|
|
777
866
|
if (withoutModel !== undefined)
|
|
778
867
|
return refuse('model-unavailable', withoutModel);
|
|
779
|
-
const verdict =
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
: validateCollection(needsModel
|
|
783
|
-
? resulting.documents
|
|
784
|
-
: Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
|
|
785
|
-
? []
|
|
786
|
-
: [[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);
|
|
787
871
|
if (verdict.verdict === 'refuse') {
|
|
788
872
|
return {
|
|
789
873
|
kind: 'refused',
|
|
@@ -792,16 +876,99 @@ export function buildBatch(input) {
|
|
|
792
876
|
retryable: false,
|
|
793
877
|
file: verdict.file,
|
|
794
878
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
879
|
+
...(verdict.line === undefined ? {} : { line: verdict.line }),
|
|
795
880
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
796
881
|
},
|
|
797
882
|
};
|
|
798
883
|
}
|
|
799
|
-
|
|
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));
|
|
800
970
|
if (limited !== null)
|
|
801
971
|
return limited;
|
|
802
|
-
const validated = typedPaths
|
|
803
|
-
.map((path) => governing(input.composition, path))
|
|
804
|
-
.find((model) => model.status !== 'unavailable');
|
|
805
972
|
return {
|
|
806
973
|
kind: 'batch',
|
|
807
974
|
batch: immutable({
|
|
@@ -810,15 +977,296 @@ export function buildBatch(input) {
|
|
|
810
977
|
changes,
|
|
811
978
|
preconditions,
|
|
812
979
|
...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
|
|
813
|
-
...(validated
|
|
814
|
-
? {}
|
|
815
|
-
: {
|
|
816
|
-
validation: {
|
|
817
|
-
identity: validated.snapshot.identity,
|
|
818
|
-
version: validated.snapshot.version,
|
|
819
|
-
source: validated.status,
|
|
820
|
-
},
|
|
821
|
-
}),
|
|
980
|
+
...validationOf(judged.validated),
|
|
822
981
|
}),
|
|
823
982
|
};
|
|
824
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
|
+
}
|