@agentshouse/mdruntime 0.1.0-c1db74d14e5fa → 0.1.0-c1f45f238a4a6

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