@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.
Files changed (308) hide show
  1. package/.env.defaults +2 -2
  2. package/SPEC.md +243 -173
  3. package/digest-sql/curation/curation.sql +1 -1
  4. package/dist/build-info.json +1 -1
  5. package/dist/content/byte-view.d.ts +14 -0
  6. package/dist/content/byte-view.d.ts.map +1 -0
  7. package/dist/content/byte-view.js +33 -0
  8. package/dist/content/byte-view.js.map +1 -0
  9. package/dist/content/edit-receipt.d.ts.map +1 -1
  10. package/dist/content/edit-receipt.js +12 -1
  11. package/dist/content/edit-receipt.js.map +1 -1
  12. package/dist/content/line-marker.d.ts +2 -1
  13. package/dist/content/line-marker.d.ts.map +1 -1
  14. package/dist/content/line-marker.js +1 -0
  15. package/dist/content/line-marker.js.map +1 -1
  16. package/dist/content/matcher.d.ts +1 -0
  17. package/dist/content/matcher.d.ts.map +1 -1
  18. package/dist/content/matcher.js +1 -0
  19. package/dist/content/matcher.js.map +1 -1
  20. package/dist/content/read-projector.d.ts +4 -1
  21. package/dist/content/read-projector.d.ts.map +1 -1
  22. package/dist/content/read-projector.js +80 -3
  23. package/dist/content/read-projector.js.map +1 -1
  24. package/dist/core/AdmittedTurnExecutor.d.ts +43 -0
  25. package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -0
  26. package/dist/core/AdmittedTurnExecutor.js +245 -0
  27. package/dist/core/AdmittedTurnExecutor.js.map +1 -0
  28. package/dist/core/BareBatchRunner.d.ts +26 -0
  29. package/dist/core/BareBatchRunner.d.ts.map +1 -0
  30. package/dist/core/BareBatchRunner.js +104 -0
  31. package/dist/core/BareBatchRunner.js.map +1 -0
  32. package/dist/core/BudgetReadout.d.ts +1 -1
  33. package/dist/core/BudgetReadout.d.ts.map +1 -1
  34. package/dist/core/BudgetReadout.js +42 -47
  35. package/dist/core/BudgetReadout.js.map +1 -1
  36. package/dist/core/CapabilityResolver.d.ts +2 -1
  37. package/dist/core/CapabilityResolver.d.ts.map +1 -1
  38. package/dist/core/CapabilityResolver.js +8 -9
  39. package/dist/core/CapabilityResolver.js.map +1 -1
  40. package/dist/core/ChannelWrite.d.ts +1 -1
  41. package/dist/core/ChannelWrite.d.ts.map +1 -1
  42. package/dist/core/DataStatementRunner.d.ts +35 -0
  43. package/dist/core/DataStatementRunner.d.ts.map +1 -0
  44. package/dist/core/DataStatementRunner.js +252 -0
  45. package/dist/core/DataStatementRunner.js.map +1 -0
  46. package/dist/core/Dispatcher.d.ts +8 -3
  47. package/dist/core/Dispatcher.d.ts.map +1 -1
  48. package/dist/core/Dispatcher.js +88 -738
  49. package/dist/core/Dispatcher.js.map +1 -1
  50. package/dist/core/EditMutations.d.ts +39 -0
  51. package/dist/core/EditMutations.d.ts.map +1 -0
  52. package/dist/core/EditMutations.js +418 -0
  53. package/dist/core/EditMutations.js.map +1 -0
  54. package/dist/core/Engine.d.ts +2 -23
  55. package/dist/core/Engine.d.ts.map +1 -1
  56. package/dist/core/Engine.js +18 -240
  57. package/dist/core/Engine.js.map +1 -1
  58. package/dist/core/Engine.sql +24 -23
  59. package/dist/core/KillHandler.d.ts +32 -0
  60. package/dist/core/KillHandler.d.ts.map +1 -0
  61. package/dist/core/KillHandler.js +179 -0
  62. package/dist/core/KillHandler.js.map +1 -0
  63. package/dist/core/LogVisibility.d.ts +1 -1
  64. package/dist/core/LogVisibility.d.ts.map +1 -1
  65. package/dist/core/LogVisibility.js +3 -4
  66. package/dist/core/LogVisibility.js.map +1 -1
  67. package/dist/core/LogWriter.d.ts +42 -0
  68. package/dist/core/LogWriter.d.ts.map +1 -0
  69. package/dist/core/LogWriter.js +136 -0
  70. package/dist/core/LogWriter.js.map +1 -0
  71. package/dist/core/LoopDriver.d.ts +46 -0
  72. package/dist/core/LoopDriver.d.ts.map +1 -0
  73. package/dist/core/LoopDriver.js +263 -0
  74. package/dist/core/LoopDriver.js.map +1 -0
  75. package/dist/core/LoopLifecycle.d.ts +2 -1
  76. package/dist/core/LoopLifecycle.d.ts.map +1 -1
  77. package/dist/core/MembershipMaterialization.d.ts +7 -0
  78. package/dist/core/MembershipMaterialization.d.ts.map +1 -0
  79. package/dist/core/MembershipMaterialization.js +283 -0
  80. package/dist/core/MembershipMaterialization.js.map +1 -0
  81. package/dist/core/MutationEffects.d.ts +29 -0
  82. package/dist/core/MutationEffects.d.ts.map +1 -0
  83. package/dist/core/MutationEffects.js +318 -0
  84. package/dist/core/MutationEffects.js.map +1 -0
  85. package/dist/core/OverflowTurn.d.ts +4 -4
  86. package/dist/core/OverflowTurn.d.ts.map +1 -1
  87. package/dist/core/OverflowTurn.js +6 -7
  88. package/dist/core/OverflowTurn.js.map +1 -1
  89. package/dist/core/PacketBuilder.d.ts.map +1 -1
  90. package/dist/core/PacketBuilder.js +31 -9
  91. package/dist/core/PacketBuilder.js.map +1 -1
  92. package/dist/core/ResourceMutations.d.ts +12 -36
  93. package/dist/core/ResourceMutations.d.ts.map +1 -1
  94. package/dist/core/ResourceMutations.js +27 -1351
  95. package/dist/core/ResourceMutations.js.map +1 -1
  96. package/dist/core/ResourceSelector.d.ts +21 -0
  97. package/dist/core/ResourceSelector.d.ts.map +1 -0
  98. package/dist/core/ResourceSelector.js +233 -0
  99. package/dist/core/ResourceSelector.js.map +1 -0
  100. package/dist/core/ResourceTransfers.d.ts +55 -0
  101. package/dist/core/ResourceTransfers.d.ts.map +1 -0
  102. package/dist/core/ResourceTransfers.js +525 -0
  103. package/dist/core/ResourceTransfers.js.map +1 -0
  104. package/dist/core/SchemeRegistry.js +2 -2
  105. package/dist/core/SchemeRegistry.js.map +1 -1
  106. package/dist/core/SendBroadcastHandler.d.ts +40 -0
  107. package/dist/core/SendBroadcastHandler.d.ts.map +1 -0
  108. package/dist/core/SendBroadcastHandler.js +159 -0
  109. package/dist/core/SendBroadcastHandler.js.map +1 -0
  110. package/dist/core/StoredPacket.d.ts +11 -0
  111. package/dist/core/StoredPacket.d.ts.map +1 -1
  112. package/dist/core/StoredPacket.js +22 -1
  113. package/dist/core/StoredPacket.js.map +1 -1
  114. package/dist/core/StrikeRail.d.ts.map +1 -1
  115. package/dist/core/StrikeRail.js +19 -9
  116. package/dist/core/StrikeRail.js.map +1 -1
  117. package/dist/core/TokenCalibration.d.ts +16 -0
  118. package/dist/core/TokenCalibration.d.ts.map +1 -0
  119. package/dist/core/TokenCalibration.js +35 -0
  120. package/dist/core/TokenCalibration.js.map +1 -0
  121. package/dist/core/ToolResources.d.ts.map +1 -1
  122. package/dist/core/ToolResources.js +15 -12
  123. package/dist/core/ToolResources.js.map +1 -1
  124. package/dist/core/TurnMaterialization.d.ts +37 -0
  125. package/dist/core/TurnMaterialization.d.ts.map +1 -0
  126. package/dist/core/TurnMaterialization.js +296 -0
  127. package/dist/core/TurnMaterialization.js.map +1 -0
  128. package/dist/core/TurnOps.d.ts.map +1 -1
  129. package/dist/core/TurnOps.js +6 -8
  130. package/dist/core/TurnOps.js.map +1 -1
  131. package/dist/core/TurnRunner.d.ts +15 -27
  132. package/dist/core/TurnRunner.d.ts.map +1 -1
  133. package/dist/core/TurnRunner.js +76 -670
  134. package/dist/core/TurnRunner.js.map +1 -1
  135. package/dist/core/WorkerControlHandler.d.ts +15 -0
  136. package/dist/core/WorkerControlHandler.d.ts.map +1 -0
  137. package/dist/core/WorkerControlHandler.js +89 -0
  138. package/dist/core/WorkerControlHandler.js.map +1 -0
  139. package/dist/core/attachments.d.ts +14 -0
  140. package/dist/core/attachments.d.ts.map +1 -0
  141. package/dist/core/attachments.js +18 -0
  142. package/dist/core/attachments.js.map +1 -0
  143. package/dist/core/fork.d.ts.map +1 -1
  144. package/dist/core/fork.js +0 -4
  145. package/dist/core/fork.js.map +1 -1
  146. package/dist/core/fork.sql +5 -16
  147. package/dist/core/git-membership.d.ts +15 -0
  148. package/dist/core/git-membership.d.ts.map +1 -1
  149. package/dist/core/git-membership.js +4 -273
  150. package/dist/core/git-membership.js.map +1 -1
  151. package/dist/core/git-state.d.ts +2 -1
  152. package/dist/core/git-state.d.ts.map +1 -1
  153. package/dist/core/mutation-types.d.ts +102 -0
  154. package/dist/core/mutation-types.d.ts.map +1 -0
  155. package/dist/core/mutation-types.js +2 -0
  156. package/dist/core/mutation-types.js.map +1 -0
  157. package/dist/core/operation-target-groups.js +1 -1
  158. package/dist/core/operation-target-groups.js.map +1 -1
  159. package/dist/core/packet-wire.d.ts +7 -6
  160. package/dist/core/packet-wire.d.ts.map +1 -1
  161. package/dist/core/packet-wire.js +96 -68
  162. package/dist/core/packet-wire.js.map +1 -1
  163. package/dist/core/plurnk-uri.d.ts +0 -1
  164. package/dist/core/plurnk-uri.d.ts.map +1 -1
  165. package/dist/core/plurnk-uri.js +1 -1
  166. package/dist/core/plurnk-uri.js.map +1 -1
  167. package/dist/core/statement-primary.d.ts +4 -0
  168. package/dist/core/statement-primary.d.ts.map +1 -0
  169. package/dist/core/statement-primary.js +3 -0
  170. package/dist/core/statement-primary.js.map +1 -0
  171. package/dist/core/teaching-corpus.d.ts +0 -1
  172. package/dist/core/teaching-corpus.d.ts.map +1 -1
  173. package/dist/core/teaching-corpus.js +0 -9
  174. package/dist/core/teaching-corpus.js.map +1 -1
  175. package/dist/core/turn-scheduler.js +3 -3
  176. package/dist/core/turn-scheduler.js.map +1 -1
  177. package/dist/core/turn-signals.d.ts +15 -0
  178. package/dist/core/turn-signals.d.ts.map +1 -0
  179. package/dist/core/turn-signals.js +19 -0
  180. package/dist/core/turn-signals.js.map +1 -0
  181. package/dist/core/worker-settings.d.ts +0 -1
  182. package/dist/core/worker-settings.d.ts.map +1 -1
  183. package/dist/core/worker-settings.js +1 -1
  184. package/dist/core/worker-settings.js.map +1 -1
  185. package/dist/digest/Digest.d.ts +3 -16
  186. package/dist/digest/Digest.d.ts.map +1 -1
  187. package/dist/digest/Digest.js +10 -958
  188. package/dist/digest/Digest.js.map +1 -1
  189. package/dist/digest/DigestRender.d.ts +12 -0
  190. package/dist/digest/DigestRender.d.ts.map +1 -0
  191. package/dist/digest/DigestRender.js +641 -0
  192. package/dist/digest/DigestRender.js.map +1 -0
  193. package/dist/digest/DigestRequiem.d.ts +13 -0
  194. package/dist/digest/DigestRequiem.d.ts.map +1 -0
  195. package/dist/digest/DigestRequiem.js +332 -0
  196. package/dist/digest/DigestRequiem.js.map +1 -0
  197. package/dist/digest/digest-rows.d.ts +303 -0
  198. package/dist/digest/digest-rows.d.ts.map +1 -0
  199. package/dist/digest/digest-rows.js +2 -0
  200. package/dist/digest/digest-rows.js.map +1 -0
  201. package/dist/digest/digest.sql +1 -7
  202. package/dist/observe/api.d.ts +0 -2
  203. package/dist/observe/api.d.ts.map +1 -1
  204. package/dist/observe/api.js +2 -2
  205. package/dist/observe/api.js.map +1 -1
  206. package/dist/observe/metrics.d.ts +0 -1
  207. package/dist/observe/metrics.d.ts.map +1 -1
  208. package/dist/observe/metrics.js +0 -1
  209. package/dist/observe/metrics.js.map +1 -1
  210. package/dist/schemes/Exec.d.ts +4 -1
  211. package/dist/schemes/Exec.d.ts.map +1 -1
  212. package/dist/schemes/Exec.js +89 -48
  213. package/dist/schemes/Exec.js.map +1 -1
  214. package/dist/schemes/ExecOutputScheme.d.ts +2 -1
  215. package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
  216. package/dist/schemes/ExecOutputScheme.js +3 -3
  217. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  218. package/dist/schemes/File.d.ts +4 -1
  219. package/dist/schemes/File.d.ts.map +1 -1
  220. package/dist/schemes/File.js +100 -13
  221. package/dist/schemes/File.js.map +1 -1
  222. package/dist/schemes/Log.d.ts +4 -5
  223. package/dist/schemes/Log.d.ts.map +1 -1
  224. package/dist/schemes/Log.js +26 -118
  225. package/dist/schemes/Log.js.map +1 -1
  226. package/dist/schemes/Log.sql +1 -66
  227. package/dist/schemes/Prompt.js +1 -1
  228. package/dist/schemes/Prompt.js.map +1 -1
  229. package/dist/schemes/Worker.d.ts.map +1 -1
  230. package/dist/schemes/Worker.js +1 -13
  231. package/dist/schemes/Worker.js.map +1 -1
  232. package/dist/schemes/_entry-crud.d.ts +3 -1
  233. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  234. package/dist/schemes/_entry-crud.js +18 -3
  235. package/dist/schemes/_entry-crud.js.map +1 -1
  236. package/dist/schemes/_entry-find.d.ts +4 -2
  237. package/dist/schemes/_entry-find.d.ts.map +1 -1
  238. package/dist/schemes/_entry-find.js +79 -10
  239. package/dist/schemes/_entry-find.js.map +1 -1
  240. package/dist/schemes/_entry-manifest.d.ts +1 -2
  241. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  242. package/dist/schemes/_entry-ops.d.ts +2 -1
  243. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  244. package/dist/schemes/_entry-ops.js +20 -2
  245. package/dist/schemes/_entry-ops.js.map +1 -1
  246. package/dist/schemes/_entry-semantic.d.ts +1 -0
  247. package/dist/schemes/_entry-semantic.d.ts.map +1 -1
  248. package/dist/schemes/_entry-semantic.js +3 -0
  249. package/dist/schemes/_entry-semantic.js.map +1 -1
  250. package/dist/schemes/_entry-semantic.sql +2 -2
  251. package/dist/schemes/_entry-send.d.ts +0 -1
  252. package/dist/schemes/_entry-send.d.ts.map +1 -1
  253. package/dist/schemes/_entry-send.js +5 -80
  254. package/dist/schemes/_entry-send.js.map +1 -1
  255. package/dist/schemes/exec-runtime.d.ts +7 -0
  256. package/dist/schemes/exec-runtime.d.ts.map +1 -0
  257. package/dist/schemes/exec-runtime.js +2 -0
  258. package/dist/schemes/exec-runtime.js.map +1 -0
  259. package/dist/server/ClientReads.d.ts +34 -0
  260. package/dist/server/ClientReads.d.ts.map +1 -0
  261. package/dist/server/ClientReads.js +210 -0
  262. package/dist/server/ClientReads.js.map +1 -0
  263. package/dist/server/Daemon.d.ts +12 -22
  264. package/dist/server/Daemon.d.ts.map +1 -1
  265. package/dist/server/Daemon.js +31 -362
  266. package/dist/server/Daemon.js.map +1 -1
  267. package/dist/server/DaemonModule.d.ts +6 -5
  268. package/dist/server/DaemonModule.d.ts.map +1 -1
  269. package/dist/server/DrainSupervisor.d.ts +3 -2
  270. package/dist/server/DrainSupervisor.d.ts.map +1 -1
  271. package/dist/server/DrainSupervisor.js +7 -3
  272. package/dist/server/DrainSupervisor.js.map +1 -1
  273. package/dist/server/Functionality.d.ts +0 -12
  274. package/dist/server/Functionality.d.ts.map +1 -1
  275. package/dist/server/Functionality.js.map +1 -1
  276. package/dist/server/FunctionalityManager.d.ts +0 -1
  277. package/dist/server/FunctionalityManager.d.ts.map +1 -1
  278. package/dist/server/FunctionalityManager.js +3 -3
  279. package/dist/server/FunctionalityManager.js.map +1 -1
  280. package/dist/server/MembersFunctionality.d.ts +2 -13
  281. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  282. package/dist/server/MembersFunctionality.js +4 -4
  283. package/dist/server/MembersFunctionality.js.map +1 -1
  284. package/dist/server/SkillsFunctionality.d.ts +1 -7
  285. package/dist/server/SkillsFunctionality.d.ts.map +1 -1
  286. package/dist/server/SkillsFunctionality.js +3 -3
  287. package/dist/server/SkillsFunctionality.js.map +1 -1
  288. package/dist/server/WorkerModelResolver.d.ts +17 -0
  289. package/dist/server/WorkerModelResolver.d.ts.map +1 -0
  290. package/dist/server/WorkerModelResolver.js +166 -0
  291. package/dist/server/WorkerModelResolver.js.map +1 -0
  292. package/dist/server/daemon-results.d.ts +5 -0
  293. package/dist/server/daemon-results.d.ts.map +1 -0
  294. package/dist/server/daemon-results.js +7 -0
  295. package/dist/server/daemon-results.js.map +1 -0
  296. package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
  297. package/dist/server/dispatch-as-plurnk.js +4 -6
  298. package/dist/server/dispatch-as-plurnk.js.map +1 -1
  299. package/dist/server/logEntry.d.ts +0 -1
  300. package/dist/server/logEntry.d.ts.map +1 -1
  301. package/dist/server/logEntry.js +1 -13
  302. package/dist/server/logEntry.js.map +1 -1
  303. package/dist/server/logEntry.sql +1 -5
  304. package/dist/server/loopDocs.d.ts.map +1 -1
  305. package/dist/server/loopDocs.js +11 -2
  306. package/dist/server/loopDocs.js.map +1 -1
  307. package/migrations/001_schema.sql +30 -347
  308. package/package.json +30 -30
@@ -1,1361 +1,39 @@
1
- var _a;
2
- import { PathSyntax, } from "@plurnk/plurnk-contracts";
3
- import { TextCoordinates } from "@plurnk/plurnk-mimetypes";
4
- import { InvalidOperationResultError, } from "@plurnk/plurnk-schemes";
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
- static #failure(code, status, detail, fields = {}, extensions = {}) {
16
- return Results.failure("engine:dispatcher", code, status, detail, fields, extensions);
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.#schemes = schemes;
34
- this.#liveSubscriptions = liveSubscriptions;
35
- this.#run = run;
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
- static #prependScopeNormalizations(result, scopeNormalizations) {
671
- if (scopeNormalizations === undefined)
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
- static #withCombinedEffects(result, ...additional) {
819
- const existing = _a.#effectsOf(result);
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
- static #settleProposalEffects(original, settlement) {
827
- const pending = original.attrs
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
- #recordEditSettlement(statement, settlement) {
853
- if (statement.op !== "EDIT")
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
- static #settlementEffects(settlement) {
878
- const effects = settlement.resolution.result
879
- ?.effects;
880
- return effects === undefined ? [] : assertResourceEffects(effects);
28
+ async handleCopy(...args) {
29
+ return this.#transfers.handleCopy(...args);
881
30
  }
882
- static #withSettlementEffects(settlement, effects) {
883
- const projected = (settlement.resolution.result ?? {});
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 = _a.#settleProposalEffects(result, settlement);
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.#preparedEdits.get(editStatement)?.merged ?? []);
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