@agentshouse/mdruntime 0.1.0-c4478f1a5ac2a → 0.1.0-c44d5cc00aee5

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 (386) hide show
  1. package/README.md +23 -19
  2. package/dist/acceptance.d.ts +3 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +299 -0
  5. package/dist/act-lanes.d.ts +30 -0
  6. package/dist/act-lanes.d.ts.map +1 -0
  7. package/dist/act-lanes.js +225 -0
  8. package/dist/actions.d.ts +26 -0
  9. package/dist/actions.d.ts.map +1 -0
  10. package/dist/actions.js +302 -0
  11. package/dist/adapter.d.ts +174 -119
  12. package/dist/adapter.d.ts.map +1 -1
  13. package/dist/attached-model.d.ts +6 -10
  14. package/dist/attached-model.d.ts.map +1 -1
  15. package/dist/attached-model.js +35 -41
  16. package/dist/binary.d.ts +0 -5
  17. package/dist/binary.d.ts.map +1 -1
  18. package/dist/binary.js +1 -1
  19. package/dist/bootstrap.d.ts +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 +3 -0
  35. package/dist/chat-types.d.ts.map +1 -0
  36. package/dist/chat-types.js +11 -0
  37. package/dist/client.d.ts +42 -24
  38. package/dist/client.d.ts.map +1 -1
  39. package/dist/client.js +133 -27
  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 +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 -118
  47. package/dist/connection.d.ts.map +1 -1
  48. package/dist/connection.js +109 -2967
  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 +143 -0
  53. package/dist/conversation-live.d.ts.map +1 -0
  54. package/dist/conversation-live.js +254 -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 +78 -47
  59. package/dist/conversation.d.ts.map +1 -1
  60. package/dist/conversation.js +1069 -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 +150 -0
  70. package/dist/durability.d.ts +23 -1
  71. package/dist/durability.d.ts.map +1 -1
  72. package/dist/durability.js +9 -0
  73. package/dist/entry.d.ts +5 -2
  74. package/dist/entry.d.ts.map +1 -1
  75. package/dist/entry.js +33 -28
  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 +27 -4
  92. package/dist/host.d.ts.map +1 -1
  93. package/dist/host.js +168 -27
  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 +40 -14
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +18 -3
  100. package/dist/install.d.ts +54 -0
  101. package/dist/install.d.ts.map +1 -0
  102. package/dist/install.js +530 -0
  103. package/dist/lifecycle.d.ts +7 -0
  104. package/dist/lifecycle.d.ts.map +1 -0
  105. package/dist/lifecycle.js +277 -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 +32 -0
  115. package/dist/lost.d.ts +5 -0
  116. package/dist/lost.d.ts.map +1 -0
  117. package/dist/lost.js +49 -0
  118. package/dist/media.d.ts +8 -0
  119. package/dist/media.d.ts.map +1 -0
  120. package/dist/media.js +218 -0
  121. package/dist/memory.d.ts +23 -7
  122. package/dist/memory.d.ts.map +1 -1
  123. package/dist/memory.js +282 -110
  124. package/dist/messages.d.ts +56 -0
  125. package/dist/messages.d.ts.map +1 -0
  126. package/dist/messages.js +158 -0
  127. package/dist/model.d.ts +11 -13
  128. package/dist/model.d.ts.map +1 -1
  129. package/dist/model.js +7 -24
  130. package/dist/models.d.ts +10 -0
  131. package/dist/models.d.ts.map +1 -0
  132. package/dist/models.js +153 -0
  133. package/dist/name.d.ts +4 -0
  134. package/dist/name.d.ts.map +1 -0
  135. package/dist/name.js +122 -0
  136. package/dist/observation.d.ts +19 -3
  137. package/dist/observation.d.ts.map +1 -1
  138. package/dist/observation.js +116 -20
  139. package/dist/opened.d.ts +22 -0
  140. package/dist/opened.d.ts.map +1 -0
  141. package/dist/opened.js +107 -0
  142. package/dist/opening.d.ts +4 -0
  143. package/dist/opening.d.ts.map +1 -0
  144. package/dist/opening.js +53 -0
  145. package/dist/outcome.d.ts +52 -8
  146. package/dist/outcome.d.ts.map +1 -1
  147. package/dist/outcome.js +57 -0
  148. package/dist/overlay.d.ts +8 -5
  149. package/dist/overlay.d.ts.map +1 -1
  150. package/dist/overlay.js +131 -166
  151. package/dist/page-metadata.d.ts +8 -0
  152. package/dist/page-metadata.d.ts.map +1 -0
  153. package/dist/page-metadata.js +19 -0
  154. package/dist/parts.d.ts +4 -0
  155. package/dist/parts.d.ts.map +1 -0
  156. package/dist/parts.js +13 -0
  157. package/dist/paths.d.ts +2 -0
  158. package/dist/paths.d.ts.map +1 -1
  159. package/dist/paths.js +26 -0
  160. package/dist/preparation.d.ts +20 -0
  161. package/dist/preparation.d.ts.map +1 -0
  162. package/dist/preparation.js +227 -0
  163. package/dist/presence.d.ts +52 -0
  164. package/dist/presence.d.ts.map +1 -0
  165. package/dist/presence.js +220 -0
  166. package/dist/queries.d.ts +16 -0
  167. package/dist/queries.d.ts.map +1 -0
  168. package/dist/queries.js +282 -0
  169. package/dist/query.d.ts +27 -186
  170. package/dist/query.d.ts.map +1 -1
  171. package/dist/query.js +3 -464
  172. package/dist/{chat/questions.d.ts → questions.d.ts} +5 -7
  173. package/dist/questions.d.ts.map +1 -0
  174. package/dist/{chat/questions.js → questions.js} +11 -7
  175. package/dist/queue.d.ts +40 -0
  176. package/dist/queue.d.ts.map +1 -0
  177. package/dist/queue.js +450 -0
  178. package/dist/reach.d.ts +21 -7
  179. package/dist/reach.d.ts.map +1 -1
  180. package/dist/reach.js +342 -97
  181. package/dist/react.d.ts +19 -4
  182. package/dist/react.d.ts.map +1 -1
  183. package/dist/react.js +40 -18
  184. package/dist/{chat/read.d.ts → read.d.ts} +2 -0
  185. package/dist/read.d.ts.map +1 -0
  186. package/dist/{chat/read.js → read.js} +4 -0
  187. package/dist/readiness.d.ts +49 -0
  188. package/dist/readiness.d.ts.map +1 -0
  189. package/dist/readiness.js +27 -0
  190. package/dist/reads.d.ts +39 -0
  191. package/dist/reads.d.ts.map +1 -0
  192. package/dist/reads.js +178 -0
  193. package/dist/recovery.d.ts +5 -2
  194. package/dist/recovery.d.ts.map +1 -1
  195. package/dist/recovery.js +65 -8
  196. package/dist/refresh.d.ts +17 -14
  197. package/dist/refresh.d.ts.map +1 -1
  198. package/dist/refresh.js +145 -54
  199. package/dist/refusal.d.ts +5 -0
  200. package/dist/refusal.d.ts.map +1 -0
  201. package/dist/refusal.js +55 -0
  202. package/dist/replica.d.ts +50 -33
  203. package/dist/replica.d.ts.map +1 -1
  204. package/dist/replica.js +338 -189
  205. package/dist/retried.d.ts +30 -0
  206. package/dist/retried.d.ts.map +1 -0
  207. package/dist/retried.js +72 -0
  208. package/dist/room-draft.d.ts +39 -10
  209. package/dist/room-draft.d.ts.map +1 -1
  210. package/dist/room-draft.js +194 -118
  211. package/dist/room-drafts.d.ts +24 -0
  212. package/dist/room-drafts.d.ts.map +1 -0
  213. package/dist/room-drafts.js +217 -0
  214. package/dist/rows.d.ts +31 -29
  215. package/dist/rows.d.ts.map +1 -1
  216. package/dist/rows.js +60 -118
  217. package/dist/runtime.d.ts +34 -0
  218. package/dist/runtime.d.ts.map +1 -0
  219. package/dist/runtime.js +1 -0
  220. package/dist/save.d.ts +43 -48
  221. package/dist/save.d.ts.map +1 -1
  222. package/dist/save.js +791 -274
  223. package/dist/saves.d.ts +32 -0
  224. package/dist/saves.d.ts.map +1 -0
  225. package/dist/saves.js +410 -0
  226. package/dist/search.d.ts +15 -77
  227. package/dist/search.d.ts.map +1 -1
  228. package/dist/search.js +10 -186
  229. package/dist/seed.d.ts +25 -0
  230. package/dist/seed.d.ts.map +1 -0
  231. package/dist/seed.js +145 -0
  232. package/dist/shared.d.ts +6 -7
  233. package/dist/shared.d.ts.map +1 -1
  234. package/dist/shared.js +75 -104
  235. package/dist/shown.d.ts +33 -0
  236. package/dist/shown.d.ts.map +1 -0
  237. package/dist/shown.js +302 -0
  238. package/dist/socket/admission.d.ts +7 -0
  239. package/dist/socket/admission.d.ts.map +1 -0
  240. package/dist/socket/admission.js +39 -0
  241. package/dist/socket/answers.d.ts +6 -0
  242. package/dist/socket/answers.d.ts.map +1 -0
  243. package/dist/socket/answers.js +20 -0
  244. package/dist/socket/lanes.d.ts +28 -0
  245. package/dist/socket/lanes.d.ts.map +1 -0
  246. package/dist/socket/lanes.js +103 -0
  247. package/dist/socket/reads.d.ts +10 -0
  248. package/dist/socket/reads.d.ts.map +1 -0
  249. package/dist/socket/reads.js +31 -0
  250. package/dist/socket/session.d.ts +38 -0
  251. package/dist/socket/session.d.ts.map +1 -0
  252. package/dist/socket/session.js +23 -0
  253. package/dist/socket.d.ts +67 -0
  254. package/dist/socket.d.ts.map +1 -0
  255. package/dist/socket.js +164 -0
  256. package/dist/source.d.ts +54 -0
  257. package/dist/source.d.ts.map +1 -0
  258. package/dist/source.js +82 -0
  259. package/dist/status.d.ts +5 -2
  260. package/dist/status.d.ts.map +1 -1
  261. package/dist/status.js +3 -7
  262. package/dist/store/installs.d.ts +30 -0
  263. package/dist/store/installs.d.ts.map +1 -0
  264. package/dist/store/installs.js +34 -0
  265. package/dist/store/readiness.d.ts +10 -0
  266. package/dist/store/readiness.d.ts.map +1 -0
  267. package/dist/store/readiness.js +52 -0
  268. package/dist/store/records.d.ts +214 -0
  269. package/dist/store/records.d.ts.map +1 -0
  270. package/dist/store/records.js +438 -0
  271. package/dist/store/room-held.d.ts +8 -0
  272. package/dist/store/room-held.d.ts.map +1 -0
  273. package/dist/store/room-held.js +17 -0
  274. package/dist/store/space.d.ts +11 -0
  275. package/dist/store/space.d.ts.map +1 -0
  276. package/dist/store/space.js +41 -0
  277. package/dist/store/watchers.d.ts +17 -0
  278. package/dist/store/watchers.d.ts.map +1 -0
  279. package/dist/store/watchers.js +56 -0
  280. package/dist/store/writes.d.ts +24 -0
  281. package/dist/store/writes.d.ts.map +1 -0
  282. package/dist/store/writes.js +125 -0
  283. package/dist/store.d.ts +111 -142
  284. package/dist/store.d.ts.map +1 -1
  285. package/dist/store.js +159 -687
  286. package/dist/stored.d.ts +157 -0
  287. package/dist/stored.d.ts.map +1 -0
  288. package/dist/stored.js +649 -0
  289. package/dist/surface.d.ts +88 -0
  290. package/dist/surface.d.ts.map +1 -0
  291. package/dist/surface.js +220 -0
  292. package/dist/sync.d.ts +19 -0
  293. package/dist/sync.d.ts.map +1 -0
  294. package/dist/sync.js +535 -0
  295. package/dist/tables.d.ts +415 -0
  296. package/dist/tables.d.ts.map +1 -0
  297. package/dist/tables.js +761 -0
  298. package/dist/tallies.d.ts +18 -0
  299. package/dist/tallies.d.ts.map +1 -0
  300. package/dist/tallies.js +128 -0
  301. package/dist/transfer.d.ts +0 -5
  302. package/dist/transfer.d.ts.map +1 -1
  303. package/dist/turn.d.ts +5 -3
  304. package/dist/turn.d.ts.map +1 -1
  305. package/dist/turn.js +51 -21
  306. package/dist/validation.d.ts +29 -0
  307. package/dist/validation.d.ts.map +1 -0
  308. package/dist/validation.js +104 -0
  309. package/dist/windows.d.ts +11 -0
  310. package/dist/windows.d.ts.map +1 -0
  311. package/dist/windows.js +16 -0
  312. package/dist/wire.d.ts +228 -36
  313. package/dist/wire.d.ts.map +1 -1
  314. package/dist/wire.js +75 -33
  315. package/package.json +19 -19
  316. package/AGENTS.md +0 -309
  317. package/dist/attempt.d.ts +0 -16
  318. package/dist/attempt.d.ts.map +0 -1
  319. package/dist/attempt.js +0 -70
  320. package/dist/chat/acts.d.ts +0 -33
  321. package/dist/chat/acts.d.ts.map +0 -1
  322. package/dist/chat/acts.js +0 -36
  323. package/dist/chat/context.d.ts +0 -3
  324. package/dist/chat/context.d.ts.map +0 -1
  325. package/dist/chat/input.d.ts +0 -9
  326. package/dist/chat/input.d.ts.map +0 -1
  327. package/dist/chat/input.js +0 -99
  328. package/dist/chat/kit.d.ts +0 -60
  329. package/dist/chat/kit.d.ts.map +0 -1
  330. package/dist/chat/kit.js +0 -123
  331. package/dist/chat/lasting.d.ts +0 -6
  332. package/dist/chat/lasting.d.ts.map +0 -1
  333. package/dist/chat/lasting.js +0 -19
  334. package/dist/chat/list.d.ts +0 -26
  335. package/dist/chat/list.d.ts.map +0 -1
  336. package/dist/chat/list.js +0 -62
  337. package/dist/chat/markdown.d.ts +0 -20
  338. package/dist/chat/markdown.d.ts.map +0 -1
  339. package/dist/chat/markdown.js +0 -188
  340. package/dist/chat/message.d.ts +0 -6
  341. package/dist/chat/message.d.ts.map +0 -1
  342. package/dist/chat/message.js +0 -192
  343. package/dist/chat/messages.d.ts +0 -21
  344. package/dist/chat/messages.d.ts.map +0 -1
  345. package/dist/chat/messages.js +0 -151
  346. package/dist/chat/name.d.ts +0 -6
  347. package/dist/chat/name.d.ts.map +0 -1
  348. package/dist/chat/name.js +0 -37
  349. package/dist/chat/opened.d.ts +0 -11
  350. package/dist/chat/opened.d.ts.map +0 -1
  351. package/dist/chat/opened.js +0 -17
  352. package/dist/chat/overlay.d.ts +0 -31
  353. package/dist/chat/overlay.d.ts.map +0 -1
  354. package/dist/chat/overlay.js +0 -97
  355. package/dist/chat/parts.d.ts +0 -57
  356. package/dist/chat/parts.d.ts.map +0 -1
  357. package/dist/chat/parts.js +0 -119
  358. package/dist/chat/process.d.ts +0 -20
  359. package/dist/chat/process.d.ts.map +0 -1
  360. package/dist/chat/process.js +0 -32
  361. package/dist/chat/provider.d.ts +0 -63
  362. package/dist/chat/provider.d.ts.map +0 -1
  363. package/dist/chat/provider.js +0 -620
  364. package/dist/chat/question.d.ts +0 -9
  365. package/dist/chat/question.d.ts.map +0 -1
  366. package/dist/chat/question.js +0 -80
  367. package/dist/chat/questions.d.ts.map +0 -1
  368. package/dist/chat/read.d.ts.map +0 -1
  369. package/dist/chat/ready.d.ts +0 -33
  370. package/dist/chat/ready.d.ts.map +0 -1
  371. package/dist/chat/ready.js +0 -91
  372. package/dist/chat/style.d.ts +0 -4
  373. package/dist/chat/style.d.ts.map +0 -1
  374. package/dist/chat/style.js +0 -302
  375. package/dist/chat.d.ts +0 -14
  376. package/dist/chat.d.ts.map +0 -1
  377. package/dist/chat.js +0 -8
  378. package/dist/fence.d.ts +0 -6
  379. package/dist/fence.d.ts.map +0 -1
  380. package/dist/fence.js +0 -10
  381. package/dist/log.d.ts +0 -5
  382. package/dist/log.d.ts.map +0 -1
  383. package/dist/log.js +0 -14
  384. package/dist/read-side.d.ts +0 -25
  385. package/dist/read-side.d.ts.map +0 -1
  386. package/dist/read-side.js +0 -639
package/dist/save.js CHANGED
@@ -1,11 +1,14 @@
1
- import { definitionOf } from './model.js';
2
- import { apply, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, serialize, target, } from '@agentshouse/mdmodel';
3
- import { immutable, queuedFile, departure, } from './change.js';
4
- import { available, changedCleanly, collectionOf, documentsOf, governing, referring, validateCollection, } from './collection.js';
5
- import { changeBatchIdentity } from './identity.js';
6
- import { overlay } from './overlay.js';
7
- import { covered, normalize } from './paths.js';
8
- function refuse(code, file, location) {
1
+ import { apply, ARRAY_INDEX, ATTACHMENT, create, 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) {
9
12
  return {
10
13
  kind: 'refused',
11
14
  refusal: {
@@ -13,14 +16,15 @@ function refuse(code, file, location) {
13
16
  retryable: false,
14
17
  ...(file === undefined ? {} : { file }),
15
18
  ...(location === undefined ? {} : { location }),
19
+ ...(line === undefined ? {} : { line }),
16
20
  },
17
21
  };
18
22
  }
23
+ function refusedBy(refusal, file) {
24
+ return refuse(refusal.rule, file, refusal.location, refusal.line);
25
+ }
19
26
  function repointed(document, definition, from, to, moves, settled) {
20
- const parsed = parse(document.content, from);
21
- if (!parsed.ok)
22
- return [];
23
- let next = parsed.document;
27
+ const changes = [];
24
28
  const names = new Set();
25
29
  for (const { pointer, value } of definition === undefined
26
30
  ? []
@@ -32,41 +36,54 @@ function repointed(document, definition, from, to, moves, settled) {
32
36
  const destination = moves.get(named) ?? named;
33
37
  if (target(to, value) === destination)
34
38
  continue;
35
- const applied = apply(next, {
39
+ changes.push({
36
40
  kind: 'reference',
37
41
  pointer,
38
42
  value: retarget(value, relative(to, destination)),
39
43
  prior: value,
40
44
  });
41
- if (applied.outcome !== 'applied')
42
- continue;
43
- next = applied.document;
44
45
  names.add(name);
45
46
  }
47
+ if (changes.length === 0)
48
+ return [];
49
+ const applied = apply(document, changes, definition);
50
+ if (applied.outcome !== 'applied')
51
+ return [];
52
+ const next = applied.document;
46
53
  return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
47
54
  }
48
- function edited(document, path, changes) {
49
- let next = document;
55
+ function edited(document, changes, definition) {
56
+ const parts = new Map();
50
57
  for (const change of changes) {
51
- if (change.path !== path)
52
- continue;
53
- if (change.kind !== 'section' && change.kind !== 'preamble')
54
- continue;
55
- const applied = apply(next, change.kind === 'preamble'
56
- ? { kind: 'preamble', value: change.value, prior: next.preamble }
57
- : {
58
+ 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
+ });
65
+ if (change.kind === 'preamble')
66
+ parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
67
+ if (change.kind === 'section')
68
+ parts.set(`section ${change.name}`, {
58
69
  kind: 'section',
59
70
  name: change.name,
60
71
  value: change.value,
61
- prior: next.sections[change.name],
72
+ prior: document.sections[change.name] ?? '',
62
73
  });
63
- if (applied.outcome === 'applied')
64
- next = applied.document;
65
74
  }
66
- return next;
75
+ if (parts.size === 0)
76
+ return document;
77
+ const applied = apply(document, [...parts.values()], definition);
78
+ return applied.outcome === 'applied' ? applied.document : document;
67
79
  }
68
80
  function bodies(path, before, after) {
69
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
+ }
70
87
  if (after.preamble !== before.preamble) {
71
88
  changes.push({ kind: 'preamble', path, value: after.preamble });
72
89
  }
@@ -77,8 +94,8 @@ function bodies(path, before, after) {
77
94
  }
78
95
  return changes;
79
96
  }
80
- function relinks(path, moved, before, moves) {
81
- let after = before;
97
+ function relinks(path, moved, before, moves, definition) {
98
+ const changes = [];
82
99
  for (const link of links(before, path)) {
83
100
  const destination = moves.get(link.target) ?? link.target;
84
101
  if (destination === link.target && moved === path)
@@ -86,20 +103,14 @@ function relinks(path, moved, before, moves) {
86
103
  const to = relative(moved, destination);
87
104
  if (to === relative(path, link.target))
88
105
  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;
106
+ changes.push({ kind: 'link', href: link.href, to });
102
107
  }
108
+ if (changes.length === 0)
109
+ return { kind: 'changes', changes: [] };
110
+ const applied = apply(before, changes, definition);
111
+ if (applied.outcome === 'refuse')
112
+ return refusedBy(applied.refusal, path);
113
+ const after = applied.document;
103
114
  if (after === before)
104
115
  return { kind: 'changes', changes: [] };
105
116
  if (before.type === undefined) {
@@ -107,19 +118,25 @@ function relinks(path, moved, before, moves) {
107
118
  }
108
119
  return { kind: 'changes', changes: bodies(path, before, after) };
109
120
  }
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
- }
121
+ function partKey(change) {
122
+ return change.kind === 'section' || change.kind === 'field'
123
+ ? `${change.kind} ${change.name}`
124
+ : change.kind;
118
125
  }
119
- function rewrites(changes, visible) {
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));
134
+ }
135
+ function* rewrites(changes, visible) {
120
136
  const moves = new Map();
121
137
  const settled = new Set();
122
- for (const change of changes) {
138
+ const parts = new Map();
139
+ for (const [index, change] of changes.entries()) {
123
140
  if (change.kind === 'move')
124
141
  moves.set(change.path, change.to);
125
142
  if (change.kind === 'remove' ||
@@ -130,54 +147,142 @@ function rewrites(changes, visible) {
130
147
  }
131
148
  if (change.kind === 'field')
132
149
  settled.add(`${change.path} ${change.name}`);
150
+ if (isPart(change)) {
151
+ const held = parts.get(change.path);
152
+ if (held === undefined)
153
+ parts.set(change.path, [index]);
154
+ else
155
+ held.push(index);
156
+ }
133
157
  }
134
158
  if (moves.size === 0)
135
159
  return { kind: 'changes', changes };
160
+ const names = movedNames(moves);
136
161
  const merged = [...changes];
137
162
  const added = [];
138
- for (const [path, document] of documentsOf(visible.collection)) {
163
+ for (const [path, document] of yield* documentsOf(visible.collection)) {
139
164
  if (settled.has(path))
140
165
  continue;
141
166
  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);
167
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
168
+ const repointing = repointed(document, definition, path, moved, moves, settled);
169
+ added.push(...repointing);
170
+ const own = parts.get(path) ?? [];
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);
145
174
  if (relinked.kind === 'refused')
146
175
  return relinked;
176
+ const keyed = new Map();
177
+ for (const index of own) {
178
+ const key = partKey(merged[index]);
179
+ if (!keyed.has(key))
180
+ keyed.set(key, index);
181
+ }
147
182
  for (const change of relinked.changes) {
148
183
  if (change.kind === 'replace') {
149
- subsumed(merged, path);
184
+ for (const index of own) {
185
+ const kind = merged[index]?.kind;
186
+ if (kind === 'field' || kind === 'section' || kind === 'preamble')
187
+ merged[index] = null;
188
+ }
150
189
  added.push(change);
151
190
  continue;
152
191
  }
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)
192
+ const index = keyed.get(partKey(change));
193
+ if (index === undefined)
157
194
  added.push(change);
158
195
  else
159
196
  merged[index] = change;
160
197
  }
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);
161
201
  }
162
- return { kind: 'changes', changes: [...added, ...merged] };
202
+ return {
203
+ kind: 'changes',
204
+ changes: [...added, ...merged.filter((change) => change !== null)],
205
+ };
163
206
  }
164
- function anticipated(changes, preconditions, rewritten, visible) {
165
- const derived = rewritten.filter((change) => !changes.includes(change));
207
+ function* anticipated(changes, preconditions, rewritten, visible) {
208
+ const given = new Set(changes);
209
+ const derived = rewritten.filter((change) => !given.has(change));
166
210
  return {
167
211
  changes: rewritten,
168
- preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
212
+ preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
169
213
  };
170
214
  }
171
- export function providerRewritten(batch, visible) {
172
- const rewritten = rewrites(batch.changes, visible);
215
+ export function* providerRewritten(batch, visible) {
216
+ const rewritten = yield* rewrites(batch.changes, visible);
173
217
  if (rewritten.kind === 'refused')
174
218
  return batch;
175
219
  return {
176
220
  ...batch,
177
- ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
221
+ ...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
222
+ };
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],
178
275
  };
179
276
  }
180
- function incoming(collection, removed, candidates) {
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
+ }
285
+ function* incoming(collection, removed, candidates) {
181
286
  const found = new Map();
182
287
  const note = (target, file) => {
183
288
  if (!removed.has(target))
@@ -186,14 +291,21 @@ function incoming(collection, removed, candidates) {
186
291
  referrers.add(file);
187
292
  found.set(target, referrers);
188
293
  };
189
- const read = candidates === null
190
- ? documentsOf(collection)
191
- : [...new Set(candidates)].sort().flatMap((path) => {
294
+ const read = function* (paths) {
295
+ const seen = new Set();
296
+ for (const path of paths) {
297
+ if (seen.has(path))
298
+ continue;
299
+ seen.add(path);
192
300
  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);
301
+ if (document !== undefined)
302
+ yield [path, document];
303
+ }
304
+ };
305
+ for (const [path, document] of candidates === null
306
+ ? yield* documentsOf(collection)
307
+ : read(candidates)) {
308
+ const definition = definitionsAt(collection.composition, path)(document.type);
197
309
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
198
310
  if (to.includes(ATTACHMENT))
199
311
  continue;
@@ -201,8 +313,9 @@ function incoming(collection, removed, candidates) {
201
313
  if (named !== null)
202
314
  note(named, path);
203
315
  }
316
+ yield ROW_WEIGHT + collection.text[path].length;
204
317
  }
205
- const [first] = [...found.keys()].sort();
318
+ const [first] = yield* sorting(found.keys());
206
319
  if (first === undefined)
207
320
  return null;
208
321
  const every = new Set();
@@ -215,14 +328,16 @@ function incoming(collection, removed, candidates) {
215
328
  code: 'referenced-missing-file',
216
329
  retryable: false,
217
330
  file: first,
218
- referrers: [...every].sort(),
331
+ referrers: yield* sorting(every),
219
332
  },
220
333
  };
221
334
  }
222
335
  function affected(change) {
223
336
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
224
337
  }
225
- function guard(changes, input) {
338
+ const WRITE = ['write'];
339
+ const MOVE_OUT = ['write', 'erase'];
340
+ function guard(changes, input, removing) {
226
341
  const overridden = new Map();
227
342
  const existing = {
228
343
  has: (path) => overridden.get(path) ??
@@ -230,21 +345,17 @@ function guard(changes, input) {
230
345
  add: (path) => void overridden.set(path, true),
231
346
  delete: (path) => void overridden.set(path, false),
232
347
  };
348
+ const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
233
349
  for (const [index, change] of changes.entries()) {
234
350
  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);
351
+ const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
352
+ if (refused !== null)
353
+ return refuse(refused.code, refused.path);
244
354
  }
245
- if (change.kind === 'create') {
246
- if (existing.has(change.path))
247
- 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);
248
359
  existing.add(change.path);
249
360
  continue;
250
361
  }
@@ -254,6 +365,9 @@ function guard(changes, input) {
254
365
  input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
255
366
  return refuse('path-collision', change.path);
256
367
  }
368
+ const taken = occupied(change.path);
369
+ if (taken !== null)
370
+ return refuse('path-collision', taken);
257
371
  if (!input.local &&
258
372
  existing.has(change.path) &&
259
373
  input.visible.revisions[departure(changes, index)] === undefined) {
@@ -265,8 +379,9 @@ function guard(changes, input) {
265
379
  if (change.kind === 'move') {
266
380
  if (!existing.has(change.path))
267
381
  return refuse('path-absent', change.path);
268
- if (existing.has(change.to))
269
- 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);
270
385
  if (unrevised(input, change.path)) {
271
386
  return refuse('unknown-revision', change.path);
272
387
  }
@@ -284,7 +399,7 @@ function guard(changes, input) {
284
399
  continue;
285
400
  }
286
401
  if (change.kind === 'replace') {
287
- if (!input.local && input.visible.revisions[change.path] === undefined) {
402
+ if (unrevised(input, change.path)) {
288
403
  return refuse('unknown-revision', change.path);
289
404
  }
290
405
  continue;
@@ -296,7 +411,23 @@ function guard(changes, input) {
296
411
  return null;
297
412
  }
298
413
  function unrevised(input, path) {
299
- 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;
415
+ }
416
+ function queuedDocuments(queue) {
417
+ const paths = new Set();
418
+ for (const batch of queue) {
419
+ for (const change of batch.changes) {
420
+ if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
421
+ continue;
422
+ if (change.kind === 'remove')
423
+ paths.delete(change.path);
424
+ else if (change.kind !== 'move')
425
+ paths.add(change.path);
426
+ else if (paths.delete(change.path))
427
+ paths.add(change.to);
428
+ }
429
+ }
430
+ return paths;
300
431
  }
301
432
  function identity(precondition) {
302
433
  const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
@@ -315,7 +446,7 @@ function sameTarget(precondition, change) {
315
446
  return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
316
447
  ((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
317
448
  }
318
- function preconditionsOf(changes, visible, bases) {
449
+ function* preconditionsOf(changes, visible, bases) {
319
450
  const preconditions = [];
320
451
  const seen = new Set();
321
452
  const add = (precondition) => {
@@ -326,6 +457,7 @@ function preconditionsOf(changes, visible, bases) {
326
457
  preconditions.push(precondition);
327
458
  };
328
459
  for (const change of changes) {
460
+ yield ROW_WEIGHT;
329
461
  if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
330
462
  const based = bases.find((base) => sameTarget(base, change));
331
463
  if (based !== undefined) {
@@ -373,10 +505,13 @@ function preconditionsOf(changes, visible, bases) {
373
505
  function depth(path) {
374
506
  return path.split('/').length;
375
507
  }
376
- function exceeded(limits, changes, effective, resulting) {
508
+ function* exceeding(limits, changes, effective, resulting) {
377
509
  const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
378
510
  const created = changes
379
- .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')
380
515
  .map((change) => (change.kind === 'move' ? change.to : change.path));
381
516
  for (const limit of limits) {
382
517
  const value = limit.value;
@@ -390,9 +525,13 @@ function exceeded(limits, changes, effective, resulting) {
390
525
  return refuse('provider-limit', undefined, limit.name);
391
526
  }
392
527
  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);
528
+ for (const path of written) {
529
+ const text = resulting.documents[path] ?? '';
530
+ if (new TextEncoder().encode(text).byteLength > value) {
531
+ return refuse('provider-limit', path, limit.name);
532
+ }
533
+ yield ROW_WEIGHT + text.length;
534
+ }
396
535
  }
397
536
  if (limit.name === 'path-length') {
398
537
  const long = created.find((path) => path.length > value);
@@ -410,67 +549,69 @@ function exceeded(limits, changes, effective, resulting) {
410
549
  function shellRelative(text, path, handle, definition) {
411
550
  if (!text.includes(`/rooms/${handle}/`))
412
551
  return null;
413
- const parsed = parse(text, path);
552
+ const parsed = parse(text, path, definition);
414
553
  if (!parsed.ok)
415
554
  return null;
416
555
  const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
417
556
  return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
418
557
  }
419
558
  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}/`))
559
+ if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
424
560
  return value;
425
- 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);
426
562
  if (held.outcome !== 'applied')
427
563
  return value;
428
564
  const result = relativize(held.document, path, handle, definition);
429
565
  return result.outcome === 'applied' ? result.document.fields[name] : value;
430
566
  }
431
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) {
432
578
  const { handle, composition, visible } = input;
433
- const typed = (path) => (type) => definitionOf(governing(composition, path), type);
434
579
  const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
435
- return input.changes.map((change) => {
436
- const definition = typed(change.path);
437
- if (change.kind === 'field') {
438
- const type = visible.collection.document(change.path)?.type;
439
- const value = relativeField(change.path, change.name, change.value, handle, definition(type));
440
- return value === change.value ? change : { ...change, value };
441
- }
442
- if (change.kind === 'section' || change.kind === 'preamble') {
443
- const value = body(change.path, change.value);
444
- return value === change.value ? change : { ...change, value };
445
- }
446
- if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
447
- const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
448
- return content === change.content ? change : { ...change, content };
449
- }
450
- if (change.kind !== 'create')
451
- return change;
452
- const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
453
- const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
454
- name,
455
- relativeField(change.path, name, value, handle, definition(type)),
456
- ]));
457
- const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
458
- return { ...change, fields, sections };
459
- });
460
- }
461
- function unmodeledType(collection, composition) {
462
- if (available(composition))
463
- return false;
464
- for (const [path, document] of documentsOf(collection)) {
465
- if (document.type !== undefined &&
466
- document.type !== 'any' &&
467
- governing(composition, path).status === 'unavailable')
468
- return true;
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 } };
585
+ }
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 } };
469
589
  }
470
- return false;
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
+ };
471
612
  }
472
613
  function unqueueable(file) {
473
- return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
614
+ return refuse('unknown-revision', file);
474
615
  }
475
616
  function isPart(change) {
476
617
  return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
@@ -478,29 +619,64 @@ function isPart(change) {
478
619
  function touched(change) {
479
620
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
480
621
  }
481
- function partApplied(content, path, change) {
482
- 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);
483
645
  if (!parsed.ok)
484
646
  return null;
485
647
  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
- ? {
648
+ const parts = new Map();
649
+ for (const change of changes) {
650
+ if (change.kind === 'field')
651
+ parts.set(`field ${change.name}`, {
652
+ kind: 'field',
653
+ name: change.name,
654
+ value: change.value,
655
+ prior: document.fields[change.name],
656
+ });
657
+ else if (change.kind === 'section')
658
+ parts.set(`section ${change.name}`, {
495
659
  kind: 'section',
496
660
  name: change.name,
497
661
  value: change.value,
498
- prior: document.sections[change.name],
499
- }
500
- : { kind: 'preamble', value: change.value, prior: document.preamble });
501
- return applied.outcome === 'applied' ? serialize(applied.document) : null;
662
+ prior: document.sections[change.name] ?? '',
663
+ });
664
+ else
665
+ parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
666
+ }
667
+ return apply(document, [...parts.values()], definitions(document.type));
502
668
  }
503
- function folded(changes, change, appending) {
669
+ function settledParts(changes, deferred, composition) {
670
+ for (const [held, parts] of deferred) {
671
+ const content = partsApplied(held.content, held.path, parts, composition);
672
+ if (content === null)
673
+ return held.path;
674
+ changes[changes.indexOf(held)] = { ...held, content };
675
+ }
676
+ deferred.clear();
677
+ return null;
678
+ }
679
+ function folded(changes, change, appending, deferred) {
504
680
  const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
505
681
  const whole = held === undefined ? -1 : changes.indexOf(held);
506
682
  const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
@@ -521,10 +697,11 @@ function folded(changes, change, appending) {
521
697
  return 'appended';
522
698
  }
523
699
  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 };
700
+ const parts = deferred.get(held);
701
+ if (parts === undefined)
702
+ deferred.set(held, [change]);
703
+ else
704
+ parts.push(change);
528
705
  return 'folded';
529
706
  }
530
707
  if (change.kind === 'preamble')
@@ -555,17 +732,23 @@ function folded(changes, change, appending) {
555
732
  changes.push(change);
556
733
  return 'appended';
557
734
  }
558
- function relinked(batch, move, before, after, appending) {
559
- const derived = rewrites([move], after);
735
+ function* relinked(batch, move, before, after, appending) {
736
+ const derived = yield* rewrites([move], after);
560
737
  if (derived.kind === 'refused')
561
738
  return derived;
562
739
  const changes = [...batch.changes];
563
740
  const appended = [];
564
741
  let changed = false;
742
+ const deferred = new Map();
565
743
  for (const change of derived.changes) {
566
744
  if (change === move)
567
745
  continue;
568
- const outcome = folded(changes, change, appending);
746
+ const unsettled = isPart(change)
747
+ ? null
748
+ : settledParts(changes, deferred, after.collection.composition);
749
+ if (unsettled !== null)
750
+ return unqueueable(unsettled);
751
+ const outcome = folded(changes, change, appending, deferred);
569
752
  if (outcome === 'refused')
570
753
  return unqueueable(change.path);
571
754
  if (outcome === 'appended')
@@ -573,9 +756,13 @@ function relinked(batch, move, before, after, appending) {
573
756
  if (outcome !== 'skipped')
574
757
  changed = true;
575
758
  }
759
+ const unsettled = settledParts(changes, deferred, after.collection.composition);
760
+ if (unsettled !== null)
761
+ return unqueueable(unsettled);
576
762
  if (!changed)
577
763
  return { kind: 'relinked', batch };
578
764
  const held = new Set(batch.preconditions.map(identity));
765
+ const added = yield* preconditionsOf(appended, before, []);
579
766
  return {
580
767
  kind: 'relinked',
581
768
  batch: immutable({
@@ -584,7 +771,7 @@ function relinked(batch, move, before, after, appending) {
584
771
  changes,
585
772
  preconditions: [
586
773
  ...batch.preconditions,
587
- ...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
774
+ ...added.filter((precondition) => !held.has(identity(precondition))),
588
775
  ],
589
776
  }),
590
777
  };
@@ -630,11 +817,10 @@ function withdrawnFile(batch, change, at) {
630
817
  ];
631
818
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
632
819
  }
633
- export function requeued(queue, changes, visible, changeable, state) {
820
+ function* requeuing(queue, changes, visible, changeable, state) {
634
821
  const next = [...queue];
635
822
  const origins = new Map();
636
823
  const withdrawn = new Map();
637
- const owners = new Set();
638
824
  const queued = new Set();
639
825
  const kept = [];
640
826
  const originOf = (batch) => origins.get(batch) ?? batch.id;
@@ -653,19 +839,19 @@ export function requeued(queue, changes, visible, changeable, state) {
653
839
  return unqueueable(change.path);
654
840
  const following = next.slice(index);
655
841
  const rebuilt = [];
656
- let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
842
+ let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
657
843
  for (const [offset, batch] of following.entries()) {
658
844
  let current = batch;
659
845
  if (change.kind === 'move') {
660
- const after = state(next.slice(0, index + offset + 1));
846
+ const after = yield* state(next.slice(0, index + offset + 1));
661
847
  const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
662
- const relinking = relinked(batch, change, before, after, appending);
848
+ const relinking = yield* relinked(batch, change, before, after, appending);
663
849
  if (relinking.kind === 'refused')
664
850
  return relinking;
665
851
  current = relinking.batch;
666
852
  before = after;
667
853
  }
668
- if (current !== batch && !open(batch))
854
+ if (current !== batch && (!open(batch) || batch.between !== undefined))
669
855
  return unqueueable(change.path);
670
856
  rebuilt.push(offset === 0
671
857
  ? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
@@ -680,7 +866,6 @@ export function requeued(queue, changes, visible, changeable, state) {
680
866
  if (replaced !== null)
681
867
  origins.set(replaced, origin);
682
868
  }
683
- owners.add(originOf(owner));
684
869
  next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
685
870
  queued.add(change.path);
686
871
  }
@@ -690,100 +875,68 @@ export function requeued(queue, changes, visible, changeable, state) {
690
875
  changes: kept,
691
876
  queued,
692
877
  withdrawn,
693
- owners,
694
878
  };
695
879
  }
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) {
880
+ function* limiting(queue, batch, limits, state) {
732
881
  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;
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;
735
885
  }
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));
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) {
761
920
  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)]);
921
+ const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
764
922
  if (referenced !== null)
765
923
  return referenced;
766
924
  }
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');
925
+ const typedPaths = [];
926
+ const pace = paced();
927
+ for (const path of touched) {
928
+ const before = visible.documents[path];
929
+ const after = resulting.documents[path];
930
+ if (typedText(before) || typedText(after))
931
+ typedPaths.push(path);
932
+ await pace((before?.length ?? 0) + (after?.length ?? 0) + ROW_WEIGHT);
933
+ }
934
+ const withoutModel = typedPaths.find((path) => governing(composition, path).status === 'unavailable');
777
935
  if (withoutModel !== undefined)
778
936
  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);
937
+ const verdict = typedPaths.length > 0
938
+ ? await validate(resulting, touched)
939
+ : await touchedLibrary(resulting, touched, composition);
787
940
  if (verdict.verdict === 'refuse') {
788
941
  return {
789
942
  kind: 'refused',
@@ -792,16 +945,99 @@ export function buildBatch(input) {
792
945
  retryable: false,
793
946
  file: verdict.file,
794
947
  ...(verdict.location === undefined ? {} : { location: verdict.location }),
948
+ ...(verdict.line === undefined ? {} : { line: verdict.line }),
795
949
  ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
796
950
  },
797
951
  };
798
952
  }
799
- const limited = exceeded(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));
800
1039
  if (limited !== null)
801
1040
  return limited;
802
- const validated = typedPaths
803
- .map((path) => governing(input.composition, path))
804
- .find((model) => model.status !== 'unavailable');
805
1041
  return {
806
1042
  kind: 'batch',
807
1043
  batch: immutable({
@@ -810,15 +1046,296 @@ export function buildBatch(input) {
810
1046
  changes,
811
1047
  preconditions,
812
1048
  ...(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
- }),
1049
+ ...validationOf(judged.validated),
822
1050
  }),
823
1051
  };
824
1052
  }
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);
1341
+ }