@plurnk/plurnk-service 1.14.2 → 1.16.0
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/.env.defaults +2 -2
- package/SPEC.md +243 -173
- package/digest-sql/curation/curation.sql +1 -1
- package/dist/build-info.json +1 -1
- package/dist/content/byte-view.d.ts +14 -0
- package/dist/content/byte-view.d.ts.map +1 -0
- package/dist/content/byte-view.js +33 -0
- package/dist/content/byte-view.js.map +1 -0
- package/dist/content/edit-receipt.d.ts.map +1 -1
- package/dist/content/edit-receipt.js +12 -1
- package/dist/content/edit-receipt.js.map +1 -1
- package/dist/content/line-marker.d.ts +2 -1
- package/dist/content/line-marker.d.ts.map +1 -1
- package/dist/content/line-marker.js +1 -0
- package/dist/content/line-marker.js.map +1 -1
- package/dist/content/matcher.d.ts +1 -0
- package/dist/content/matcher.d.ts.map +1 -1
- package/dist/content/matcher.js +1 -0
- package/dist/content/matcher.js.map +1 -1
- package/dist/content/read-projector.d.ts +4 -1
- package/dist/content/read-projector.d.ts.map +1 -1
- package/dist/content/read-projector.js +80 -3
- package/dist/content/read-projector.js.map +1 -1
- package/dist/core/AdmittedTurnExecutor.d.ts +43 -0
- package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -0
- package/dist/core/AdmittedTurnExecutor.js +245 -0
- package/dist/core/AdmittedTurnExecutor.js.map +1 -0
- package/dist/core/BareBatchRunner.d.ts +26 -0
- package/dist/core/BareBatchRunner.d.ts.map +1 -0
- package/dist/core/BareBatchRunner.js +104 -0
- package/dist/core/BareBatchRunner.js.map +1 -0
- package/dist/core/BudgetReadout.d.ts +1 -1
- package/dist/core/BudgetReadout.d.ts.map +1 -1
- package/dist/core/BudgetReadout.js +42 -47
- package/dist/core/BudgetReadout.js.map +1 -1
- package/dist/core/CapabilityResolver.d.ts +2 -1
- package/dist/core/CapabilityResolver.d.ts.map +1 -1
- package/dist/core/CapabilityResolver.js +8 -9
- package/dist/core/CapabilityResolver.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +1 -1
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/DataStatementRunner.d.ts +35 -0
- package/dist/core/DataStatementRunner.d.ts.map +1 -0
- package/dist/core/DataStatementRunner.js +252 -0
- package/dist/core/DataStatementRunner.js.map +1 -0
- package/dist/core/Dispatcher.d.ts +8 -3
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +88 -738
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/EditMutations.d.ts +39 -0
- package/dist/core/EditMutations.d.ts.map +1 -0
- package/dist/core/EditMutations.js +418 -0
- package/dist/core/EditMutations.js.map +1 -0
- package/dist/core/Engine.d.ts +2 -23
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +18 -240
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +24 -23
- package/dist/core/KillHandler.d.ts +32 -0
- package/dist/core/KillHandler.d.ts.map +1 -0
- package/dist/core/KillHandler.js +179 -0
- package/dist/core/KillHandler.js.map +1 -0
- package/dist/core/LogVisibility.d.ts +1 -1
- package/dist/core/LogVisibility.d.ts.map +1 -1
- package/dist/core/LogVisibility.js +3 -4
- package/dist/core/LogVisibility.js.map +1 -1
- package/dist/core/LogWriter.d.ts +42 -0
- package/dist/core/LogWriter.d.ts.map +1 -0
- package/dist/core/LogWriter.js +136 -0
- package/dist/core/LogWriter.js.map +1 -0
- package/dist/core/LoopDriver.d.ts +46 -0
- package/dist/core/LoopDriver.d.ts.map +1 -0
- package/dist/core/LoopDriver.js +263 -0
- package/dist/core/LoopDriver.js.map +1 -0
- package/dist/core/LoopLifecycle.d.ts +2 -1
- package/dist/core/LoopLifecycle.d.ts.map +1 -1
- package/dist/core/MembershipMaterialization.d.ts +7 -0
- package/dist/core/MembershipMaterialization.d.ts.map +1 -0
- package/dist/core/MembershipMaterialization.js +283 -0
- package/dist/core/MembershipMaterialization.js.map +1 -0
- package/dist/core/MutationEffects.d.ts +29 -0
- package/dist/core/MutationEffects.d.ts.map +1 -0
- package/dist/core/MutationEffects.js +318 -0
- package/dist/core/MutationEffects.js.map +1 -0
- package/dist/core/OverflowTurn.d.ts +4 -4
- package/dist/core/OverflowTurn.d.ts.map +1 -1
- package/dist/core/OverflowTurn.js +6 -7
- package/dist/core/OverflowTurn.js.map +1 -1
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +31 -9
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ResourceMutations.d.ts +12 -36
- package/dist/core/ResourceMutations.d.ts.map +1 -1
- package/dist/core/ResourceMutations.js +27 -1351
- package/dist/core/ResourceMutations.js.map +1 -1
- package/dist/core/ResourceSelector.d.ts +21 -0
- package/dist/core/ResourceSelector.d.ts.map +1 -0
- package/dist/core/ResourceSelector.js +233 -0
- package/dist/core/ResourceSelector.js.map +1 -0
- package/dist/core/ResourceTransfers.d.ts +55 -0
- package/dist/core/ResourceTransfers.d.ts.map +1 -0
- package/dist/core/ResourceTransfers.js +525 -0
- package/dist/core/ResourceTransfers.js.map +1 -0
- package/dist/core/SchemeRegistry.js +2 -2
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/SendBroadcastHandler.d.ts +40 -0
- package/dist/core/SendBroadcastHandler.d.ts.map +1 -0
- package/dist/core/SendBroadcastHandler.js +159 -0
- package/dist/core/SendBroadcastHandler.js.map +1 -0
- package/dist/core/StoredPacket.d.ts +11 -0
- package/dist/core/StoredPacket.d.ts.map +1 -1
- package/dist/core/StoredPacket.js +22 -1
- package/dist/core/StoredPacket.js.map +1 -1
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +19 -9
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/TokenCalibration.d.ts +16 -0
- package/dist/core/TokenCalibration.d.ts.map +1 -0
- package/dist/core/TokenCalibration.js +35 -0
- package/dist/core/TokenCalibration.js.map +1 -0
- package/dist/core/ToolResources.d.ts.map +1 -1
- package/dist/core/ToolResources.js +15 -12
- package/dist/core/ToolResources.js.map +1 -1
- package/dist/core/TurnMaterialization.d.ts +37 -0
- package/dist/core/TurnMaterialization.d.ts.map +1 -0
- package/dist/core/TurnMaterialization.js +296 -0
- package/dist/core/TurnMaterialization.js.map +1 -0
- package/dist/core/TurnOps.d.ts.map +1 -1
- package/dist/core/TurnOps.js +6 -8
- package/dist/core/TurnOps.js.map +1 -1
- package/dist/core/TurnRunner.d.ts +15 -27
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +76 -670
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/WorkerControlHandler.d.ts +15 -0
- package/dist/core/WorkerControlHandler.d.ts.map +1 -0
- package/dist/core/WorkerControlHandler.js +89 -0
- package/dist/core/WorkerControlHandler.js.map +1 -0
- package/dist/core/attachments.d.ts +14 -0
- package/dist/core/attachments.d.ts.map +1 -0
- package/dist/core/attachments.js +18 -0
- package/dist/core/attachments.js.map +1 -0
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +0 -4
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +5 -16
- package/dist/core/git-membership.d.ts +15 -0
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +4 -273
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.d.ts +2 -1
- package/dist/core/git-state.d.ts.map +1 -1
- package/dist/core/mutation-types.d.ts +102 -0
- package/dist/core/mutation-types.d.ts.map +1 -0
- package/dist/core/mutation-types.js +2 -0
- package/dist/core/mutation-types.js.map +1 -0
- package/dist/core/operation-target-groups.js +1 -1
- package/dist/core/operation-target-groups.js.map +1 -1
- package/dist/core/packet-wire.d.ts +7 -6
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +96 -68
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +0 -1
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +1 -1
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/statement-primary.d.ts +4 -0
- package/dist/core/statement-primary.d.ts.map +1 -0
- package/dist/core/statement-primary.js +3 -0
- package/dist/core/statement-primary.js.map +1 -0
- package/dist/core/teaching-corpus.d.ts +0 -1
- package/dist/core/teaching-corpus.d.ts.map +1 -1
- package/dist/core/teaching-corpus.js +0 -9
- package/dist/core/teaching-corpus.js.map +1 -1
- package/dist/core/turn-scheduler.js +3 -3
- package/dist/core/turn-scheduler.js.map +1 -1
- package/dist/core/turn-signals.d.ts +15 -0
- package/dist/core/turn-signals.d.ts.map +1 -0
- package/dist/core/turn-signals.js +19 -0
- package/dist/core/turn-signals.js.map +1 -0
- package/dist/core/worker-settings.d.ts +0 -1
- package/dist/core/worker-settings.d.ts.map +1 -1
- package/dist/core/worker-settings.js +1 -1
- package/dist/core/worker-settings.js.map +1 -1
- package/dist/digest/Digest.d.ts +3 -16
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +10 -958
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/DigestRender.d.ts +12 -0
- package/dist/digest/DigestRender.d.ts.map +1 -0
- package/dist/digest/DigestRender.js +641 -0
- package/dist/digest/DigestRender.js.map +1 -0
- package/dist/digest/DigestRequiem.d.ts +13 -0
- package/dist/digest/DigestRequiem.d.ts.map +1 -0
- package/dist/digest/DigestRequiem.js +332 -0
- package/dist/digest/DigestRequiem.js.map +1 -0
- package/dist/digest/digest-rows.d.ts +303 -0
- package/dist/digest/digest-rows.d.ts.map +1 -0
- package/dist/digest/digest-rows.js +2 -0
- package/dist/digest/digest-rows.js.map +1 -0
- package/dist/digest/digest.sql +1 -7
- package/dist/observe/api.d.ts +0 -2
- package/dist/observe/api.d.ts.map +1 -1
- package/dist/observe/api.js +2 -2
- package/dist/observe/api.js.map +1 -1
- package/dist/observe/metrics.d.ts +0 -1
- package/dist/observe/metrics.d.ts.map +1 -1
- package/dist/observe/metrics.js +0 -1
- package/dist/observe/metrics.js.map +1 -1
- package/dist/schemes/Exec.d.ts +4 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +89 -48
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.d.ts +2 -1
- package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +3 -3
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/File.d.ts +4 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +100 -13
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts +4 -5
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +26 -118
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +1 -66
- package/dist/schemes/Prompt.js +1 -1
- package/dist/schemes/Prompt.js.map +1 -1
- package/dist/schemes/Worker.d.ts.map +1 -1
- package/dist/schemes/Worker.js +1 -13
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +3 -1
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +18 -3
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts +4 -2
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +79 -10
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts +1 -2
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +2 -1
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +20 -2
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-semantic.d.ts +1 -0
- package/dist/schemes/_entry-semantic.d.ts.map +1 -1
- package/dist/schemes/_entry-semantic.js +3 -0
- package/dist/schemes/_entry-semantic.js.map +1 -1
- package/dist/schemes/_entry-semantic.sql +2 -2
- package/dist/schemes/_entry-send.d.ts +0 -1
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +5 -80
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/schemes/exec-runtime.d.ts +7 -0
- package/dist/schemes/exec-runtime.d.ts.map +1 -0
- package/dist/schemes/exec-runtime.js +2 -0
- package/dist/schemes/exec-runtime.js.map +1 -0
- package/dist/server/ClientReads.d.ts +34 -0
- package/dist/server/ClientReads.d.ts.map +1 -0
- package/dist/server/ClientReads.js +210 -0
- package/dist/server/ClientReads.js.map +1 -0
- package/dist/server/Daemon.d.ts +12 -22
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +31 -362
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DaemonModule.d.ts +6 -5
- package/dist/server/DaemonModule.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +3 -2
- package/dist/server/DrainSupervisor.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.js +7 -3
- package/dist/server/DrainSupervisor.js.map +1 -1
- package/dist/server/Functionality.d.ts +0 -12
- package/dist/server/Functionality.d.ts.map +1 -1
- package/dist/server/Functionality.js.map +1 -1
- package/dist/server/FunctionalityManager.d.ts +0 -1
- package/dist/server/FunctionalityManager.d.ts.map +1 -1
- package/dist/server/FunctionalityManager.js +3 -3
- package/dist/server/FunctionalityManager.js.map +1 -1
- package/dist/server/MembersFunctionality.d.ts +2 -13
- package/dist/server/MembersFunctionality.d.ts.map +1 -1
- package/dist/server/MembersFunctionality.js +4 -4
- package/dist/server/MembersFunctionality.js.map +1 -1
- package/dist/server/SkillsFunctionality.d.ts +1 -7
- package/dist/server/SkillsFunctionality.d.ts.map +1 -1
- package/dist/server/SkillsFunctionality.js +3 -3
- package/dist/server/SkillsFunctionality.js.map +1 -1
- package/dist/server/WorkerModelResolver.d.ts +17 -0
- package/dist/server/WorkerModelResolver.d.ts.map +1 -0
- package/dist/server/WorkerModelResolver.js +166 -0
- package/dist/server/WorkerModelResolver.js.map +1 -0
- package/dist/server/daemon-results.d.ts +5 -0
- package/dist/server/daemon-results.d.ts.map +1 -0
- package/dist/server/daemon-results.js +7 -0
- package/dist/server/daemon-results.js.map +1 -0
- package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
- package/dist/server/dispatch-as-plurnk.js +4 -6
- package/dist/server/dispatch-as-plurnk.js.map +1 -1
- package/dist/server/logEntry.d.ts +0 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +1 -13
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/logEntry.sql +1 -5
- package/dist/server/loopDocs.d.ts.map +1 -1
- package/dist/server/loopDocs.js +11 -2
- package/dist/server/loopDocs.js.map +1 -1
- package/migrations/001_schema.sql +30 -347
- package/package.json +30 -30
|
@@ -1,1361 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { entryCoordinateOf, renderAddress, renderTarget, schemeNameOf } from "./plurnk-uri.js";
|
|
6
|
-
import { assertEditBatchReceipt, assertEditReceipt, assertResourceEffects, EditCollision, editReceipt, LineAnchors, LineMarkerOps, MimetypeBinary, PathMimetype, projectEditReceipt, } from "../content/index.js";
|
|
7
|
-
import DbProjectionCaps from "./caps/DbProjectionCaps.js";
|
|
8
|
-
import SchemeCtxImpl from "./caps/SchemeCtxImpl.js";
|
|
9
|
-
import EntryCrud from "../schemes/_entry-crud.js";
|
|
10
|
-
import Results from "./results.js";
|
|
11
|
-
import EntryManifest from "../schemes/_entry-manifest.js";
|
|
1
|
+
import MutationEffects from "./MutationEffects.js";
|
|
2
|
+
import EditMutations from "./EditMutations.js";
|
|
3
|
+
import ResourceSelector from "./ResourceSelector.js";
|
|
4
|
+
import ResourceTransfers from "./ResourceTransfers.js";
|
|
12
5
|
// Owns EDIT batch state and COPY/MOVE resource mutation composition.
|
|
13
6
|
// Dispatcher retains admission, generic scheme routing, proposal lifecycle, and durable operation recording.
|
|
14
|
-
class ResourceMutations {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
#schemes;
|
|
19
|
-
#liveSubscriptions;
|
|
20
|
-
#run;
|
|
21
|
-
#checkWritable;
|
|
22
|
-
#checkCapabilities;
|
|
23
|
-
#editTargetIdentity;
|
|
24
|
-
#canonicalFilePath;
|
|
25
|
-
#prepareDataRepresentation;
|
|
26
|
-
#resolveDataEntryAddress;
|
|
27
|
-
#readEntry;
|
|
28
|
-
#writeEntry;
|
|
29
|
-
#deleteChannel;
|
|
30
|
-
#applyProposal;
|
|
31
|
-
#preparedEdits = new WeakMap();
|
|
7
|
+
export default class ResourceMutations {
|
|
8
|
+
#edits;
|
|
9
|
+
#selection;
|
|
10
|
+
#transfers;
|
|
32
11
|
constructor({ schemes, liveSubscriptions, run, checkWritable, checkCapabilities, editTargetIdentity, canonicalFilePath, prepareDataRepresentation, resolveDataEntryAddress, readEntry, writeEntry, deleteChannel, applyProposal, }) {
|
|
33
|
-
this.#
|
|
34
|
-
this.#
|
|
35
|
-
this.#
|
|
36
|
-
this.#checkWritable = checkWritable;
|
|
37
|
-
this.#checkCapabilities = checkCapabilities;
|
|
38
|
-
this.#editTargetIdentity = editTargetIdentity;
|
|
39
|
-
this.#canonicalFilePath = canonicalFilePath;
|
|
40
|
-
this.#prepareDataRepresentation = prepareDataRepresentation;
|
|
41
|
-
this.#resolveDataEntryAddress = resolveDataEntryAddress;
|
|
42
|
-
this.#readEntry = readEntry;
|
|
43
|
-
this.#writeEntry = writeEntry;
|
|
44
|
-
this.#deleteChannel = deleteChannel;
|
|
45
|
-
this.#applyProposal = applyProposal;
|
|
46
|
-
}
|
|
47
|
-
async #resolveEditAnchors(statements, identity, schemeName, manifest, ctx) {
|
|
48
|
-
const anchored = statements.filter(({ lineMarker }) => LineAnchors.hasAnchor(lineMarker));
|
|
49
|
-
// {§edit-batch-merges} — a body that looks like a pasted READ rendering needs the current
|
|
50
|
-
// anchors to be verified, so it takes the anchored path even without an anchor of its own.
|
|
51
|
-
const looksRendered = ({ body }) => {
|
|
52
|
-
const rows = (body ?? "").split("\n").filter((row) => row.length > 0);
|
|
53
|
-
return rows.length > 0 && rows.every((row) => LineAnchors.isAnchoredLine(row));
|
|
54
|
-
};
|
|
55
|
-
const rendered = statements.some(looksRendered)
|
|
56
|
-
&& manifest.textEditScopes === true && manifest.writableBy.includes("model");
|
|
57
|
-
if (anchored.length === 0 && !rendered) {
|
|
58
|
-
return { statements: statements, precondition: null, prefixFacts: new Map() };
|
|
59
|
-
}
|
|
60
|
-
if (manifest.textEditScopes !== true || !manifest.writableBy.includes("model")) {
|
|
61
|
-
return {
|
|
62
|
-
result: _a.#failure("line-anchor-unsupported", 400, `Scheme '${schemeName}' does not support textual EDIT scopes.`, {}, {
|
|
63
|
-
scheme: schemeName,
|
|
64
|
-
operation: "EDIT",
|
|
65
|
-
recovery: "Remove the scope and submit the scheme's complete editable unit.",
|
|
66
|
-
retryable: false,
|
|
67
|
-
}),
|
|
68
|
-
failedStatement: null,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
if (identity === null) {
|
|
72
|
-
return {
|
|
73
|
-
result: _a.#failure("edit-target-required", 400, "A line-anchored EDIT requires a target resource.", {}, { recovery: "Provide the target that rendered the line anchor.", retryable: false }),
|
|
74
|
-
failedStatement: null,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
const first = statements[0];
|
|
78
|
-
if (first === undefined)
|
|
79
|
-
return { statements: [], precondition: null, prefixFacts: new Map() };
|
|
80
|
-
const current = await this.#run(schemeName, {
|
|
81
|
-
op: "READ",
|
|
82
|
-
delimiter: first.delimiter,
|
|
83
|
-
annotation: null,
|
|
84
|
-
signal: null,
|
|
85
|
-
target: first.target,
|
|
86
|
-
metadata: first.metadata,
|
|
87
|
-
lineMarker: { marks: [1, -1] },
|
|
88
|
-
body: null,
|
|
89
|
-
position: first.position,
|
|
90
|
-
}, ctx);
|
|
91
|
-
if (current.status === 204 || current.status === 404) {
|
|
92
|
-
return { result: EditCollision.result(identity), failedStatement: null };
|
|
93
|
-
}
|
|
94
|
-
if (current.status >= 300) {
|
|
95
|
-
return {
|
|
96
|
-
result: _a.#failure("line-anchor-validation-failed", current.status, `EDIT could not validate its line anchor at ${identity}: ${current.problem?.detail ?? `READ returned ${current.status}`}`, {}, {
|
|
97
|
-
target: identity,
|
|
98
|
-
upstreamStatus: current.status,
|
|
99
|
-
stage: "mutation-precondition",
|
|
100
|
-
retryable: false,
|
|
101
|
-
}),
|
|
102
|
-
failedStatement: null,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
if (current.status !== 200) {
|
|
106
|
-
return { result: EditCollision.result(identity), failedStatement: null };
|
|
107
|
-
}
|
|
108
|
-
const content = current.content;
|
|
109
|
-
if (typeof content !== "string") {
|
|
110
|
-
throw new InvalidOperationResultError(`Scheme '${schemeName}' returned READ ${current.status} without textual content while validating an EDIT anchor.`);
|
|
111
|
-
}
|
|
112
|
-
const lineAnchors = current.lineAnchors;
|
|
113
|
-
const lineAnchorIdentity = current.lineAnchorIdentity;
|
|
114
|
-
try {
|
|
115
|
-
LineAnchors.assertProjection(content, lineAnchors);
|
|
116
|
-
if (typeof lineAnchorIdentity !== "string" || lineAnchorIdentity.length === 0) {
|
|
117
|
-
throw new TypeError("READ line anchors require their canonical derivation identity.");
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
catch (cause) {
|
|
121
|
-
throw new InvalidOperationResultError(`Scheme '${schemeName}' returned READ 200 without its core-owned line-anchor projection.`, { cause });
|
|
122
|
-
}
|
|
123
|
-
const resolved = [];
|
|
124
|
-
const checks = [];
|
|
125
|
-
// {§edit-batch-receipt} — every anchor is resolved before any verdict, so a collision
|
|
126
|
-
// names each anchor that no longer resolves, not just the first (#428).
|
|
127
|
-
const stale = [];
|
|
128
|
-
// {§edit-batch-merges} — a body whose every line carries this resource's own rendered
|
|
129
|
-
// `@xxxxx L:` prefix, hash-verified at its ordinal against the current anchors, is the
|
|
130
|
-
// READ rendering pasted back: the prefixes are stripped and the row says so. A look-alike
|
|
131
|
-
// that does not verify is content and is written as authored, with the fact reported.
|
|
132
|
-
const prefixFacts = new Map();
|
|
133
|
-
let publishedReads = null;
|
|
134
|
-
const publishedAnchors = async () => {
|
|
135
|
-
if (publishedReads !== null)
|
|
136
|
-
return publishedReads;
|
|
137
|
-
const rows = await ctx.db.engine_render_log.all({ worker_id: ctx.workerId });
|
|
138
|
-
const reads = [];
|
|
139
|
-
for (const row of rows) {
|
|
140
|
-
if (row.op !== "READ" || row.status_rx !== 200 || typeof row.rx !== "string")
|
|
141
|
-
continue;
|
|
142
|
-
let parsed;
|
|
143
|
-
try {
|
|
144
|
-
parsed = JSON.parse(row.rx);
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (parsed.lineAnchorIdentity !== lineAnchorIdentity || !Array.isArray(parsed.lineAnchors))
|
|
150
|
-
continue;
|
|
151
|
-
reads.push({ startLine: typeof parsed.startLine === "number" ? parsed.startLine : 1, anchors: parsed.lineAnchors });
|
|
152
|
-
}
|
|
153
|
-
publishedReads = reads;
|
|
154
|
-
return reads;
|
|
155
|
-
};
|
|
156
|
-
const stripRendered = async (statement) => {
|
|
157
|
-
const body = statement.body;
|
|
158
|
-
if (typeof body !== "string" || body.length === 0)
|
|
159
|
-
return statement;
|
|
160
|
-
const rows = body.split("\n");
|
|
161
|
-
const filled = rows.filter((row) => row.length > 0);
|
|
162
|
-
if (filled.length === 0 || !filled.every((row) => LineAnchors.isAnchoredLine(row)))
|
|
163
|
-
return statement;
|
|
164
|
-
const pasted = filled.map((row) => { const match = /^(@[0-9A-Za-z]{5}) +(\d+):/.exec(row); return { hash: match[1], line: Number(match[2]) }; });
|
|
165
|
-
const verifies = (anchors, startLine) => pasted.every(({ hash, line }) => anchors[line - startLine] === hash);
|
|
166
|
-
const current = verifies(lineAnchors, 1);
|
|
167
|
-
const source = current ? "current" : (await publishedAnchors()).some(({ anchors, startLine }) => verifies(anchors, startLine)) ? "log" : null;
|
|
168
|
-
if (source === null) {
|
|
169
|
-
prefixFacts.set(statement, { rule: "rendered-prefix-unverified", lines: filled.length });
|
|
170
|
-
return statement;
|
|
171
|
-
}
|
|
172
|
-
prefixFacts.set(statement, { rule: "rendered-prefix-stripped", lines: filled.length, source });
|
|
173
|
-
// A rendered row per line: a blank last line keeps its terminator so the paste
|
|
174
|
-
// reproduces exactly the lines it rendered.
|
|
175
|
-
const stripped = rows.map((row) => row.replace(/^@[0-9A-Za-z]{5} +\d+:/, ""));
|
|
176
|
-
return { ...statement, body: stripped.join("\n") + (stripped.length > 1 && stripped.at(-1) === "" ? "\n" : "") };
|
|
177
|
-
};
|
|
178
|
-
for (const authored of statements) {
|
|
179
|
-
const statement = await stripRendered(authored);
|
|
180
|
-
if (statement.lineMarker === null || !LineAnchors.hasAnchor(statement.lineMarker)) {
|
|
181
|
-
resolved.push(statement);
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
const resolution = LineAnchors.resolve(lineAnchors, statement.lineMarker);
|
|
185
|
-
if (!resolution.ok) {
|
|
186
|
-
const { anchor, kind } = resolution.failure;
|
|
187
|
-
const invalid = kind === "invalid";
|
|
188
|
-
if (!invalid) {
|
|
189
|
-
stale.push({ anchor, kind, ...("matches" in resolution.failure ? { lines: resolution.failure.matches } : {}) });
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
result: _a.#failure("line-anchor-invalid", 400, LineAnchors.invalidCoordinateDetail, {}, {
|
|
194
|
-
anchor,
|
|
195
|
-
target: identity,
|
|
196
|
-
recovery: LineAnchors.invalidCoordinateRecovery,
|
|
197
|
-
retryable: false,
|
|
198
|
-
}),
|
|
199
|
-
failedStatement: authored,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
for (const [index, anchor] of statement.lineMarker.marks.entries()) {
|
|
203
|
-
if (typeof anchor !== "string")
|
|
204
|
-
continue;
|
|
205
|
-
const line = resolution.marker.marks[index];
|
|
206
|
-
if (typeof line !== "number") {
|
|
207
|
-
throw new InvalidOperationResultError("An EDIT line anchor did not lower to a numeric line.");
|
|
208
|
-
}
|
|
209
|
-
checks.push({ anchor, line });
|
|
210
|
-
}
|
|
211
|
-
resolved.push({ ...statement, lineMarker: resolution.marker });
|
|
212
|
-
}
|
|
213
|
-
if (stale.length > 0) {
|
|
214
|
-
const named = stale.map((s) => s.kind === "ambiguous" ? `${s.anchor} (matches lines ${(s.lines ?? []).join(", ")})` : s.anchor).join(", ");
|
|
215
|
-
return {
|
|
216
|
-
result: EditCollision.result(lineAnchorIdentity, {}, {
|
|
217
|
-
staleAnchors: stale,
|
|
218
|
-
editCount: statements.length,
|
|
219
|
-
applied: 0,
|
|
220
|
-
recovery: `${named} no longer resolve${stale.length === 1 ? "s" : ""} — the line moved or changed since the READ that rendered ${stale.length === 1 ? "it" : "them"}; 0 of ${statements.length} edits in this batch were applied. READ the target again before selecting current coordinates.`,
|
|
221
|
-
}),
|
|
222
|
-
failedStatement: null,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
const uniqueChecks = [...new Map(checks.map((check) => [`${check.anchor}:${check.line}`, check])).values()];
|
|
226
|
-
return {
|
|
227
|
-
statements: resolved,
|
|
228
|
-
// A strip-only batch authored no anchor and so carries no compare-and-swap precondition.
|
|
229
|
-
precondition: uniqueChecks.length === 0 ? null : { identity: lineAnchorIdentity, checks: uniqueChecks },
|
|
230
|
-
prefixFacts,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
async prepareEditBatches(statements, context, schemeCtx) {
|
|
234
|
-
const { workspaceId, loopId, origin } = context;
|
|
235
|
-
const ctx = schemeCtx;
|
|
236
|
-
const groups = new Map();
|
|
237
|
-
for (const statement of statements) {
|
|
238
|
-
const { key, identity } = await this.#editTargetIdentity(statement, workspaceId, ctx.functionalityWorkerId);
|
|
239
|
-
const group = groups.get(key);
|
|
240
|
-
if (group === undefined)
|
|
241
|
-
groups.set(key, {
|
|
242
|
-
identity,
|
|
243
|
-
statements: [statement],
|
|
244
|
-
});
|
|
245
|
-
else
|
|
246
|
-
group.statements.push(statement);
|
|
247
|
-
}
|
|
248
|
-
for (const preparedGroup of groups.values()) {
|
|
249
|
-
const group = preparedGroup.statements;
|
|
250
|
-
const first = group[0];
|
|
251
|
-
const schemeName = schemeNameOf(first.target);
|
|
252
|
-
let initial;
|
|
253
|
-
let projections = null;
|
|
254
|
-
let prefixFacts = new Map();
|
|
255
|
-
let denial = group.map((statement) => this.#checkWritable(statement, origin, ctx.functionalityWorkerId)).find((result) => result !== null) ?? null;
|
|
256
|
-
if (denial === null) {
|
|
257
|
-
for (const statement of group) {
|
|
258
|
-
denial = await this.#checkCapabilities(statement, ctx.workspaceId, loopId, ctx.functionalityWorkerId);
|
|
259
|
-
if (denial !== null)
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
if (denial !== null) {
|
|
264
|
-
initial = denial;
|
|
265
|
-
}
|
|
266
|
-
else if (schemeName === null) {
|
|
267
|
-
initial = _a.#failure("target-required", 400, "EDIT requires a target scheme.", {}, { retryable: false });
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
const handler = this.#schemes.get(schemeName, ctx.functionalityWorkerId);
|
|
271
|
-
const method = handler?.editBatch;
|
|
272
|
-
const manifest = this.#schemes.manifestFor(schemeName, ctx.functionalityWorkerId);
|
|
273
|
-
if (handler === undefined || typeof method !== "function" || manifest?.category !== "data") {
|
|
274
|
-
initial = _a.#failure("operation-not-implemented", 501, `Scheme '${schemeName}' does not implement EDIT batches.`, {}, {
|
|
275
|
-
scheme: schemeName,
|
|
276
|
-
operation: "EDIT",
|
|
277
|
-
retryable: false,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
else if (group.some(({ metadata }) => metadata !== null) && manifest.metadataModifier !== true) {
|
|
281
|
-
initial = _a.#failure("scheme-metadata-unsupported", 400, `Scheme '${schemeName}' does not accept the {metadata} modifier.`, {}, { scheme: schemeName, operation: "EDIT", retryable: false });
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
try {
|
|
285
|
-
const resolved = await this.#resolveEditAnchors(group, preparedGroup.identity, schemeName, manifest, schemeCtx);
|
|
286
|
-
if ("result" in resolved) {
|
|
287
|
-
initial = resolved.result;
|
|
288
|
-
if (resolved.failedStatement !== null) {
|
|
289
|
-
projections = new Map(group.map((statement) => [
|
|
290
|
-
statement,
|
|
291
|
-
statement === resolved.failedStatement
|
|
292
|
-
? resolved.result
|
|
293
|
-
: _a.#failure("edit-batch-rejected", 424, "This EDIT was not applied because another EDIT in the same resource batch was invalid.", {}, {
|
|
294
|
-
operation: "EDIT",
|
|
295
|
-
target: preparedGroup.identity,
|
|
296
|
-
retryable: false,
|
|
297
|
-
}),
|
|
298
|
-
]));
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
prefixFacts = resolved.prefixFacts;
|
|
303
|
-
const addressedScheme = first.target?.kind === "url" ? first.target.scheme : schemeName;
|
|
304
|
-
const publishedChannel = first.target?.kind === "url"
|
|
305
|
-
? first.target.fragment ?? manifest.defaultChannel
|
|
306
|
-
: manifest.defaultChannel;
|
|
307
|
-
if (first.target === null) {
|
|
308
|
-
throw new InvalidOperationResultError("An EDIT batch has no target.");
|
|
309
|
-
}
|
|
310
|
-
const binding = await this.#resolveDataEntryAddress({
|
|
311
|
-
target: first.target,
|
|
312
|
-
routedScheme: schemeName,
|
|
313
|
-
handler,
|
|
314
|
-
manifest,
|
|
315
|
-
ctx: schemeCtx,
|
|
316
|
-
});
|
|
317
|
-
if (binding.result !== null) {
|
|
318
|
-
initial = binding.result;
|
|
319
|
-
}
|
|
320
|
-
else if (binding.address === null) {
|
|
321
|
-
initial = _a.#failure("entry-not-found", 404, "The EDIT target could not be resolved.");
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
initial = Results.assert(await method.call(handler, resolved.statements, new SchemeCtxImpl(schemeCtx, addressedScheme ?? schemeName, manifest, this.#liveSubscriptions, {
|
|
325
|
-
authority: binding.address.authority,
|
|
326
|
-
ownerId: binding.address.ownerId,
|
|
327
|
-
publishedChannel,
|
|
328
|
-
editPrecondition: resolved.precondition,
|
|
329
|
-
})));
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
catch (err) {
|
|
334
|
-
if (err instanceof InvalidOperationResultError)
|
|
335
|
-
throw err;
|
|
336
|
-
console.error(`Scheme '${schemeName}' EDIT batch threw outside its operation result contract:`, err);
|
|
337
|
-
initial = _a.#failure("scheme-handler-threw", 500, `The '${schemeName}' scheme did not produce an EDIT result.`, {}, {
|
|
338
|
-
stage: "scheme-dispatch",
|
|
339
|
-
scheme: schemeName,
|
|
340
|
-
operation: "EDIT",
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
let resolveSettled;
|
|
346
|
-
const settled = new Promise((resolve) => { resolveSettled = resolve; });
|
|
347
|
-
const candidate = initial.editReceipt;
|
|
348
|
-
const expectedNormalizations = group.filter(({ lineMarker }) => lineMarker?.marks.length === 3).length;
|
|
349
|
-
if (initial.status < 400
|
|
350
|
-
&& (initial.scopeNormalizations?.length ?? 0) !== expectedNormalizations) {
|
|
351
|
-
throw new InvalidOperationResultError(`EDIT batch normalized ${initial.scopeNormalizations?.length ?? 0} scope(s), expected ${expectedNormalizations}.`);
|
|
352
|
-
}
|
|
353
|
-
const batch = {
|
|
354
|
-
initial,
|
|
355
|
-
settled,
|
|
356
|
-
aggregate: candidate === undefined || candidate === null
|
|
357
|
-
? undefined
|
|
358
|
-
: assertEditBatchReceipt(candidate),
|
|
359
|
-
settle: resolveSettled,
|
|
360
|
-
};
|
|
361
|
-
// {§edit-batch-merges} — the scheme's merge facts are keyed by authored index; a dropped
|
|
362
|
-
// duplicate has no effect in the applied-edits receipt, so receipt indices re-align.
|
|
363
|
-
const merges = initial.status < 400
|
|
364
|
-
? (initial.merges ?? [])
|
|
365
|
-
: [];
|
|
366
|
-
const DROPPING_RULES = new Set(["duplicate-of", "contained-relocated", "contained-already-applied"]);
|
|
367
|
-
const droppedIndices = new Set(merges.filter(({ rule }) => DROPPING_RULES.has(rule)).map(({ index }) => index));
|
|
368
|
-
let normalizationIndex = 0;
|
|
369
|
-
for (const [index, statement] of group.entries()) {
|
|
370
|
-
const dropped = droppedIndices.has(index);
|
|
371
|
-
const ownsNormalization = statement.lineMarker?.marks.length === 3 && initial.status < 400 && !dropped;
|
|
372
|
-
const own = merges.filter((merge) => merge.index === index).map(({ index: _index, ...fact }) => fact);
|
|
373
|
-
const prefix = prefixFacts.get(statement);
|
|
374
|
-
if (prefix !== undefined)
|
|
375
|
-
own.push(prefix);
|
|
376
|
-
this.#preparedEdits.set(statement, {
|
|
377
|
-
first: index === 0,
|
|
378
|
-
index,
|
|
379
|
-
normalizationIndex: ownsNormalization ? normalizationIndex++ : null,
|
|
380
|
-
projection: projections?.get(statement) ?? null,
|
|
381
|
-
batch,
|
|
382
|
-
receiptIndex: dropped ? null : index - [...droppedIndices].filter((d) => d < index).length,
|
|
383
|
-
merged: own,
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
preparedEditResult(statement) {
|
|
389
|
-
const prepared = this.#preparedEdits.get(statement);
|
|
390
|
-
if (prepared === undefined) {
|
|
391
|
-
throw new InvalidOperationResultError("EDIT reached dispatch without a prepared resource batch.");
|
|
392
|
-
}
|
|
393
|
-
return this.#projectPreparedEdit(prepared);
|
|
394
|
-
}
|
|
395
|
-
async #projectPreparedEdit(prepared) {
|
|
396
|
-
if (prepared.projection !== null)
|
|
397
|
-
return prepared.projection;
|
|
398
|
-
const settled = prepared.first
|
|
399
|
-
? prepared.batch.initial
|
|
400
|
-
: await prepared.batch.settled;
|
|
401
|
-
const normalization = prepared.normalizationIndex === null
|
|
402
|
-
? undefined
|
|
403
|
-
: settled.scopeNormalizations?.[prepared.normalizationIndex];
|
|
404
|
-
const { scopeNormalizations: _scopeNormalizations, merges: _merges, applied: _applied, merged: _merged, ...projectedSettlement } = settled;
|
|
405
|
-
const statementResult = {
|
|
406
|
-
...(normalization === undefined ? projectedSettlement : { ...projectedSettlement, scopeNormalizations: [normalization] }),
|
|
407
|
-
...(prepared.merged.length === 0 ? {} : { merged: prepared.merged }),
|
|
408
|
-
};
|
|
409
|
-
const aggregate = prepared.batch.aggregate;
|
|
410
|
-
if (aggregate === undefined)
|
|
411
|
-
return statementResult;
|
|
412
|
-
const receipt = assertEditBatchReceipt(aggregate);
|
|
413
|
-
// The settled result may already carry the first statement's projected receipt; every
|
|
414
|
-
// row projects its own from the batch aggregate.
|
|
415
|
-
const { editReceipt: _editReceipt, receipt: _receipt, ...withoutAggregate } = statementResult;
|
|
416
|
-
// A dropped duplicate applied nothing: its row carries the merge fact, not a twin's effect.
|
|
417
|
-
if (prepared.receiptIndex === null)
|
|
418
|
-
return withoutAggregate;
|
|
419
|
-
return {
|
|
420
|
-
...withoutAggregate,
|
|
421
|
-
receipt: projectEditReceipt(receipt, prepared.receiptIndex),
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
withMergeFacts(statement, result) {
|
|
425
|
-
const prepared = this.#preparedEdits.get(statement);
|
|
426
|
-
if (prepared === undefined || prepared.merged.length === 0)
|
|
427
|
-
return result;
|
|
428
|
-
return Results.assert({ ...result, merged: prepared.merged });
|
|
429
|
-
}
|
|
430
|
-
settleEdit(statement, result) {
|
|
431
|
-
const prepared = this.#preparedEdits.get(statement);
|
|
432
|
-
if (prepared?.first !== true)
|
|
433
|
-
return;
|
|
434
|
-
const normalizations = prepared.batch.initial.scopeNormalizations;
|
|
435
|
-
prepared.batch.settle(normalizations === undefined
|
|
436
|
-
? result
|
|
437
|
-
: Results.assert({ ...result, scopeNormalizations: normalizations }));
|
|
438
|
-
}
|
|
439
|
-
// The documented append region is exactly `<-1>`: one mark, the line after the last.
|
|
440
|
-
static #isAppendMarker(marker) {
|
|
441
|
-
const marks = marker?.marks;
|
|
442
|
-
return Array.isArray(marks) && marks.length === 1 && marks[0] === -1;
|
|
443
|
-
}
|
|
444
|
-
static #isCompleteAbsentDestinationMarker(marker, content) {
|
|
445
|
-
const marks = marker?.marks;
|
|
446
|
-
if (!Array.isArray(marks))
|
|
447
|
-
return false;
|
|
448
|
-
if (marks.length === 2 && marks[0] === 1 && marks[1] === -1)
|
|
449
|
-
return true;
|
|
450
|
-
const lineCount = TextCoordinates.logicalLines(content).length;
|
|
451
|
-
return lineCount > 0 && marks[0] === 1 && ((marks.length === 1 && lineCount === 1)
|
|
452
|
-
|| (marks.length === 2 && marks[1] === lineCount));
|
|
453
|
-
}
|
|
454
|
-
async handleCopy(statement, ctx) {
|
|
455
|
-
return this.#copyOrchestration({
|
|
456
|
-
statement,
|
|
457
|
-
source: statement.source,
|
|
458
|
-
destination: statement.destination,
|
|
459
|
-
ctx,
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
async handleMove(statement, ctx) {
|
|
463
|
-
const sourceMarks = statement.source.lineMarker?.marks;
|
|
464
|
-
const sourceLineMarker = sourceMarks?.length === 2
|
|
465
|
-
&& sourceMarks[0] === 1
|
|
466
|
-
&& sourceMarks[1] === -1
|
|
467
|
-
? null
|
|
468
|
-
: statement.source.lineMarker;
|
|
469
|
-
return this.#moveOrchestration({
|
|
470
|
-
statement,
|
|
471
|
-
source: {
|
|
472
|
-
...statement.source,
|
|
473
|
-
// Canonicalize only the execution selection. #writeLog retains
|
|
474
|
-
// the authored marker as operation evidence. {§move-canonical-whole-source}
|
|
475
|
-
lineMarker: sourceLineMarker,
|
|
476
|
-
},
|
|
477
|
-
destination: statement.destination,
|
|
478
|
-
ctx,
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
static #isDispatchResult(value) {
|
|
482
|
-
return "status" in value;
|
|
483
|
-
}
|
|
484
|
-
async #resolveResourceSelection(selection, ctx) {
|
|
485
|
-
const { target, metadata, lineMarker } = selection;
|
|
486
|
-
const scheme = schemeNameOf(target);
|
|
487
|
-
if (scheme === null) {
|
|
488
|
-
return _a.#failure("resource-scheme-required", 400, "COPY and MOVE resources require a scheme.", {}, { retryable: false });
|
|
489
|
-
}
|
|
490
|
-
// `~` is the caller's own space ({§worker-authority-carving}), not a name:
|
|
491
|
-
// COPY and MOVE reach the commons and the private space, never another worker's.
|
|
492
|
-
const workerAuthority = target.kind === "url" && target.scheme === "worker" ? target.hostname ?? "" : "";
|
|
493
|
-
if (workerAuthority !== "" && workerAuthority !== "~") {
|
|
494
|
-
return _a.#failure("worker-copy-address-invalid", 400, "COPY and MOVE do not address named worker spaces.", {}, {
|
|
495
|
-
recovery: "Move worker-space content with READ and EDIT.",
|
|
496
|
-
retryable: false,
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
const handler = this.#schemes.get(scheme, ctx.functionalityWorkerId);
|
|
500
|
-
const manifest = this.#schemes.manifestFor(scheme, ctx.functionalityWorkerId);
|
|
501
|
-
if (handler === undefined || manifest === undefined) {
|
|
502
|
-
return _a.#failure("scheme-not-found", 501, `COPY or MOVE addressed the unregistered scheme '${scheme}'.`, {}, {
|
|
503
|
-
scheme,
|
|
504
|
-
retryable: false,
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
if (manifest.category !== "data") {
|
|
508
|
-
return _a.#failure("entry-operation-unsupported", 400, `COPY and MOVE require entry-bearing resources; '${scheme}' is a ${manifest.category} scheme.`, {}, {
|
|
509
|
-
scheme,
|
|
510
|
-
category: manifest.category,
|
|
511
|
-
retryable: false,
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
const fragment = target.kind === "url" ? target.fragment : null;
|
|
515
|
-
const channel = fragment ?? manifest.defaultChannel;
|
|
516
|
-
if (channel.length === 0) {
|
|
517
|
-
return _a.#failure("channel-required", 400, `The '${scheme}' scheme has no default channel.`, {}, {
|
|
518
|
-
scheme,
|
|
519
|
-
recovery: "Address a named channel with a URI fragment.",
|
|
520
|
-
retryable: false,
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
if (fragment !== null
|
|
524
|
-
&& fragment !== manifest.defaultChannel
|
|
525
|
-
&& !Object.hasOwn(manifest.channels, fragment)) {
|
|
526
|
-
const availableChannels = [
|
|
527
|
-
...new Set([manifest.defaultChannel, ...Object.keys(manifest.channels)]),
|
|
528
|
-
].filter((candidate) => candidate.length > 0);
|
|
529
|
-
return _a.#failure("channel-not-found", 400, `Channel #${fragment} is not declared by the '${scheme}' scheme.`, {}, {
|
|
530
|
-
requestedChannel: fragment,
|
|
531
|
-
availableChannels,
|
|
532
|
-
retryable: false,
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
const coordinate = entryCoordinateOf(target, manifest.authority ?? "namespace");
|
|
536
|
-
const { authority, pathname } = coordinate;
|
|
537
|
-
const canonicalFilePath = scheme === "file"
|
|
538
|
-
? await this.#canonicalFilePath(pathname, ctx.workspaceId)
|
|
539
|
-
: pathname;
|
|
540
|
-
return {
|
|
541
|
-
target,
|
|
542
|
-
metadata,
|
|
543
|
-
lineMarker,
|
|
544
|
-
scheme,
|
|
545
|
-
authority,
|
|
546
|
-
pathname,
|
|
547
|
-
identityPathname: canonicalFilePath ?? pathname,
|
|
548
|
-
channel,
|
|
549
|
-
manifest,
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
#resolveResourceLineMarker(selection, content, operation) {
|
|
553
|
-
if (!LineAnchors.hasAnchor(selection.lineMarker)) {
|
|
554
|
-
return {
|
|
555
|
-
selection: {
|
|
556
|
-
...selection,
|
|
557
|
-
lineMarker: selection.lineMarker,
|
|
558
|
-
},
|
|
559
|
-
precondition: null,
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
const target = this.#resourceAddress(selection);
|
|
563
|
-
if (selection.manifest.textEditScopes !== true || !selection.manifest.writableBy.includes("model")) {
|
|
564
|
-
return {
|
|
565
|
-
result: _a.#failure("line-anchor-unsupported", 400, `The representation at ${target} does not publish line anchors.`, {}, {
|
|
566
|
-
operation,
|
|
567
|
-
target,
|
|
568
|
-
recovery: "Use numeric text coordinates.",
|
|
569
|
-
retryable: false,
|
|
570
|
-
}),
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
const resolution = LineAnchors.resolve(LineAnchors.tokens(target, content), selection.lineMarker);
|
|
574
|
-
if (!resolution.ok) {
|
|
575
|
-
if (resolution.failure.kind === "invalid") {
|
|
576
|
-
return {
|
|
577
|
-
result: _a.#failure("line-anchor-invalid", 400, LineAnchors.invalidCoordinateDetail, {}, {
|
|
578
|
-
operation,
|
|
579
|
-
target,
|
|
580
|
-
recovery: LineAnchors.invalidCoordinateRecovery,
|
|
581
|
-
retryable: false,
|
|
582
|
-
}),
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
return {
|
|
586
|
-
result: _a.#failure("line-anchor-collision", 409, `${operation} coordinates collided with current content at ${target}.`, {}, {
|
|
587
|
-
operation,
|
|
588
|
-
target,
|
|
589
|
-
recovery: `READ ${target} again and retry against its current coordinates.`,
|
|
590
|
-
retryable: false,
|
|
591
|
-
}),
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
return {
|
|
595
|
-
selection: { ...selection, lineMarker: resolution.marker },
|
|
596
|
-
precondition: {
|
|
597
|
-
identity: target,
|
|
598
|
-
checks: LineAnchors.checks(selection.lineMarker, resolution.marker),
|
|
599
|
-
},
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
async #selectSource(selection, ctx, operation) {
|
|
603
|
-
const handler = this.#schemes.get(selection.scheme, ctx.functionalityWorkerId);
|
|
604
|
-
if (handler === undefined) {
|
|
605
|
-
throw new InvalidOperationResultError(`Resolved COPY/MOVE source scheme '${selection.scheme}' is no longer registered.`);
|
|
606
|
-
}
|
|
607
|
-
const prepared = await this.#prepareDataRepresentation({
|
|
608
|
-
target: selection.target,
|
|
609
|
-
metadata: selection.metadata,
|
|
610
|
-
routedScheme: selection.scheme,
|
|
611
|
-
handler,
|
|
612
|
-
manifest: selection.manifest,
|
|
613
|
-
ctx,
|
|
614
|
-
publishedChannel: selection.channel,
|
|
615
|
-
});
|
|
616
|
-
if (prepared.result !== null)
|
|
617
|
-
return prepared.result;
|
|
618
|
-
if (prepared.address === null) {
|
|
619
|
-
return _a.#failure("entry-not-found", 404, `No entry exists at ${this.#resourceAddress(selection)}.`, {}, { target: this.#resourceAddress(selection) });
|
|
620
|
-
}
|
|
621
|
-
const storageAddress = prepared.address;
|
|
622
|
-
const read = await EntryCrud.readEntry(storageAddress, ctx, storageAddress.scheme, storageAddress.ownerId);
|
|
623
|
-
if (read.status >= 400)
|
|
624
|
-
return read;
|
|
625
|
-
if (read.status !== 200 || read.entry === null) {
|
|
626
|
-
throw new InvalidOperationResultError(`The '${selection.scheme}' scheme returned status ${read.status} without a COPY/MOVE source entry.`);
|
|
627
|
-
}
|
|
628
|
-
const selected = read.entry.channels[selection.channel];
|
|
629
|
-
if (selected === undefined) {
|
|
630
|
-
return _a.#failure("channel-not-found", 404, `No channel named #${selection.channel} exists at ${renderAddress(storageAddress)}.`, {}, {
|
|
631
|
-
target: renderAddress(storageAddress),
|
|
632
|
-
requestedChannel: selection.channel,
|
|
633
|
-
availableChannels: Object.keys(read.entry.channels),
|
|
634
|
-
retryable: false,
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
const resolvedMarker = this.#resolveResourceLineMarker(selection, selected.content, operation);
|
|
638
|
-
if ("result" in resolvedMarker)
|
|
639
|
-
return resolvedMarker.result;
|
|
640
|
-
let content = selected.content;
|
|
641
|
-
let scopeNormalizations;
|
|
642
|
-
if (await MimetypeBinary.isBinaryMimetype(selected.mimetype, ctx.mimetypes)) {
|
|
643
|
-
return _a.#failure("binary-source-unsupported", 415, `Channel #${selection.channel} is a binary marker and cannot be copied or moved.`, {}, {
|
|
644
|
-
channel: selection.channel,
|
|
645
|
-
mimetype: selected.mimetype,
|
|
646
|
-
recovery: "Use a source with a readable text projection.",
|
|
647
|
-
retryable: false,
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
if (resolvedMarker.selection.lineMarker !== null) {
|
|
651
|
-
const sliced = LineMarkerOps.sliceLinesRaw(content, resolvedMarker.selection.lineMarker);
|
|
652
|
-
if (sliced.status !== 200)
|
|
653
|
-
return Results.assert(sliced);
|
|
654
|
-
content = sliced.text ?? "";
|
|
655
|
-
scopeNormalizations = sliced.scopeNormalizations;
|
|
656
|
-
}
|
|
657
|
-
if (selected.producerResult !== undefined && selected.producerResult.status >= 400) {
|
|
658
|
-
return Results.assert(selected.producerResult);
|
|
659
|
-
}
|
|
660
|
-
return {
|
|
661
|
-
...resolvedMarker.selection,
|
|
662
|
-
storageAddress,
|
|
663
|
-
content,
|
|
664
|
-
completeContent: selected.content,
|
|
665
|
-
mimetype: selected.mimetype,
|
|
666
|
-
lineAnchorPrecondition: resolvedMarker.precondition,
|
|
667
|
-
...(scopeNormalizations === undefined ? {} : { scopeNormalizations }),
|
|
668
|
-
};
|
|
12
|
+
this.#selection = new ResourceSelector({ schemes, canonicalFilePath, prepareDataRepresentation });
|
|
13
|
+
this.#edits = new EditMutations({ schemes, liveSubscriptions, run, checkWritable, checkCapabilities, editTargetIdentity, resolveDataEntryAddress });
|
|
14
|
+
this.#transfers = new ResourceTransfers({ schemes, liveSubscriptions, resolveDataEntryAddress, readEntry, writeEntry, deleteChannel, applyProposal, selection: this.#selection });
|
|
669
15
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return result;
|
|
673
|
-
return Results.assert({
|
|
674
|
-
...result,
|
|
675
|
-
scopeNormalizations: [
|
|
676
|
-
...scopeNormalizations,
|
|
677
|
-
...(result.scopeNormalizations ?? []),
|
|
678
|
-
],
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
static #mergeLineAnchorPreconditions(...values) {
|
|
682
|
-
const present = values.filter((value) => value !== null);
|
|
683
|
-
if (present.length === 0)
|
|
684
|
-
return null;
|
|
685
|
-
const identity = present[0].identity;
|
|
686
|
-
if (present.some((value) => value.identity !== identity)) {
|
|
687
|
-
throw new TypeError("Line-anchor preconditions for one edit batch must share a resource identity.");
|
|
688
|
-
}
|
|
689
|
-
const checks = [...new Map(present.flatMap(({ checks: valueChecks }) => valueChecks)
|
|
690
|
-
.map((check) => [`${check.anchor}:${check.line}`, check])).values()];
|
|
691
|
-
return { identity, checks };
|
|
692
|
-
}
|
|
693
|
-
#resourceAddress(selection) {
|
|
694
|
-
const address = selection.scheme === "file"
|
|
695
|
-
? renderTarget({
|
|
696
|
-
scheme: null,
|
|
697
|
-
pathname: selection.identityPathname.replace(/^\//, ""),
|
|
698
|
-
})
|
|
699
|
-
: renderAddress({
|
|
700
|
-
scheme: selection.scheme,
|
|
701
|
-
// {§worker-authority-carving} — storage keys the owner, so the
|
|
702
|
-
// address the model typed (`~`) is re-applied for it to recognize.
|
|
703
|
-
authority: selection.scheme === "worker" && selection.target.kind === "url"
|
|
704
|
-
? selection.target.hostname ?? ""
|
|
705
|
-
: selection.authority,
|
|
706
|
-
pathname: selection.identityPathname,
|
|
707
|
-
});
|
|
708
|
-
if (address === null)
|
|
709
|
-
throw new Error("resolved resource selection has no renderable address");
|
|
710
|
-
return selection.channel === selection.manifest.defaultChannel
|
|
711
|
-
? address
|
|
712
|
-
: `${address}#${PathSyntax.escapeTarget(selection.channel)}`;
|
|
713
|
-
}
|
|
714
|
-
#pendingEffect(selection, action) {
|
|
715
|
-
return {
|
|
716
|
-
target: this.#resourceAddress(selection),
|
|
717
|
-
action,
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
static #appliedEffects(result, pending) {
|
|
721
|
-
// {§edit-result-copy-move-effects} — only an applied mutation earns
|
|
722
|
-
// engine-composed effects; native scheme receipts remain internal input.
|
|
723
|
-
const exact = Results.assert(result);
|
|
724
|
-
if (exact.status === 304 || exact.status === 202 || exact.status >= 300)
|
|
725
|
-
return exact;
|
|
726
|
-
if (exact.effects !== undefined) {
|
|
727
|
-
throw new InvalidOperationResultError("A COPY/MOVE mutation result supplied effects before engine composition.");
|
|
728
|
-
}
|
|
729
|
-
const batch = exact.editReceipt === undefined
|
|
730
|
-
? undefined
|
|
731
|
-
: assertEditBatchReceipt(exact.editReceipt);
|
|
732
|
-
const single = batch === undefined && exact.receipt !== undefined
|
|
733
|
-
? assertEditReceipt(exact.receipt)
|
|
734
|
-
: undefined;
|
|
735
|
-
const batchSize = batch === undefined
|
|
736
|
-
? undefined
|
|
737
|
-
: "disposition" in batch
|
|
738
|
-
? batch.superseded.length
|
|
739
|
-
: batch.effects.length;
|
|
740
|
-
if (batchSize !== undefined && batchSize !== pending.length) {
|
|
741
|
-
throw new InvalidOperationResultError(`COPY/MOVE expected ${pending.length} receipt projections, got ${batchSize}.`);
|
|
742
|
-
}
|
|
743
|
-
if (single !== undefined && pending.length !== 1) {
|
|
744
|
-
throw new InvalidOperationResultError(`COPY/MOVE received one receipt for ${pending.length} resource effects.`);
|
|
745
|
-
}
|
|
746
|
-
let effects;
|
|
747
|
-
if (batch !== undefined && "disposition" in batch) {
|
|
748
|
-
const replacement = pending[0];
|
|
749
|
-
if (replacement === undefined) {
|
|
750
|
-
throw new InvalidOperationResultError("A reviewer-replaced COPY/MOVE batch has no resource effect.");
|
|
751
|
-
}
|
|
752
|
-
if (pending.some(({ target, action }) => target !== replacement.target || action !== replacement.action)) {
|
|
753
|
-
throw new InvalidOperationResultError("A reviewer-replaced COPY/MOVE batch spans incompatible resource effects.");
|
|
754
|
-
}
|
|
755
|
-
effects = [{
|
|
756
|
-
...replacement,
|
|
757
|
-
receipt: projectEditReceipt(batch, 0),
|
|
758
|
-
}];
|
|
759
|
-
}
|
|
760
|
-
else {
|
|
761
|
-
effects = pending.map((effect, index) => ({
|
|
762
|
-
...effect,
|
|
763
|
-
...(batch !== undefined
|
|
764
|
-
? { receipt: projectEditReceipt(batch, index) }
|
|
765
|
-
: single !== undefined
|
|
766
|
-
? { receipt: single }
|
|
767
|
-
: {}),
|
|
768
|
-
}));
|
|
769
|
-
}
|
|
770
|
-
assertResourceEffects(effects);
|
|
771
|
-
const { editReceipt: _editReceipt, receipt: _receipt, ...withoutInternalReceipts } = exact;
|
|
772
|
-
return {
|
|
773
|
-
...withoutInternalReceipts,
|
|
774
|
-
effects,
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
// {§copy-move-observation} {§edit-result-copy-move-effects} — scoped
|
|
778
|
-
// COPY/MOVE into an unscoped channel is a text materialization even when
|
|
779
|
-
// CRUD creates the channel wholesale. Carry the receipt through synchronous
|
|
780
|
-
// writes and proposals; the destination scheme owns reviewed output.
|
|
781
|
-
static #withEditMaterialization(result, receipt) {
|
|
782
|
-
const exact = Results.assert(result);
|
|
783
|
-
if (exact.status !== 200 && exact.status !== 201 && exact.status !== 202)
|
|
784
|
-
return exact;
|
|
785
|
-
const materialized = exact.editReceipt === undefined
|
|
786
|
-
? assertEditBatchReceipt(receipt)
|
|
787
|
-
: assertEditBatchReceipt(exact.editReceipt);
|
|
788
|
-
return {
|
|
789
|
-
...exact,
|
|
790
|
-
editReceipt: materialized,
|
|
791
|
-
...(exact.status === 202
|
|
792
|
-
? {
|
|
793
|
-
attrs: {
|
|
794
|
-
...exact.attrs,
|
|
795
|
-
editReceipt: materialized,
|
|
796
|
-
},
|
|
797
|
-
}
|
|
798
|
-
: {}),
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
#finalizeEffects(result, selection, pending) {
|
|
802
|
-
const routed = this.#withProposalRoute(result, selection);
|
|
803
|
-
if (routed.status !== 202)
|
|
804
|
-
return _a.#appliedEffects(routed, pending);
|
|
805
|
-
return {
|
|
806
|
-
...routed,
|
|
807
|
-
attrs: {
|
|
808
|
-
...routed.attrs,
|
|
809
|
-
proposalEffects: pending,
|
|
810
|
-
},
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
static #effectsOf(result) {
|
|
814
|
-
return result.effects === undefined
|
|
815
|
-
? []
|
|
816
|
-
: assertResourceEffects(result.effects);
|
|
16
|
+
async prepareEditBatches(...args) {
|
|
17
|
+
return this.#edits.prepareEditBatches(...args);
|
|
817
18
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
const effects = [...existing, ...additional.flat()];
|
|
821
|
-
const { effects: _effects, ...withoutEffects } = result;
|
|
822
|
-
return effects.length === 0
|
|
823
|
-
? withoutEffects
|
|
824
|
-
: { ...withoutEffects, effects: assertResourceEffects(effects) };
|
|
19
|
+
preparedEditResult(...args) {
|
|
20
|
+
return this.#edits.preparedEditResult(...args);
|
|
825
21
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
?.proposalEffects;
|
|
829
|
-
if (pending === undefined
|
|
830
|
-
|| settlement.resolution.decision !== "accept"
|
|
831
|
-
|| settlement.applied === undefined
|
|
832
|
-
|| settlement.applied.status >= 300) {
|
|
833
|
-
return settlement;
|
|
834
|
-
}
|
|
835
|
-
const projected = settlement.resolution.result ?? {};
|
|
836
|
-
const aggregate = settlement.applied.editReceipt;
|
|
837
|
-
const applied = _a.#appliedEffects({
|
|
838
|
-
...projected,
|
|
839
|
-
status: settlement.applied.status,
|
|
840
|
-
...(aggregate === undefined ? {} : { editReceipt: aggregate }),
|
|
841
|
-
}, pending);
|
|
842
|
-
const { status: _status, body: _body, ...result } = applied;
|
|
843
|
-
const { body: _resolutionBody, ...resolution } = settlement.resolution;
|
|
844
|
-
return {
|
|
845
|
-
...settlement,
|
|
846
|
-
resolution: {
|
|
847
|
-
...resolution,
|
|
848
|
-
result,
|
|
849
|
-
},
|
|
850
|
-
};
|
|
22
|
+
withMergeFacts(...args) {
|
|
23
|
+
return this.#edits.withMergeFacts(...args);
|
|
851
24
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
return;
|
|
855
|
-
const prepared = this.#preparedEdits.get(statement);
|
|
856
|
-
if (prepared === undefined || !prepared.first) {
|
|
857
|
-
throw new InvalidOperationResultError("An EDIT proposal settled without its prepared batch owner.");
|
|
858
|
-
}
|
|
859
|
-
const { resolution, applied } = settlement;
|
|
860
|
-
if (resolution.decision !== "accept"
|
|
861
|
-
|| applied === undefined
|
|
862
|
-
|| applied.status >= 300) {
|
|
863
|
-
prepared.batch.aggregate = undefined;
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
|
-
if (applied.editReceipt === null) {
|
|
867
|
-
prepared.batch.aggregate = undefined;
|
|
868
|
-
return;
|
|
869
|
-
}
|
|
870
|
-
if (applied.editReceipt !== undefined) {
|
|
871
|
-
prepared.batch.aggregate = assertEditBatchReceipt(applied.editReceipt);
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
if (resolution.body !== undefined)
|
|
875
|
-
prepared.batch.aggregate = undefined;
|
|
25
|
+
settleEdit(...args) {
|
|
26
|
+
return this.#edits.settleEdit(...args);
|
|
876
27
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
?.effects;
|
|
880
|
-
return effects === undefined ? [] : assertResourceEffects(effects);
|
|
28
|
+
async handleCopy(...args) {
|
|
29
|
+
return this.#transfers.handleCopy(...args);
|
|
881
30
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const { effects: _effects, ...withoutEffects } = projected;
|
|
885
|
-
const result = effects.length === 0
|
|
886
|
-
? withoutEffects
|
|
887
|
-
: {
|
|
888
|
-
...withoutEffects,
|
|
889
|
-
effects: assertResourceEffects(effects),
|
|
890
|
-
};
|
|
891
|
-
return {
|
|
892
|
-
...settlement,
|
|
893
|
-
resolution: {
|
|
894
|
-
...settlement.resolution,
|
|
895
|
-
result,
|
|
896
|
-
},
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
#sameChannel(left, right) {
|
|
900
|
-
return left.scheme === right.scheme
|
|
901
|
-
&& left.authority === right.authority
|
|
902
|
-
&& left.identityPathname === right.identityPathname
|
|
903
|
-
&& left.channel === right.channel;
|
|
904
|
-
}
|
|
905
|
-
#withProposalRoute(result, selection) {
|
|
906
|
-
if (result.status !== 202)
|
|
907
|
-
return result;
|
|
908
|
-
return {
|
|
909
|
-
...result,
|
|
910
|
-
attrs: {
|
|
911
|
-
...result.attrs,
|
|
912
|
-
proposalScheme: selection.scheme,
|
|
913
|
-
proposalTarget: {
|
|
914
|
-
scheme: selection.scheme,
|
|
915
|
-
authority: selection.authority,
|
|
916
|
-
pathname: selection.identityPathname,
|
|
917
|
-
},
|
|
918
|
-
},
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
async #invokeEditBatch(selection, edits, ctx, precondition = null) {
|
|
922
|
-
const handler = this.#schemes.get(selection.scheme, ctx.functionalityWorkerId);
|
|
923
|
-
if (typeof handler?.editBatch !== "function") {
|
|
924
|
-
return _a.#failure("operation-not-implemented", 501, `Scheme '${selection.scheme}' does not implement EDIT batches.`, {}, {
|
|
925
|
-
scheme: selection.scheme,
|
|
926
|
-
operation: "EDIT",
|
|
927
|
-
retryable: false,
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
const statements = edits.map(({ marker, body, position }) => ({
|
|
931
|
-
op: "EDIT",
|
|
932
|
-
delimiter: "",
|
|
933
|
-
annotation: null,
|
|
934
|
-
signal: null,
|
|
935
|
-
target: selection.target,
|
|
936
|
-
metadata: selection.metadata,
|
|
937
|
-
lineMarker: marker,
|
|
938
|
-
body,
|
|
939
|
-
position,
|
|
940
|
-
}));
|
|
941
|
-
const addressedScheme = selection.target.kind === "url"
|
|
942
|
-
? selection.target.scheme
|
|
943
|
-
: selection.scheme;
|
|
944
|
-
try {
|
|
945
|
-
const binding = await this.#resolveDataEntryAddress({
|
|
946
|
-
target: selection.target,
|
|
947
|
-
routedScheme: selection.scheme,
|
|
948
|
-
handler,
|
|
949
|
-
manifest: selection.manifest,
|
|
950
|
-
ctx,
|
|
951
|
-
});
|
|
952
|
-
if (binding.result !== null)
|
|
953
|
-
return binding.result;
|
|
954
|
-
if (binding.address === null) {
|
|
955
|
-
return _a.#failure("entry-not-found", 404, `No entry exists at ${this.#resourceAddress(selection)}.`);
|
|
956
|
-
}
|
|
957
|
-
const result = Results.assert(await handler.editBatch(statements, new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions, {
|
|
958
|
-
authority: binding.address.authority,
|
|
959
|
-
ownerId: binding.address.ownerId,
|
|
960
|
-
publishedChannel: selection.channel,
|
|
961
|
-
editPrecondition: precondition,
|
|
962
|
-
})));
|
|
963
|
-
return this.#withProposalRoute(result, selection);
|
|
964
|
-
}
|
|
965
|
-
catch (err) {
|
|
966
|
-
if (err instanceof InvalidOperationResultError)
|
|
967
|
-
throw err;
|
|
968
|
-
console.error(`Scheme '${selection.scheme}' COPY/MOVE edit threw outside its operation result contract:`, err);
|
|
969
|
-
return _a.#failure("scheme-handler-threw", 500, `The '${selection.scheme}' scheme did not produce a COPY/MOVE edit result.`, {}, {
|
|
970
|
-
stage: "scheme-dispatch",
|
|
971
|
-
scheme: selection.scheme,
|
|
972
|
-
operation: "EDIT",
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
async #writeDestination(statement, source, destination, ctx) {
|
|
977
|
-
const handler = this.#schemes.get(destination.scheme, ctx.functionalityWorkerId);
|
|
978
|
-
if (handler === undefined) {
|
|
979
|
-
throw new InvalidOperationResultError(`Resolved COPY/MOVE destination scheme '${destination.scheme}' is no longer registered.`);
|
|
980
|
-
}
|
|
981
|
-
const binding = await this.#resolveDataEntryAddress({
|
|
982
|
-
target: destination.target,
|
|
983
|
-
routedScheme: destination.scheme,
|
|
984
|
-
handler,
|
|
985
|
-
manifest: destination.manifest,
|
|
986
|
-
ctx,
|
|
987
|
-
});
|
|
988
|
-
if (binding.result !== null)
|
|
989
|
-
return binding.result;
|
|
990
|
-
if (binding.address === null) {
|
|
991
|
-
return _a.#failure("entry-not-found", 404, `No destination entry address exists at ${this.#resourceAddress(destination)}.`);
|
|
992
|
-
}
|
|
993
|
-
const storageAddress = binding.address;
|
|
994
|
-
const existingResult = await this.#readEntry(destination.scheme, storageAddress, ctx);
|
|
995
|
-
if (existingResult.status >= 400 && existingResult.status !== 404) {
|
|
996
|
-
return existingResult;
|
|
997
|
-
}
|
|
998
|
-
const existing = existingResult.status === 200
|
|
999
|
-
? existingResult.entry
|
|
1000
|
-
: null;
|
|
1001
|
-
if (existingResult.status === 200 && existing === null) {
|
|
1002
|
-
throw new InvalidOperationResultError(`The '${destination.scheme}' scheme returned 200 without a destination entry.`);
|
|
1003
|
-
}
|
|
1004
|
-
const destinationChannel = existing?.channels[destination.channel];
|
|
1005
|
-
const expectedMimetype = destinationChannel?.mimetype
|
|
1006
|
-
?? await PathMimetype.resolveEntryMimetype(destination.pathname, destination.manifest.channels[destination.channel] ?? source.mimetype, ctx.mimetypes);
|
|
1007
|
-
if (source.mimetype !== expectedMimetype) {
|
|
1008
|
-
return _a.#failure("mimetype-mismatch", 415, `COPY or MOVE cannot write '${source.mimetype}' into a '${expectedMimetype}' channel.`, {}, {
|
|
1009
|
-
channel: destination.channel,
|
|
1010
|
-
sourceMimetype: source.mimetype,
|
|
1011
|
-
destinationMimetype: expectedMimetype,
|
|
1012
|
-
retryable: false,
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
const destinationEffect = this.#pendingEffect(destination, destinationChannel === undefined ? "create" : "update");
|
|
1016
|
-
// {§fs-write-surface} — a scope that describes the complete resulting
|
|
1017
|
-
// value on an absent destination is creation, so the create path below
|
|
1018
|
-
// writes the source content. Any partial region needs existing lines.
|
|
1019
|
-
const scopedCreation = destination.lineMarker !== null
|
|
1020
|
-
&& destinationChannel === undefined
|
|
1021
|
-
&& (_a.#isAppendMarker(destination.lineMarker)
|
|
1022
|
-
|| _a.#isCompleteAbsentDestinationMarker(destination.lineMarker, source.content));
|
|
1023
|
-
if (destination.lineMarker !== null && !scopedCreation) {
|
|
1024
|
-
if (destinationChannel === undefined) {
|
|
1025
|
-
const address = this.#resourceAddress(destination);
|
|
1026
|
-
return _a.#failure("destination-region-not-found", 404, `A destination region requires an existing #${destination.channel} channel at ${address}.`, {}, {
|
|
1027
|
-
destination: address,
|
|
1028
|
-
recovery: `Use \`<-1>\`, \`<1,-1>\`, or the exact whole-value \`<1,N>\` extent to create ${address}; partial regions require an existing resource.`,
|
|
1029
|
-
retryable: false,
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
|
-
if (await MimetypeBinary.isBinaryMimetype(destinationChannel.mimetype, ctx.mimetypes)) {
|
|
1033
|
-
return _a.#failure("binary-region-unsupported", 415, `Channel #${destination.channel} is binary and cannot receive a textual region.`, {}, {
|
|
1034
|
-
channel: destination.channel,
|
|
1035
|
-
mimetype: destinationChannel.mimetype,
|
|
1036
|
-
retryable: false,
|
|
1037
|
-
});
|
|
1038
|
-
}
|
|
1039
|
-
const resolvedMarker = this.#resolveResourceLineMarker(destination, destinationChannel.content, statement.op);
|
|
1040
|
-
if ("result" in resolvedMarker)
|
|
1041
|
-
return resolvedMarker.result;
|
|
1042
|
-
const edited = await this.#invokeEditBatch(resolvedMarker.selection, [{
|
|
1043
|
-
marker: resolvedMarker.selection.lineMarker,
|
|
1044
|
-
body: source.content,
|
|
1045
|
-
position: statement.position,
|
|
1046
|
-
}], ctx, resolvedMarker.precondition);
|
|
1047
|
-
return this.#finalizeEffects(edited, resolvedMarker.selection, [destinationEffect]);
|
|
1048
|
-
}
|
|
1049
|
-
if (destinationChannel !== undefined
|
|
1050
|
-
&& destinationChannel.content !== source.content) {
|
|
1051
|
-
return _a.#failure("copy-destination-exists", 409, `COPY or MOVE destination ${this.#resourceAddress(destination)} already contains different content.`, {}, {
|
|
1052
|
-
destination: this.#resourceAddress(destination),
|
|
1053
|
-
retryable: false,
|
|
1054
|
-
});
|
|
1055
|
-
}
|
|
1056
|
-
if (destinationChannel !== undefined)
|
|
1057
|
-
return { status: 304 };
|
|
1058
|
-
const channels = {
|
|
1059
|
-
...(existing?.channels ?? {}),
|
|
1060
|
-
[destination.channel]: {
|
|
1061
|
-
content: source.content,
|
|
1062
|
-
mimetype: source.mimetype,
|
|
1063
|
-
},
|
|
1064
|
-
};
|
|
1065
|
-
const written = await this.#writeEntry(destination.scheme, storageAddress, { channels }, ctx);
|
|
1066
|
-
const exactWritten = Results.assert(written);
|
|
1067
|
-
const parseIssues = exactWritten.status === 200 || exactWritten.status === 201
|
|
1068
|
-
? await new DbProjectionCaps(ctx).parseIssueTransition(null, source.content, source.mimetype)
|
|
1069
|
-
: undefined;
|
|
1070
|
-
const materialized = source.lineMarker === null
|
|
1071
|
-
|| (exactWritten.status !== 200 && exactWritten.status !== 201 && exactWritten.status !== 202)
|
|
1072
|
-
? exactWritten
|
|
1073
|
-
: _a.#withEditMaterialization(exactWritten, editReceipt("", source.content, [{
|
|
1074
|
-
marker: { marks: [1, -1] },
|
|
1075
|
-
body: source.content,
|
|
1076
|
-
}], parseIssues,
|
|
1077
|
-
// {§edit-receipt-anchored-context} — the destination's READ identity
|
|
1078
|
-
destination.channel === destination.manifest.defaultChannel
|
|
1079
|
-
? EntryManifest.toPath(destination.scheme, storageAddress.authority, storageAddress.pathname)
|
|
1080
|
-
: `${EntryManifest.toPath(destination.scheme, storageAddress.authority, storageAddress.pathname)}#${PathSyntax.escapeTarget(destination.channel)}`));
|
|
1081
|
-
return this.#finalizeEffects(materialized, destination, [destinationEffect]);
|
|
1082
|
-
}
|
|
1083
|
-
async #copyOrchestration({ statement, source, destination, ctx, }) {
|
|
1084
|
-
const resolvedSource = await this.#resolveResourceSelection(source, ctx);
|
|
1085
|
-
if (_a.#isDispatchResult(resolvedSource))
|
|
1086
|
-
return resolvedSource;
|
|
1087
|
-
const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
|
|
1088
|
-
if (_a.#isDispatchResult(resolvedDestination))
|
|
1089
|
-
return resolvedDestination;
|
|
1090
|
-
const selected = await this.#selectSource(resolvedSource, ctx, "COPY");
|
|
1091
|
-
if (_a.#isDispatchResult(selected))
|
|
1092
|
-
return selected;
|
|
1093
|
-
const result = await this.#writeDestination(statement, selected, resolvedDestination, ctx);
|
|
1094
|
-
return _a.#prependScopeNormalizations(result, selected.scopeNormalizations);
|
|
1095
|
-
}
|
|
1096
|
-
#deferredMoveSource(source, destination, lineAnchorPrecondition) {
|
|
1097
|
-
return {
|
|
1098
|
-
target: source.target,
|
|
1099
|
-
metadata: source.metadata,
|
|
1100
|
-
lineMarker: source.lineMarker,
|
|
1101
|
-
scheme: source.scheme,
|
|
1102
|
-
authority: source.authority,
|
|
1103
|
-
pathname: source.pathname,
|
|
1104
|
-
channel: source.channel,
|
|
1105
|
-
destination: this.#resourceAddress(destination),
|
|
1106
|
-
lineAnchorPrecondition,
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
#moveFailureAfterDestination(result, destination, destinationEffects) {
|
|
1110
|
-
const exact = Results.assert(result);
|
|
1111
|
-
if (exact.status < 400) {
|
|
1112
|
-
throw new InvalidOperationResultError("A successful MOVE source result was classified as a partial failure.");
|
|
1113
|
-
}
|
|
1114
|
-
if (exact.problem === undefined) {
|
|
1115
|
-
throw new InvalidOperationResultError("A failed MOVE source mutation has no Problem Details.");
|
|
1116
|
-
}
|
|
1117
|
-
const failed = Results.assert({
|
|
1118
|
-
...exact,
|
|
1119
|
-
problem: {
|
|
1120
|
-
...exact.problem,
|
|
1121
|
-
operation: "MOVE",
|
|
1122
|
-
destinationWritten: true,
|
|
1123
|
-
destination,
|
|
1124
|
-
},
|
|
1125
|
-
});
|
|
1126
|
-
return _a.#withCombinedEffects(failed, destinationEffects);
|
|
1127
|
-
}
|
|
1128
|
-
async #removeMoveSource(statement, source, ctx, lineAnchorPrecondition = null) {
|
|
1129
|
-
const effect = this.#pendingEffect(source, source.lineMarker === null ? "delete" : "update");
|
|
1130
|
-
if (source.lineMarker === null) {
|
|
1131
|
-
const handler = this.#schemes.get(source.scheme, ctx.functionalityWorkerId);
|
|
1132
|
-
if (handler === undefined) {
|
|
1133
|
-
throw new InvalidOperationResultError(`Resolved MOVE source scheme '${source.scheme}' is no longer registered.`);
|
|
1134
|
-
}
|
|
1135
|
-
const binding = await this.#resolveDataEntryAddress({
|
|
1136
|
-
target: source.target,
|
|
1137
|
-
routedScheme: source.scheme,
|
|
1138
|
-
handler,
|
|
1139
|
-
manifest: source.manifest,
|
|
1140
|
-
ctx,
|
|
1141
|
-
});
|
|
1142
|
-
if (binding.result !== null)
|
|
1143
|
-
return binding.result;
|
|
1144
|
-
if (binding.address === null) {
|
|
1145
|
-
return _a.#failure("entry-not-found", 404, `No MOVE source entry exists at ${this.#resourceAddress(source)}.`);
|
|
1146
|
-
}
|
|
1147
|
-
const deleted = await this.#deleteChannel(source.scheme, binding.address, source.channel, ctx);
|
|
1148
|
-
return this.#finalizeEffects(Results.assert(deleted), source, [effect]);
|
|
1149
|
-
}
|
|
1150
|
-
const edited = await this.#invokeEditBatch(source, [{
|
|
1151
|
-
marker: source.lineMarker,
|
|
1152
|
-
body: "",
|
|
1153
|
-
position: statement.position,
|
|
1154
|
-
}], ctx, lineAnchorPrecondition);
|
|
1155
|
-
return this.#finalizeEffects(edited, source, [effect]);
|
|
1156
|
-
}
|
|
1157
|
-
async #moveWithinChannel(statement, source, destination, ctx) {
|
|
1158
|
-
if (source.lineMarker === null) {
|
|
1159
|
-
if (destination.lineMarker !== null) {
|
|
1160
|
-
return _a.#failure("move-region-overlap", 409, "MOVE cannot insert a whole channel into itself and then remove that channel.", {}, {
|
|
1161
|
-
source: this.#resourceAddress(source),
|
|
1162
|
-
destination: this.#resourceAddress(destination),
|
|
1163
|
-
retryable: false,
|
|
1164
|
-
});
|
|
1165
|
-
}
|
|
1166
|
-
return this.#writeDestination(statement, source, destination, ctx);
|
|
1167
|
-
}
|
|
1168
|
-
if (destination.lineMarker === null) {
|
|
1169
|
-
return this.#writeDestination(statement, source, destination, ctx);
|
|
1170
|
-
}
|
|
1171
|
-
const resolvedDestination = this.#resolveResourceLineMarker(destination, source.completeContent, "MOVE");
|
|
1172
|
-
if ("result" in resolvedDestination)
|
|
1173
|
-
return resolvedDestination.result;
|
|
1174
|
-
const precondition = _a.#mergeLineAnchorPreconditions(source.lineAnchorPrecondition, resolvedDestination.precondition);
|
|
1175
|
-
const moved = await this.#invokeEditBatch(resolvedDestination.selection, [
|
|
1176
|
-
{
|
|
1177
|
-
marker: resolvedDestination.selection.lineMarker,
|
|
1178
|
-
body: source.content,
|
|
1179
|
-
position: statement.position,
|
|
1180
|
-
},
|
|
1181
|
-
{
|
|
1182
|
-
marker: source.lineMarker,
|
|
1183
|
-
body: "",
|
|
1184
|
-
position: statement.position,
|
|
1185
|
-
},
|
|
1186
|
-
], ctx, precondition);
|
|
1187
|
-
const effect = this.#pendingEffect(resolvedDestination.selection, "update");
|
|
1188
|
-
return this.#finalizeEffects(moved, resolvedDestination.selection, [effect, effect]);
|
|
1189
|
-
}
|
|
1190
|
-
async #moveOrchestration({ statement, source, destination, ctx, }) {
|
|
1191
|
-
const resolvedSource = await this.#resolveResourceSelection(source, ctx);
|
|
1192
|
-
if (_a.#isDispatchResult(resolvedSource))
|
|
1193
|
-
return resolvedSource;
|
|
1194
|
-
const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
|
|
1195
|
-
if (_a.#isDispatchResult(resolvedDestination))
|
|
1196
|
-
return resolvedDestination;
|
|
1197
|
-
const selected = await this.#selectSource(resolvedSource, ctx, "MOVE");
|
|
1198
|
-
if (_a.#isDispatchResult(selected))
|
|
1199
|
-
return selected;
|
|
1200
|
-
if (this.#sameChannel(resolvedSource, resolvedDestination)) {
|
|
1201
|
-
const result = await this.#moveWithinChannel(statement, selected, resolvedDestination, ctx);
|
|
1202
|
-
return resolvedDestination.lineMarker === null
|
|
1203
|
-
? _a.#prependScopeNormalizations(result, selected.scopeNormalizations)
|
|
1204
|
-
: result;
|
|
1205
|
-
}
|
|
1206
|
-
const destinationResult = _a.#prependScopeNormalizations(await this.#writeDestination(statement, selected, resolvedDestination, ctx), selected.scopeNormalizations);
|
|
1207
|
-
if (destinationResult.status >= 400)
|
|
1208
|
-
return destinationResult;
|
|
1209
|
-
const destinationAddress = this.#resourceAddress(resolvedDestination);
|
|
1210
|
-
const destinationEffects = _a.#effectsOf(destinationResult);
|
|
1211
|
-
if (destinationResult.status === 202) {
|
|
1212
|
-
return {
|
|
1213
|
-
...destinationResult,
|
|
1214
|
-
attrs: {
|
|
1215
|
-
...destinationResult.attrs,
|
|
1216
|
-
moveSource: this.#deferredMoveSource(selected, resolvedDestination, selected.lineAnchorPrecondition),
|
|
1217
|
-
},
|
|
1218
|
-
};
|
|
1219
|
-
}
|
|
1220
|
-
const sourceResult = await this.#removeMoveSource(statement, selected, ctx, selected.lineAnchorPrecondition);
|
|
1221
|
-
if (sourceResult.status === 202) {
|
|
1222
|
-
return {
|
|
1223
|
-
...sourceResult,
|
|
1224
|
-
...(destinationResult.scopeNormalizations === undefined
|
|
1225
|
-
? {}
|
|
1226
|
-
: { scopeNormalizations: destinationResult.scopeNormalizations }),
|
|
1227
|
-
attrs: {
|
|
1228
|
-
...sourceResult.attrs,
|
|
1229
|
-
moveDestinationWritten: destinationAddress,
|
|
1230
|
-
moveDestinationEffects: destinationEffects,
|
|
1231
|
-
},
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
|
-
if (sourceResult.status >= 400) {
|
|
1235
|
-
return this.#moveFailureAfterDestination(destinationResult.scopeNormalizations === undefined
|
|
1236
|
-
? sourceResult
|
|
1237
|
-
: Results.assert({
|
|
1238
|
-
...sourceResult,
|
|
1239
|
-
scopeNormalizations: destinationResult.scopeNormalizations,
|
|
1240
|
-
}), destinationAddress, destinationEffects);
|
|
1241
|
-
}
|
|
1242
|
-
const base = destinationResult.status === 304
|
|
1243
|
-
? { ...destinationResult, status: 200 }
|
|
1244
|
-
: destinationResult;
|
|
1245
|
-
return _a.#withCombinedEffects(base, _a.#effectsOf(sourceResult));
|
|
1246
|
-
}
|
|
1247
|
-
async #settleMoveProposal({ statement, result, settlement, ctx, ids, }) {
|
|
1248
|
-
if (statement.op !== "MOVE")
|
|
1249
|
-
return settlement;
|
|
1250
|
-
const attrs = result.attrs;
|
|
1251
|
-
const destinationWritten = attrs?.moveDestinationWritten;
|
|
1252
|
-
if (destinationWritten !== undefined) {
|
|
1253
|
-
const destinationEffects = attrs?.moveDestinationEffects === undefined
|
|
1254
|
-
? []
|
|
1255
|
-
: assertResourceEffects(attrs.moveDestinationEffects);
|
|
1256
|
-
if (settlement.resolution.decision !== "accept") {
|
|
1257
|
-
const decision = settlement.resolution.decision;
|
|
1258
|
-
return {
|
|
1259
|
-
resolution: settlement.resolution,
|
|
1260
|
-
applied: this.#moveFailureAfterDestination(_a.#failure("move-source-not-applied", decision === "cancel" ? 499 : 409, `The MOVE destination was written, but source removal was ${decision === "cancel" ? "cancelled" : "rejected"}.`, {}, {
|
|
1261
|
-
retryable: false,
|
|
1262
|
-
}), destinationWritten, destinationEffects),
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
if (settlement.applied === undefined) {
|
|
1266
|
-
return {
|
|
1267
|
-
resolution: settlement.resolution,
|
|
1268
|
-
applied: this.#moveFailureAfterDestination(_a.#failure("proposal-apply-missing", 500, "The source scheme accepted its MOVE proposal without applying the source mutation.", {}, {
|
|
1269
|
-
stage: "proposal-application",
|
|
1270
|
-
retryable: false,
|
|
1271
|
-
}), destinationWritten, destinationEffects),
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
if (settlement.applied.status >= 400) {
|
|
1275
|
-
return {
|
|
1276
|
-
resolution: settlement.resolution,
|
|
1277
|
-
applied: this.#moveFailureAfterDestination(settlement.applied, destinationWritten, destinationEffects),
|
|
1278
|
-
};
|
|
1279
|
-
}
|
|
1280
|
-
return _a.#withSettlementEffects(settlement, [
|
|
1281
|
-
...destinationEffects,
|
|
1282
|
-
..._a.#settlementEffects(settlement),
|
|
1283
|
-
]);
|
|
1284
|
-
}
|
|
1285
|
-
const deferred = attrs?.moveSource;
|
|
1286
|
-
if (deferred === undefined
|
|
1287
|
-
|| settlement.resolution.decision !== "accept"
|
|
1288
|
-
|| (settlement.applied?.status ?? 200) >= 400) {
|
|
1289
|
-
return settlement;
|
|
1290
|
-
}
|
|
1291
|
-
if (settlement.applied === undefined) {
|
|
1292
|
-
return {
|
|
1293
|
-
resolution: settlement.resolution,
|
|
1294
|
-
applied: _a.#failure("proposal-apply-missing", 500, "The destination scheme accepted its MOVE proposal without applying the destination mutation.", {}, {
|
|
1295
|
-
stage: "proposal-application",
|
|
1296
|
-
retryable: false,
|
|
1297
|
-
}),
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
const destinationEffects = _a.#settlementEffects(settlement);
|
|
1301
|
-
const resolvedSource = await this.#resolveResourceSelection({
|
|
1302
|
-
target: deferred.target,
|
|
1303
|
-
metadata: deferred.metadata,
|
|
1304
|
-
lineMarker: deferred.lineMarker,
|
|
1305
|
-
}, ctx);
|
|
1306
|
-
if (_a.#isDispatchResult(resolvedSource)) {
|
|
1307
|
-
return {
|
|
1308
|
-
resolution: settlement.resolution,
|
|
1309
|
-
applied: this.#moveFailureAfterDestination(resolvedSource, deferred.destination, destinationEffects),
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
if (resolvedSource.scheme !== deferred.scheme
|
|
1313
|
-
|| resolvedSource.authority !== deferred.authority
|
|
1314
|
-
|| resolvedSource.pathname !== deferred.pathname
|
|
1315
|
-
|| resolvedSource.channel !== deferred.channel) {
|
|
1316
|
-
throw new InvalidOperationResultError("A deferred MOVE source no longer resolves to its recorded identity.");
|
|
1317
|
-
}
|
|
1318
|
-
const removed = await this.#removeMoveSource(statement, {
|
|
1319
|
-
...resolvedSource,
|
|
1320
|
-
lineMarker: resolvedSource.lineMarker,
|
|
1321
|
-
}, ctx, deferred.lineAnchorPrecondition);
|
|
1322
|
-
if (removed.status >= 400) {
|
|
1323
|
-
return {
|
|
1324
|
-
resolution: settlement.resolution,
|
|
1325
|
-
applied: this.#moveFailureAfterDestination(removed, deferred.destination, destinationEffects),
|
|
1326
|
-
};
|
|
1327
|
-
}
|
|
1328
|
-
if (removed.status !== 202) {
|
|
1329
|
-
return _a.#withSettlementEffects(settlement, [
|
|
1330
|
-
...destinationEffects,
|
|
1331
|
-
..._a.#effectsOf(removed),
|
|
1332
|
-
]);
|
|
1333
|
-
}
|
|
1334
|
-
const initialSourceSettlement = await this.#applyProposal(statement, removed, { decision: "accept" }, ids);
|
|
1335
|
-
const sourceSettlement = _a.#settleProposalEffects(removed, initialSourceSettlement);
|
|
1336
|
-
if (sourceSettlement.applied === undefined) {
|
|
1337
|
-
return {
|
|
1338
|
-
resolution: settlement.resolution,
|
|
1339
|
-
applied: this.#moveFailureAfterDestination(_a.#failure("proposal-apply-missing", 500, "The source scheme accepted its MOVE proposal without applying the source mutation.", {}, {
|
|
1340
|
-
stage: "proposal-application",
|
|
1341
|
-
retryable: false,
|
|
1342
|
-
}), deferred.destination, destinationEffects),
|
|
1343
|
-
};
|
|
1344
|
-
}
|
|
1345
|
-
if (sourceSettlement.applied.status >= 400) {
|
|
1346
|
-
return {
|
|
1347
|
-
resolution: settlement.resolution,
|
|
1348
|
-
applied: this.#moveFailureAfterDestination(sourceSettlement.applied, deferred.destination, destinationEffects),
|
|
1349
|
-
};
|
|
1350
|
-
}
|
|
1351
|
-
return _a.#withSettlementEffects(settlement, [
|
|
1352
|
-
...destinationEffects,
|
|
1353
|
-
..._a.#settlementEffects(sourceSettlement),
|
|
1354
|
-
]);
|
|
31
|
+
async handleMove(...args) {
|
|
32
|
+
return this.#transfers.handleMove(...args);
|
|
1355
33
|
}
|
|
1356
34
|
async settleProposal({ statement, result, settlement, ctx, ids, }) {
|
|
1357
|
-
const withEffects =
|
|
1358
|
-
const settledMove = await this.#settleMoveProposal({
|
|
35
|
+
const withEffects = MutationEffects.settleProposalEffects(result, settlement);
|
|
36
|
+
const settledMove = await this.#transfers.settleMoveProposal({
|
|
1359
37
|
statement,
|
|
1360
38
|
result,
|
|
1361
39
|
settlement: withEffects,
|
|
@@ -1365,7 +43,7 @@ class ResourceMutations {
|
|
|
1365
43
|
// {§edit-batch-merges} — the applied result is what the row records; the statement's
|
|
1366
44
|
// merge facts ride it here, whichever route settled the proposal.
|
|
1367
45
|
const editStatement = statement.op === "EDIT" ? statement : null;
|
|
1368
|
-
const facts = editStatement === null ? [] : (this.#
|
|
46
|
+
const facts = editStatement === null ? [] : (this.#edits.mergeFacts(editStatement));
|
|
1369
47
|
const effective = editStatement === null || facts.length === 0
|
|
1370
48
|
? settledMove
|
|
1371
49
|
: {
|
|
@@ -1374,10 +52,8 @@ class ResourceMutations {
|
|
|
1374
52
|
// The accepted row is composed from resolution.result ({§proposal-projection}).
|
|
1375
53
|
resolution: { ...settledMove.resolution, result: { ...(settledMove.resolution.result ?? {}), merged: facts } },
|
|
1376
54
|
};
|
|
1377
|
-
this.#recordEditSettlement(statement, effective);
|
|
55
|
+
this.#edits.recordEditSettlement(statement, effective);
|
|
1378
56
|
return effective;
|
|
1379
57
|
}
|
|
1380
58
|
}
|
|
1381
|
-
_a = ResourceMutations;
|
|
1382
|
-
export default ResourceMutations;
|
|
1383
59
|
//# sourceMappingURL=ResourceMutations.js.map
|