@agentshouse/mdruntime 0.1.0-cec6690d5712e → 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 +207 -94
- 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 +18 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +31 -11
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +311 -92
- package/dist/cell.d.ts +6 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +9 -0
- package/dist/chain.d.ts +12 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +13 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +17 -62
- package/dist/change.d.ts +46 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +39 -0
- 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 +51 -27
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +133 -26
- package/dist/collection.d.ts +34 -9
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +55 -79
- 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 +8 -111
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +113 -2221
- 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 +84 -45
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1118 -584
- package/dist/declaration.d.ts +10 -0
- package/dist/declaration.d.ts.map +1 -0
- package/dist/declaration.js +24 -0
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/discovery.d.ts +10 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +150 -0
- package/dist/durability.d.ts +23 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +9 -0
- package/dist/entry.d.ts +20 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +161 -0
- package/dist/expectation.d.ts +26 -13
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +72 -19
- package/dist/failure.d.ts +3 -0
- package/dist/failure.d.ts.map +1 -1
- package/dist/failure.js +6 -0
- package/dist/figures.d.ts +49 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +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 +29 -4
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +179 -25
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +3 -0
- package/dist/index.d.ts +42 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -2
- 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 +26 -8
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +307 -97
- 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 +11 -12
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +7 -15
- package/dist/models.d.ts +10 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +153 -0
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +42 -9
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +238 -33
- 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 +63 -5
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +70 -0
- package/dist/overlay.d.ts +21 -6
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +163 -187
- package/dist/page-metadata.d.ts +8 -0
- package/dist/page-metadata.d.ts.map +1 -0
- package/dist/page-metadata.js +19 -0
- package/dist/parts.d.ts +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 +51 -183
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -464
- package/dist/{chat/questions.d.ts → questions.d.ts} +5 -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 +20 -5
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +357 -80
- package/dist/react.d.ts +23 -7
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +40 -18
- package/dist/{chat/read.d.ts → read.d.ts} +2 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/{chat/read.js → read.js} +4 -0
- package/dist/readiness.d.ts +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 +28 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +254 -0
- package/dist/refresh.d.ts +17 -14
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +145 -58
- 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 +53 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +349 -167
- 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 +82 -11
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +490 -78
- 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 +31 -35
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +60 -126
- package/dist/runtime.d.ts +34 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/save.d.ts +45 -22
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +977 -215
- 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 +15 -55
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -121
- package/dist/seed.d.ts +25 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +145 -0
- package/dist/shared.d.ts +10 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +339 -0
- package/dist/shown.d.ts +33 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +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 +5 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +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 -116
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +164 -494
- 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 +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +75 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +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 +221 -34
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +74 -31
- package/package.json +28 -24
- package/AGENTS.md +0 -299
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
- package/dist/attempt.d.ts +0 -16
- package/dist/attempt.d.ts.map +0 -1
- package/dist/attempt.js +0 -57
- package/dist/chat/acts.d.ts +0 -34
- package/dist/chat/acts.d.ts.map +0 -1
- package/dist/chat/acts.js +0 -36
- package/dist/chat/context.d.ts +0 -3
- package/dist/chat/context.d.ts.map +0 -1
- package/dist/chat/input.d.ts +0 -9
- package/dist/chat/input.d.ts.map +0 -1
- package/dist/chat/input.js +0 -99
- package/dist/chat/kit.d.ts +0 -60
- package/dist/chat/kit.d.ts.map +0 -1
- package/dist/chat/kit.js +0 -123
- package/dist/chat/lasting.d.ts +0 -6
- package/dist/chat/lasting.d.ts.map +0 -1
- package/dist/chat/lasting.js +0 -19
- package/dist/chat/list.d.ts +0 -20
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -59
- package/dist/chat/markdown.d.ts +0 -19
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -187
- package/dist/chat/message.d.ts +0 -6
- package/dist/chat/message.d.ts.map +0 -1
- package/dist/chat/message.js +0 -192
- package/dist/chat/messages.d.ts +0 -21
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -151
- package/dist/chat/name.d.ts +0 -6
- package/dist/chat/name.d.ts.map +0 -1
- package/dist/chat/name.js +0 -37
- package/dist/chat/opened.d.ts +0 -11
- package/dist/chat/opened.d.ts.map +0 -1
- package/dist/chat/opened.js +0 -17
- package/dist/chat/overlay.d.ts +0 -31
- package/dist/chat/overlay.d.ts.map +0 -1
- package/dist/chat/overlay.js +0 -97
- package/dist/chat/parts.d.ts +0 -57
- package/dist/chat/parts.d.ts.map +0 -1
- package/dist/chat/parts.js +0 -119
- package/dist/chat/process.d.ts +0 -20
- package/dist/chat/process.d.ts.map +0 -1
- package/dist/chat/process.js +0 -32
- package/dist/chat/provider.d.ts +0 -63
- package/dist/chat/provider.d.ts.map +0 -1
- package/dist/chat/provider.js +0 -645
- package/dist/chat/question.d.ts +0 -9
- package/dist/chat/question.d.ts.map +0 -1
- package/dist/chat/question.js +0 -80
- package/dist/chat/questions.d.ts.map +0 -1
- package/dist/chat/read.d.ts.map +0 -1
- package/dist/chat/ready.d.ts +0 -33
- package/dist/chat/ready.d.ts.map +0 -1
- package/dist/chat/ready.js +0 -91
- package/dist/chat/style.d.ts +0 -4
- package/dist/chat/style.d.ts.map +0 -1
- package/dist/chat/style.js +0 -302
- package/dist/chat.d.ts +0 -14
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -8
- package/dist/log.d.ts +0 -5
- package/dist/log.d.ts.map +0 -1
- package/dist/log.js +0 -14
- package/dist/read-side.d.ts +0 -18
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -433
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,95 +61,187 @@ 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
|
-
if (change.kind === 'remove' ||
|
|
100
|
+
if (change.kind === 'remove' ||
|
|
101
|
+
change.kind === 'replace' ||
|
|
102
|
+
change.kind === 'create' ||
|
|
103
|
+
change.kind === 'binary') {
|
|
126
104
|
settled.add(change.path);
|
|
127
105
|
}
|
|
128
106
|
if (change.kind === 'field')
|
|
129
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
|
+
}
|
|
130
115
|
}
|
|
131
116
|
if (moves.size === 0)
|
|
132
117
|
return { kind: 'changes', changes };
|
|
118
|
+
const names = movedNames(moves);
|
|
133
119
|
const merged = [...changes];
|
|
134
120
|
const added = [];
|
|
135
|
-
for (const [path, document] of documentsOf(visible.collection)) {
|
|
121
|
+
for (const [path, document] of yield* documentsOf(visible.collection)) {
|
|
136
122
|
if (settled.has(path))
|
|
137
123
|
continue;
|
|
138
124
|
const moved = moves.get(path) ?? path;
|
|
139
|
-
const definition =
|
|
140
|
-
|
|
141
|
-
|
|
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);
|
|
142
133
|
if (relinked.kind === 'refused')
|
|
143
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
|
+
}
|
|
144
141
|
for (const change of relinked.changes) {
|
|
145
142
|
if (change.kind === 'replace') {
|
|
146
|
-
|
|
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
|
+
}
|
|
147
148
|
added.push(change);
|
|
148
149
|
continue;
|
|
149
150
|
}
|
|
150
|
-
const index =
|
|
151
|
-
|
|
152
|
-
(change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
|
|
153
|
-
if (index === -1)
|
|
151
|
+
const index = keyed.get(partKey(change));
|
|
152
|
+
if (index === undefined)
|
|
154
153
|
added.push(change);
|
|
155
154
|
else
|
|
156
155
|
merged[index] = change;
|
|
157
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);
|
|
158
160
|
}
|
|
159
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
kind: 'changes',
|
|
163
|
+
changes: [...added, ...merged.filter((change) => change !== null)],
|
|
164
|
+
};
|
|
160
165
|
}
|
|
161
|
-
function anticipated(changes, preconditions, rewritten, visible) {
|
|
162
|
-
const
|
|
166
|
+
function* anticipated(changes, preconditions, rewritten, visible) {
|
|
167
|
+
const given = new Set(changes);
|
|
168
|
+
const derived = rewritten.filter((change) => !given.has(change));
|
|
163
169
|
return {
|
|
164
170
|
changes: rewritten,
|
|
165
|
-
preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
|
|
171
|
+
preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
|
|
166
172
|
};
|
|
167
173
|
}
|
|
168
|
-
export function providerRewritten(batch, visible) {
|
|
169
|
-
const rewritten = rewrites(batch.changes, visible);
|
|
174
|
+
export function* providerRewritten(batch, visible) {
|
|
175
|
+
const rewritten = yield* rewrites(batch.changes, visible);
|
|
170
176
|
if (rewritten.kind === 'refused')
|
|
171
177
|
return batch;
|
|
172
178
|
return {
|
|
173
179
|
...batch,
|
|
174
|
-
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
180
|
+
...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
|
|
175
181
|
};
|
|
176
182
|
}
|
|
177
|
-
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) {
|
|
178
245
|
const found = new Map();
|
|
179
246
|
const note = (target, file) => {
|
|
180
247
|
if (!removed.has(target))
|
|
@@ -183,8 +250,21 @@ function incoming(collection, removed) {
|
|
|
183
250
|
referrers.add(file);
|
|
184
251
|
found.set(target, referrers);
|
|
185
252
|
};
|
|
186
|
-
|
|
187
|
-
const
|
|
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);
|
|
259
|
+
const document = collection.document(path);
|
|
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);
|
|
188
268
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
189
269
|
if (to.includes(ATTACHMENT))
|
|
190
270
|
continue;
|
|
@@ -192,8 +272,9 @@ function incoming(collection, removed) {
|
|
|
192
272
|
if (named !== null)
|
|
193
273
|
note(named, path);
|
|
194
274
|
}
|
|
275
|
+
yield ROW_WEIGHT + collection.text[path].length;
|
|
195
276
|
}
|
|
196
|
-
const [first] =
|
|
277
|
+
const [first] = yield* sorting(found.keys());
|
|
197
278
|
if (first === undefined)
|
|
198
279
|
return null;
|
|
199
280
|
const every = new Set();
|
|
@@ -206,42 +287,61 @@ function incoming(collection, removed) {
|
|
|
206
287
|
code: 'referenced-missing-file',
|
|
207
288
|
retryable: false,
|
|
208
289
|
file: first,
|
|
209
|
-
referrers:
|
|
290
|
+
referrers: yield* sorting(every),
|
|
210
291
|
},
|
|
211
292
|
};
|
|
212
293
|
}
|
|
213
294
|
function affected(change) {
|
|
214
295
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
215
296
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
297
|
+
const WRITE = ['write'];
|
|
298
|
+
const MOVE_OUT = ['write', 'erase'];
|
|
299
|
+
function guard(changes, input, removing) {
|
|
300
|
+
const overridden = new Map();
|
|
301
|
+
const existing = {
|
|
302
|
+
has: (path) => overridden.get(path) ??
|
|
303
|
+
(Object.hasOwn(input.visible.documents, path) || Object.hasOwn(input.visible.binaries, path)),
|
|
304
|
+
add: (path) => void overridden.set(path, true),
|
|
305
|
+
delete: (path) => void overridden.set(path, false),
|
|
306
|
+
};
|
|
307
|
+
const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
|
|
308
|
+
for (const [index, change] of changes.entries()) {
|
|
222
309
|
for (const path of affected(change)) {
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
return refuse(
|
|
226
|
-
if (covered(input.providerOwnedPaths, normalized)) {
|
|
227
|
-
return refuse('provider-owned-path', normalized);
|
|
228
|
-
}
|
|
229
|
-
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
230
|
-
if (!input.local && !covered(input.operations[operation], normalized))
|
|
231
|
-
return refuse(`${operation}-grant`, normalized);
|
|
310
|
+
const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
|
|
311
|
+
if (refused !== null)
|
|
312
|
+
return refuse(refused.code, refused.path);
|
|
232
313
|
}
|
|
233
|
-
if (change.kind === 'create') {
|
|
234
|
-
|
|
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);
|
|
318
|
+
existing.add(change.path);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (change.kind === 'binary') {
|
|
322
|
+
if (input.visible.documents[change.path] !== undefined ||
|
|
323
|
+
(existing.has(change.path) &&
|
|
324
|
+
input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
|
|
235
325
|
return refuse('path-collision', change.path);
|
|
326
|
+
}
|
|
327
|
+
const taken = occupied(change.path);
|
|
328
|
+
if (taken !== null)
|
|
329
|
+
return refuse('path-collision', taken);
|
|
330
|
+
if (!input.local &&
|
|
331
|
+
existing.has(change.path) &&
|
|
332
|
+
input.visible.revisions[departure(changes, index)] === undefined) {
|
|
333
|
+
return refuse('unknown-revision', change.path);
|
|
334
|
+
}
|
|
236
335
|
existing.add(change.path);
|
|
237
336
|
continue;
|
|
238
337
|
}
|
|
239
338
|
if (change.kind === 'move') {
|
|
240
339
|
if (!existing.has(change.path))
|
|
241
340
|
return refuse('path-absent', change.path);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
341
|
+
const taken = existing.has(change.to) ? change.to : occupied(change.to);
|
|
342
|
+
if (taken !== null)
|
|
343
|
+
return refuse('path-collision', taken);
|
|
344
|
+
if (unrevised(input, change.path)) {
|
|
245
345
|
return refuse('unknown-revision', change.path);
|
|
246
346
|
}
|
|
247
347
|
existing.delete(change.path);
|
|
@@ -251,14 +351,14 @@ function guard(changes, input) {
|
|
|
251
351
|
if (!existing.has(change.path))
|
|
252
352
|
return refuse('path-absent', change.path);
|
|
253
353
|
if (change.kind === 'remove') {
|
|
254
|
-
if (
|
|
354
|
+
if (unrevised(input, change.path)) {
|
|
255
355
|
return refuse('unknown-revision', change.path);
|
|
256
356
|
}
|
|
257
357
|
existing.delete(change.path);
|
|
258
358
|
continue;
|
|
259
359
|
}
|
|
260
360
|
if (change.kind === 'replace') {
|
|
261
|
-
if (
|
|
361
|
+
if (unrevised(input, change.path)) {
|
|
262
362
|
return refuse('unknown-revision', change.path);
|
|
263
363
|
}
|
|
264
364
|
continue;
|
|
@@ -269,8 +369,28 @@ function guard(changes, input) {
|
|
|
269
369
|
}
|
|
270
370
|
return null;
|
|
271
371
|
}
|
|
272
|
-
function
|
|
273
|
-
return visible.revisions[path] === undefined
|
|
372
|
+
function unrevised(input, path) {
|
|
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;
|
|
390
|
+
}
|
|
391
|
+
function identity(precondition) {
|
|
392
|
+
const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
|
|
393
|
+
return `${precondition.kind} ${precondition.path} ${part}`;
|
|
274
394
|
}
|
|
275
395
|
function existence(visible, path) {
|
|
276
396
|
const revision = visible.revisions[path];
|
|
@@ -285,19 +405,19 @@ function sameTarget(precondition, change) {
|
|
|
285
405
|
return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
|
|
286
406
|
((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
|
|
287
407
|
}
|
|
288
|
-
function preconditionsOf(changes, visible, bases) {
|
|
408
|
+
function* preconditionsOf(changes, visible, bases) {
|
|
289
409
|
const preconditions = [];
|
|
290
410
|
const seen = new Set();
|
|
291
411
|
const add = (precondition) => {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
if (seen.has(identity))
|
|
412
|
+
const held = identity(precondition);
|
|
413
|
+
if (seen.has(held))
|
|
295
414
|
return;
|
|
296
|
-
seen.add(
|
|
415
|
+
seen.add(held);
|
|
297
416
|
preconditions.push(precondition);
|
|
298
417
|
};
|
|
299
418
|
for (const change of changes) {
|
|
300
|
-
|
|
419
|
+
yield ROW_WEIGHT;
|
|
420
|
+
if (isPart(change)) {
|
|
301
421
|
const based = bases.find((base) => sameTarget(base, change));
|
|
302
422
|
if (based !== undefined) {
|
|
303
423
|
add(based);
|
|
@@ -328,6 +448,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
328
448
|
add({ kind: 'absent', path: change.path });
|
|
329
449
|
continue;
|
|
330
450
|
}
|
|
451
|
+
if (change.kind === 'binary') {
|
|
452
|
+
const revision = visible.revisions[change.path];
|
|
453
|
+
add(visible.binaries[change.path] === undefined || revision === undefined
|
|
454
|
+
? { kind: 'absent', path: change.path }
|
|
455
|
+
: { kind: 'revision', path: change.path, revision });
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
331
458
|
add(existence(visible, change.path));
|
|
332
459
|
if (change.kind === 'move')
|
|
333
460
|
add({ kind: 'absent', path: change.to });
|
|
@@ -337,10 +464,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
337
464
|
function depth(path) {
|
|
338
465
|
return path.split('/').length;
|
|
339
466
|
}
|
|
340
|
-
function
|
|
341
|
-
const written =
|
|
467
|
+
function* exceeding(limits, changes, effective, resulting) {
|
|
468
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
342
469
|
const created = changes
|
|
343
|
-
.filter((change) => change.kind === 'create' ||
|
|
470
|
+
.filter((change) => change.kind === 'create' ||
|
|
471
|
+
change.kind === 'move' ||
|
|
472
|
+
change.kind === 'binary' ||
|
|
473
|
+
change.kind === 'arrival')
|
|
344
474
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
345
475
|
for (const limit of limits) {
|
|
346
476
|
const value = limit.value;
|
|
@@ -354,9 +484,13 @@ function exceeded(limits, changes, resulting) {
|
|
|
354
484
|
return refuse('provider-limit', undefined, limit.name);
|
|
355
485
|
}
|
|
356
486
|
if (limit.name === 'document-size') {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
+
}
|
|
360
494
|
}
|
|
361
495
|
if (limit.name === 'path-length') {
|
|
362
496
|
const long = created.find((path) => path.length > value);
|
|
@@ -374,102 +508,366 @@ function exceeded(limits, changes, resulting) {
|
|
|
374
508
|
function shellRelative(text, path, handle, definition) {
|
|
375
509
|
if (!text.includes(`/rooms/${handle}/`))
|
|
376
510
|
return null;
|
|
377
|
-
const parsed = parse(text, path);
|
|
511
|
+
const parsed = parse(text, path, definition);
|
|
378
512
|
if (!parsed.ok)
|
|
379
513
|
return null;
|
|
380
514
|
const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
|
|
381
515
|
return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
|
|
382
516
|
}
|
|
383
517
|
function relativeField(path, name, value, handle, definition) {
|
|
384
|
-
|
|
385
|
-
if (definition === undefined ||
|
|
386
|
-
!opened.ok ||
|
|
387
|
-
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
518
|
+
if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
388
519
|
return value;
|
|
389
|
-
const held = apply(
|
|
520
|
+
const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
|
|
390
521
|
if (held.outcome !== 'applied')
|
|
391
522
|
return value;
|
|
392
523
|
const result = relativize(held.document, path, handle, definition);
|
|
393
524
|
return result.outcome === 'applied' ? result.document.fields[name] : value;
|
|
394
525
|
}
|
|
395
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) {
|
|
396
537
|
const { handle, composition, visible } = input;
|
|
397
|
-
const typed = (path) => (type) => definitionOf(governing(composition, path), type);
|
|
398
538
|
const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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 } };
|
|
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
|
+
};
|
|
424
571
|
}
|
|
425
|
-
function
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
572
|
+
function unqueueable(file) {
|
|
573
|
+
return refuse('unknown-revision', file);
|
|
574
|
+
}
|
|
575
|
+
function touched(change) {
|
|
576
|
+
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
577
|
+
}
|
|
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]);
|
|
431
587
|
}
|
|
432
|
-
|
|
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;
|
|
433
597
|
}
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
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) {
|
|
614
|
+
const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
|
|
615
|
+
const whole = held === undefined ? -1 : changes.indexOf(held);
|
|
616
|
+
const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
|
|
617
|
+
if (isPart(change)) {
|
|
618
|
+
const same = changes.findIndex((item) => item.kind === change.kind &&
|
|
619
|
+
item.path === change.path &&
|
|
620
|
+
(change.kind === 'preamble' || ('name' in item && item.name === change.name)));
|
|
621
|
+
if (same !== -1) {
|
|
622
|
+
changes[same] = change;
|
|
623
|
+
return 'folded';
|
|
624
|
+
}
|
|
625
|
+
if (held === undefined) {
|
|
626
|
+
if (!appending)
|
|
627
|
+
return 'skipped';
|
|
628
|
+
if (occupied)
|
|
629
|
+
return 'refused';
|
|
630
|
+
changes.push(change);
|
|
631
|
+
return 'appended';
|
|
632
|
+
}
|
|
633
|
+
if ('content' in held) {
|
|
634
|
+
const parts = deferred.get(held);
|
|
635
|
+
if (parts === undefined)
|
|
636
|
+
deferred.set(held, [change]);
|
|
637
|
+
else
|
|
638
|
+
parts.push(change);
|
|
639
|
+
return 'folded';
|
|
640
|
+
}
|
|
641
|
+
if (change.kind === 'preamble')
|
|
642
|
+
return 'refused';
|
|
643
|
+
changes[whole] =
|
|
644
|
+
change.kind === 'field'
|
|
645
|
+
? { ...held, fields: { ...held.fields, [change.name]: change.value } }
|
|
646
|
+
: { ...held, sections: { ...held.sections, [change.name]: change.value } };
|
|
647
|
+
return 'folded';
|
|
648
|
+
}
|
|
649
|
+
if (change.kind === 'replace') {
|
|
650
|
+
if (held !== undefined) {
|
|
651
|
+
if (!('content' in held))
|
|
652
|
+
return 'refused';
|
|
653
|
+
changes[whole] = { ...held, content: change.content };
|
|
654
|
+
return 'folded';
|
|
655
|
+
}
|
|
656
|
+
const parts = changes.filter((item) => isPart(item) && item.path === change.path);
|
|
657
|
+
if (parts.length === 0 && !appending)
|
|
658
|
+
return 'skipped';
|
|
659
|
+
if (occupied)
|
|
660
|
+
return 'refused';
|
|
661
|
+
changes.splice(0, changes.length, ...changes.filter((item) => !parts.includes(item)), change);
|
|
662
|
+
return 'appended';
|
|
663
|
+
}
|
|
664
|
+
if (changes.some((item) => touched(change).some((path) => touched(item).includes(path))))
|
|
665
|
+
return 'refused';
|
|
666
|
+
changes.push(change);
|
|
667
|
+
return 'appended';
|
|
668
|
+
}
|
|
669
|
+
function* relinked(batch, move, before, after, appending) {
|
|
670
|
+
const derived = yield* rewrites([move], after);
|
|
671
|
+
if (derived.kind === 'refused')
|
|
672
|
+
return derived;
|
|
673
|
+
const changes = [...batch.changes];
|
|
674
|
+
const appended = [];
|
|
675
|
+
let changed = false;
|
|
676
|
+
const deferred = new Map();
|
|
677
|
+
for (const change of derived.changes) {
|
|
678
|
+
if (change === move)
|
|
679
|
+
continue;
|
|
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);
|
|
686
|
+
if (outcome === 'refused')
|
|
687
|
+
return unqueueable(change.path);
|
|
688
|
+
if (outcome === 'appended')
|
|
689
|
+
appended.push(change);
|
|
690
|
+
if (outcome !== 'skipped')
|
|
691
|
+
changed = true;
|
|
692
|
+
}
|
|
693
|
+
const unsettled = settledParts(changes, deferred, after.collection.composition);
|
|
694
|
+
if (unsettled !== null)
|
|
695
|
+
return unqueueable(unsettled);
|
|
696
|
+
if (!changed)
|
|
697
|
+
return { kind: 'relinked', batch };
|
|
698
|
+
const held = new Set(batch.preconditions.map(identity));
|
|
699
|
+
const added = yield* preconditionsOf(appended, before, []);
|
|
700
|
+
return {
|
|
701
|
+
kind: 'relinked',
|
|
702
|
+
batch: immutable({
|
|
703
|
+
...batch,
|
|
704
|
+
id: changeBatchIdentity(),
|
|
705
|
+
changes,
|
|
706
|
+
preconditions: [
|
|
707
|
+
...batch.preconditions,
|
|
708
|
+
...added.filter((precondition) => !held.has(identity(precondition))),
|
|
709
|
+
],
|
|
710
|
+
}),
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function leading(changes, at) {
|
|
714
|
+
const from = departure(changes, at);
|
|
715
|
+
return new Set(changes.filter((change, index) => index < at && change.kind === 'move' && departure(changes, index) === from));
|
|
716
|
+
}
|
|
717
|
+
function created(batch, at) {
|
|
718
|
+
const file = batch.changes[at];
|
|
719
|
+
return (leading(batch.changes, at).size === 0 &&
|
|
720
|
+
batch.preconditions.some((precondition) => precondition.kind === 'absent' && precondition.path === file.path));
|
|
721
|
+
}
|
|
722
|
+
function withdrawnFile(batch, change, at) {
|
|
723
|
+
const file = batch.changes[at];
|
|
724
|
+
const from = departure(batch.changes, at);
|
|
725
|
+
const moves = leading(batch.changes, at);
|
|
726
|
+
const fresh = created(batch, at);
|
|
727
|
+
const vacated = new Set([...moves].map((move) => move.to));
|
|
728
|
+
if (fresh)
|
|
729
|
+
vacated.add(file.path);
|
|
730
|
+
const replaced = fresh
|
|
731
|
+
? change.kind === 'remove'
|
|
732
|
+
? []
|
|
733
|
+
: [{ ...file, path: change.to }]
|
|
734
|
+
: change.kind === 'remove'
|
|
735
|
+
? [{ kind: 'remove', path: from }]
|
|
736
|
+
: change.to === from
|
|
737
|
+
? [{ ...file, path: from }]
|
|
738
|
+
: [
|
|
739
|
+
{ kind: 'move', path: from, to: change.to },
|
|
740
|
+
{ ...file, path: change.to },
|
|
741
|
+
];
|
|
742
|
+
const added = change.kind === 'move' && (fresh || change.to !== from)
|
|
743
|
+
? [{ kind: 'absent', path: change.to }]
|
|
744
|
+
: [];
|
|
745
|
+
const changes = batch.changes.flatMap((item) => item === file ? replaced : item.kind === 'move' && moves.has(item) ? [] : [item]);
|
|
439
746
|
if (changes.length === 0)
|
|
440
|
-
return
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const
|
|
747
|
+
return null;
|
|
748
|
+
const preconditions = [
|
|
749
|
+
...added,
|
|
750
|
+
...batch.preconditions.filter((precondition) => precondition.kind !== 'absent' || !vacated.has(precondition.path)),
|
|
751
|
+
];
|
|
752
|
+
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
753
|
+
}
|
|
754
|
+
function* requeuing(queue, changes, visible, changeable, state) {
|
|
755
|
+
const next = [...queue];
|
|
756
|
+
const origins = new Map();
|
|
757
|
+
const withdrawn = new Map();
|
|
758
|
+
const queued = new Set();
|
|
759
|
+
const kept = [];
|
|
760
|
+
const originOf = (batch) => origins.get(batch) ?? batch.id;
|
|
761
|
+
const open = (batch) => origins.has(batch) || changeable(batch);
|
|
762
|
+
for (const change of changes) {
|
|
763
|
+
const held = visible.binaries[change.path];
|
|
764
|
+
if ((change.kind !== 'move' && change.kind !== 'remove') || held?.state !== 'pending') {
|
|
765
|
+
kept.push(change);
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
let index = next.length - 1;
|
|
769
|
+
while (index >= 0 && queuedFile(next[index], change.path, held.localId) === -1)
|
|
770
|
+
index -= 1;
|
|
771
|
+
const owner = next[index];
|
|
772
|
+
if (owner === undefined || !open(owner))
|
|
773
|
+
return unqueueable(change.path);
|
|
774
|
+
const following = next.slice(index);
|
|
775
|
+
const rebuilt = [];
|
|
776
|
+
let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
|
|
777
|
+
for (const [offset, batch] of following.entries()) {
|
|
778
|
+
let current = batch;
|
|
779
|
+
if (change.kind === 'move') {
|
|
780
|
+
const after = yield* state(next.slice(0, index + offset + 1));
|
|
781
|
+
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
782
|
+
const relinking = yield* relinked(batch, change, before, after, appending);
|
|
783
|
+
if (relinking.kind === 'refused')
|
|
784
|
+
return relinking;
|
|
785
|
+
current = relinking.batch;
|
|
786
|
+
before = after;
|
|
787
|
+
}
|
|
788
|
+
if (current !== batch && (!open(batch) || batch.between !== undefined))
|
|
789
|
+
return unqueueable(change.path);
|
|
790
|
+
rebuilt.push(offset === 0
|
|
791
|
+
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
792
|
+
: current);
|
|
793
|
+
}
|
|
794
|
+
for (const [offset, batch] of following.entries()) {
|
|
795
|
+
const replaced = rebuilt[offset];
|
|
796
|
+
if (replaced === batch)
|
|
797
|
+
continue;
|
|
798
|
+
const origin = originOf(batch);
|
|
799
|
+
withdrawn.set(origin, replaced);
|
|
800
|
+
if (replaced !== null)
|
|
801
|
+
origins.set(replaced, origin);
|
|
802
|
+
}
|
|
803
|
+
next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
|
|
804
|
+
queued.add(change.path);
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
kind: 'requeued',
|
|
808
|
+
queue: withdrawn.size === 0 ? queue : next,
|
|
809
|
+
changes: kept,
|
|
810
|
+
queued,
|
|
811
|
+
withdrawn,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function* limiting(queue, batch, limits, state) {
|
|
815
|
+
const at = queue.indexOf(batch);
|
|
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;
|
|
819
|
+
}
|
|
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) {
|
|
453
851
|
if (removed.size > 0) {
|
|
454
|
-
const referenced = incoming(collectionOf(resulting.documents,
|
|
852
|
+
const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
|
|
455
853
|
if (referenced !== null)
|
|
456
854
|
return referenced;
|
|
457
855
|
}
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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');
|
|
467
866
|
if (withoutModel !== undefined)
|
|
468
867
|
return refuse('model-unavailable', withoutModel);
|
|
469
|
-
const
|
|
470
|
-
? resulting
|
|
471
|
-
:
|
|
472
|
-
const verdict = validateCollection(validationText, resulting.binaryTargets, input.composition);
|
|
868
|
+
const verdict = typedPaths.length > 0
|
|
869
|
+
? await validate(resulting, touched)
|
|
870
|
+
: await touchedLibrary(resulting, touched, composition);
|
|
473
871
|
if (verdict.verdict === 'refuse') {
|
|
474
872
|
return {
|
|
475
873
|
kind: 'refused',
|
|
@@ -478,16 +876,99 @@ export function buildBatch(input) {
|
|
|
478
876
|
retryable: false,
|
|
479
877
|
file: verdict.file,
|
|
480
878
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
879
|
+
...(verdict.line === undefined ? {} : { line: verdict.line }),
|
|
481
880
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
482
881
|
},
|
|
483
882
|
};
|
|
484
883
|
}
|
|
485
|
-
|
|
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));
|
|
486
970
|
if (limited !== null)
|
|
487
971
|
return limited;
|
|
488
|
-
const validated = typedPaths
|
|
489
|
-
.map((path) => governing(input.composition, path))
|
|
490
|
-
.find((model) => model.status !== 'unavailable');
|
|
491
972
|
return {
|
|
492
973
|
kind: 'batch',
|
|
493
974
|
batch: immutable({
|
|
@@ -496,15 +977,296 @@ export function buildBatch(input) {
|
|
|
496
977
|
changes,
|
|
497
978
|
preconditions,
|
|
498
979
|
...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
|
|
499
|
-
...(validated
|
|
500
|
-
? {}
|
|
501
|
-
: {
|
|
502
|
-
validation: {
|
|
503
|
-
identity: validated.snapshot.identity,
|
|
504
|
-
version: validated.snapshot.version,
|
|
505
|
-
source: validated.status,
|
|
506
|
-
},
|
|
507
|
-
}),
|
|
980
|
+
...validationOf(judged.validated),
|
|
508
981
|
}),
|
|
509
982
|
};
|
|
510
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
|
+
}
|