@agentshouse/mdruntime 0.1.0-c698c0ebd65bd → 0.1.0-c6b1e70f2da66

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