@agentshouse/mdruntime 0.1.0-cd50a4607cb7d → 0.1.0-cd71aba4d0215

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