@agentshouse/mdruntime 0.1.0-c3fa75348e547 → 0.1.0-c3faae279d4d2

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 (368) 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 +385 -0
  5. package/dist/actions.d.ts +20 -0
  6. package/dist/actions.d.ts.map +1 -0
  7. package/dist/actions.js +251 -0
  8. package/dist/adapter.d.ts +150 -81
  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 +320 -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 +14 -3
  31. package/dist/chat-types.d.ts +5 -0
  32. package/dist/chat-types.d.ts.map +1 -0
  33. package/dist/chat-types.js +13 -0
  34. package/dist/client.d.ts +33 -10
  35. package/dist/client.d.ts.map +1 -1
  36. package/dist/client.js +90 -10
  37. package/dist/collection.d.ts +11 -21
  38. package/dist/collection.d.ts.map +1 -1
  39. package/dist/collection.js +39 -291
  40. package/dist/compile.d.ts +16 -0
  41. package/dist/compile.d.ts.map +1 -0
  42. package/dist/compile.js +711 -0
  43. package/dist/connection.d.ts +7 -132
  44. package/dist/connection.d.ts.map +1 -1
  45. package/dist/connection.js +100 -3568
  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-live.d.ts +136 -0
  50. package/dist/conversation-live.d.ts.map +1 -0
  51. package/dist/conversation-live.js +248 -0
  52. package/dist/conversation-row.d.ts +33 -0
  53. package/dist/conversation-row.d.ts.map +1 -0
  54. package/dist/conversation-row.js +53 -0
  55. package/dist/conversation.d.ts +53 -44
  56. package/dist/conversation.d.ts.map +1 -1
  57. package/dist/conversation.js +938 -715
  58. package/dist/digest.d.ts +7 -0
  59. package/dist/digest.d.ts.map +1 -0
  60. package/dist/digest.js +113 -0
  61. package/dist/discovery.d.ts +10 -0
  62. package/dist/discovery.d.ts.map +1 -0
  63. package/dist/discovery.js +175 -0
  64. package/dist/durability.d.ts +16 -4
  65. package/dist/durability.d.ts.map +1 -1
  66. package/dist/durability.js +5 -0
  67. package/dist/entry.d.ts +5 -2
  68. package/dist/entry.d.ts.map +1 -1
  69. package/dist/entry.js +33 -28
  70. package/dist/figures.d.ts +49 -0
  71. package/dist/figures.d.ts.map +1 -0
  72. package/dist/figures.js +197 -0
  73. package/dist/file.d.ts +2 -19
  74. package/dist/file.d.ts.map +1 -1
  75. package/dist/file.js +7 -122
  76. package/dist/holdings.d.ts +29 -0
  77. package/dist/holdings.d.ts.map +1 -0
  78. package/dist/holdings.js +95 -0
  79. package/dist/host.d.ts +4 -10
  80. package/dist/host.d.ts.map +1 -1
  81. package/dist/host.js +74 -41
  82. package/dist/identity.d.ts +0 -2
  83. package/dist/identity.d.ts.map +1 -1
  84. package/dist/identity.js +0 -6
  85. package/dist/index.d.ts +33 -13
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +12 -2
  88. package/dist/install.d.ts +56 -0
  89. package/dist/install.d.ts.map +1 -0
  90. package/dist/install.js +554 -0
  91. package/dist/job.d.ts +33 -0
  92. package/dist/job.d.ts.map +1 -0
  93. package/dist/job.js +54 -0
  94. package/dist/lifecycle.d.ts +7 -0
  95. package/dist/lifecycle.d.ts.map +1 -0
  96. package/dist/lifecycle.js +268 -0
  97. package/dist/listing.d.ts +53 -0
  98. package/dist/listing.d.ts.map +1 -0
  99. package/dist/listing.js +176 -0
  100. package/dist/live.d.ts +23 -0
  101. package/dist/live.d.ts.map +1 -0
  102. package/dist/live.js +163 -0
  103. package/dist/locks.d.ts +9 -0
  104. package/dist/locks.d.ts.map +1 -0
  105. package/dist/locks.js +61 -0
  106. package/dist/log.d.ts +1 -0
  107. package/dist/log.d.ts.map +1 -1
  108. package/dist/lost.d.ts +5 -0
  109. package/dist/lost.d.ts.map +1 -0
  110. package/dist/lost.js +61 -0
  111. package/dist/media.d.ts +8 -0
  112. package/dist/media.d.ts.map +1 -0
  113. package/dist/media.js +218 -0
  114. package/dist/memory.d.ts +14 -7
  115. package/dist/memory.d.ts.map +1 -1
  116. package/dist/memory.js +241 -107
  117. package/dist/messages.d.ts +52 -0
  118. package/dist/messages.d.ts.map +1 -0
  119. package/dist/messages.js +162 -0
  120. package/dist/model.d.ts +8 -10
  121. package/dist/model.d.ts.map +1 -1
  122. package/dist/model.js +6 -18
  123. package/dist/models.d.ts +14 -0
  124. package/dist/models.d.ts.map +1 -0
  125. package/dist/models.js +195 -0
  126. package/dist/name.d.ts +4 -0
  127. package/dist/name.d.ts.map +1 -0
  128. package/dist/name.js +122 -0
  129. package/dist/observation.d.ts +17 -2
  130. package/dist/observation.d.ts.map +1 -1
  131. package/dist/observation.js +107 -17
  132. package/dist/opened.d.ts +22 -0
  133. package/dist/opened.d.ts.map +1 -0
  134. package/dist/opened.js +107 -0
  135. package/dist/opening.d.ts +4 -0
  136. package/dist/opening.d.ts.map +1 -0
  137. package/dist/opening.js +57 -0
  138. package/dist/outcome.d.ts +49 -8
  139. package/dist/outcome.d.ts.map +1 -1
  140. package/dist/outcome.js +42 -0
  141. package/dist/overlay.d.ts +3 -5
  142. package/dist/overlay.d.ts.map +1 -1
  143. package/dist/overlay.js +98 -172
  144. package/dist/{chat/parts.d.ts → parts.d.ts} +12 -9
  145. package/dist/parts.d.ts.map +1 -0
  146. package/dist/{chat/parts.js → parts.js} +53 -46
  147. package/dist/paths.d.ts +2 -0
  148. package/dist/paths.d.ts.map +1 -1
  149. package/dist/paths.js +26 -0
  150. package/dist/preparation.d.ts +20 -0
  151. package/dist/preparation.d.ts.map +1 -0
  152. package/dist/preparation.js +226 -0
  153. package/dist/queries.d.ts +16 -0
  154. package/dist/queries.d.ts.map +1 -0
  155. package/dist/queries.js +272 -0
  156. package/dist/query.d.ts +25 -191
  157. package/dist/query.d.ts.map +1 -1
  158. package/dist/query.js +3 -470
  159. package/dist/{chat/questions.d.ts → questions.d.ts} +5 -2
  160. package/dist/questions.d.ts.map +1 -0
  161. package/dist/{chat/questions.js → questions.js} +13 -2
  162. package/dist/reach.d.ts +20 -9
  163. package/dist/reach.d.ts.map +1 -1
  164. package/dist/reach.js +287 -99
  165. package/dist/react.d.ts +11 -4
  166. package/dist/react.d.ts.map +1 -1
  167. package/dist/react.js +2 -14
  168. package/dist/{chat/read.d.ts → read.d.ts} +2 -0
  169. package/dist/read.d.ts.map +1 -0
  170. package/dist/{chat/read.js → read.js} +4 -0
  171. package/dist/readiness.d.ts +13 -17
  172. package/dist/readiness.d.ts.map +1 -1
  173. package/dist/readiness.js +2 -8
  174. package/dist/reads.d.ts +36 -10
  175. package/dist/reads.d.ts.map +1 -1
  176. package/dist/reads.js +157 -84
  177. package/dist/recovery.d.ts +3 -2
  178. package/dist/recovery.d.ts.map +1 -1
  179. package/dist/recovery.js +43 -8
  180. package/dist/refresh.d.ts +18 -15
  181. package/dist/refresh.d.ts.map +1 -1
  182. package/dist/refresh.js +143 -58
  183. package/dist/refusal.d.ts +5 -0
  184. package/dist/refusal.d.ts.map +1 -0
  185. package/dist/refusal.js +55 -0
  186. package/dist/replica.d.ts +56 -33
  187. package/dist/replica.d.ts.map +1 -1
  188. package/dist/replica.js +332 -190
  189. package/dist/room-draft.d.ts +25 -8
  190. package/dist/room-draft.d.ts.map +1 -1
  191. package/dist/room-draft.js +161 -74
  192. package/dist/room-drafts.d.ts +24 -0
  193. package/dist/room-drafts.d.ts.map +1 -0
  194. package/dist/room-drafts.js +217 -0
  195. package/dist/rows.d.ts +31 -30
  196. package/dist/rows.d.ts.map +1 -1
  197. package/dist/rows.js +59 -118
  198. package/dist/runtime.d.ts +43 -0
  199. package/dist/runtime.d.ts.map +1 -0
  200. package/dist/runtime.js +1 -0
  201. package/dist/save.d.ts +57 -14
  202. package/dist/save.d.ts.map +1 -1
  203. package/dist/save.js +537 -192
  204. package/dist/saves.d.ts +70 -0
  205. package/dist/saves.d.ts.map +1 -0
  206. package/dist/saves.js +1005 -0
  207. package/dist/search.d.ts +12 -82
  208. package/dist/search.d.ts.map +1 -1
  209. package/dist/search.js +10 -207
  210. package/dist/seed.d.ts +11 -2
  211. package/dist/seed.d.ts.map +1 -1
  212. package/dist/seed.js +89 -5
  213. package/dist/shared.d.ts +6 -7
  214. package/dist/shared.d.ts.map +1 -1
  215. package/dist/shared.js +75 -104
  216. package/dist/shown.d.ts +31 -0
  217. package/dist/shown.d.ts.map +1 -0
  218. package/dist/shown.js +292 -0
  219. package/dist/socket/admission.d.ts +7 -0
  220. package/dist/socket/admission.d.ts.map +1 -0
  221. package/dist/socket/admission.js +39 -0
  222. package/dist/socket/answers.d.ts +6 -0
  223. package/dist/socket/answers.d.ts.map +1 -0
  224. package/dist/socket/answers.js +20 -0
  225. package/dist/socket/lanes.d.ts +20 -0
  226. package/dist/socket/lanes.d.ts.map +1 -0
  227. package/dist/socket/lanes.js +77 -0
  228. package/dist/socket/reads.d.ts +10 -0
  229. package/dist/socket/reads.d.ts.map +1 -0
  230. package/dist/socket/reads.js +31 -0
  231. package/dist/socket/rooms.d.ts +44 -0
  232. package/dist/socket/rooms.d.ts.map +1 -0
  233. package/dist/socket/rooms.js +90 -0
  234. package/dist/socket/session.d.ts +38 -0
  235. package/dist/socket/session.d.ts.map +1 -0
  236. package/dist/socket/session.js +23 -0
  237. package/dist/socket.d.ts +70 -0
  238. package/dist/socket.d.ts.map +1 -0
  239. package/dist/socket.js +175 -0
  240. package/dist/source.d.ts +61 -0
  241. package/dist/source.d.ts.map +1 -0
  242. package/dist/source.js +82 -0
  243. package/dist/status.d.ts +4 -5
  244. package/dist/status.d.ts.map +1 -1
  245. package/dist/status.js +3 -7
  246. package/dist/store/fences.d.ts +16 -0
  247. package/dist/store/fences.d.ts.map +1 -0
  248. package/dist/store/fences.js +26 -0
  249. package/dist/store/installs.d.ts +30 -0
  250. package/dist/store/installs.d.ts.map +1 -0
  251. package/dist/store/installs.js +34 -0
  252. package/dist/store/readiness.d.ts +10 -0
  253. package/dist/store/readiness.d.ts.map +1 -0
  254. package/dist/store/readiness.js +52 -0
  255. package/dist/store/records.d.ts +212 -0
  256. package/dist/store/records.d.ts.map +1 -0
  257. package/dist/store/records.js +431 -0
  258. package/dist/store/room-held.d.ts +8 -0
  259. package/dist/store/room-held.d.ts.map +1 -0
  260. package/dist/store/room-held.js +17 -0
  261. package/dist/store/space.d.ts +11 -0
  262. package/dist/store/space.d.ts.map +1 -0
  263. package/dist/store/space.js +41 -0
  264. package/dist/store/watchers.d.ts +17 -0
  265. package/dist/store/watchers.d.ts.map +1 -0
  266. package/dist/store/watchers.js +56 -0
  267. package/dist/store/writes.d.ts +24 -0
  268. package/dist/store/writes.d.ts.map +1 -0
  269. package/dist/store/writes.js +125 -0
  270. package/dist/store.d.ts +106 -158
  271. package/dist/store.d.ts.map +1 -1
  272. package/dist/store.js +136 -734
  273. package/dist/stored.d.ts +156 -0
  274. package/dist/stored.d.ts.map +1 -0
  275. package/dist/stored.js +649 -0
  276. package/dist/surface.d.ts +88 -0
  277. package/dist/surface.d.ts.map +1 -0
  278. package/dist/surface.js +223 -0
  279. package/dist/sync.d.ts +19 -0
  280. package/dist/sync.d.ts.map +1 -0
  281. package/dist/sync.js +532 -0
  282. package/dist/tables.d.ts +416 -0
  283. package/dist/tables.d.ts.map +1 -0
  284. package/dist/tables.js +740 -0
  285. package/dist/tallies.d.ts +18 -0
  286. package/dist/tallies.d.ts.map +1 -0
  287. package/dist/tallies.js +129 -0
  288. package/dist/transfer.d.ts +0 -5
  289. package/dist/transfer.d.ts.map +1 -1
  290. package/dist/turn.d.ts +5 -3
  291. package/dist/turn.d.ts.map +1 -1
  292. package/dist/turn.js +51 -21
  293. package/dist/validation.d.ts +29 -0
  294. package/dist/validation.d.ts.map +1 -0
  295. package/dist/validation.js +104 -0
  296. package/dist/windows.d.ts +11 -0
  297. package/dist/windows.d.ts.map +1 -0
  298. package/dist/windows.js +16 -0
  299. package/dist/wire.d.ts +50 -6
  300. package/dist/wire.d.ts.map +1 -1
  301. package/dist/wire.js +16 -2
  302. package/package.json +18 -19
  303. package/AGENTS.md +0 -299
  304. package/dist/acquisition.d.ts +0 -14
  305. package/dist/acquisition.d.ts.map +0 -1
  306. package/dist/acquisition.js +0 -24
  307. package/dist/chat/acts.d.ts +0 -33
  308. package/dist/chat/acts.d.ts.map +0 -1
  309. package/dist/chat/acts.js +0 -36
  310. package/dist/chat/context.d.ts +0 -3
  311. package/dist/chat/context.d.ts.map +0 -1
  312. package/dist/chat/input.d.ts +0 -9
  313. package/dist/chat/input.d.ts.map +0 -1
  314. package/dist/chat/input.js +0 -99
  315. package/dist/chat/kit.d.ts +0 -61
  316. package/dist/chat/kit.d.ts.map +0 -1
  317. package/dist/chat/kit.js +0 -123
  318. package/dist/chat/lasting.d.ts +0 -6
  319. package/dist/chat/lasting.d.ts.map +0 -1
  320. package/dist/chat/lasting.js +0 -19
  321. package/dist/chat/list.d.ts +0 -27
  322. package/dist/chat/list.d.ts.map +0 -1
  323. package/dist/chat/list.js +0 -63
  324. package/dist/chat/markdown.d.ts +0 -20
  325. package/dist/chat/markdown.d.ts.map +0 -1
  326. package/dist/chat/markdown.js +0 -188
  327. package/dist/chat/message.d.ts +0 -6
  328. package/dist/chat/message.d.ts.map +0 -1
  329. package/dist/chat/message.js +0 -192
  330. package/dist/chat/messages.d.ts +0 -22
  331. package/dist/chat/messages.d.ts.map +0 -1
  332. package/dist/chat/messages.js +0 -155
  333. package/dist/chat/name.d.ts +0 -6
  334. package/dist/chat/name.d.ts.map +0 -1
  335. package/dist/chat/name.js +0 -37
  336. package/dist/chat/opened.d.ts +0 -11
  337. package/dist/chat/opened.d.ts.map +0 -1
  338. package/dist/chat/opened.js +0 -17
  339. package/dist/chat/overlay.d.ts +0 -31
  340. package/dist/chat/overlay.d.ts.map +0 -1
  341. package/dist/chat/overlay.js +0 -97
  342. package/dist/chat/parts.d.ts.map +0 -1
  343. package/dist/chat/process.d.ts +0 -20
  344. package/dist/chat/process.d.ts.map +0 -1
  345. package/dist/chat/process.js +0 -32
  346. package/dist/chat/provider.d.ts +0 -63
  347. package/dist/chat/provider.d.ts.map +0 -1
  348. package/dist/chat/provider.js +0 -629
  349. package/dist/chat/question.d.ts +0 -9
  350. package/dist/chat/question.d.ts.map +0 -1
  351. package/dist/chat/question.js +0 -80
  352. package/dist/chat/questions.d.ts.map +0 -1
  353. package/dist/chat/read.d.ts.map +0 -1
  354. package/dist/chat/ready.d.ts +0 -34
  355. package/dist/chat/ready.d.ts.map +0 -1
  356. package/dist/chat/ready.js +0 -91
  357. package/dist/chat/style.d.ts +0 -4
  358. package/dist/chat/style.d.ts.map +0 -1
  359. package/dist/chat/style.js +0 -302
  360. package/dist/chat.d.ts +0 -14
  361. package/dist/chat.d.ts.map +0 -1
  362. package/dist/chat.js +0 -8
  363. package/dist/fence.d.ts +0 -6
  364. package/dist/fence.d.ts.map +0 -1
  365. package/dist/fence.js +0 -10
  366. package/dist/read-side.d.ts +0 -25
  367. package/dist/read-side.d.ts.map +0 -1
  368. package/dist/read-side.js +0 -642
package/dist/save.js CHANGED
@@ -1,10 +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';
6
+ import { overlaying } from './overlay.js';
7
+ import { ancestors, authoredAt, covered, normalize } from './paths.js';
8
+ import { entriesIn, sorting } from './shared.js';
9
+ import { paced, ROW_WEIGHT, stepped } from './turn.js';
10
+ import { validatedCollection } from './validation.js';
8
11
  function refuse(code, file, location) {
9
12
  return {
10
13
  kind: 'refused',
@@ -17,10 +20,7 @@ function refuse(code, file, location) {
17
20
  };
18
21
  }
19
22
  function repointed(document, definition, from, to, moves, settled) {
20
- const parsed = parse(document.content, from);
21
- if (!parsed.ok)
22
- return [];
23
- let next = parsed.document;
23
+ const changes = [];
24
24
  const names = new Set();
25
25
  for (const { pointer, value } of definition === undefined
26
26
  ? []
@@ -32,38 +32,39 @@ function repointed(document, definition, from, to, moves, settled) {
32
32
  const destination = moves.get(named) ?? named;
33
33
  if (target(to, value) === destination)
34
34
  continue;
35
- const applied = apply(next, {
35
+ changes.push({
36
36
  kind: 'reference',
37
37
  pointer,
38
38
  value: retarget(value, relative(to, destination)),
39
39
  prior: value,
40
40
  });
41
- if (applied.outcome !== 'applied')
42
- continue;
43
- next = applied.document;
44
41
  names.add(name);
45
42
  }
43
+ if (changes.length === 0)
44
+ return [];
45
+ const applied = apply(document, changes);
46
+ if (applied.outcome !== 'applied')
47
+ return [];
48
+ const next = applied.document;
46
49
  return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
47
50
  }
48
- function edited(document, path, changes) {
49
- let next = document;
51
+ function edited(document, changes) {
52
+ const parts = new Map();
50
53
  for (const change of changes) {
51
- if (change.path !== path)
52
- continue;
53
- if (change.kind !== 'section' && change.kind !== 'preamble')
54
- continue;
55
- const applied = apply(next, change.kind === 'preamble'
56
- ? { kind: 'preamble', value: change.value, prior: next.preamble }
57
- : {
54
+ if (change.kind === 'preamble')
55
+ parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
56
+ if (change.kind === 'section')
57
+ parts.set(`section ${change.name}`, {
58
58
  kind: 'section',
59
59
  name: change.name,
60
60
  value: change.value,
61
- prior: next.sections[change.name],
61
+ prior: document.sections[change.name] ?? '',
62
62
  });
63
- if (applied.outcome === 'applied')
64
- next = applied.document;
65
63
  }
66
- return next;
64
+ if (parts.size === 0)
65
+ return document;
66
+ const applied = apply(document, [...parts.values()]);
67
+ return applied.outcome === 'applied' ? applied.document : document;
67
68
  }
68
69
  function bodies(path, before, after) {
69
70
  const changes = [];
@@ -78,7 +79,7 @@ function bodies(path, before, after) {
78
79
  return changes;
79
80
  }
80
81
  function relinks(path, moved, before, moves) {
81
- let after = before;
82
+ const changes = [];
82
83
  for (const link of links(before, path)) {
83
84
  const destination = moves.get(link.target) ?? link.target;
84
85
  if (destination === link.target && moved === path)
@@ -86,20 +87,23 @@ function relinks(path, moved, before, moves) {
86
87
  const to = relative(moved, destination);
87
88
  if (to === relative(path, link.target))
88
89
  continue;
89
- const applied = apply(after, { kind: 'link', href: link.href, to });
90
- if (applied.outcome === 'refuse') {
91
- return {
92
- kind: 'refused',
93
- refusal: {
94
- code: applied.refusal.rule,
95
- retryable: false,
96
- file: path,
97
- ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
98
- },
99
- };
100
- }
101
- after = applied.document;
90
+ changes.push({ kind: 'link', href: link.href, to });
91
+ }
92
+ if (changes.length === 0)
93
+ return { kind: 'changes', changes: [] };
94
+ const applied = apply(before, changes);
95
+ if (applied.outcome === 'refuse') {
96
+ return {
97
+ kind: 'refused',
98
+ refusal: {
99
+ code: applied.refusal.rule,
100
+ retryable: false,
101
+ file: path,
102
+ ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
103
+ },
104
+ };
102
105
  }
106
+ const after = applied.document;
103
107
  if (after === before)
104
108
  return { kind: 'changes', changes: [] };
105
109
  if (before.type === undefined) {
@@ -107,19 +111,14 @@ function relinks(path, moved, before, moves) {
107
111
  }
108
112
  return { kind: 'changes', changes: bodies(path, before, after) };
109
113
  }
110
- function subsumed(merged, path) {
111
- for (let index = merged.length - 1; index >= 0; index -= 1) {
112
- const item = merged[index];
113
- if (item.path !== path)
114
- continue;
115
- if (item.kind === 'section' || item.kind === 'preamble')
116
- merged.splice(index, 1);
117
- }
114
+ function partKey(change) {
115
+ return change.kind === 'section' ? `section ${change.name}` : change.kind;
118
116
  }
119
- function rewrites(changes, visible) {
117
+ function* rewrites(changes, visible) {
120
118
  const moves = new Map();
121
119
  const settled = new Set();
122
- for (const change of changes) {
120
+ const parts = new Map();
121
+ for (const [index, change] of changes.entries()) {
123
122
  if (change.kind === 'move')
124
123
  moves.set(change.path, change.to);
125
124
  if (change.kind === 'remove' ||
@@ -130,54 +129,143 @@ function rewrites(changes, visible) {
130
129
  }
131
130
  if (change.kind === 'field')
132
131
  settled.add(`${change.path} ${change.name}`);
132
+ if (isPart(change)) {
133
+ const held = parts.get(change.path);
134
+ if (held === undefined)
135
+ parts.set(change.path, [index]);
136
+ else
137
+ held.push(index);
138
+ }
133
139
  }
134
140
  if (moves.size === 0)
135
141
  return { kind: 'changes', changes };
136
142
  const merged = [...changes];
137
143
  const added = [];
138
- for (const [path, document] of documentsOf(visible.collection)) {
144
+ for (const [path, document] of yield* documentsOf(visible.collection)) {
139
145
  if (settled.has(path))
140
146
  continue;
141
147
  const moved = moves.get(path) ?? path;
142
148
  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);
149
+ const repointing = repointed(document, definition, path, moved, moves, settled);
150
+ added.push(...repointing);
151
+ const own = parts.get(path) ?? [];
152
+ const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
145
153
  if (relinked.kind === 'refused')
146
154
  return relinked;
155
+ const keyed = new Map();
156
+ for (const index of own) {
157
+ const key = partKey(merged[index]);
158
+ if (!keyed.has(key))
159
+ keyed.set(key, index);
160
+ }
147
161
  for (const change of relinked.changes) {
148
162
  if (change.kind === 'replace') {
149
- subsumed(merged, path);
163
+ for (const index of own) {
164
+ const kind = merged[index]?.kind;
165
+ if (kind === 'section' || kind === 'preamble')
166
+ merged[index] = null;
167
+ }
150
168
  added.push(change);
151
169
  continue;
152
170
  }
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)
171
+ const index = keyed.get(partKey(change));
172
+ if (index === undefined)
157
173
  added.push(change);
158
174
  else
159
175
  merged[index] = change;
160
176
  }
177
+ const text = visible.collection.text[path].length;
178
+ const rewritten = repointing.length > 0 || relinked.changes.length > 0;
179
+ yield ROW_WEIGHT + text + (rewritten ? text : 0);
161
180
  }
162
- return { kind: 'changes', changes: [...added, ...merged] };
181
+ return {
182
+ kind: 'changes',
183
+ changes: [...added, ...merged.filter((change) => change !== null)],
184
+ };
163
185
  }
164
- function anticipated(changes, preconditions, rewritten, visible) {
165
- const derived = rewritten.filter((change) => !changes.includes(change));
186
+ function* anticipated(changes, preconditions, rewritten, visible) {
187
+ const given = new Set(changes);
188
+ const derived = rewritten.filter((change) => !given.has(change));
166
189
  return {
167
190
  changes: rewritten,
168
- preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
191
+ preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
169
192
  };
170
193
  }
171
- export function providerRewritten(batch, visible) {
172
- const rewritten = rewrites(batch.changes, visible);
194
+ export function* providerRewritten(batch, visible) {
195
+ const rewritten = yield* rewrites(batch.changes, visible);
173
196
  if (rewritten.kind === 'refused')
174
197
  return batch;
175
198
  return {
176
199
  ...batch,
177
- ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
200
+ ...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
201
+ };
202
+ }
203
+ function crossed(document, definition, path, to) {
204
+ const changes = [];
205
+ for (const link of links(document, path)) {
206
+ const written = to(link.target, link.href);
207
+ if (written !== undefined)
208
+ changes.push({ kind: 'link', href: link.href, to: written });
209
+ }
210
+ for (const { pointer, value } of definition === undefined
211
+ ? []
212
+ : references(document, definition)) {
213
+ const named = target(path, value);
214
+ const written = named === null ? undefined : to(named, value);
215
+ if (written !== undefined) {
216
+ changes.push({ kind: 'reference', pointer, value: retarget(value, written), prior: value });
217
+ }
218
+ }
219
+ return changes;
220
+ }
221
+ function rewritten(path, document, changes) {
222
+ if (changes.length === 0)
223
+ return [];
224
+ const applied = apply(document, changes);
225
+ if (applied.outcome !== 'applied')
226
+ return [];
227
+ const after = applied.document;
228
+ if (document.type === undefined)
229
+ return [{ kind: 'replace', path, content: serialize(after) }];
230
+ const names = new Set(changes.flatMap((change) => change.kind === 'reference' ? [pointerSegments(change.pointer)[0] ?? ''] : []));
231
+ return [
232
+ ...[...names].map((name) => ({ kind: 'field', path, name, value: after.fields[name] })),
233
+ ...bodies(path, document, after),
234
+ ];
235
+ }
236
+ function roomPathOf(handle, path) {
237
+ return `/rooms/${handle}/${path}`;
238
+ }
239
+ export function* leavingRewritten(batch, visible) {
240
+ const between = batch.between;
241
+ if (between === undefined)
242
+ return batch;
243
+ const moved = new Map(between.moves.map(({ path, to }) => [path, roomPathOf(between.destination.handle, to)]));
244
+ const derived = [];
245
+ for (const [path, document] of yield* documentsOf(visible.collection)) {
246
+ if (moved.has(path))
247
+ continue;
248
+ const definition = definitionOf(governing(visible.collection.composition, path), document.type);
249
+ derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named))));
250
+ yield ROW_WEIGHT + visible.collection.text[path].length;
251
+ }
252
+ if (derived.length === 0)
253
+ return batch;
254
+ return {
255
+ ...batch,
256
+ changes: [...derived, ...batch.changes],
257
+ preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...batch.preconditions],
178
258
  };
179
259
  }
180
- function incoming(collection, removed, candidates) {
260
+ export function rewriting(batch, locator) {
261
+ return leaving(batch, locator) || batch.changes.some((change) => change.kind === 'move');
262
+ }
263
+ export function* effectiveIn(batch, locator, visible) {
264
+ return leaving(batch, locator)
265
+ ? yield* leavingRewritten(batch, visible)
266
+ : yield* providerRewritten(batch, visible);
267
+ }
268
+ function* incoming(collection, removed, candidates) {
181
269
  const found = new Map();
182
270
  const note = (target, file) => {
183
271
  if (!removed.has(target))
@@ -186,13 +274,20 @@ function incoming(collection, removed, candidates) {
186
274
  referrers.add(file);
187
275
  found.set(target, referrers);
188
276
  };
189
- const read = candidates === null
190
- ? documentsOf(collection)
191
- : [...new Set(candidates)].sort().flatMap((path) => {
277
+ const read = function* (paths) {
278
+ const seen = new Set();
279
+ for (const path of paths) {
280
+ if (seen.has(path))
281
+ continue;
282
+ seen.add(path);
192
283
  const document = collection.document(path);
193
- return document === undefined ? [] : [[path, document]];
194
- });
195
- for (const [path, document] of read) {
284
+ if (document !== undefined)
285
+ yield [path, document];
286
+ }
287
+ };
288
+ for (const [path, document] of candidates === null
289
+ ? yield* documentsOf(collection)
290
+ : read(candidates)) {
196
291
  const definition = definitionOf(governing(collection.composition, path), document.type);
197
292
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
198
293
  if (to.includes(ATTACHMENT))
@@ -201,8 +296,9 @@ function incoming(collection, removed, candidates) {
201
296
  if (named !== null)
202
297
  note(named, path);
203
298
  }
299
+ yield ROW_WEIGHT + collection.text[path].length;
204
300
  }
205
- const [first] = [...found.keys()].sort();
301
+ const [first] = yield* sorting(found.keys());
206
302
  if (first === undefined)
207
303
  return null;
208
304
  const every = new Set();
@@ -215,14 +311,16 @@ function incoming(collection, removed, candidates) {
215
311
  code: 'referenced-missing-file',
216
312
  retryable: false,
217
313
  file: first,
218
- referrers: [...every].sort(),
314
+ referrers: yield* sorting(every),
219
315
  },
220
316
  };
221
317
  }
222
318
  function affected(change) {
223
319
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
224
320
  }
225
- function guard(changes, input) {
321
+ const WRITE = ['write'];
322
+ const MOVE_OUT = ['write', 'erase'];
323
+ function guard(changes, input, removing) {
226
324
  const overridden = new Map();
227
325
  const existing = {
228
326
  has: (path) => overridden.get(path) ??
@@ -230,21 +328,26 @@ function guard(changes, input) {
230
328
  add: (path) => void overridden.set(path, true),
231
329
  delete: (path) => void overridden.set(path, false),
232
330
  };
331
+ const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
233
332
  for (const [index, change] of changes.entries()) {
234
333
  for (const path of affected(change)) {
235
334
  const normalized = normalize(path);
236
335
  if (normalized === null || normalized === '')
237
336
  return refuse('room-root', path);
238
- if (covered(input.providerOwnedPaths, normalized)) {
337
+ if (covered(input.providerOwnedPaths, normalized) ||
338
+ (input.authoredPaths !== undefined && !authoredAt(input.authoredPaths, normalized))) {
239
339
  return refuse('provider-owned-path', normalized);
240
340
  }
241
- const operation = change.kind === 'remove' ? 'delete' : 'write';
242
- if (!input.local && !covered(input.operations[operation], normalized))
243
- return refuse(`${operation}-grant`, normalized);
341
+ if (!input.pathRule(normalized))
342
+ return refuse('invalid-path', 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);
244
346
  }
245
- if (change.kind === 'create') {
246
- if (existing.has(change.path))
247
- 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);
248
351
  existing.add(change.path);
249
352
  continue;
250
353
  }
@@ -254,6 +357,9 @@ function guard(changes, input) {
254
357
  input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
255
358
  return refuse('path-collision', change.path);
256
359
  }
360
+ const taken = occupied(change.path);
361
+ if (taken !== null)
362
+ return refuse('path-collision', taken);
257
363
  if (!input.local &&
258
364
  existing.has(change.path) &&
259
365
  input.visible.revisions[departure(changes, index)] === undefined) {
@@ -265,8 +371,9 @@ function guard(changes, input) {
265
371
  if (change.kind === 'move') {
266
372
  if (!existing.has(change.path))
267
373
  return refuse('path-absent', change.path);
268
- if (existing.has(change.to))
269
- 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);
270
377
  if (unrevised(input, change.path)) {
271
378
  return refuse('unknown-revision', change.path);
272
379
  }
@@ -284,7 +391,7 @@ function guard(changes, input) {
284
391
  continue;
285
392
  }
286
393
  if (change.kind === 'replace') {
287
- if (!input.local && input.visible.revisions[change.path] === undefined) {
394
+ if (unrevised(input, change.path)) {
288
395
  return refuse('unknown-revision', change.path);
289
396
  }
290
397
  continue;
@@ -298,6 +405,22 @@ function guard(changes, input) {
298
405
  function unrevised(input, path) {
299
406
  return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
300
407
  }
408
+ export function queuedDocuments(queue) {
409
+ const paths = new Set();
410
+ for (const batch of queue) {
411
+ for (const change of batch.changes) {
412
+ if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
413
+ continue;
414
+ if (change.kind === 'remove')
415
+ paths.delete(change.path);
416
+ else if (change.kind !== 'move')
417
+ paths.add(change.path);
418
+ else if (paths.delete(change.path))
419
+ paths.add(change.to);
420
+ }
421
+ }
422
+ return paths;
423
+ }
301
424
  function identity(precondition) {
302
425
  const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
303
426
  return `${precondition.kind} ${precondition.path} ${part}`;
@@ -315,7 +438,7 @@ function sameTarget(precondition, change) {
315
438
  return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
316
439
  ((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
317
440
  }
318
- function preconditionsOf(changes, visible, bases) {
441
+ function* preconditionsOf(changes, visible, bases) {
319
442
  const preconditions = [];
320
443
  const seen = new Set();
321
444
  const add = (precondition) => {
@@ -326,6 +449,7 @@ function preconditionsOf(changes, visible, bases) {
326
449
  preconditions.push(precondition);
327
450
  };
328
451
  for (const change of changes) {
452
+ yield ROW_WEIGHT;
329
453
  if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
330
454
  const based = bases.find((base) => sameTarget(base, change));
331
455
  if (based !== undefined) {
@@ -373,10 +497,13 @@ function preconditionsOf(changes, visible, bases) {
373
497
  function depth(path) {
374
498
  return path.split('/').length;
375
499
  }
376
- function exceeded(limits, changes, effective, resulting) {
500
+ function* exceeding(limits, changes, effective, resulting) {
377
501
  const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
378
502
  const created = changes
379
- .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')
380
507
  .map((change) => (change.kind === 'move' ? change.to : change.path));
381
508
  for (const limit of limits) {
382
509
  const value = limit.value;
@@ -390,9 +517,13 @@ function exceeded(limits, changes, effective, resulting) {
390
517
  return refuse('provider-limit', undefined, limit.name);
391
518
  }
392
519
  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);
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
+ }
396
527
  }
397
528
  if (limit.name === 'path-length') {
398
529
  const long = created.find((path) => path.length > value);
@@ -422,7 +553,7 @@ function relativeField(path, name, value, handle, definition) {
422
553
  !opened.ok ||
423
554
  !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
424
555
  return value;
425
- const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
556
+ const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
426
557
  if (held.outcome !== 'applied')
427
558
  return value;
428
559
  const result = relativize(held.document, path, handle, definition);
@@ -458,17 +589,6 @@ function relativeChanges(input) {
458
589
  return { ...change, fields, sections };
459
590
  });
460
591
  }
461
- function unmodeledType(collection, composition) {
462
- if (available(composition))
463
- return false;
464
- for (const [path, document] of documentsOf(collection)) {
465
- if (document.type !== undefined &&
466
- document.type !== 'any' &&
467
- governing(composition, path).status === 'unavailable')
468
- return true;
469
- }
470
- return false;
471
- }
472
592
  function unqueueable(file) {
473
593
  return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
474
594
  }
@@ -478,29 +598,44 @@ function isPart(change) {
478
598
  function touched(change) {
479
599
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
480
600
  }
481
- function partApplied(content, path, change) {
601
+ function partsApplied(content, path, changes) {
482
602
  const parsed = parse(content, path);
483
603
  if (!parsed.ok)
484
604
  return null;
485
605
  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
- ? {
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}`, {
495
617
  kind: 'section',
496
618
  name: change.name,
497
619
  value: change.value,
498
- prior: document.sections[change.name],
499
- }
500
- : { 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()]);
501
626
  return applied.outcome === 'applied' ? serialize(applied.document) : null;
502
627
  }
503
- 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) {
504
639
  const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
505
640
  const whole = held === undefined ? -1 : changes.indexOf(held);
506
641
  const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
@@ -521,10 +656,11 @@ function folded(changes, change, appending) {
521
656
  return 'appended';
522
657
  }
523
658
  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 };
659
+ const parts = deferred.get(held);
660
+ if (parts === undefined)
661
+ deferred.set(held, [change]);
662
+ else
663
+ parts.push(change);
528
664
  return 'folded';
529
665
  }
530
666
  if (change.kind === 'preamble')
@@ -555,17 +691,21 @@ function folded(changes, change, appending) {
555
691
  changes.push(change);
556
692
  return 'appended';
557
693
  }
558
- function relinked(batch, move, before, after, appending) {
559
- const derived = rewrites([move], after);
694
+ function* relinked(batch, move, before, after, appending) {
695
+ const derived = yield* rewrites([move], after);
560
696
  if (derived.kind === 'refused')
561
697
  return derived;
562
698
  const changes = [...batch.changes];
563
699
  const appended = [];
564
700
  let changed = false;
701
+ const deferred = new Map();
565
702
  for (const change of derived.changes) {
566
703
  if (change === move)
567
704
  continue;
568
- 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);
569
709
  if (outcome === 'refused')
570
710
  return unqueueable(change.path);
571
711
  if (outcome === 'appended')
@@ -573,9 +713,13 @@ function relinked(batch, move, before, after, appending) {
573
713
  if (outcome !== 'skipped')
574
714
  changed = true;
575
715
  }
716
+ const unsettled = settledParts(changes, deferred);
717
+ if (unsettled !== null)
718
+ return unqueueable(unsettled);
576
719
  if (!changed)
577
720
  return { kind: 'relinked', batch };
578
721
  const held = new Set(batch.preconditions.map(identity));
722
+ const added = yield* preconditionsOf(appended, before, []);
579
723
  return {
580
724
  kind: 'relinked',
581
725
  batch: immutable({
@@ -584,7 +728,7 @@ function relinked(batch, move, before, after, appending) {
584
728
  changes,
585
729
  preconditions: [
586
730
  ...batch.preconditions,
587
- ...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
731
+ ...added.filter((precondition) => !held.has(identity(precondition))),
588
732
  ],
589
733
  }),
590
734
  };
@@ -630,7 +774,7 @@ function withdrawnFile(batch, change, at) {
630
774
  ];
631
775
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
632
776
  }
633
- export function requeued(queue, changes, visible, changeable, state) {
777
+ export function* requeuing(queue, changes, visible, changeable, state) {
634
778
  const next = [...queue];
635
779
  const origins = new Map();
636
780
  const withdrawn = new Map();
@@ -653,19 +797,19 @@ export function requeued(queue, changes, visible, changeable, state) {
653
797
  return unqueueable(change.path);
654
798
  const following = next.slice(index);
655
799
  const rebuilt = [];
656
- let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
800
+ let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
657
801
  for (const [offset, batch] of following.entries()) {
658
802
  let current = batch;
659
803
  if (change.kind === 'move') {
660
- const after = state(next.slice(0, index + offset + 1));
804
+ const after = yield* state(next.slice(0, index + offset + 1));
661
805
  const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
662
- const relinking = relinked(batch, change, before, after, appending);
806
+ const relinking = yield* relinked(batch, change, before, after, appending);
663
807
  if (relinking.kind === 'refused')
664
808
  return relinking;
665
809
  current = relinking.batch;
666
810
  before = after;
667
811
  }
668
- if (current !== batch && !open(batch))
812
+ if (current !== batch && (!open(batch) || batch.between !== undefined))
669
813
  return unqueueable(change.path);
670
814
  rebuilt.push(offset === 0
671
815
  ? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
@@ -706,10 +850,17 @@ export function joined(queue, owner, extra) {
706
850
  if (clash !== undefined)
707
851
  return unqueueable(clash);
708
852
  const changes = [...owner.changes];
853
+ const deferred = new Map();
709
854
  for (const change of extra.changes) {
710
- 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')
711
859
  return unqueueable(change.path);
712
860
  }
861
+ const unsettled = settledParts(changes, deferred);
862
+ if (unsettled !== null)
863
+ return unqueueable(unsettled);
713
864
  const held = new Set(owner.preconditions.map(covering));
714
865
  const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
715
866
  const preconditions = [
@@ -728,62 +879,66 @@ export function joined(queue, owner, extra) {
728
879
  });
729
880
  return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
730
881
  }
731
- export function limitedIn(queue, batch, limits, state) {
882
+ export function* limiting(queue, batch, limits, state) {
732
883
  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;
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)));
886
+ return limited?.refusal ?? null;
735
887
  }
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));
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(Array.from(entriesIn(resulting.binaries), ([path]) => path));
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) {
761
922
  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)]);
923
+ const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
764
924
  if (referenced !== null)
765
925
  return referenced;
766
926
  }
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');
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');
777
937
  if (withoutModel !== undefined)
778
938
  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);
939
+ const verdict = typedPaths.length > 0
940
+ ? await validate(resulting, touched)
941
+ : await touchedLibrary(resulting, touched, composition);
787
942
  if (verdict.verdict === 'refuse') {
788
943
  return {
789
944
  kind: 'refused',
@@ -796,12 +951,92 @@ export function buildBatch(input) {
796
951
  },
797
952
  };
798
953
  }
799
- 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));
800
1038
  if (limited !== null)
801
1039
  return limited;
802
- const validated = typedPaths
803
- .map((path) => governing(input.composition, path))
804
- .find((model) => model.status !== 'unavailable');
805
1040
  return {
806
1041
  kind: 'batch',
807
1042
  batch: immutable({
@@ -810,15 +1045,125 @@ export function buildBatch(input) {
810
1045
  changes,
811
1046
  preconditions,
812
1047
  ...(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
- }),
1048
+ ...validationOf(judged.validated),
822
1049
  }),
823
1050
  };
824
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, pathRule } = input;
1104
+ const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1105
+ const leaves = guard(removals, { ...source, local: input.local, bases: [], pathRule }, MOVE_OUT);
1106
+ if (leaves !== null)
1107
+ return leaves;
1108
+ const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
1109
+ if (uploading !== undefined)
1110
+ return refuse('unknown-revision', uploading.path);
1111
+ const moved = new Map(moves.map(({ path, to }) => [path, to]));
1112
+ const arrivals = moves.map((move) => arrivalOf(move, source, moved));
1113
+ const arrives = guard(arrivals, { ...destination, local: input.local, bases: [], pathRule }, WRITE);
1114
+ if (arrives !== null)
1115
+ return arrives;
1116
+ const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
1117
+ if (typeof bound === 'number' && moves.length > bound) {
1118
+ return refuse('provider-limit', undefined, MOVE_DOCUMENTS);
1119
+ }
1120
+ if (source.unmodeled)
1121
+ return refuse('model-unavailable', moves[0].path);
1122
+ const id = changeBatchIdentity();
1123
+ const between = {
1124
+ source: { room: source.room, baseRoomVersion: source.baseRoomVersion },
1125
+ destination: {
1126
+ room: destination.room,
1127
+ baseRoomVersion: destination.baseRoomVersion,
1128
+ handle: destination.handle,
1129
+ },
1130
+ moves,
1131
+ };
1132
+ const written = input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy };
1133
+ const leavingBatch = {
1134
+ id,
1135
+ baseRoomVersion: source.baseRoomVersion,
1136
+ changes: removals,
1137
+ preconditions: moves.map(({ path }) => existence(source.visible, path)),
1138
+ ...written,
1139
+ between,
1140
+ };
1141
+ const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1142
+ const left = await stepped(overlaying(source.visible, [effective]));
1143
+ 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))]);
1144
+ if (sourceJudged.kind === 'refused')
1145
+ return sourceJudged;
1146
+ const arrivingBatch = {
1147
+ id,
1148
+ baseRoomVersion: destination.baseRoomVersion,
1149
+ changes: arrivals,
1150
+ preconditions: moves.map(({ to }) => ({ kind: 'absent', path: to })),
1151
+ ...written,
1152
+ between,
1153
+ };
1154
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1155
+ const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1156
+ if (destinationJudged.kind === 'refused')
1157
+ return destinationJudged;
1158
+ const limited = (await stepped(exceeding(input.providerLimits, removals, effective.changes, left))) ??
1159
+ (await stepped(exceeding(input.providerLimits, arrivals, arrivals, arrived)));
1160
+ if (limited !== null)
1161
+ return limited;
1162
+ return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1163
+ }
1164
+ export function buildMove(input) {
1165
+ return moving(input);
1166
+ }
1167
+ export function buildBatch(input) {
1168
+ return batching(input);
1169
+ }