@agentshouse/mdruntime 0.1.0-caee7332d2235 → 0.1.0-caf080a9a9c60

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