@agentshouse/mdruntime 0.1.0-cedb03eaa48b6 → 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 (395) 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 +173 -125
  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 +0 -5
  17. package/dist/binary.d.ts.map +1 -1
  18. package/dist/binary.js +1 -1
  19. package/dist/bootstrap.d.ts +30 -11
  20. package/dist/bootstrap.d.ts.map +1 -1
  21. package/dist/bootstrap.js +309 -113
  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 -16
  29. package/dist/change-set.d.ts.map +1 -1
  30. package/dist/change-set.js +17 -89
  31. package/dist/change.d.ts +36 -1
  32. package/dist/change.d.ts.map +1 -1
  33. package/dist/change.js +17 -3
  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 +37 -26
  38. package/dist/client.d.ts.map +1 -1
  39. package/dist/client.js +113 -23
  40. package/dist/collection.d.ts +32 -21
  41. package/dist/collection.d.ts.map +1 -1
  42. package/dist/collection.js +53 -318
  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 +7 -137
  47. package/dist/connection.d.ts.map +1 -1
  48. package/dist/connection.js +110 -3602
  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 +75 -49
  59. package/dist/conversation.d.ts.map +1 -1
  60. package/dist/conversation.js +1029 -806
  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 +20 -4
  71. package/dist/durability.d.ts.map +1 -1
  72. package/dist/durability.js +9 -0
  73. package/dist/entry.d.ts +5 -2
  74. package/dist/entry.d.ts.map +1 -1
  75. package/dist/entry.js +33 -29
  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 +21 -8
  92. package/dist/host.d.ts.map +1 -1
  93. package/dist/host.js +119 -51
  94. package/dist/identity.d.ts +0 -2
  95. package/dist/identity.d.ts.map +1 -1
  96. package/dist/identity.js +0 -6
  97. package/dist/index.d.ts +41 -16
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +19 -4
  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 +19 -7
  125. package/dist/memory.d.ts.map +1 -1
  126. package/dist/memory.js +281 -118
  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 +8 -11
  131. package/dist/model.d.ts.map +1 -1
  132. package/dist/model.js +6 -23
  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 +38 -10
  140. package/dist/observation.d.ts.map +1 -1
  141. package/dist/observation.js +232 -44
  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 +51 -9
  149. package/dist/outcome.d.ts.map +1 -1
  150. package/dist/outcome.js +62 -2
  151. package/dist/overlay.d.ts +8 -6
  152. package/dist/overlay.d.ts.map +1 -1
  153. package/dist/overlay.js +121 -182
  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 +21 -207
  173. package/dist/query.d.ts.map +1 -1
  174. package/dist/query.js +3 -493
  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 +21 -11
  182. package/dist/reach.d.ts.map +1 -1
  183. package/dist/reach.js +342 -112
  184. package/dist/react.d.ts +17 -3
  185. package/dist/react.d.ts.map +1 -1
  186. package/dist/react.js +33 -1
  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 +22 -20
  191. package/dist/readiness.d.ts.map +1 -1
  192. package/dist/readiness.js +5 -13
  193. package/dist/reads.d.ts +38 -10
  194. package/dist/reads.d.ts.map +1 -1
  195. package/dist/reads.js +163 -83
  196. package/dist/recovery.d.ts +5 -2
  197. package/dist/recovery.d.ts.map +1 -1
  198. package/dist/recovery.js +65 -8
  199. package/dist/refresh.d.ts +18 -15
  200. package/dist/refresh.d.ts.map +1 -1
  201. package/dist/refresh.js +145 -60
  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 +49 -36
  206. package/dist/replica.d.ts.map +1 -1
  207. package/dist/replica.js +322 -198
  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 +55 -13
  212. package/dist/room-draft.d.ts.map +1 -1
  213. package/dist/room-draft.js +416 -122
  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 +32 -31
  218. package/dist/rows.d.ts.map +1 -1
  219. package/dist/rows.js +59 -124
  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 +43 -49
  224. package/dist/save.d.ts.map +1 -1
  225. package/dist/save.js +736 -302
  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 +12 -95
  230. package/dist/search.d.ts.map +1 -1
  231. package/dist/search.js +10 -266
  232. package/dist/seed.d.ts +11 -2
  233. package/dist/seed.d.ts.map +1 -1
  234. package/dist/seed.js +71 -1
  235. package/dist/shared.d.ts +6 -7
  236. package/dist/shared.d.ts.map +1 -1
  237. package/dist/shared.js +75 -104
  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 +4 -5
  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 -163
  290. package/dist/store.d.ts.map +1 -1
  291. package/dist/store.js +163 -750
  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 +4 -2
  310. package/dist/turn.d.ts.map +1 -1
  311. package/dist/turn.js +46 -22
  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 +1 -16
  316. package/dist/windows.d.ts.map +1 -1
  317. package/dist/windows.js +2 -79
  318. package/dist/wire.d.ts +74 -9
  319. package/dist/wire.d.ts.map +1 -1
  320. package/dist/wire.js +26 -6
  321. package/package.json +19 -19
  322. package/AGENTS.md +0 -305
  323. package/dist/attempt.d.ts +0 -16
  324. package/dist/attempt.d.ts.map +0 -1
  325. package/dist/attempt.js +0 -70
  326. package/dist/chat/acts.d.ts +0 -33
  327. package/dist/chat/acts.d.ts.map +0 -1
  328. package/dist/chat/acts.js +0 -36
  329. package/dist/chat/context.d.ts +0 -3
  330. package/dist/chat/context.d.ts.map +0 -1
  331. package/dist/chat/earlier.d.ts +0 -10
  332. package/dist/chat/earlier.d.ts.map +0 -1
  333. package/dist/chat/earlier.js +0 -54
  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 -61
  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 -27
  344. package/dist/chat/list.d.ts.map +0 -1
  345. package/dist/chat/list.js +0 -99
  346. package/dist/chat/markdown.d.ts +0 -20
  347. package/dist/chat/markdown.d.ts.map +0 -1
  348. package/dist/chat/markdown.js +0 -188
  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 -22
  353. package/dist/chat/messages.d.ts.map +0 -1
  354. package/dist/chat/messages.js +0 -155
  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 -628
  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 -69
  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 -34
  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/fence.d.ts +0 -6
  388. package/dist/fence.d.ts.map +0 -1
  389. package/dist/fence.js +0 -10
  390. package/dist/log.d.ts +0 -5
  391. package/dist/log.d.ts.map +0 -1
  392. package/dist/log.js +0 -14
  393. package/dist/read-side.d.ts +0 -27
  394. package/dist/read-side.d.ts.map +0 -1
  395. package/dist/read-side.js +0 -824
package/dist/save.js CHANGED
@@ -1,12 +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
- import { differing } from './shared.js';
9
- 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) {
10
12
  return {
11
13
  kind: 'refused',
12
14
  refusal: {
@@ -14,14 +16,15 @@ function refuse(code, file, location) {
14
16
  retryable: false,
15
17
  ...(file === undefined ? {} : { file }),
16
18
  ...(location === undefined ? {} : { location }),
19
+ ...(line === undefined ? {} : { line }),
17
20
  },
18
21
  };
19
22
  }
23
+ function refusedBy(refusal, file) {
24
+ return refuse(refusal.rule, file, refusal.location, refusal.line);
25
+ }
20
26
  function repointed(document, definition, from, to, moves, settled) {
21
- const parsed = parse(document.content, from);
22
- if (!parsed.ok)
23
- return [];
24
- let next = parsed.document;
27
+ const changes = [];
25
28
  const names = new Set();
26
29
  for (const { pointer, value } of definition === undefined
27
30
  ? []
@@ -33,53 +36,24 @@ function repointed(document, definition, from, to, moves, settled) {
33
36
  const destination = moves.get(named) ?? named;
34
37
  if (target(to, value) === destination)
35
38
  continue;
36
- const applied = apply(next, {
39
+ changes.push({
37
40
  kind: 'reference',
38
41
  pointer,
39
42
  value: retarget(value, relative(to, destination)),
40
43
  prior: value,
41
44
  });
42
- if (applied.outcome !== 'applied')
43
- continue;
44
- next = applied.document;
45
45
  names.add(name);
46
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;
47
53
  return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
48
54
  }
49
- function edited(document, path, changes) {
50
- let next = document;
51
- for (const change of changes) {
52
- if (change.path !== path)
53
- continue;
54
- if (change.kind !== 'section' && change.kind !== 'preamble')
55
- continue;
56
- const applied = apply(next, change.kind === 'preamble'
57
- ? { kind: 'preamble', value: change.value, prior: next.preamble }
58
- : {
59
- kind: 'section',
60
- name: change.name,
61
- value: change.value,
62
- prior: next.sections[change.name],
63
- });
64
- if (applied.outcome === 'applied')
65
- next = applied.document;
66
- }
67
- return next;
68
- }
69
- function bodies(path, before, after) {
55
+ function relinks(path, moved, before, moves, definition) {
70
56
  const changes = [];
71
- if (after.preamble !== before.preamble) {
72
- changes.push({ kind: 'preamble', path, value: after.preamble });
73
- }
74
- for (const [name, value] of Object.entries(after.sections)) {
75
- if (before.sections[name] === value)
76
- continue;
77
- changes.push({ kind: 'section', path, name, value });
78
- }
79
- return changes;
80
- }
81
- function relinks(path, moved, before, moves) {
82
- let after = before;
83
57
  for (const link of links(before, path)) {
84
58
  const destination = moves.get(link.target) ?? link.target;
85
59
  if (destination === link.target && moved === path)
@@ -87,40 +61,40 @@ function relinks(path, moved, before, moves) {
87
61
  const to = relative(moved, destination);
88
62
  if (to === relative(path, link.target))
89
63
  continue;
90
- const applied = apply(after, { kind: 'link', href: link.href, to });
91
- if (applied.outcome === 'refuse') {
92
- return {
93
- kind: 'refused',
94
- refusal: {
95
- code: applied.refusal.rule,
96
- retryable: false,
97
- file: path,
98
- ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
99
- },
100
- };
101
- }
102
- after = applied.document;
64
+ changes.push({ kind: 'link', href: link.href, to });
103
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;
104
72
  if (after === before)
105
73
  return { kind: 'changes', changes: [] };
106
74
  if (before.type === undefined) {
107
75
  return { kind: 'changes', changes: [{ kind: 'replace', path, content: serialize(after) }] };
108
76
  }
109
- return { kind: 'changes', changes: bodies(path, before, after) };
77
+ return { kind: 'changes', changes: partsBetween(path, before, after) };
110
78
  }
111
- function subsumed(merged, path) {
112
- for (let index = merged.length - 1; index >= 0; index -= 1) {
113
- const item = merged[index];
114
- if (item.path !== path)
115
- continue;
116
- if (item.kind === 'section' || item.kind === 'preamble')
117
- merged.splice(index, 1);
118
- }
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));
119
92
  }
120
- function rewrites(changes, visible) {
93
+ function* rewrites(changes, visible) {
121
94
  const moves = new Map();
122
95
  const settled = new Set();
123
- for (const change of changes) {
96
+ const parts = new Map();
97
+ for (const [index, change] of changes.entries()) {
124
98
  if (change.kind === 'move')
125
99
  moves.set(change.path, change.to);
126
100
  if (change.kind === 'remove' ||
@@ -131,54 +105,143 @@ function rewrites(changes, visible) {
131
105
  }
132
106
  if (change.kind === 'field')
133
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
+ }
134
115
  }
135
116
  if (moves.size === 0)
136
117
  return { kind: 'changes', changes };
118
+ const names = movedNames(moves);
137
119
  const merged = [...changes];
138
120
  const added = [];
139
- for (const [path, document] of documentsOf(visible.collection)) {
121
+ for (const [path, document] of yield* documentsOf(visible.collection)) {
140
122
  if (settled.has(path))
141
123
  continue;
142
124
  const moved = moves.get(path) ?? path;
143
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
144
- added.push(...repointed(document, definition, path, moved, moves, settled));
145
- 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);
146
133
  if (relinked.kind === 'refused')
147
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
+ }
148
141
  for (const change of relinked.changes) {
149
142
  if (change.kind === 'replace') {
150
- 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
+ }
151
148
  added.push(change);
152
149
  continue;
153
150
  }
154
- const index = merged.findIndex((item) => item.kind === change.kind &&
155
- item.path === path &&
156
- (change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
157
- if (index === -1)
151
+ const index = keyed.get(partKey(change));
152
+ if (index === undefined)
158
153
  added.push(change);
159
154
  else
160
155
  merged[index] = change;
161
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);
162
160
  }
163
- return { kind: 'changes', changes: [...added, ...merged] };
161
+ return {
162
+ kind: 'changes',
163
+ changes: [...added, ...merged.filter((change) => change !== null)],
164
+ };
164
165
  }
165
- function anticipated(changes, preconditions, rewritten, visible) {
166
- 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));
167
169
  return {
168
170
  changes: rewritten,
169
- preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
171
+ preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
170
172
  };
171
173
  }
172
- export function providerRewritten(batch, visible) {
173
- const rewritten = rewrites(batch.changes, visible);
174
+ export function* providerRewritten(batch, visible) {
175
+ const rewritten = yield* rewrites(batch.changes, visible);
174
176
  if (rewritten.kind === 'refused')
175
177
  return batch;
176
178
  return {
177
179
  ...batch,
178
- ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
180
+ ...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
179
181
  };
180
182
  }
181
- function incoming(collection, removed, candidates) {
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) {
182
245
  const found = new Map();
183
246
  const note = (target, file) => {
184
247
  if (!removed.has(target))
@@ -187,14 +250,21 @@ function incoming(collection, removed, candidates) {
187
250
  referrers.add(file);
188
251
  found.set(target, referrers);
189
252
  };
190
- const read = candidates === null
191
- ? documentsOf(collection)
192
- : [...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);
193
259
  const document = collection.document(path);
194
- return document === undefined ? [] : [[path, document]];
195
- });
196
- for (const [path, document] of read) {
197
- 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);
198
268
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
199
269
  if (to.includes(ATTACHMENT))
200
270
  continue;
@@ -202,8 +272,9 @@ function incoming(collection, removed, candidates) {
202
272
  if (named !== null)
203
273
  note(named, path);
204
274
  }
275
+ yield ROW_WEIGHT + collection.text[path].length;
205
276
  }
206
- const [first] = [...found.keys()].sort();
277
+ const [first] = yield* sorting(found.keys());
207
278
  if (first === undefined)
208
279
  return null;
209
280
  const every = new Set();
@@ -216,14 +287,16 @@ function incoming(collection, removed, candidates) {
216
287
  code: 'referenced-missing-file',
217
288
  retryable: false,
218
289
  file: first,
219
- referrers: [...every].sort(),
290
+ referrers: yield* sorting(every),
220
291
  },
221
292
  };
222
293
  }
223
294
  function affected(change) {
224
295
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
225
296
  }
226
- function guard(changes, input) {
297
+ const WRITE = ['write'];
298
+ const MOVE_OUT = ['write', 'erase'];
299
+ function guard(changes, input, removing) {
227
300
  const overridden = new Map();
228
301
  const existing = {
229
302
  has: (path) => overridden.get(path) ??
@@ -231,21 +304,17 @@ function guard(changes, input) {
231
304
  add: (path) => void overridden.set(path, true),
232
305
  delete: (path) => void overridden.set(path, false),
233
306
  };
307
+ const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
234
308
  for (const [index, change] of changes.entries()) {
235
309
  for (const path of affected(change)) {
236
- const normalized = normalize(path);
237
- if (normalized === null || normalized === '')
238
- return refuse('room-root', path);
239
- if (covered(input.providerOwnedPaths, normalized)) {
240
- return refuse('provider-owned-path', normalized);
241
- }
242
- const operation = change.kind === 'remove' ? 'delete' : 'write';
243
- if (!input.local && !covered(input.operations[operation], normalized))
244
- 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);
245
313
  }
246
- if (change.kind === 'create') {
247
- if (existing.has(change.path))
248
- 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);
249
318
  existing.add(change.path);
250
319
  continue;
251
320
  }
@@ -255,6 +324,9 @@ function guard(changes, input) {
255
324
  input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
256
325
  return refuse('path-collision', change.path);
257
326
  }
327
+ const taken = occupied(change.path);
328
+ if (taken !== null)
329
+ return refuse('path-collision', taken);
258
330
  if (!input.local &&
259
331
  existing.has(change.path) &&
260
332
  input.visible.revisions[departure(changes, index)] === undefined) {
@@ -266,8 +338,9 @@ function guard(changes, input) {
266
338
  if (change.kind === 'move') {
267
339
  if (!existing.has(change.path))
268
340
  return refuse('path-absent', change.path);
269
- if (existing.has(change.to))
270
- 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);
271
344
  if (unrevised(input, change.path)) {
272
345
  return refuse('unknown-revision', change.path);
273
346
  }
@@ -297,13 +370,13 @@ function guard(changes, input) {
297
370
  return null;
298
371
  }
299
372
  function unrevised(input, path) {
300
- 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;
301
374
  }
302
- export function queuedDocuments(queue) {
375
+ function queuedDocuments(queue) {
303
376
  const paths = new Set();
304
377
  for (const batch of queue) {
305
378
  for (const change of batch.changes) {
306
- if (change.kind === 'binary')
379
+ if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
307
380
  continue;
308
381
  if (change.kind === 'remove')
309
382
  paths.delete(change.path);
@@ -332,7 +405,7 @@ function sameTarget(precondition, change) {
332
405
  return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
333
406
  ((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
334
407
  }
335
- function preconditionsOf(changes, visible, bases) {
408
+ function* preconditionsOf(changes, visible, bases) {
336
409
  const preconditions = [];
337
410
  const seen = new Set();
338
411
  const add = (precondition) => {
@@ -343,7 +416,8 @@ function preconditionsOf(changes, visible, bases) {
343
416
  preconditions.push(precondition);
344
417
  };
345
418
  for (const change of changes) {
346
- if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
419
+ yield ROW_WEIGHT;
420
+ if (isPart(change)) {
347
421
  const based = bases.find((base) => sameTarget(base, change));
348
422
  if (based !== undefined) {
349
423
  add(based);
@@ -390,10 +464,13 @@ function preconditionsOf(changes, visible, bases) {
390
464
  function depth(path) {
391
465
  return path.split('/').length;
392
466
  }
393
- function exceeded(limits, changes, effective, resulting) {
467
+ function* exceeding(limits, changes, effective, resulting) {
394
468
  const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
395
469
  const created = changes
396
- .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')
397
474
  .map((change) => (change.kind === 'move' ? change.to : change.path));
398
475
  for (const limit of limits) {
399
476
  const value = limit.value;
@@ -407,9 +484,13 @@ function exceeded(limits, changes, effective, resulting) {
407
484
  return refuse('provider-limit', undefined, limit.name);
408
485
  }
409
486
  if (limit.name === 'document-size') {
410
- const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
411
- if (large !== undefined)
412
- 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
+ }
413
494
  }
414
495
  if (limit.name === 'path-length') {
415
496
  const long = created.find((path) => path.length > value);
@@ -427,97 +508,109 @@ function exceeded(limits, changes, effective, resulting) {
427
508
  function shellRelative(text, path, handle, definition) {
428
509
  if (!text.includes(`/rooms/${handle}/`))
429
510
  return null;
430
- const parsed = parse(text, path);
511
+ const parsed = parse(text, path, definition);
431
512
  if (!parsed.ok)
432
513
  return null;
433
514
  const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
434
515
  return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
435
516
  }
436
517
  function relativeField(path, name, value, handle, definition) {
437
- const opened = parse('---\n---\n');
438
- if (definition === undefined ||
439
- !opened.ok ||
440
- !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
518
+ if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
441
519
  return value;
442
- 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);
443
521
  if (held.outcome !== 'applied')
444
522
  return value;
445
523
  const result = relativize(held.document, path, handle, definition);
446
524
  return result.outcome === 'applied' ? result.document.fields[name] : value;
447
525
  }
448
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) {
449
537
  const { handle, composition, visible } = input;
450
- const typed = (path) => (type) => definitionOf(governing(composition, path), type);
451
538
  const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
452
- return input.changes.map((change) => {
453
- const definition = typed(change.path);
454
- if (change.kind === 'field') {
455
- const type = visible.collection.document(change.path)?.type;
456
- const value = relativeField(change.path, change.name, change.value, handle, definition(type));
457
- return value === change.value ? change : { ...change, value };
458
- }
459
- if (change.kind === 'section' || change.kind === 'preamble') {
460
- const value = body(change.path, change.value);
461
- return value === change.value ? change : { ...change, value };
462
- }
463
- if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
464
- const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
465
- return content === change.content ? change : { ...change, content };
466
- }
467
- if (change.kind !== 'create')
468
- return change;
469
- const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
470
- const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
471
- name,
472
- relativeField(change.path, name, value, handle, definition(type)),
473
- ]));
474
- const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
475
- return { ...change, fields, sections };
476
- });
477
- }
478
- function unmodeledType(collection, composition) {
479
- if (available(composition))
480
- return false;
481
- for (const [path, document] of documentsOf(collection)) {
482
- if (document.type !== undefined &&
483
- document.type !== 'any' &&
484
- governing(composition, path).status === 'unavailable')
485
- 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 } };
486
544
  }
487
- return false;
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
+ };
488
571
  }
489
572
  function unqueueable(file) {
490
- return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
491
- }
492
- function isPart(change) {
493
- return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
573
+ return refuse('unknown-revision', file);
494
574
  }
495
575
  function touched(change) {
496
576
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
497
577
  }
498
- function partApplied(content, path, change) {
499
- const parsed = parse(content, path);
500
- if (!parsed.ok)
501
- return null;
502
- const document = parsed.document;
503
- const applied = apply(document, change.kind === 'field'
504
- ? {
505
- kind: 'field',
506
- name: change.name,
507
- value: change.value,
508
- prior: document.fields[change.name],
509
- }
510
- : change.kind === 'section'
511
- ? {
512
- kind: 'section',
513
- name: change.name,
514
- value: change.value,
515
- prior: document.sections[change.name],
516
- }
517
- : { kind: 'preamble', value: change.value, prior: document.preamble });
518
- 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;
519
597
  }
520
- function folded(changes, change, appending) {
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) {
521
614
  const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
522
615
  const whole = held === undefined ? -1 : changes.indexOf(held);
523
616
  const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
@@ -538,10 +631,11 @@ function folded(changes, change, appending) {
538
631
  return 'appended';
539
632
  }
540
633
  if ('content' in held) {
541
- const content = partApplied(held.content, held.path, change);
542
- if (content === null)
543
- return 'refused';
544
- 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);
545
639
  return 'folded';
546
640
  }
547
641
  if (change.kind === 'preamble')
@@ -572,17 +666,23 @@ function folded(changes, change, appending) {
572
666
  changes.push(change);
573
667
  return 'appended';
574
668
  }
575
- function relinked(batch, move, before, after, appending) {
576
- const derived = rewrites([move], after);
669
+ function* relinked(batch, move, before, after, appending) {
670
+ const derived = yield* rewrites([move], after);
577
671
  if (derived.kind === 'refused')
578
672
  return derived;
579
673
  const changes = [...batch.changes];
580
674
  const appended = [];
581
675
  let changed = false;
676
+ const deferred = new Map();
582
677
  for (const change of derived.changes) {
583
678
  if (change === move)
584
679
  continue;
585
- 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);
586
686
  if (outcome === 'refused')
587
687
  return unqueueable(change.path);
588
688
  if (outcome === 'appended')
@@ -590,9 +690,13 @@ function relinked(batch, move, before, after, appending) {
590
690
  if (outcome !== 'skipped')
591
691
  changed = true;
592
692
  }
693
+ const unsettled = settledParts(changes, deferred, after.collection.composition);
694
+ if (unsettled !== null)
695
+ return unqueueable(unsettled);
593
696
  if (!changed)
594
697
  return { kind: 'relinked', batch };
595
698
  const held = new Set(batch.preconditions.map(identity));
699
+ const added = yield* preconditionsOf(appended, before, []);
596
700
  return {
597
701
  kind: 'relinked',
598
702
  batch: immutable({
@@ -601,7 +705,7 @@ function relinked(batch, move, before, after, appending) {
601
705
  changes,
602
706
  preconditions: [
603
707
  ...batch.preconditions,
604
- ...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
708
+ ...added.filter((precondition) => !held.has(identity(precondition))),
605
709
  ],
606
710
  }),
607
711
  };
@@ -647,11 +751,10 @@ function withdrawnFile(batch, change, at) {
647
751
  ];
648
752
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
649
753
  }
650
- export function requeued(queue, changes, visible, changeable, state) {
754
+ function* requeuing(queue, changes, visible, changeable, state) {
651
755
  const next = [...queue];
652
756
  const origins = new Map();
653
757
  const withdrawn = new Map();
654
- const owners = new Set();
655
758
  const queued = new Set();
656
759
  const kept = [];
657
760
  const originOf = (batch) => origins.get(batch) ?? batch.id;
@@ -670,19 +773,19 @@ export function requeued(queue, changes, visible, changeable, state) {
670
773
  return unqueueable(change.path);
671
774
  const following = next.slice(index);
672
775
  const rebuilt = [];
673
- let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
776
+ let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
674
777
  for (const [offset, batch] of following.entries()) {
675
778
  let current = batch;
676
779
  if (change.kind === 'move') {
677
- const after = state(next.slice(0, index + offset + 1));
780
+ const after = yield* state(next.slice(0, index + offset + 1));
678
781
  const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
679
- const relinking = relinked(batch, change, before, after, appending);
782
+ const relinking = yield* relinked(batch, change, before, after, appending);
680
783
  if (relinking.kind === 'refused')
681
784
  return relinking;
682
785
  current = relinking.batch;
683
786
  before = after;
684
787
  }
685
- if (current !== batch && !open(batch))
788
+ if (current !== batch && (!open(batch) || batch.between !== undefined))
686
789
  return unqueueable(change.path);
687
790
  rebuilt.push(offset === 0
688
791
  ? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
@@ -697,7 +800,6 @@ export function requeued(queue, changes, visible, changeable, state) {
697
800
  if (replaced !== null)
698
801
  origins.set(replaced, origin);
699
802
  }
700
- owners.add(originOf(owner));
701
803
  next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
702
804
  queued.add(change.path);
703
805
  }
@@ -707,97 +809,65 @@ export function requeued(queue, changes, visible, changeable, state) {
707
809
  changes: kept,
708
810
  queued,
709
811
  withdrawn,
710
- owners,
711
812
  };
712
813
  }
713
- function covering(precondition) {
714
- return precondition.kind === 'field' ||
715
- precondition.kind === 'section' ||
716
- precondition.kind === 'preamble'
717
- ? identity(precondition)
718
- : `path ${precondition.path}`;
719
- }
720
- export function joined(queue, owner, extra) {
721
- const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
722
- const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
723
- if (clash !== undefined)
724
- return unqueueable(clash);
725
- const changes = [...owner.changes];
726
- for (const change of extra.changes) {
727
- if (folded(changes, change, true) === 'refused')
728
- return unqueueable(change.path);
729
- }
730
- const held = new Set(owner.preconditions.map(covering));
731
- const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
732
- const preconditions = [
733
- ...owner.preconditions,
734
- ...extra.preconditions.filter((precondition) => {
735
- const key = covering(precondition);
736
- return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
737
- }),
738
- ];
739
- const batch = immutable({
740
- ...owner,
741
- ...(extra.validation === undefined ? {} : { validation: extra.validation }),
742
- id: changeBatchIdentity(),
743
- changes,
744
- preconditions,
745
- });
746
- return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
747
- }
748
- export function limitedIn(queue, batch, limits, state) {
814
+ function* limiting(queue, batch, limits, state) {
749
815
  const at = queue.indexOf(batch);
750
- const limited = exceeded(limits, batch.changes, providerRewritten(batch, state(queue.slice(0, at))).changes, state(queue.slice(0, at + 1)));
751
- 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;
752
819
  }
753
- export function buildBatch(input) {
754
- const changes = relativeChanges(input);
755
- const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
756
- if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
757
- return refuse('model-unavailable', structural.path);
758
- if (changes.length === 0)
759
- return refuse('no-change');
760
- const rewritten = rewrites(changes, input.visible);
761
- if (rewritten.kind === 'refused')
762
- return rewritten;
763
- const refusal = guard(changes, input);
764
- if (refusal !== null)
765
- return refusal;
766
- const preconditions = preconditionsOf(changes, input.visible, input.bases);
767
- const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
768
- const resulting = overlay(input.visible, [
769
- { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
770
- ]);
771
- const clean = input.clean !== undefined && available(input.composition) ? input.clean : null;
772
- 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) {
773
851
  if (removed.size > 0) {
774
- const referrers = clean === null ? null : referring(clean, input.composition, removed);
775
- const referenced = incoming(collectionOf(resulting.documents, input.composition), removed, clean === null || referrers === null
776
- ? null
777
- : [...referrers, ...differing(clean.text, resulting.documents)]);
852
+ const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
778
853
  if (referenced !== null)
779
854
  return referenced;
780
855
  }
781
- const typed = (content) => {
782
- if (content === undefined)
783
- return false;
784
- const parsed = parse(content);
785
- return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
786
- };
787
- const touched = [...new Set(predicted.changes.flatMap(affected))];
788
- const typedPaths = touched.filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
789
- const needsModel = typedPaths.length > 0;
790
- 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');
791
866
  if (withoutModel !== undefined)
792
867
  return refuse('model-unavailable', withoutModel);
793
- const verdict = clean !== null &&
794
- changedCleanly({ text: resulting.documents, binaries: resulting.binaries }, clean, input.composition)
795
- ? { verdict: 'accept' }
796
- : validateCollection(needsModel
797
- ? resulting.documents
798
- : Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
799
- ? []
800
- : [[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);
801
871
  if (verdict.verdict === 'refuse') {
802
872
  return {
803
873
  kind: 'refused',
@@ -806,16 +876,99 @@ export function buildBatch(input) {
806
876
  retryable: false,
807
877
  file: verdict.file,
808
878
  ...(verdict.location === undefined ? {} : { location: verdict.location }),
879
+ ...(verdict.line === undefined ? {} : { line: verdict.line }),
809
880
  ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
810
881
  },
811
882
  };
812
883
  }
813
- 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));
814
970
  if (limited !== null)
815
971
  return limited;
816
- const validated = typedPaths
817
- .map((path) => governing(input.composition, path))
818
- .find((model) => model.status !== 'unavailable');
819
972
  return {
820
973
  kind: 'batch',
821
974
  batch: immutable({
@@ -824,15 +977,296 @@ export function buildBatch(input) {
824
977
  changes,
825
978
  preconditions,
826
979
  ...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
827
- ...(validated === undefined
828
- ? {}
829
- : {
830
- validation: {
831
- identity: validated.snapshot.identity,
832
- version: validated.snapshot.version,
833
- source: validated.status,
834
- },
835
- }),
980
+ ...validationOf(judged.validated),
836
981
  }),
837
982
  };
838
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
+ }