@agentshouse/mdruntime 0.1.0-cec6690d5712e → 0.1.0-cedd665dffa4c

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