@agentshouse/mdruntime 0.1.0-c94cfdbdb6f68 → 0.1.0-c95f0f04f2113

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 (361) hide show
  1. package/README.md +19 -16
  2. package/dist/acceptance.d.ts +6 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +389 -0
  5. package/dist/actions.d.ts +20 -0
  6. package/dist/actions.d.ts.map +1 -0
  7. package/dist/actions.js +248 -0
  8. package/dist/adapter.d.ts +139 -85
  9. package/dist/adapter.d.ts.map +1 -1
  10. package/dist/attached-model.d.ts +7 -10
  11. package/dist/attached-model.d.ts.map +1 -1
  12. package/dist/attached-model.js +40 -41
  13. package/dist/binary.d.ts +0 -5
  14. package/dist/binary.d.ts.map +1 -1
  15. package/dist/binary.js +1 -1
  16. package/dist/bootstrap.d.ts +30 -11
  17. package/dist/bootstrap.d.ts.map +1 -1
  18. package/dist/bootstrap.js +319 -111
  19. package/dist/cell.d.ts +6 -0
  20. package/dist/cell.d.ts.map +1 -0
  21. package/dist/cell.js +9 -0
  22. package/dist/chain.d.ts +12 -0
  23. package/dist/chain.d.ts.map +1 -0
  24. package/dist/chain.js +64 -0
  25. package/dist/change-set.d.ts +9 -16
  26. package/dist/change-set.d.ts.map +1 -1
  27. package/dist/change-set.js +11 -89
  28. package/dist/change.d.ts +32 -1
  29. package/dist/change.d.ts.map +1 -1
  30. package/dist/change.js +10 -1
  31. package/dist/chat-types.d.ts +3 -0
  32. package/dist/chat-types.d.ts.map +1 -0
  33. package/dist/chat-types.js +2 -0
  34. package/dist/client.d.ts +28 -11
  35. package/dist/client.d.ts.map +1 -1
  36. package/dist/client.js +83 -7
  37. package/dist/collection.d.ts +11 -21
  38. package/dist/collection.d.ts.map +1 -1
  39. package/dist/collection.js +39 -321
  40. package/dist/compile.d.ts +16 -0
  41. package/dist/compile.d.ts.map +1 -0
  42. package/dist/compile.js +717 -0
  43. package/dist/connection.d.ts +7 -137
  44. package/dist/connection.d.ts.map +1 -1
  45. package/dist/connection.js +98 -3600
  46. package/dist/context.d.ts +2 -0
  47. package/dist/context.d.ts.map +1 -0
  48. package/dist/{chat/context.js → context.js} +1 -4
  49. package/dist/conversation-row.d.ts +33 -0
  50. package/dist/conversation-row.d.ts.map +1 -0
  51. package/dist/conversation-row.js +47 -0
  52. package/dist/conversation.d.ts +36 -18
  53. package/dist/conversation.d.ts.map +1 -1
  54. package/dist/conversation.js +570 -287
  55. package/dist/digest.d.ts +7 -0
  56. package/dist/digest.d.ts.map +1 -0
  57. package/dist/digest.js +113 -0
  58. package/dist/discovery.d.ts +10 -0
  59. package/dist/discovery.d.ts.map +1 -0
  60. package/dist/discovery.js +175 -0
  61. package/dist/durability.d.ts +7 -4
  62. package/dist/durability.d.ts.map +1 -1
  63. package/dist/durability.js +5 -0
  64. package/dist/entry.d.ts +5 -2
  65. package/dist/entry.d.ts.map +1 -1
  66. package/dist/entry.js +33 -29
  67. package/dist/figures.d.ts +48 -0
  68. package/dist/figures.d.ts.map +1 -0
  69. package/dist/figures.js +197 -0
  70. package/dist/file.d.ts +2 -19
  71. package/dist/file.d.ts.map +1 -1
  72. package/dist/file.js +7 -122
  73. package/dist/host.d.ts +4 -7
  74. package/dist/host.d.ts.map +1 -1
  75. package/dist/host.js +67 -39
  76. package/dist/identity.d.ts +0 -2
  77. package/dist/identity.d.ts.map +1 -1
  78. package/dist/identity.js +0 -6
  79. package/dist/index.d.ts +30 -13
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +10 -2
  82. package/dist/install.d.ts +56 -0
  83. package/dist/install.d.ts.map +1 -0
  84. package/dist/install.js +560 -0
  85. package/dist/job.d.ts +33 -0
  86. package/dist/job.d.ts.map +1 -0
  87. package/dist/job.js +54 -0
  88. package/dist/lifecycle.d.ts +7 -0
  89. package/dist/lifecycle.d.ts.map +1 -0
  90. package/dist/lifecycle.js +267 -0
  91. package/dist/listing.d.ts +53 -0
  92. package/dist/listing.d.ts.map +1 -0
  93. package/dist/listing.js +175 -0
  94. package/dist/live.d.ts +23 -0
  95. package/dist/live.d.ts.map +1 -0
  96. package/dist/live.js +165 -0
  97. package/dist/locks.d.ts +9 -0
  98. package/dist/locks.d.ts.map +1 -0
  99. package/dist/locks.js +61 -0
  100. package/dist/log.d.ts +1 -0
  101. package/dist/log.d.ts.map +1 -1
  102. package/dist/lost.d.ts +5 -0
  103. package/dist/lost.d.ts.map +1 -0
  104. package/dist/lost.js +61 -0
  105. package/dist/media.d.ts +8 -0
  106. package/dist/media.d.ts.map +1 -0
  107. package/dist/media.js +218 -0
  108. package/dist/memory.d.ts +14 -6
  109. package/dist/memory.d.ts.map +1 -1
  110. package/dist/memory.js +237 -105
  111. package/dist/messages.d.ts +52 -0
  112. package/dist/messages.d.ts.map +1 -0
  113. package/dist/messages.js +168 -0
  114. package/dist/model.d.ts +8 -10
  115. package/dist/model.d.ts.map +1 -1
  116. package/dist/model.js +6 -18
  117. package/dist/models.d.ts +14 -0
  118. package/dist/models.d.ts.map +1 -0
  119. package/dist/models.js +193 -0
  120. package/dist/name.d.ts +4 -0
  121. package/dist/name.d.ts.map +1 -0
  122. package/dist/name.js +122 -0
  123. package/dist/observation.d.ts +15 -2
  124. package/dist/observation.d.ts.map +1 -1
  125. package/dist/observation.js +91 -2
  126. package/dist/opened.d.ts +22 -0
  127. package/dist/opened.d.ts.map +1 -0
  128. package/dist/opened.js +107 -0
  129. package/dist/opening.d.ts +4 -0
  130. package/dist/opening.d.ts.map +1 -0
  131. package/dist/opening.js +56 -0
  132. package/dist/outcome.d.ts +41 -8
  133. package/dist/outcome.d.ts.map +1 -1
  134. package/dist/outcome.js +41 -2
  135. package/dist/overlay.d.ts +3 -5
  136. package/dist/overlay.d.ts.map +1 -1
  137. package/dist/overlay.js +98 -172
  138. package/dist/{chat/parts.d.ts → parts.d.ts} +11 -9
  139. package/dist/parts.d.ts.map +1 -0
  140. package/dist/{chat/parts.js → parts.js} +45 -46
  141. package/dist/paths.d.ts +1 -0
  142. package/dist/paths.d.ts.map +1 -1
  143. package/dist/paths.js +4 -0
  144. package/dist/preparation.d.ts +20 -0
  145. package/dist/preparation.d.ts.map +1 -0
  146. package/dist/preparation.js +226 -0
  147. package/dist/queries.d.ts +16 -0
  148. package/dist/queries.d.ts.map +1 -0
  149. package/dist/queries.js +272 -0
  150. package/dist/query.d.ts +21 -206
  151. package/dist/query.d.ts.map +1 -1
  152. package/dist/query.js +3 -493
  153. package/dist/{chat/questions.d.ts → questions.d.ts} +3 -2
  154. package/dist/questions.d.ts.map +1 -0
  155. package/dist/{chat/questions.js → questions.js} +8 -1
  156. package/dist/reach.d.ts +20 -9
  157. package/dist/reach.d.ts.map +1 -1
  158. package/dist/reach.js +278 -103
  159. package/dist/react.d.ts +6 -2
  160. package/dist/react.d.ts.map +1 -1
  161. package/dist/{chat/read.d.ts → read.d.ts} +2 -0
  162. package/dist/read.d.ts.map +1 -0
  163. package/dist/{chat/read.js → read.js} +4 -0
  164. package/dist/readiness.d.ts +14 -17
  165. package/dist/readiness.d.ts.map +1 -1
  166. package/dist/readiness.js +2 -8
  167. package/dist/reads.d.ts +35 -10
  168. package/dist/reads.d.ts.map +1 -1
  169. package/dist/reads.js +122 -84
  170. package/dist/recovery.d.ts +3 -2
  171. package/dist/recovery.d.ts.map +1 -1
  172. package/dist/recovery.js +43 -8
  173. package/dist/refresh.d.ts +18 -15
  174. package/dist/refresh.d.ts.map +1 -1
  175. package/dist/refresh.js +143 -58
  176. package/dist/refusal.d.ts +5 -0
  177. package/dist/refusal.d.ts.map +1 -0
  178. package/dist/refusal.js +56 -0
  179. package/dist/replica.d.ts +54 -36
  180. package/dist/replica.d.ts.map +1 -1
  181. package/dist/replica.js +313 -188
  182. package/dist/room-draft.d.ts +25 -6
  183. package/dist/room-draft.d.ts.map +1 -1
  184. package/dist/room-draft.js +158 -71
  185. package/dist/room-drafts.d.ts +24 -0
  186. package/dist/room-drafts.d.ts.map +1 -0
  187. package/dist/room-drafts.js +216 -0
  188. package/dist/rows.d.ts +31 -31
  189. package/dist/rows.d.ts.map +1 -1
  190. package/dist/rows.js +59 -123
  191. package/dist/runtime.d.ts +42 -0
  192. package/dist/runtime.d.ts.map +1 -0
  193. package/dist/runtime.js +1 -0
  194. package/dist/save.d.ts +52 -14
  195. package/dist/save.d.ts.map +1 -1
  196. package/dist/save.js +518 -185
  197. package/dist/saves.d.ts +70 -0
  198. package/dist/saves.d.ts.map +1 -0
  199. package/dist/saves.js +1004 -0
  200. package/dist/search.d.ts +12 -95
  201. package/dist/search.d.ts.map +1 -1
  202. package/dist/search.js +10 -266
  203. package/dist/seed.d.ts +11 -2
  204. package/dist/seed.d.ts.map +1 -1
  205. package/dist/seed.js +71 -0
  206. package/dist/shared.d.ts +7 -3
  207. package/dist/shared.d.ts.map +1 -1
  208. package/dist/shared.js +119 -66
  209. package/dist/shown.d.ts +31 -0
  210. package/dist/shown.d.ts.map +1 -0
  211. package/dist/shown.js +294 -0
  212. package/dist/socket/admission.d.ts +7 -0
  213. package/dist/socket/admission.d.ts.map +1 -0
  214. package/dist/socket/admission.js +39 -0
  215. package/dist/socket/answers.d.ts +6 -0
  216. package/dist/socket/answers.d.ts.map +1 -0
  217. package/dist/socket/answers.js +20 -0
  218. package/dist/socket/lanes.d.ts +20 -0
  219. package/dist/socket/lanes.d.ts.map +1 -0
  220. package/dist/socket/lanes.js +77 -0
  221. package/dist/socket/reads.d.ts +10 -0
  222. package/dist/socket/reads.d.ts.map +1 -0
  223. package/dist/socket/reads.js +31 -0
  224. package/dist/socket/rooms.d.ts +43 -0
  225. package/dist/socket/rooms.d.ts.map +1 -0
  226. package/dist/socket/rooms.js +90 -0
  227. package/dist/socket/session.d.ts +39 -0
  228. package/dist/socket/session.d.ts.map +1 -0
  229. package/dist/socket/session.js +23 -0
  230. package/dist/socket.d.ts +70 -0
  231. package/dist/socket.d.ts.map +1 -0
  232. package/dist/socket.js +175 -0
  233. package/dist/source.d.ts +61 -0
  234. package/dist/source.d.ts.map +1 -0
  235. package/dist/source.js +81 -0
  236. package/dist/status.d.ts +4 -5
  237. package/dist/status.d.ts.map +1 -1
  238. package/dist/status.js +3 -7
  239. package/dist/store/fences.d.ts +16 -0
  240. package/dist/store/fences.d.ts.map +1 -0
  241. package/dist/store/fences.js +26 -0
  242. package/dist/store/installs.d.ts +30 -0
  243. package/dist/store/installs.d.ts.map +1 -0
  244. package/dist/store/installs.js +34 -0
  245. package/dist/store/readiness.d.ts +10 -0
  246. package/dist/store/readiness.d.ts.map +1 -0
  247. package/dist/store/readiness.js +52 -0
  248. package/dist/store/records.d.ts +212 -0
  249. package/dist/store/records.d.ts.map +1 -0
  250. package/dist/store/records.js +408 -0
  251. package/dist/store/room-held.d.ts +8 -0
  252. package/dist/store/room-held.d.ts.map +1 -0
  253. package/dist/store/room-held.js +17 -0
  254. package/dist/store/space.d.ts +11 -0
  255. package/dist/store/space.d.ts.map +1 -0
  256. package/dist/store/space.js +43 -0
  257. package/dist/store/watchers.d.ts +17 -0
  258. package/dist/store/watchers.d.ts.map +1 -0
  259. package/dist/store/watchers.js +57 -0
  260. package/dist/store/writes.d.ts +24 -0
  261. package/dist/store/writes.d.ts.map +1 -0
  262. package/dist/store/writes.js +125 -0
  263. package/dist/store.d.ts +106 -164
  264. package/dist/store.d.ts.map +1 -1
  265. package/dist/store.js +136 -752
  266. package/dist/stored.d.ts +156 -0
  267. package/dist/stored.d.ts.map +1 -0
  268. package/dist/stored.js +637 -0
  269. package/dist/surface.d.ts +86 -0
  270. package/dist/surface.d.ts.map +1 -0
  271. package/dist/surface.js +217 -0
  272. package/dist/sync.d.ts +20 -0
  273. package/dist/sync.d.ts.map +1 -0
  274. package/dist/sync.js +531 -0
  275. package/dist/tables.d.ts +407 -0
  276. package/dist/tables.d.ts.map +1 -0
  277. package/dist/tables.js +724 -0
  278. package/dist/tallies.d.ts +18 -0
  279. package/dist/tallies.d.ts.map +1 -0
  280. package/dist/tallies.js +129 -0
  281. package/dist/transfer.d.ts +0 -5
  282. package/dist/transfer.d.ts.map +1 -1
  283. package/dist/turn.d.ts +4 -2
  284. package/dist/turn.d.ts.map +1 -1
  285. package/dist/turn.js +46 -22
  286. package/dist/validation.d.ts +29 -0
  287. package/dist/validation.d.ts.map +1 -0
  288. package/dist/validation.js +104 -0
  289. package/dist/windows.d.ts +1 -16
  290. package/dist/windows.d.ts.map +1 -1
  291. package/dist/windows.js +2 -79
  292. package/dist/wire.d.ts +33 -6
  293. package/dist/wire.d.ts.map +1 -1
  294. package/dist/wire.js +11 -2
  295. package/package.json +18 -19
  296. package/AGENTS.md +0 -299
  297. package/dist/chat/acts.d.ts +0 -33
  298. package/dist/chat/acts.d.ts.map +0 -1
  299. package/dist/chat/acts.js +0 -36
  300. package/dist/chat/context.d.ts +0 -3
  301. package/dist/chat/context.d.ts.map +0 -1
  302. package/dist/chat/earlier.d.ts +0 -10
  303. package/dist/chat/earlier.d.ts.map +0 -1
  304. package/dist/chat/earlier.js +0 -54
  305. package/dist/chat/input.d.ts +0 -9
  306. package/dist/chat/input.d.ts.map +0 -1
  307. package/dist/chat/input.js +0 -99
  308. package/dist/chat/kit.d.ts +0 -61
  309. package/dist/chat/kit.d.ts.map +0 -1
  310. package/dist/chat/kit.js +0 -123
  311. package/dist/chat/lasting.d.ts +0 -6
  312. package/dist/chat/lasting.d.ts.map +0 -1
  313. package/dist/chat/lasting.js +0 -19
  314. package/dist/chat/list.d.ts +0 -27
  315. package/dist/chat/list.d.ts.map +0 -1
  316. package/dist/chat/list.js +0 -99
  317. package/dist/chat/markdown.d.ts +0 -20
  318. package/dist/chat/markdown.d.ts.map +0 -1
  319. package/dist/chat/markdown.js +0 -188
  320. package/dist/chat/message.d.ts +0 -6
  321. package/dist/chat/message.d.ts.map +0 -1
  322. package/dist/chat/message.js +0 -192
  323. package/dist/chat/messages.d.ts +0 -22
  324. package/dist/chat/messages.d.ts.map +0 -1
  325. package/dist/chat/messages.js +0 -155
  326. package/dist/chat/name.d.ts +0 -6
  327. package/dist/chat/name.d.ts.map +0 -1
  328. package/dist/chat/name.js +0 -37
  329. package/dist/chat/opened.d.ts +0 -11
  330. package/dist/chat/opened.d.ts.map +0 -1
  331. package/dist/chat/opened.js +0 -17
  332. package/dist/chat/overlay.d.ts +0 -31
  333. package/dist/chat/overlay.d.ts.map +0 -1
  334. package/dist/chat/overlay.js +0 -97
  335. package/dist/chat/parts.d.ts.map +0 -1
  336. package/dist/chat/process.d.ts +0 -20
  337. package/dist/chat/process.d.ts.map +0 -1
  338. package/dist/chat/process.js +0 -32
  339. package/dist/chat/provider.d.ts +0 -63
  340. package/dist/chat/provider.d.ts.map +0 -1
  341. package/dist/chat/provider.js +0 -628
  342. package/dist/chat/question.d.ts +0 -9
  343. package/dist/chat/question.d.ts.map +0 -1
  344. package/dist/chat/question.js +0 -69
  345. package/dist/chat/questions.d.ts.map +0 -1
  346. package/dist/chat/read.d.ts.map +0 -1
  347. package/dist/chat/ready.d.ts +0 -34
  348. package/dist/chat/ready.d.ts.map +0 -1
  349. package/dist/chat/ready.js +0 -91
  350. package/dist/chat/style.d.ts +0 -4
  351. package/dist/chat/style.d.ts.map +0 -1
  352. package/dist/chat/style.js +0 -302
  353. package/dist/chat.d.ts +0 -14
  354. package/dist/chat.d.ts.map +0 -1
  355. package/dist/chat.js +0 -8
  356. package/dist/fence.d.ts +0 -6
  357. package/dist/fence.d.ts.map +0 -1
  358. package/dist/fence.js +0 -10
  359. package/dist/read-side.d.ts +0 -27
  360. package/dist/read-side.d.ts.map +0 -1
  361. package/dist/read-side.js +0 -824
package/dist/save.js CHANGED
@@ -1,11 +1,13 @@
1
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';
2
+ import { apply, ARRAY_INDEX, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
3
+ import { immutable, leaving, queuedFile, departure, } from './change.js';
4
+ import { collectionOf, documentsOf, governing, } from './collection.js';
5
5
  import { changeBatchIdentity } from './identity.js';
6
- import { overlay } from './overlay.js';
7
- import { covered, normalize } from './paths.js';
8
- import { differing } from './shared.js';
6
+ import { overlaying } from './overlay.js';
7
+ import { ancestors, covered, normalize } from './paths.js';
8
+ import { sorting } from './shared.js';
9
+ import { paced, ROW_WEIGHT, stepped } from './turn.js';
10
+ import { validatedCollection } from './validation.js';
9
11
  function refuse(code, file, location) {
10
12
  return {
11
13
  kind: 'refused',
@@ -21,7 +23,7 @@ function repointed(document, definition, from, to, moves, settled) {
21
23
  const parsed = parse(document.content, from);
22
24
  if (!parsed.ok)
23
25
  return [];
24
- let next = parsed.document;
26
+ const changes = [];
25
27
  const names = new Set();
26
28
  for (const { pointer, value } of definition === undefined
27
29
  ? []
@@ -33,38 +35,39 @@ function repointed(document, definition, from, to, moves, settled) {
33
35
  const destination = moves.get(named) ?? named;
34
36
  if (target(to, value) === destination)
35
37
  continue;
36
- const applied = apply(next, {
38
+ changes.push({
37
39
  kind: 'reference',
38
40
  pointer,
39
41
  value: retarget(value, relative(to, destination)),
40
42
  prior: value,
41
43
  });
42
- if (applied.outcome !== 'applied')
43
- continue;
44
- next = applied.document;
45
44
  names.add(name);
46
45
  }
46
+ if (changes.length === 0)
47
+ return [];
48
+ const applied = apply(parsed.document, changes);
49
+ if (applied.outcome !== 'applied')
50
+ return [];
51
+ const next = applied.document;
47
52
  return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
48
53
  }
49
- function edited(document, path, changes) {
50
- let next = document;
54
+ function edited(document, changes) {
55
+ const parts = new Map();
51
56
  for (const change of changes) {
52
- if (change.path !== path)
53
- continue;
54
- if (change.kind !== 'section' && change.kind !== 'preamble')
55
- continue;
56
- const applied = apply(next, change.kind === 'preamble'
57
- ? { kind: 'preamble', value: change.value, prior: next.preamble }
58
- : {
57
+ if (change.kind === 'preamble')
58
+ parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
59
+ if (change.kind === 'section')
60
+ parts.set(`section ${change.name}`, {
59
61
  kind: 'section',
60
62
  name: change.name,
61
63
  value: change.value,
62
- prior: next.sections[change.name],
64
+ prior: document.sections[change.name] ?? '',
63
65
  });
64
- if (applied.outcome === 'applied')
65
- next = applied.document;
66
66
  }
67
- return next;
67
+ if (parts.size === 0)
68
+ return document;
69
+ const applied = apply(document, [...parts.values()]);
70
+ return applied.outcome === 'applied' ? applied.document : document;
68
71
  }
69
72
  function bodies(path, before, after) {
70
73
  const changes = [];
@@ -79,7 +82,7 @@ function bodies(path, before, after) {
79
82
  return changes;
80
83
  }
81
84
  function relinks(path, moved, before, moves) {
82
- let after = before;
85
+ const changes = [];
83
86
  for (const link of links(before, path)) {
84
87
  const destination = moves.get(link.target) ?? link.target;
85
88
  if (destination === link.target && moved === path)
@@ -87,20 +90,23 @@ function relinks(path, moved, before, moves) {
87
90
  const to = relative(moved, destination);
88
91
  if (to === relative(path, link.target))
89
92
  continue;
90
- const applied = apply(after, { kind: 'link', href: link.href, to });
91
- if (applied.outcome === 'refuse') {
92
- return {
93
- kind: 'refused',
94
- refusal: {
95
- code: applied.refusal.rule,
96
- retryable: false,
97
- file: path,
98
- ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
99
- },
100
- };
101
- }
102
- after = applied.document;
93
+ changes.push({ kind: 'link', href: link.href, to });
103
94
  }
95
+ if (changes.length === 0)
96
+ return { kind: 'changes', changes: [] };
97
+ const applied = apply(before, changes);
98
+ if (applied.outcome === 'refuse') {
99
+ return {
100
+ kind: 'refused',
101
+ refusal: {
102
+ code: applied.refusal.rule,
103
+ retryable: false,
104
+ file: path,
105
+ ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
106
+ },
107
+ };
108
+ }
109
+ const after = applied.document;
104
110
  if (after === before)
105
111
  return { kind: 'changes', changes: [] };
106
112
  if (before.type === undefined) {
@@ -108,19 +114,14 @@ function relinks(path, moved, before, moves) {
108
114
  }
109
115
  return { kind: 'changes', changes: bodies(path, before, after) };
110
116
  }
111
- function subsumed(merged, path) {
112
- for (let index = merged.length - 1; index >= 0; index -= 1) {
113
- const item = merged[index];
114
- if (item.path !== path)
115
- continue;
116
- if (item.kind === 'section' || item.kind === 'preamble')
117
- merged.splice(index, 1);
118
- }
117
+ function partKey(change) {
118
+ return change.kind === 'section' ? `section ${change.name}` : change.kind;
119
119
  }
120
- function rewrites(changes, visible) {
120
+ function* rewrites(changes, visible) {
121
121
  const moves = new Map();
122
122
  const settled = new Set();
123
- for (const change of changes) {
123
+ const parts = new Map();
124
+ for (const [index, change] of changes.entries()) {
124
125
  if (change.kind === 'move')
125
126
  moves.set(change.path, change.to);
126
127
  if (change.kind === 'remove' ||
@@ -131,54 +132,143 @@ function rewrites(changes, visible) {
131
132
  }
132
133
  if (change.kind === 'field')
133
134
  settled.add(`${change.path} ${change.name}`);
135
+ if (isPart(change)) {
136
+ const held = parts.get(change.path);
137
+ if (held === undefined)
138
+ parts.set(change.path, [index]);
139
+ else
140
+ held.push(index);
141
+ }
134
142
  }
135
143
  if (moves.size === 0)
136
144
  return { kind: 'changes', changes };
137
145
  const merged = [...changes];
138
146
  const added = [];
139
- for (const [path, document] of documentsOf(visible.collection)) {
147
+ for (const [path, document] of yield* documentsOf(visible.collection)) {
140
148
  if (settled.has(path))
141
149
  continue;
142
150
  const moved = moves.get(path) ?? path;
143
151
  const definition = definitionOf(governing(visible.collection.composition, path), document.type);
144
- added.push(...repointed(document, definition, path, moved, moves, settled));
145
- const relinked = relinks(path, moved, edited(document, path, merged), moves);
152
+ const repointing = repointed(document, definition, path, moved, moves, settled);
153
+ added.push(...repointing);
154
+ const own = parts.get(path) ?? [];
155
+ const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
146
156
  if (relinked.kind === 'refused')
147
157
  return relinked;
158
+ const keyed = new Map();
159
+ for (const index of own) {
160
+ const key = partKey(merged[index]);
161
+ if (!keyed.has(key))
162
+ keyed.set(key, index);
163
+ }
148
164
  for (const change of relinked.changes) {
149
165
  if (change.kind === 'replace') {
150
- subsumed(merged, path);
166
+ for (const index of own) {
167
+ const kind = merged[index]?.kind;
168
+ if (kind === 'section' || kind === 'preamble')
169
+ merged[index] = null;
170
+ }
151
171
  added.push(change);
152
172
  continue;
153
173
  }
154
- const index = merged.findIndex((item) => item.kind === change.kind &&
155
- item.path === path &&
156
- (change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
157
- if (index === -1)
174
+ const index = keyed.get(partKey(change));
175
+ if (index === undefined)
158
176
  added.push(change);
159
177
  else
160
178
  merged[index] = change;
161
179
  }
180
+ const text = visible.collection.text[path].length;
181
+ const rewritten = repointing.length > 0 || relinked.changes.length > 0;
182
+ yield ROW_WEIGHT + text + (rewritten ? text : 0);
162
183
  }
163
- return { kind: 'changes', changes: [...added, ...merged] };
184
+ return {
185
+ kind: 'changes',
186
+ changes: [...added, ...merged.filter((change) => change !== null)],
187
+ };
164
188
  }
165
- function anticipated(changes, preconditions, rewritten, visible) {
166
- const derived = rewritten.filter((change) => !changes.includes(change));
189
+ function* anticipated(changes, preconditions, rewritten, visible) {
190
+ const given = new Set(changes);
191
+ const derived = rewritten.filter((change) => !given.has(change));
167
192
  return {
168
193
  changes: rewritten,
169
- preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
194
+ preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
170
195
  };
171
196
  }
172
- export function providerRewritten(batch, visible) {
173
- const rewritten = rewrites(batch.changes, visible);
197
+ export function* providerRewritten(batch, visible) {
198
+ const rewritten = yield* rewrites(batch.changes, visible);
174
199
  if (rewritten.kind === 'refused')
175
200
  return batch;
176
201
  return {
177
202
  ...batch,
178
- ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
203
+ ...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
179
204
  };
180
205
  }
181
- function incoming(collection, removed, candidates) {
206
+ function crossed(document, definition, path, to) {
207
+ const changes = [];
208
+ for (const link of links(document, path)) {
209
+ const written = to(link.target, link.href);
210
+ if (written !== undefined)
211
+ changes.push({ kind: 'link', href: link.href, to: written });
212
+ }
213
+ for (const { pointer, value } of definition === undefined
214
+ ? []
215
+ : references(document, definition)) {
216
+ const named = target(path, value);
217
+ const written = named === null ? undefined : to(named, value);
218
+ if (written !== undefined) {
219
+ changes.push({ kind: 'reference', pointer, value: retarget(value, written), prior: value });
220
+ }
221
+ }
222
+ return changes;
223
+ }
224
+ function rewritten(path, document, changes) {
225
+ if (changes.length === 0)
226
+ return [];
227
+ const applied = apply(document, changes);
228
+ if (applied.outcome !== 'applied')
229
+ return [];
230
+ const after = applied.document;
231
+ if (document.type === undefined)
232
+ return [{ kind: 'replace', path, content: serialize(after) }];
233
+ const names = new Set(changes.flatMap((change) => change.kind === 'reference' ? [pointerSegments(change.pointer)[0] ?? ''] : []));
234
+ return [
235
+ ...[...names].map((name) => ({ kind: 'field', path, name, value: after.fields[name] })),
236
+ ...bodies(path, document, after),
237
+ ];
238
+ }
239
+ function roomPathOf(handle, path) {
240
+ return `/rooms/${handle}/${path}`;
241
+ }
242
+ export function* leavingRewritten(batch, visible) {
243
+ const between = batch.between;
244
+ if (between === undefined)
245
+ return batch;
246
+ const moved = new Map(between.moves.map(({ path, to }) => [path, roomPathOf(between.destination.handle, to)]));
247
+ const derived = [];
248
+ for (const [path, document] of yield* documentsOf(visible.collection)) {
249
+ if (moved.has(path))
250
+ continue;
251
+ const definition = definitionOf(governing(visible.collection.composition, path), document.type);
252
+ derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named))));
253
+ yield ROW_WEIGHT + visible.collection.text[path].length;
254
+ }
255
+ if (derived.length === 0)
256
+ return batch;
257
+ return {
258
+ ...batch,
259
+ changes: [...derived, ...batch.changes],
260
+ preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...batch.preconditions],
261
+ };
262
+ }
263
+ export function rewriting(batch, locator) {
264
+ return leaving(batch, locator) || batch.changes.some((change) => change.kind === 'move');
265
+ }
266
+ export function* effectiveIn(batch, locator, visible) {
267
+ return leaving(batch, locator)
268
+ ? yield* leavingRewritten(batch, visible)
269
+ : yield* providerRewritten(batch, visible);
270
+ }
271
+ function* incoming(collection, removed, candidates) {
182
272
  const found = new Map();
183
273
  const note = (target, file) => {
184
274
  if (!removed.has(target))
@@ -187,13 +277,20 @@ function incoming(collection, removed, candidates) {
187
277
  referrers.add(file);
188
278
  found.set(target, referrers);
189
279
  };
190
- const read = candidates === null
191
- ? documentsOf(collection)
192
- : [...new Set(candidates)].sort().flatMap((path) => {
280
+ const read = function* (paths) {
281
+ const seen = new Set();
282
+ for (const path of paths) {
283
+ if (seen.has(path))
284
+ continue;
285
+ seen.add(path);
193
286
  const document = collection.document(path);
194
- return document === undefined ? [] : [[path, document]];
195
- });
196
- for (const [path, document] of read) {
287
+ if (document !== undefined)
288
+ yield [path, document];
289
+ }
290
+ };
291
+ for (const [path, document] of candidates === null
292
+ ? yield* documentsOf(collection)
293
+ : read(candidates)) {
197
294
  const definition = definitionOf(governing(collection.composition, path), document.type);
198
295
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
199
296
  if (to.includes(ATTACHMENT))
@@ -202,8 +299,9 @@ function incoming(collection, removed, candidates) {
202
299
  if (named !== null)
203
300
  note(named, path);
204
301
  }
302
+ yield ROW_WEIGHT + collection.text[path].length;
205
303
  }
206
- const [first] = [...found.keys()].sort();
304
+ const [first] = yield* sorting(found.keys());
207
305
  if (first === undefined)
208
306
  return null;
209
307
  const every = new Set();
@@ -216,14 +314,16 @@ function incoming(collection, removed, candidates) {
216
314
  code: 'referenced-missing-file',
217
315
  retryable: false,
218
316
  file: first,
219
- referrers: [...every].sort(),
317
+ referrers: yield* sorting(every),
220
318
  },
221
319
  };
222
320
  }
223
321
  function affected(change) {
224
322
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
225
323
  }
226
- function guard(changes, input) {
324
+ const WRITE = ['write'];
325
+ const MOVE_OUT = ['write', 'erase'];
326
+ function guard(changes, input, removing) {
227
327
  const overridden = new Map();
228
328
  const existing = {
229
329
  has: (path) => overridden.get(path) ??
@@ -231,6 +331,7 @@ function guard(changes, input) {
231
331
  add: (path) => void overridden.set(path, true),
232
332
  delete: (path) => void overridden.set(path, false),
233
333
  };
334
+ const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
234
335
  for (const [index, change] of changes.entries()) {
235
336
  for (const path of affected(change)) {
236
337
  const normalized = normalize(path);
@@ -239,13 +340,14 @@ function guard(changes, input) {
239
340
  if (covered(input.providerOwnedPaths, normalized)) {
240
341
  return refuse('provider-owned-path', normalized);
241
342
  }
242
- const operation = change.kind === 'remove' ? 'delete' : 'write';
243
- if (!input.local && !covered(input.operations[operation], normalized))
244
- return refuse(`${operation}-grant`, normalized);
343
+ for (const operation of change.kind === 'remove' ? removing : WRITE)
344
+ if (!input.local && !covered(input.operations[operation], normalized))
345
+ return refuse(`${operation}-grant`, normalized);
245
346
  }
246
- if (change.kind === 'create') {
247
- if (existing.has(change.path))
248
- return refuse('path-collision', change.path);
347
+ if (change.kind === 'create' || change.kind === 'arrival') {
348
+ const taken = existing.has(change.path) ? change.path : occupied(change.path);
349
+ if (taken !== null)
350
+ return refuse('path-collision', taken);
249
351
  existing.add(change.path);
250
352
  continue;
251
353
  }
@@ -255,6 +357,9 @@ function guard(changes, input) {
255
357
  input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
256
358
  return refuse('path-collision', change.path);
257
359
  }
360
+ const taken = occupied(change.path);
361
+ if (taken !== null)
362
+ return refuse('path-collision', taken);
258
363
  if (!input.local &&
259
364
  existing.has(change.path) &&
260
365
  input.visible.revisions[departure(changes, index)] === undefined) {
@@ -266,8 +371,9 @@ function guard(changes, input) {
266
371
  if (change.kind === 'move') {
267
372
  if (!existing.has(change.path))
268
373
  return refuse('path-absent', change.path);
269
- if (existing.has(change.to))
270
- return refuse('path-collision', change.to);
374
+ const taken = existing.has(change.to) ? change.to : occupied(change.to);
375
+ if (taken !== null)
376
+ return refuse('path-collision', taken);
271
377
  if (unrevised(input, change.path)) {
272
378
  return refuse('unknown-revision', change.path);
273
379
  }
@@ -303,7 +409,7 @@ export function queuedDocuments(queue) {
303
409
  const paths = new Set();
304
410
  for (const batch of queue) {
305
411
  for (const change of batch.changes) {
306
- if (change.kind === 'binary')
412
+ if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
307
413
  continue;
308
414
  if (change.kind === 'remove')
309
415
  paths.delete(change.path);
@@ -332,7 +438,7 @@ function sameTarget(precondition, change) {
332
438
  return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
333
439
  ((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
334
440
  }
335
- function preconditionsOf(changes, visible, bases) {
441
+ function* preconditionsOf(changes, visible, bases) {
336
442
  const preconditions = [];
337
443
  const seen = new Set();
338
444
  const add = (precondition) => {
@@ -343,6 +449,7 @@ function preconditionsOf(changes, visible, bases) {
343
449
  preconditions.push(precondition);
344
450
  };
345
451
  for (const change of changes) {
452
+ yield ROW_WEIGHT;
346
453
  if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
347
454
  const based = bases.find((base) => sameTarget(base, change));
348
455
  if (based !== undefined) {
@@ -390,10 +497,13 @@ function preconditionsOf(changes, visible, bases) {
390
497
  function depth(path) {
391
498
  return path.split('/').length;
392
499
  }
393
- function exceeded(limits, changes, effective, resulting) {
500
+ function* exceeding(limits, changes, effective, resulting) {
394
501
  const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
395
502
  const created = changes
396
- .filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
503
+ .filter((change) => change.kind === 'create' ||
504
+ change.kind === 'move' ||
505
+ change.kind === 'binary' ||
506
+ change.kind === 'arrival')
397
507
  .map((change) => (change.kind === 'move' ? change.to : change.path));
398
508
  for (const limit of limits) {
399
509
  const value = limit.value;
@@ -407,9 +517,13 @@ function exceeded(limits, changes, effective, resulting) {
407
517
  return refuse('provider-limit', undefined, limit.name);
408
518
  }
409
519
  if (limit.name === 'document-size') {
410
- const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
411
- if (large !== undefined)
412
- return refuse('provider-limit', large, limit.name);
520
+ for (const path of written) {
521
+ const text = resulting.documents[path] ?? '';
522
+ if (new TextEncoder().encode(text).byteLength > value) {
523
+ return refuse('provider-limit', path, limit.name);
524
+ }
525
+ yield ROW_WEIGHT + text.length;
526
+ }
413
527
  }
414
528
  if (limit.name === 'path-length') {
415
529
  const long = created.find((path) => path.length > value);
@@ -439,7 +553,7 @@ function relativeField(path, name, value, handle, definition) {
439
553
  !opened.ok ||
440
554
  !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
441
555
  return value;
442
- const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
556
+ const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
443
557
  if (held.outcome !== 'applied')
444
558
  return value;
445
559
  const result = relativize(held.document, path, handle, definition);
@@ -475,17 +589,6 @@ function relativeChanges(input) {
475
589
  return { ...change, fields, sections };
476
590
  });
477
591
  }
478
- function unmodeledType(collection, composition) {
479
- if (available(composition))
480
- return false;
481
- for (const [path, document] of documentsOf(collection)) {
482
- if (document.type !== undefined &&
483
- document.type !== 'any' &&
484
- governing(composition, path).status === 'unavailable')
485
- return true;
486
- }
487
- return false;
488
- }
489
592
  function unqueueable(file) {
490
593
  return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
491
594
  }
@@ -495,29 +598,44 @@ function isPart(change) {
495
598
  function touched(change) {
496
599
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
497
600
  }
498
- function partApplied(content, path, change) {
601
+ function partsApplied(content, path, changes) {
499
602
  const parsed = parse(content, path);
500
603
  if (!parsed.ok)
501
604
  return null;
502
605
  const document = parsed.document;
503
- const applied = apply(document, change.kind === 'field'
504
- ? {
505
- kind: 'field',
506
- name: change.name,
507
- value: change.value,
508
- prior: document.fields[change.name],
509
- }
510
- : change.kind === 'section'
511
- ? {
606
+ const parts = new Map();
607
+ for (const change of changes) {
608
+ if (change.kind === 'field')
609
+ parts.set(`field ${change.name}`, {
610
+ kind: 'field',
611
+ name: change.name,
612
+ value: change.value,
613
+ prior: document.fields[change.name],
614
+ });
615
+ else if (change.kind === 'section')
616
+ parts.set(`section ${change.name}`, {
512
617
  kind: 'section',
513
618
  name: change.name,
514
619
  value: change.value,
515
- prior: document.sections[change.name],
516
- }
517
- : { kind: 'preamble', value: change.value, prior: document.preamble });
620
+ prior: document.sections[change.name] ?? '',
621
+ });
622
+ else
623
+ parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
624
+ }
625
+ const applied = apply(document, [...parts.values()]);
518
626
  return applied.outcome === 'applied' ? serialize(applied.document) : null;
519
627
  }
520
- function folded(changes, change, appending) {
628
+ function settledParts(changes, deferred) {
629
+ for (const [held, parts] of deferred) {
630
+ const content = partsApplied(held.content, held.path, parts);
631
+ if (content === null)
632
+ return held.path;
633
+ changes[changes.indexOf(held)] = { ...held, content };
634
+ }
635
+ deferred.clear();
636
+ return null;
637
+ }
638
+ function folded(changes, change, appending, deferred) {
521
639
  const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
522
640
  const whole = held === undefined ? -1 : changes.indexOf(held);
523
641
  const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
@@ -538,10 +656,11 @@ function folded(changes, change, appending) {
538
656
  return 'appended';
539
657
  }
540
658
  if ('content' in held) {
541
- const content = partApplied(held.content, held.path, change);
542
- if (content === null)
543
- return 'refused';
544
- changes[whole] = { ...held, content };
659
+ const parts = deferred.get(held);
660
+ if (parts === undefined)
661
+ deferred.set(held, [change]);
662
+ else
663
+ parts.push(change);
545
664
  return 'folded';
546
665
  }
547
666
  if (change.kind === 'preamble')
@@ -572,17 +691,21 @@ function folded(changes, change, appending) {
572
691
  changes.push(change);
573
692
  return 'appended';
574
693
  }
575
- function relinked(batch, move, before, after, appending) {
576
- const derived = rewrites([move], after);
694
+ function* relinked(batch, move, before, after, appending) {
695
+ const derived = yield* rewrites([move], after);
577
696
  if (derived.kind === 'refused')
578
697
  return derived;
579
698
  const changes = [...batch.changes];
580
699
  const appended = [];
581
700
  let changed = false;
701
+ const deferred = new Map();
582
702
  for (const change of derived.changes) {
583
703
  if (change === move)
584
704
  continue;
585
- const outcome = folded(changes, change, appending);
705
+ const unsettled = isPart(change) ? null : settledParts(changes, deferred);
706
+ if (unsettled !== null)
707
+ return unqueueable(unsettled);
708
+ const outcome = folded(changes, change, appending, deferred);
586
709
  if (outcome === 'refused')
587
710
  return unqueueable(change.path);
588
711
  if (outcome === 'appended')
@@ -590,9 +713,13 @@ function relinked(batch, move, before, after, appending) {
590
713
  if (outcome !== 'skipped')
591
714
  changed = true;
592
715
  }
716
+ const unsettled = settledParts(changes, deferred);
717
+ if (unsettled !== null)
718
+ return unqueueable(unsettled);
593
719
  if (!changed)
594
720
  return { kind: 'relinked', batch };
595
721
  const held = new Set(batch.preconditions.map(identity));
722
+ const added = yield* preconditionsOf(appended, before, []);
596
723
  return {
597
724
  kind: 'relinked',
598
725
  batch: immutable({
@@ -601,7 +728,7 @@ function relinked(batch, move, before, after, appending) {
601
728
  changes,
602
729
  preconditions: [
603
730
  ...batch.preconditions,
604
- ...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
731
+ ...added.filter((precondition) => !held.has(identity(precondition))),
605
732
  ],
606
733
  }),
607
734
  };
@@ -647,7 +774,7 @@ function withdrawnFile(batch, change, at) {
647
774
  ];
648
775
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
649
776
  }
650
- export function requeued(queue, changes, visible, changeable, state) {
777
+ export function* requeuing(queue, changes, visible, changeable, state) {
651
778
  const next = [...queue];
652
779
  const origins = new Map();
653
780
  const withdrawn = new Map();
@@ -670,19 +797,19 @@ export function requeued(queue, changes, visible, changeable, state) {
670
797
  return unqueueable(change.path);
671
798
  const following = next.slice(index);
672
799
  const rebuilt = [];
673
- let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
800
+ let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
674
801
  for (const [offset, batch] of following.entries()) {
675
802
  let current = batch;
676
803
  if (change.kind === 'move') {
677
- const after = state(next.slice(0, index + offset + 1));
804
+ const after = yield* state(next.slice(0, index + offset + 1));
678
805
  const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
679
- const relinking = relinked(batch, change, before, after, appending);
806
+ const relinking = yield* relinked(batch, change, before, after, appending);
680
807
  if (relinking.kind === 'refused')
681
808
  return relinking;
682
809
  current = relinking.batch;
683
810
  before = after;
684
811
  }
685
- if (current !== batch && !open(batch))
812
+ if (current !== batch && (!open(batch) || batch.between !== undefined))
686
813
  return unqueueable(change.path);
687
814
  rebuilt.push(offset === 0
688
815
  ? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
@@ -723,10 +850,17 @@ export function joined(queue, owner, extra) {
723
850
  if (clash !== undefined)
724
851
  return unqueueable(clash);
725
852
  const changes = [...owner.changes];
853
+ const deferred = new Map();
726
854
  for (const change of extra.changes) {
727
- if (folded(changes, change, true) === 'refused')
855
+ const unsettled = isPart(change) ? null : settledParts(changes, deferred);
856
+ if (unsettled !== null)
857
+ return unqueueable(unsettled);
858
+ if (folded(changes, change, true, deferred) === 'refused')
728
859
  return unqueueable(change.path);
729
860
  }
861
+ const unsettled = settledParts(changes, deferred);
862
+ if (unsettled !== null)
863
+ return unqueueable(unsettled);
730
864
  const held = new Set(owner.preconditions.map(covering));
731
865
  const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
732
866
  const preconditions = [
@@ -745,59 +879,66 @@ export function joined(queue, owner, extra) {
745
879
  });
746
880
  return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
747
881
  }
748
- export function limitedIn(queue, batch, limits, state) {
882
+ export function* limiting(queue, batch, limits, state) {
749
883
  const at = queue.indexOf(batch);
750
- const limited = exceeded(limits, batch.changes, providerRewritten(batch, state(queue.slice(0, at))).changes, state(queue.slice(0, at + 1)));
884
+ const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
885
+ const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
751
886
  return limited?.kind === 'refused' ? limited.refusal : null;
752
887
  }
753
- export function buildBatch(input) {
754
- const changes = relativeChanges(input);
755
- const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
756
- if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
757
- return refuse('model-unavailable', structural.path);
758
- if (changes.length === 0)
759
- return refuse('no-change');
760
- const rewritten = rewrites(changes, input.visible);
761
- if (rewritten.kind === 'refused')
762
- return rewritten;
763
- const refusal = guard(changes, input);
764
- if (refusal !== null)
765
- return refusal;
766
- const preconditions = preconditionsOf(changes, input.visible, input.bases);
767
- const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
768
- const resulting = overlay(input.visible, [
769
- { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
770
- ]);
771
- const clean = input.clean !== undefined && available(input.composition) ? input.clean : null;
772
- const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
888
+ function typedText(content) {
889
+ if (content === undefined)
890
+ return false;
891
+ const parsed = parse(content);
892
+ return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
893
+ }
894
+ function touchedLibrary(resulting, touched, composition) {
895
+ const entries = touched
896
+ .flatMap((path) => {
897
+ const content = resulting.documents[path];
898
+ const binary = resulting.binaries[path] !== undefined;
899
+ return content === undefined && !binary ? [] : [{ path, content: content ?? null }];
900
+ })
901
+ .sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
902
+ const contents = new Map(entries.map(({ path, content }) => [path, content]));
903
+ const binaries = new Set(Object.keys(resulting.binaries).filter((path) => resulting.binaries[path] !== undefined));
904
+ return validatedCollection(() => Promise.resolve({ entries, next: null }), async (paths) => {
905
+ const known = new Map();
906
+ const pace = paced();
907
+ for (const path of paths) {
908
+ const content = contents.get(path);
909
+ if (binaries.has(path))
910
+ known.set(path, { type: undefined, binary: true });
911
+ else if (content !== undefined && content !== null) {
912
+ const read = parse(content, path);
913
+ if (read.ok)
914
+ known.set(path, { type: read.document.type ?? 'any', binary: false });
915
+ await pace(content.length + ROW_WEIGHT);
916
+ }
917
+ }
918
+ return known;
919
+ }, composition);
920
+ }
921
+ async function judging(visible, resulting, composition, validate, removed, touched) {
773
922
  if (removed.size > 0) {
774
- const referrers = clean === null ? null : referring(clean, input.composition, removed);
775
- const referenced = incoming(collectionOf(resulting.documents, input.composition), removed, clean === null || referrers === null
776
- ? null
777
- : [...referrers, ...differing(clean.text, resulting.documents)]);
923
+ const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
778
924
  if (referenced !== null)
779
925
  return referenced;
780
926
  }
781
- const typed = (content) => {
782
- if (content === undefined)
783
- return false;
784
- const parsed = parse(content);
785
- return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
786
- };
787
- const touched = [...new Set(predicted.changes.flatMap(affected))];
788
- const typedPaths = touched.filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
789
- const needsModel = typedPaths.length > 0;
790
- const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
927
+ const typedPaths = [];
928
+ const pace = paced();
929
+ for (const path of touched) {
930
+ const before = visible.documents[path];
931
+ const after = resulting.documents[path];
932
+ if (typedText(before) || typedText(after))
933
+ typedPaths.push(path);
934
+ await pace((before?.length ?? 0) + (after?.length ?? 0) + ROW_WEIGHT);
935
+ }
936
+ const withoutModel = typedPaths.find((path) => governing(composition, path).status === 'unavailable');
791
937
  if (withoutModel !== undefined)
792
938
  return refuse('model-unavailable', withoutModel);
793
- const verdict = clean !== null &&
794
- changedCleanly({ text: resulting.documents, binaries: resulting.binaries }, clean, input.composition)
795
- ? { verdict: 'accept' }
796
- : validateCollection(needsModel
797
- ? resulting.documents
798
- : Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
799
- ? []
800
- : [[path, resulting.documents[path]]])), Object.keys(resulting.binaries), input.composition);
939
+ const verdict = typedPaths.length > 0
940
+ ? await validate(resulting, touched)
941
+ : await touchedLibrary(resulting, touched, composition);
801
942
  if (verdict.verdict === 'refuse') {
802
943
  return {
803
944
  kind: 'refused',
@@ -810,12 +951,92 @@ export function buildBatch(input) {
810
951
  },
811
952
  };
812
953
  }
813
- const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
954
+ return {
955
+ kind: 'judged',
956
+ validated: typedPaths
957
+ .map((path) => governing(composition, path))
958
+ .find((model) => model.status !== 'unavailable'),
959
+ };
960
+ }
961
+ function placed(reference) {
962
+ const key = roomPathOf(reference.handle, reference.path);
963
+ if (reference.kind === 'link')
964
+ return `link ${key}`;
965
+ if (reference.kind === 'item')
966
+ return `section ${reference.section} ${key}`;
967
+ const pointer = reference.pointer
968
+ .split('/')
969
+ .map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
970
+ .join('/');
971
+ return `field ${pointer} ${key}`;
972
+ }
973
+ function roomReferencesIn(path, content, composition) {
974
+ if (content === undefined)
975
+ return [];
976
+ const parsed = parse(content, path);
977
+ if (!parsed.ok)
978
+ return [];
979
+ return roomReferences(parsed.document, definitionOf(governing(composition, path), parsed.document.type));
980
+ }
981
+ function* admittedRooms(changes, visible, resulting, composition) {
982
+ const from = new Map();
983
+ for (const [index, change] of changes.entries()) {
984
+ if (change.kind === 'move')
985
+ from.set(change.to, departure(changes, index));
986
+ }
987
+ for (const path of [...new Set(changes.flatMap(affected))].sort()) {
988
+ const now = resulting.documents[path];
989
+ if (now === undefined)
990
+ continue;
991
+ const held = new Set(roomReferencesIn(path, visible.documents[from.get(path) ?? path], composition).map(placed));
992
+ for (const reference of roomReferencesIn(path, now, composition)) {
993
+ if (reference.kind === 'link' || reference.crossRoom === true)
994
+ continue;
995
+ if (held.has(placed(reference)))
996
+ continue;
997
+ return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
998
+ }
999
+ yield ROW_WEIGHT + now.length;
1000
+ }
1001
+ return null;
1002
+ }
1003
+ function validationOf(validated) {
1004
+ return validated === undefined
1005
+ ? {}
1006
+ : {
1007
+ validation: {
1008
+ identity: validated.snapshot.identity,
1009
+ version: validated.snapshot.version,
1010
+ source: validated.status,
1011
+ },
1012
+ };
1013
+ }
1014
+ async function batching(input) {
1015
+ const changes = relativeChanges(input);
1016
+ const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
1017
+ if (structural !== undefined && input.unmodeled) {
1018
+ return refuse('model-unavailable', structural.path);
1019
+ }
1020
+ if (changes.length === 0)
1021
+ return refuse('no-change');
1022
+ const rewritten = await stepped(rewrites(changes, input.visible));
1023
+ if (rewritten.kind === 'refused')
1024
+ return rewritten;
1025
+ const refusal = guard(changes, input, WRITE);
1026
+ if (refusal !== null)
1027
+ return refusal;
1028
+ const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
1029
+ const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
1030
+ const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }]));
1031
+ 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))]);
1032
+ if (judged.kind === 'refused')
1033
+ return judged;
1034
+ const crossing = await stepped(admittedRooms(changes, input.visible, resulting, input.composition));
1035
+ if (crossing !== null)
1036
+ return crossing;
1037
+ const limited = await stepped(exceeding(input.providerLimits, changes, predicted.changes, resulting));
814
1038
  if (limited !== null)
815
1039
  return limited;
816
- const validated = typedPaths
817
- .map((path) => governing(input.composition, path))
818
- .find((model) => model.status !== 'unavailable');
819
1040
  return {
820
1041
  kind: 'batch',
821
1042
  batch: immutable({
@@ -824,15 +1045,127 @@ export function buildBatch(input) {
824
1045
  changes,
825
1046
  preconditions,
826
1047
  ...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
827
- ...(validated === undefined
828
- ? {}
829
- : {
830
- validation: {
831
- identity: validated.snapshot.identity,
832
- version: validated.snapshot.version,
833
- source: validated.status,
834
- },
835
- }),
1048
+ ...validationOf(judged.validated),
836
1049
  }),
837
1050
  };
838
1051
  }
1052
+ export const MOVE_DOCUMENTS = 'move-documents';
1053
+ function heldAt(visible, path) {
1054
+ return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
1055
+ }
1056
+ function arrivingText(path, to, source, moved) {
1057
+ const content = source.visible.documents[path];
1058
+ const document = source.visible.collection.document(path);
1059
+ if (document === undefined)
1060
+ return content;
1061
+ const definition = definitionOf(governing(source.composition, path), document.type);
1062
+ const changes = crossed(document, definition, path, (named, href) => {
1063
+ const at = moved.get(named);
1064
+ if (at !== undefined)
1065
+ return target(to, href) === at ? undefined : relative(to, at);
1066
+ return heldAt(source.visible, named) ? roomPathOf(source.handle, named) : undefined;
1067
+ });
1068
+ if (changes.length === 0)
1069
+ return content;
1070
+ const applied = apply(document, changes);
1071
+ return applied.outcome === 'applied' ? serialize(applied.document) : content;
1072
+ }
1073
+ function arrivalOf({ path, to }, source, moved) {
1074
+ const { documents, revisions, fileIds, created, changes, binaries } = source.visible;
1075
+ const kept = {
1076
+ ...(fileIds[path] === undefined ? {} : { fileId: fileIds[path] }),
1077
+ ...(created[path] === undefined ? {} : { created: created[path] }),
1078
+ ...(changes[path] === undefined ? {} : { change: changes[path] }),
1079
+ };
1080
+ const heads = {
1081
+ ...(revisions[path] === undefined ? {} : { revision: revisions[path] }),
1082
+ ...kept,
1083
+ };
1084
+ const binary = binaries[path];
1085
+ if (binary?.state === 'accepted') {
1086
+ const { byteLength, mediaType, sha256 } = binary;
1087
+ return {
1088
+ kind: 'arrival',
1089
+ path: to,
1090
+ binary: { byteLength, mediaType, sha256 },
1091
+ ...heads,
1092
+ };
1093
+ }
1094
+ const content = arrivingText(path, to, source, moved);
1095
+ return {
1096
+ kind: 'arrival',
1097
+ path: to,
1098
+ content,
1099
+ ...(content === documents[path] ? heads : kept),
1100
+ };
1101
+ }
1102
+ async function moving(input) {
1103
+ const { source, destination, moves } = input;
1104
+ if (moves.length === 0)
1105
+ return refuse('no-change');
1106
+ const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1107
+ const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
1108
+ if (leaves !== null)
1109
+ return leaves;
1110
+ const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
1111
+ if (uploading !== undefined)
1112
+ return refuse('unknown-revision', uploading.path);
1113
+ const moved = new Map(moves.map(({ path, to }) => [path, to]));
1114
+ const arrivals = moves.map((move) => arrivalOf(move, source, moved));
1115
+ const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, WRITE);
1116
+ if (arrives !== null)
1117
+ return arrives;
1118
+ const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
1119
+ if (typeof bound === 'number' && moves.length > bound) {
1120
+ return refuse('provider-limit', undefined, MOVE_DOCUMENTS);
1121
+ }
1122
+ if (source.unmodeled)
1123
+ return refuse('model-unavailable', moves[0].path);
1124
+ const id = changeBatchIdentity();
1125
+ const between = {
1126
+ source: { room: source.room, baseRoomVersion: source.baseRoomVersion },
1127
+ destination: {
1128
+ room: destination.room,
1129
+ baseRoomVersion: destination.baseRoomVersion,
1130
+ handle: destination.handle,
1131
+ },
1132
+ moves,
1133
+ };
1134
+ const written = input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy };
1135
+ const leavingBatch = {
1136
+ id,
1137
+ baseRoomVersion: source.baseRoomVersion,
1138
+ changes: removals,
1139
+ preconditions: moves.map(({ path }) => existence(source.visible, path)),
1140
+ ...written,
1141
+ between,
1142
+ };
1143
+ const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1144
+ const left = await stepped(overlaying(source.visible, [effective]));
1145
+ 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))]);
1146
+ if (sourceJudged.kind === 'refused')
1147
+ return sourceJudged;
1148
+ const arrivingBatch = {
1149
+ id,
1150
+ baseRoomVersion: destination.baseRoomVersion,
1151
+ changes: arrivals,
1152
+ preconditions: moves.map(({ to }) => ({ kind: 'absent', path: to })),
1153
+ ...written,
1154
+ between,
1155
+ };
1156
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1157
+ const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1158
+ if (destinationJudged.kind === 'refused')
1159
+ return destinationJudged;
1160
+ const limited = (await stepped(exceeding(input.providerLimits, removals, effective.changes, left))) ??
1161
+ (await stepped(exceeding(input.providerLimits, arrivals, arrivals, arrived)));
1162
+ if (limited !== null)
1163
+ return limited;
1164
+ return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1165
+ }
1166
+ export function buildMove(input) {
1167
+ return moving(input);
1168
+ }
1169
+ export function buildBatch(input) {
1170
+ return batching(input);
1171
+ }