@agentshouse/mdruntime 0.1.0-cd3ee807e46ae → 0.1.0-cd507e16e5fc0

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