@agentshouse/mdruntime 0.1.0-cd50a4607cb7d → 0.1.0-cd574a610e9c8

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