@agentshouse/mdruntime 0.1.0-cff8ba8fa2542 → 0.1.0-cffcba9cd9c2f

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 (327) hide show
  1. package/README.md +24 -11
  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 +195 -88
  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 -28
  13. package/dist/attempt.d.ts +16 -0
  14. package/dist/attempt.d.ts.map +1 -0
  15. package/dist/attempt.js +70 -0
  16. package/dist/binary.d.ts +18 -0
  17. package/dist/binary.d.ts.map +1 -0
  18. package/dist/binary.js +8 -0
  19. package/dist/bootstrap.d.ts +37 -5
  20. package/dist/bootstrap.d.ts.map +1 -1
  21. package/dist/bootstrap.js +324 -51
  22. package/dist/cell.d.ts +6 -0
  23. package/dist/cell.d.ts.map +1 -0
  24. package/dist/cell.js +9 -0
  25. package/dist/chain.d.ts +12 -0
  26. package/dist/chain.d.ts.map +1 -0
  27. package/dist/chain.js +64 -0
  28. package/dist/change-set.d.ts +13 -14
  29. package/dist/change-set.d.ts.map +1 -1
  30. package/dist/change-set.js +17 -56
  31. package/dist/change.d.ts +46 -0
  32. package/dist/change.d.ts.map +1 -1
  33. package/dist/change.js +36 -0
  34. package/dist/chat-types.d.ts +5 -0
  35. package/dist/chat-types.d.ts.map +1 -0
  36. package/dist/chat-types.js +13 -0
  37. package/dist/client.d.ts +56 -18
  38. package/dist/client.d.ts.map +1 -1
  39. package/dist/client.js +164 -23
  40. package/dist/collection.d.ts +31 -14
  41. package/dist/collection.d.ts.map +1 -1
  42. package/dist/collection.js +61 -55
  43. package/dist/compile.d.ts +16 -0
  44. package/dist/compile.d.ts.map +1 -0
  45. package/dist/compile.js +711 -0
  46. package/dist/connection.d.ts +8 -103
  47. package/dist/connection.d.ts.map +1 -1
  48. package/dist/connection.js +103 -2076
  49. package/dist/context.d.ts +2 -0
  50. package/dist/context.d.ts.map +1 -0
  51. package/dist/context.js +7 -0
  52. package/dist/conversation-live.d.ts +135 -0
  53. package/dist/conversation-live.d.ts.map +1 -0
  54. package/dist/conversation-live.js +247 -0
  55. package/dist/conversation-row.d.ts +33 -0
  56. package/dist/conversation-row.d.ts.map +1 -0
  57. package/dist/conversation-row.js +53 -0
  58. package/dist/conversation.d.ts +67 -44
  59. package/dist/conversation.d.ts.map +1 -1
  60. package/dist/conversation.js +1010 -549
  61. package/dist/declaration.d.ts +10 -0
  62. package/dist/declaration.d.ts.map +1 -0
  63. package/dist/declaration.js +24 -0
  64. package/dist/digest.d.ts +7 -0
  65. package/dist/digest.d.ts.map +1 -0
  66. package/dist/digest.js +113 -0
  67. package/dist/discovery.d.ts +10 -0
  68. package/dist/discovery.d.ts.map +1 -0
  69. package/dist/discovery.js +177 -0
  70. package/dist/durability.d.ts +19 -1
  71. package/dist/durability.d.ts.map +1 -1
  72. package/dist/durability.js +5 -0
  73. package/dist/entry.d.ts +20 -0
  74. package/dist/entry.d.ts.map +1 -0
  75. package/dist/entry.js +161 -0
  76. package/dist/expectation.d.ts +23 -0
  77. package/dist/expectation.d.ts.map +1 -0
  78. package/dist/expectation.js +92 -0
  79. package/dist/failure.d.ts +14 -0
  80. package/dist/failure.d.ts.map +1 -0
  81. package/dist/failure.js +22 -0
  82. package/dist/figures.d.ts +49 -0
  83. package/dist/figures.d.ts.map +1 -0
  84. package/dist/figures.js +197 -0
  85. package/dist/file.d.ts +2 -19
  86. package/dist/file.d.ts.map +1 -1
  87. package/dist/file.js +7 -122
  88. package/dist/holdings.d.ts +29 -0
  89. package/dist/holdings.d.ts.map +1 -0
  90. package/dist/holdings.js +95 -0
  91. package/dist/host.d.ts +28 -3
  92. package/dist/host.d.ts.map +1 -1
  93. package/dist/host.js +149 -26
  94. package/dist/identity.d.ts +3 -0
  95. package/dist/identity.d.ts.map +1 -1
  96. package/dist/identity.js +13 -1
  97. package/dist/index.d.ts +39 -10
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +16 -0
  100. package/dist/install.d.ts +55 -0
  101. package/dist/install.d.ts.map +1 -0
  102. package/dist/install.js +543 -0
  103. package/dist/job.d.ts +5 -0
  104. package/dist/job.d.ts.map +1 -0
  105. package/dist/job.js +7 -0
  106. package/dist/lifecycle.d.ts +7 -0
  107. package/dist/lifecycle.d.ts.map +1 -0
  108. package/dist/lifecycle.js +271 -0
  109. package/dist/listing.d.ts +52 -0
  110. package/dist/listing.d.ts.map +1 -0
  111. package/dist/listing.js +168 -0
  112. package/dist/live.d.ts +23 -0
  113. package/dist/live.d.ts.map +1 -0
  114. package/dist/live.js +165 -0
  115. package/dist/locks.d.ts +9 -0
  116. package/dist/locks.d.ts.map +1 -0
  117. package/dist/locks.js +62 -0
  118. package/dist/log.d.ts +1 -0
  119. package/dist/log.d.ts.map +1 -1
  120. package/dist/lost.d.ts +5 -0
  121. package/dist/lost.d.ts.map +1 -0
  122. package/dist/lost.js +61 -0
  123. package/dist/media.d.ts +8 -0
  124. package/dist/media.d.ts.map +1 -0
  125. package/dist/media.js +218 -0
  126. package/dist/memory.d.ts +25 -8
  127. package/dist/memory.d.ts.map +1 -1
  128. package/dist/memory.js +296 -93
  129. package/dist/messages.d.ts +49 -0
  130. package/dist/messages.d.ts.map +1 -0
  131. package/dist/messages.js +148 -0
  132. package/dist/model.d.ts +16 -9
  133. package/dist/model.d.ts.map +1 -1
  134. package/dist/model.js +9 -15
  135. package/dist/models.d.ts +10 -0
  136. package/dist/models.d.ts.map +1 -0
  137. package/dist/models.js +170 -0
  138. package/dist/name.d.ts +4 -0
  139. package/dist/name.d.ts.map +1 -0
  140. package/dist/name.js +122 -0
  141. package/dist/observation.d.ts +22 -4
  142. package/dist/observation.d.ts.map +1 -1
  143. package/dist/observation.js +127 -26
  144. package/dist/open.d.ts +2 -0
  145. package/dist/open.d.ts.map +1 -0
  146. package/dist/open.js +1 -0
  147. package/dist/opened.d.ts +22 -0
  148. package/dist/opened.d.ts.map +1 -0
  149. package/dist/opened.js +107 -0
  150. package/dist/opening.d.ts +4 -0
  151. package/dist/opening.d.ts.map +1 -0
  152. package/dist/opening.js +59 -0
  153. package/dist/outcome.d.ts +75 -5
  154. package/dist/outcome.d.ts.map +1 -1
  155. package/dist/outcome.js +84 -1
  156. package/dist/overlay.d.ts +17 -5
  157. package/dist/overlay.d.ts.map +1 -1
  158. package/dist/overlay.js +168 -190
  159. package/dist/parts.d.ts +11 -0
  160. package/dist/parts.d.ts.map +1 -0
  161. package/dist/parts.js +21 -0
  162. package/dist/paths.d.ts +2 -1
  163. package/dist/paths.d.ts.map +1 -1
  164. package/dist/paths.js +26 -16
  165. package/dist/preparation.d.ts +20 -0
  166. package/dist/preparation.d.ts.map +1 -0
  167. package/dist/preparation.js +233 -0
  168. package/dist/queries.d.ts +16 -0
  169. package/dist/queries.d.ts.map +1 -0
  170. package/dist/queries.js +272 -0
  171. package/dist/query.d.ts +51 -181
  172. package/dist/query.d.ts.map +1 -1
  173. package/dist/query.js +3 -431
  174. package/dist/questions.d.ts +34 -0
  175. package/dist/questions.d.ts.map +1 -0
  176. package/dist/questions.js +122 -0
  177. package/dist/queue.d.ts +32 -0
  178. package/dist/queue.d.ts.map +1 -0
  179. package/dist/queue.js +402 -0
  180. package/dist/reach.d.ts +21 -5
  181. package/dist/reach.d.ts.map +1 -1
  182. package/dist/reach.js +306 -96
  183. package/dist/react.d.ts +43 -19
  184. package/dist/react.d.ts.map +1 -1
  185. package/dist/react.js +22 -21
  186. package/dist/read.d.ts +7 -0
  187. package/dist/read.d.ts.map +1 -0
  188. package/dist/read.js +17 -0
  189. package/dist/readiness.d.ts +49 -0
  190. package/dist/readiness.d.ts.map +1 -0
  191. package/dist/readiness.js +27 -0
  192. package/dist/reads.d.ts +37 -0
  193. package/dist/reads.d.ts.map +1 -0
  194. package/dist/reads.js +174 -0
  195. package/dist/recovery.d.ts +26 -0
  196. package/dist/recovery.d.ts.map +1 -0
  197. package/dist/recovery.js +232 -0
  198. package/dist/refresh.d.ts +20 -7
  199. package/dist/refresh.d.ts.map +1 -1
  200. package/dist/refresh.js +155 -59
  201. package/dist/refusal.d.ts +5 -0
  202. package/dist/refusal.d.ts.map +1 -0
  203. package/dist/refusal.js +57 -0
  204. package/dist/replica.d.ts +53 -33
  205. package/dist/replica.d.ts.map +1 -1
  206. package/dist/replica.js +361 -164
  207. package/dist/room-draft.d.ts +59 -12
  208. package/dist/room-draft.d.ts.map +1 -1
  209. package/dist/room-draft.js +299 -90
  210. package/dist/room-drafts.d.ts +24 -0
  211. package/dist/room-drafts.d.ts.map +1 -0
  212. package/dist/room-drafts.js +217 -0
  213. package/dist/rows.d.ts +32 -34
  214. package/dist/rows.d.ts.map +1 -1
  215. package/dist/rows.js +67 -87
  216. package/dist/runtime.d.ts +32 -0
  217. package/dist/runtime.d.ts.map +1 -0
  218. package/dist/runtime.js +1 -0
  219. package/dist/save.d.ts +74 -5
  220. package/dist/save.d.ts.map +1 -1
  221. package/dist/save.js +914 -188
  222. package/dist/saves.d.ts +32 -0
  223. package/dist/saves.d.ts.map +1 -0
  224. package/dist/saves.js +679 -0
  225. package/dist/search.d.ts +15 -54
  226. package/dist/search.d.ts.map +1 -1
  227. package/dist/search.js +10 -121
  228. package/dist/seed.d.ts +25 -0
  229. package/dist/seed.d.ts.map +1 -0
  230. package/dist/seed.js +145 -0
  231. package/dist/shared.d.ts +10 -0
  232. package/dist/shared.d.ts.map +1 -0
  233. package/dist/shared.js +339 -0
  234. package/dist/shown.d.ts +32 -0
  235. package/dist/shown.d.ts.map +1 -0
  236. package/dist/shown.js +299 -0
  237. package/dist/socket/admission.d.ts +7 -0
  238. package/dist/socket/admission.d.ts.map +1 -0
  239. package/dist/socket/admission.js +39 -0
  240. package/dist/socket/answers.d.ts +6 -0
  241. package/dist/socket/answers.d.ts.map +1 -0
  242. package/dist/socket/answers.js +20 -0
  243. package/dist/socket/lanes.d.ts +20 -0
  244. package/dist/socket/lanes.d.ts.map +1 -0
  245. package/dist/socket/lanes.js +77 -0
  246. package/dist/socket/reads.d.ts +10 -0
  247. package/dist/socket/reads.d.ts.map +1 -0
  248. package/dist/socket/reads.js +31 -0
  249. package/dist/socket/rooms.d.ts +41 -0
  250. package/dist/socket/rooms.d.ts.map +1 -0
  251. package/dist/socket/rooms.js +90 -0
  252. package/dist/socket/session.d.ts +38 -0
  253. package/dist/socket/session.d.ts.map +1 -0
  254. package/dist/socket/session.js +23 -0
  255. package/dist/socket.d.ts +69 -0
  256. package/dist/socket.d.ts.map +1 -0
  257. package/dist/socket.js +175 -0
  258. package/dist/source.d.ts +61 -0
  259. package/dist/source.d.ts.map +1 -0
  260. package/dist/source.js +82 -0
  261. package/dist/status.d.ts +7 -4
  262. package/dist/status.d.ts.map +1 -1
  263. package/dist/status.js +3 -15
  264. package/dist/store/fences.d.ts +16 -0
  265. package/dist/store/fences.d.ts.map +1 -0
  266. package/dist/store/fences.js +26 -0
  267. package/dist/store/installs.d.ts +30 -0
  268. package/dist/store/installs.d.ts.map +1 -0
  269. package/dist/store/installs.js +34 -0
  270. package/dist/store/readiness.d.ts +10 -0
  271. package/dist/store/readiness.d.ts.map +1 -0
  272. package/dist/store/readiness.js +52 -0
  273. package/dist/store/records.d.ts +210 -0
  274. package/dist/store/records.d.ts.map +1 -0
  275. package/dist/store/records.js +428 -0
  276. package/dist/store/room-held.d.ts +8 -0
  277. package/dist/store/room-held.d.ts.map +1 -0
  278. package/dist/store/room-held.js +17 -0
  279. package/dist/store/space.d.ts +11 -0
  280. package/dist/store/space.d.ts.map +1 -0
  281. package/dist/store/space.js +41 -0
  282. package/dist/store/watchers.d.ts +17 -0
  283. package/dist/store/watchers.d.ts.map +1 -0
  284. package/dist/store/watchers.js +56 -0
  285. package/dist/store/writes.d.ts +24 -0
  286. package/dist/store/writes.d.ts.map +1 -0
  287. package/dist/store/writes.js +125 -0
  288. package/dist/store.d.ts +106 -92
  289. package/dist/store.d.ts.map +1 -1
  290. package/dist/store.js +142 -385
  291. package/dist/stored.d.ts +156 -0
  292. package/dist/stored.d.ts.map +1 -0
  293. package/dist/stored.js +649 -0
  294. package/dist/surface.d.ts +88 -0
  295. package/dist/surface.d.ts.map +1 -0
  296. package/dist/surface.js +224 -0
  297. package/dist/sync.d.ts +19 -0
  298. package/dist/sync.d.ts.map +1 -0
  299. package/dist/sync.js +530 -0
  300. package/dist/tables.d.ts +412 -0
  301. package/dist/tables.d.ts.map +1 -0
  302. package/dist/tables.js +737 -0
  303. package/dist/tallies.d.ts +18 -0
  304. package/dist/tallies.d.ts.map +1 -0
  305. package/dist/tallies.js +129 -0
  306. package/dist/transfer.d.ts +0 -5
  307. package/dist/transfer.d.ts.map +1 -1
  308. package/dist/turn.d.ts +9 -0
  309. package/dist/turn.d.ts.map +1 -0
  310. package/dist/turn.js +75 -0
  311. package/dist/validation.d.ts +29 -0
  312. package/dist/validation.d.ts.map +1 -0
  313. package/dist/validation.js +104 -0
  314. package/dist/windows.d.ts +11 -0
  315. package/dist/windows.d.ts.map +1 -0
  316. package/dist/windows.js +16 -0
  317. package/dist/wire.d.ts +189 -37
  318. package/dist/wire.d.ts.map +1 -1
  319. package/dist/wire.js +77 -33
  320. package/package.json +27 -15
  321. package/AGENTS.md +0 -231
  322. package/dist/attachment.d.ts +0 -3
  323. package/dist/attachment.d.ts.map +0 -1
  324. package/dist/attachment.js +0 -8
  325. package/dist/read-side.d.ts +0 -18
  326. package/dist/read-side.d.ts.map +0 -1
  327. package/dist/read-side.js +0 -377
package/dist/save.js CHANGED
@@ -1,52 +1,69 @@
1
- import { definitionOf, referenceField } from './model.js';
2
- import { apply, links, parse, retarget, serialize, target, } from '@agentshouse/mdmodel';
3
- import { immutable, } from './change.js';
4
- import { governing, parseCollection, 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, relative } from './paths.js';
8
- function refuse(rule, file, location) {
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';
10
+ function refuse(code, file, location) {
9
11
  return {
10
12
  kind: 'refused',
11
13
  refusal: {
12
- rule,
14
+ code,
15
+ retryable: false,
13
16
  ...(file === undefined ? {} : { file }),
14
17
  ...(location === undefined ? {} : { location }),
15
18
  },
16
19
  };
17
20
  }
18
- function repointed(value, from, to, moves) {
19
- if (Array.isArray(value)) {
20
- const next = value.map((item) => repointed(item, from, to, moves) ?? item);
21
- return next.some((item, index) => item !== value[index]) ? next : undefined;
22
- }
23
- if (typeof value !== 'string')
24
- return undefined;
25
- const named = target(from, value);
26
- if (named === null)
27
- return undefined;
28
- const destination = moves.get(named) ?? named;
29
- return target(to, value) === destination ? undefined : retarget(value, relative(to, destination));
30
- }
31
- function edited(document, path, changes) {
32
- let next = document;
33
- for (const change of changes) {
34
- if (change.path !== path)
21
+ function repointed(document, definition, from, to, moves, settled) {
22
+ const changes = [];
23
+ const names = new Set();
24
+ for (const { pointer, value } of definition === undefined
25
+ ? []
26
+ : references(document, definition)) {
27
+ const [name = ''] = pointerSegments(pointer);
28
+ const named = target(from, value);
29
+ if (named === null || settled.has(`${from} ${name}`))
35
30
  continue;
36
- if (change.kind !== 'section' && change.kind !== 'preamble')
31
+ const destination = moves.get(named) ?? named;
32
+ if (target(to, value) === destination)
37
33
  continue;
38
- const applied = apply(next, change.kind === 'preamble'
39
- ? { kind: 'preamble', value: change.value, prior: next.preamble }
40
- : {
34
+ changes.push({
35
+ kind: 'reference',
36
+ pointer,
37
+ value: retarget(value, relative(to, destination)),
38
+ prior: value,
39
+ });
40
+ names.add(name);
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;
48
+ return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
49
+ }
50
+ function edited(document, changes, definition) {
51
+ const parts = new Map();
52
+ for (const change of changes) {
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}`, {
41
57
  kind: 'section',
42
58
  name: change.name,
43
59
  value: change.value,
44
- prior: next.sections[change.name],
60
+ prior: document.sections[change.name] ?? '',
45
61
  });
46
- if (applied.outcome === 'applied')
47
- next = applied.document;
48
62
  }
49
- 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;
50
67
  }
51
68
  function bodies(path, before, after) {
52
69
  const changes = [];
@@ -60,8 +77,8 @@ function bodies(path, before, after) {
60
77
  }
61
78
  return changes;
62
79
  }
63
- function relinks(path, moved, before, moves) {
64
- let after = before;
80
+ function relinks(path, moved, before, moves, definition) {
81
+ const changes = [];
65
82
  for (const link of links(before, path)) {
66
83
  const destination = moves.get(link.target) ?? link.target;
67
84
  if (destination === link.target && moved === path)
@@ -69,19 +86,23 @@ function relinks(path, moved, before, moves) {
69
86
  const to = relative(moved, destination);
70
87
  if (to === relative(path, link.target))
71
88
  continue;
72
- const applied = apply(after, { kind: 'link', href: link.href, to });
73
- if (applied.outcome === 'refuse') {
74
- return {
75
- kind: 'refused',
76
- refusal: {
77
- rule: applied.refusal.rule,
78
- file: path,
79
- ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
80
- },
81
- };
82
- }
83
- 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
+ };
84
104
  }
105
+ const after = applied.document;
85
106
  if (after === before)
86
107
  return { kind: 'changes', changes: [] };
87
108
  if (before.type === undefined) {
@@ -89,93 +110,173 @@ function relinks(path, moved, before, moves) {
89
110
  }
90
111
  return { kind: 'changes', changes: bodies(path, before, after) };
91
112
  }
92
- function subsumed(merged, path) {
93
- for (let index = merged.length - 1; index >= 0; index -= 1) {
94
- const item = merged[index];
95
- if (item.path !== path)
96
- continue;
97
- if (item.kind === 'section' || item.kind === 'preamble')
98
- merged.splice(index, 1);
99
- }
113
+ function partKey(change) {
114
+ return change.kind === 'section' ? `section ${change.name}` : change.kind;
100
115
  }
101
- function rewrites(changes, visible) {
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];
121
+ }
122
+ function cannotLink(text, names) {
123
+ return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
124
+ }
125
+ function* rewrites(changes, visible) {
102
126
  const moves = new Map();
103
127
  const settled = new Set();
104
- for (const change of changes) {
128
+ const parts = new Map();
129
+ for (const [index, change] of changes.entries()) {
105
130
  if (change.kind === 'move')
106
131
  moves.set(change.path, change.to);
107
- if (change.kind === 'remove' || change.kind === 'replace' || change.kind === 'create') {
132
+ if (change.kind === 'remove' ||
133
+ change.kind === 'replace' ||
134
+ change.kind === 'create' ||
135
+ change.kind === 'binary') {
108
136
  settled.add(change.path);
109
137
  }
110
138
  if (change.kind === 'field')
111
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
+ }
112
147
  }
113
148
  if (moves.size === 0)
114
149
  return { kind: 'changes', changes };
150
+ const names = movedNames(moves);
115
151
  const merged = [...changes];
116
152
  const added = [];
117
- for (const [path, document] of Object.entries(visible.collection.documents)) {
153
+ for (const [path, document] of yield* documentsOf(visible.collection)) {
118
154
  if (settled.has(path))
119
155
  continue;
120
156
  const moved = moves.get(path) ?? path;
121
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
122
- for (const [name, spec] of Object.entries(definition?.fields ?? {})) {
123
- if (!referenceField(spec) || settled.has(`${path} ${name}`))
124
- continue;
125
- const value = repointed(document.fields[name], path, moved, moves);
126
- if (value === undefined)
127
- continue;
128
- added.push({ kind: 'field', path, name, value });
129
- }
130
- 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);
131
164
  if (relinked.kind === 'refused')
132
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
+ }
133
172
  for (const change of relinked.changes) {
134
173
  if (change.kind === 'replace') {
135
- 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
+ }
136
179
  added.push(change);
137
180
  continue;
138
181
  }
139
- const index = merged.findIndex((item) => item.kind === change.kind &&
140
- item.path === path &&
141
- (change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
142
- if (index === -1)
182
+ const index = keyed.get(partKey(change));
183
+ if (index === undefined)
143
184
  added.push(change);
144
185
  else
145
186
  merged[index] = change;
146
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);
147
191
  }
148
- return { kind: 'changes', changes: [...added, ...merged] };
192
+ return {
193
+ kind: 'changes',
194
+ changes: [...added, ...merged.filter((change) => change !== null)],
195
+ };
149
196
  }
150
- function anticipated(changes, preconditions, rewritten, visible) {
151
- 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));
152
200
  return {
153
201
  changes: rewritten,
154
- preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
202
+ preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
155
203
  };
156
204
  }
157
- export function providerRewritten(batch, visible) {
158
- const rewritten = rewrites(batch.changes, visible);
205
+ export function* providerRewritten(batch, visible) {
206
+ const rewritten = yield* rewrites(batch.changes, visible);
159
207
  if (rewritten.kind === 'refused')
160
208
  return batch;
161
209
  return {
162
210
  ...batch,
163
- ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
211
+ ...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
164
212
  };
165
213
  }
166
- function targets(value, from) {
167
- const values = Array.isArray(value) ? value : [value];
168
- const resolved = [];
169
- for (const item of values) {
170
- if (typeof item !== 'string')
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))
171
258
  continue;
172
- const named = target(from, item);
173
- if (named !== null)
174
- resolved.push(named);
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;
175
262
  }
176
- return resolved;
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
+ };
177
270
  }
178
- function incoming(collection, removed) {
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) {
179
280
  const found = new Map();
180
281
  const note = (target, file) => {
181
282
  if (!removed.has(target))
@@ -184,18 +285,31 @@ function incoming(collection, removed) {
184
285
  referrers.add(file);
185
286
  found.set(target, referrers);
186
287
  };
187
- for (const [path, document] of Object.entries(collection.documents)) {
188
- const definition = definitionOf(governing(collection.composition, path), document.type);
189
- for (const [name, spec] of Object.entries(definition?.fields ?? {})) {
190
- if (!referenceField(spec))
288
+ const read = function* (paths) {
289
+ const seen = new Set();
290
+ for (const path of paths) {
291
+ if (seen.has(path))
191
292
  continue;
192
- for (const named of targets(document.fields[name], path))
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);
303
+ for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
304
+ if (to.includes(ATTACHMENT))
305
+ continue;
306
+ const named = target(path, value);
307
+ if (named !== null)
193
308
  note(named, path);
194
309
  }
195
- for (const link of links(document, path))
196
- note(link.target, path);
310
+ yield ROW_WEIGHT + collection.text[path].length;
197
311
  }
198
- const [first] = [...found.keys()].sort();
312
+ const [first] = yield* sorting(found.keys());
199
313
  if (first === undefined)
200
314
  return null;
201
315
  const every = new Set();
@@ -204,41 +318,65 @@ function incoming(collection, removed) {
204
318
  every.add(file);
205
319
  return {
206
320
  kind: 'refused',
207
- refusal: { rule: 'referenced-missing-file', file: first, referrers: [...every].sort() },
321
+ refusal: {
322
+ code: 'referenced-missing-file',
323
+ retryable: false,
324
+ file: first,
325
+ referrers: yield* sorting(every),
326
+ },
208
327
  };
209
328
  }
210
329
  function affected(change) {
211
330
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
212
331
  }
213
- function guard(changes, input) {
214
- const existing = new Set([
215
- ...Object.keys(input.visible.documents),
216
- ...input.visible.binaryTargets,
217
- ]);
218
- for (const change of changes) {
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);
343
+ for (const [index, change] of changes.entries()) {
219
344
  for (const path of affected(change)) {
220
- const normalized = normalize(path);
221
- if (normalized === null || normalized === '')
222
- return refuse('room-root', path);
223
- if (covered(input.providerOwnedPaths, normalized)) {
224
- return refuse('provider-owned-path', normalized);
225
- }
226
- const operation = change.kind === 'remove' ? 'delete' : 'write';
227
- if (!covered(input.operations[operation], normalized))
228
- 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);
229
348
  }
230
- if (change.kind === 'create') {
231
- if (existing.has(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);
353
+ existing.add(change.path);
354
+ continue;
355
+ }
356
+ if (change.kind === 'binary') {
357
+ if (input.visible.documents[change.path] !== undefined ||
358
+ (existing.has(change.path) &&
359
+ input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
232
360
  return refuse('path-collision', change.path);
361
+ }
362
+ const taken = occupied(change.path);
363
+ if (taken !== null)
364
+ return refuse('path-collision', taken);
365
+ if (!input.local &&
366
+ existing.has(change.path) &&
367
+ input.visible.revisions[departure(changes, index)] === undefined) {
368
+ return refuse('unknown-revision', change.path);
369
+ }
233
370
  existing.add(change.path);
234
371
  continue;
235
372
  }
236
373
  if (change.kind === 'move') {
237
374
  if (!existing.has(change.path))
238
375
  return refuse('path-absent', change.path);
239
- if (existing.has(change.to))
240
- return refuse('path-collision', change.to);
241
- if (unrevisionedDocument(input.visible, change.path)) {
376
+ const taken = existing.has(change.to) ? change.to : occupied(change.to);
377
+ if (taken !== null)
378
+ return refuse('path-collision', taken);
379
+ if (unrevised(input, change.path)) {
242
380
  return refuse('unknown-revision', change.path);
243
381
  }
244
382
  existing.delete(change.path);
@@ -248,30 +386,53 @@ function guard(changes, input) {
248
386
  if (!existing.has(change.path))
249
387
  return refuse('path-absent', change.path);
250
388
  if (change.kind === 'remove') {
251
- if (unrevisionedDocument(input.visible, change.path)) {
389
+ if (unrevised(input, change.path)) {
252
390
  return refuse('unknown-revision', change.path);
253
391
  }
254
392
  existing.delete(change.path);
255
393
  continue;
256
394
  }
257
395
  if (change.kind === 'replace') {
258
- if (input.visible.revisions[change.path] === undefined) {
396
+ if (unrevised(input, change.path)) {
259
397
  return refuse('unknown-revision', change.path);
260
398
  }
261
399
  continue;
262
400
  }
263
- if (input.visible.collection.documents[change.path] === undefined) {
401
+ if (input.visible.collection.document(change.path) === undefined) {
264
402
  return refuse('path-absent', change.path);
265
403
  }
266
404
  }
267
405
  return null;
268
406
  }
269
- function unrevisionedDocument(visible, path) {
270
- return visible.revisions[path] === undefined && !visible.binaryTargets.includes(path);
407
+ function unrevised(input, path) {
408
+ return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
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
+ }
426
+ function identity(precondition) {
427
+ const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
428
+ return `${precondition.kind} ${precondition.path} ${part}`;
271
429
  }
272
430
  function existence(visible, path) {
273
431
  const revision = visible.revisions[path];
274
- return revision === undefined ? { kind: 'present', path } : { kind: 'revision', path, revision };
432
+ if (revision !== undefined)
433
+ return { kind: 'revision', path, revision };
434
+ const content = visible.documents[path];
435
+ return content === undefined ? { kind: 'present', path } : { kind: 'content', path, content };
275
436
  }
276
437
  function sameTarget(precondition, change) {
277
438
  if (precondition.kind !== change.kind || precondition.path !== change.path)
@@ -279,25 +440,25 @@ function sameTarget(precondition, change) {
279
440
  return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
280
441
  ((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
281
442
  }
282
- function preconditionsOf(changes, visible, bases) {
443
+ function* preconditionsOf(changes, visible, bases) {
283
444
  const preconditions = [];
284
445
  const seen = new Set();
285
446
  const add = (precondition) => {
286
- const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
287
- const identity = `${precondition.kind} ${precondition.path} ${part}`;
288
- if (seen.has(identity))
447
+ const held = identity(precondition);
448
+ if (seen.has(held))
289
449
  return;
290
- seen.add(identity);
450
+ seen.add(held);
291
451
  preconditions.push(precondition);
292
452
  };
293
453
  for (const change of changes) {
454
+ yield ROW_WEIGHT;
294
455
  if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
295
456
  const based = bases.find((base) => sameTarget(base, change));
296
457
  if (based !== undefined) {
297
458
  add(based);
298
459
  continue;
299
460
  }
300
- const document = visible.collection.documents[change.path];
461
+ const document = visible.collection.document(change.path);
301
462
  if (change.kind === 'field') {
302
463
  add({
303
464
  kind: 'field',
@@ -322,6 +483,13 @@ function preconditionsOf(changes, visible, bases) {
322
483
  add({ kind: 'absent', path: change.path });
323
484
  continue;
324
485
  }
486
+ if (change.kind === 'binary') {
487
+ const revision = visible.revisions[change.path];
488
+ add(visible.binaries[change.path] === undefined || revision === undefined
489
+ ? { kind: 'absent', path: change.path }
490
+ : { kind: 'revision', path: change.path, revision });
491
+ continue;
492
+ }
325
493
  add(existence(visible, change.path));
326
494
  if (change.kind === 'move')
327
495
  add({ kind: 'absent', path: change.to });
@@ -331,10 +499,13 @@ function preconditionsOf(changes, visible, bases) {
331
499
  function depth(path) {
332
500
  return path.split('/').length;
333
501
  }
334
- function exceeded(limits, changes, resulting) {
335
- const written = changes.map((change) => (change.kind === 'move' ? change.to : change.path));
502
+ function* exceeding(limits, changes, effective, resulting) {
503
+ const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
336
504
  const created = changes
337
- .filter((change) => change.kind === 'create' || change.kind === 'move')
505
+ .filter((change) => change.kind === 'create' ||
506
+ change.kind === 'move' ||
507
+ change.kind === 'binary' ||
508
+ change.kind === 'arrival')
338
509
  .map((change) => (change.kind === 'move' ? change.to : change.path));
339
510
  for (const limit of limits) {
340
511
  const value = limit.value;
@@ -348,9 +519,13 @@ function exceeded(limits, changes, resulting) {
348
519
  return refuse('provider-limit', undefined, limit.name);
349
520
  }
350
521
  if (limit.name === 'document-size') {
351
- const large = written.find((path) => (resulting.documents[path] ?? '').length > value);
352
- if (large !== undefined)
353
- 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
+ }
354
529
  }
355
530
  if (limit.name === 'path-length') {
356
531
  const long = created.find((path) => path.length > value);
@@ -365,65 +540,506 @@ function exceeded(limits, changes, resulting) {
365
540
  }
366
541
  return null;
367
542
  }
368
- export function buildBatch(input) {
369
- const structural = input.changes.find((change) => change.kind === 'move' || change.kind === 'remove');
370
- if (structural !== undefined &&
371
- Object.entries(input.visible.collection.documents).some(([path, document]) => document.type !== undefined &&
372
- document.type !== 'any' &&
373
- governing(input.composition, path).status === 'unavailable'))
374
- return refuse('model-unavailable', structural.path);
375
- if (input.changes.length === 0)
376
- return refuse('no-change');
377
- const rewritten = rewrites(input.changes, input.visible);
378
- if (rewritten.kind === 'refused')
379
- return rewritten;
380
- const changes = input.changes;
381
- const refusal = guard(changes, input);
382
- if (refusal !== null)
383
- return refusal;
384
- const preconditions = preconditionsOf(changes, input.visible, input.bases);
385
- const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
386
- const resulting = overlay(input.visible, [
387
- { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
388
- ]);
389
- const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
543
+ function shellRelative(text, path, handle, definition) {
544
+ if (!text.includes(`/rooms/${handle}/`))
545
+ return null;
546
+ const parsed = parse(text, path, definition);
547
+ if (!parsed.ok)
548
+ return null;
549
+ const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
550
+ return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
551
+ }
552
+ function relativeField(path, name, value, handle, definition) {
553
+ if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
554
+ return value;
555
+ const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
556
+ if (held.outcome !== 'applied')
557
+ return value;
558
+ const result = relativize(held.document, path, handle, definition);
559
+ return result.outcome === 'applied' ? result.document.fields[name] : value;
560
+ }
561
+ function relativeChanges(input) {
562
+ const { handle, composition, visible } = input;
563
+ const typed = (path) => definitionsAt(composition, path);
564
+ const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
565
+ return input.changes.map((change) => {
566
+ const definition = typed(change.path);
567
+ if (change.kind === 'field') {
568
+ const type = visible.collection.document(change.path)?.type;
569
+ const value = relativeField(change.path, change.name, change.value, handle, definition(type));
570
+ return value === change.value ? change : { ...change, value };
571
+ }
572
+ if (change.kind === 'section' || change.kind === 'preamble') {
573
+ const value = body(change.path, change.value);
574
+ return value === change.value ? change : { ...change, value };
575
+ }
576
+ if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
577
+ const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
578
+ return content === change.content ? change : { ...change, content };
579
+ }
580
+ if (change.kind !== 'create')
581
+ return change;
582
+ const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
583
+ const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
584
+ name,
585
+ relativeField(change.path, name, value, handle, definition(type)),
586
+ ]));
587
+ const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
588
+ return { ...change, fields, sections };
589
+ });
590
+ }
591
+ function unqueueable(file) {
592
+ return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
593
+ }
594
+ function isPart(change) {
595
+ return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
596
+ }
597
+ function touched(change) {
598
+ return change.kind === 'move' ? [change.path, change.to] : [change.path];
599
+ }
600
+ function partsApplied(content, path, changes, composition) {
601
+ const definitions = definitionsAt(composition, path);
602
+ const parsed = parse(content, path, definitions);
603
+ if (!parsed.ok)
604
+ return null;
605
+ const document = parsed.document;
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}`, {
617
+ kind: 'section',
618
+ name: change.name,
619
+ value: change.value,
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));
626
+ return applied.outcome === 'applied' ? serialize(applied.document) : null;
627
+ }
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) {
639
+ const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
640
+ const whole = held === undefined ? -1 : changes.indexOf(held);
641
+ const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
642
+ if (isPart(change)) {
643
+ const same = changes.findIndex((item) => item.kind === change.kind &&
644
+ item.path === change.path &&
645
+ (change.kind === 'preamble' || ('name' in item && item.name === change.name)));
646
+ if (same !== -1) {
647
+ changes[same] = change;
648
+ return 'folded';
649
+ }
650
+ if (held === undefined) {
651
+ if (!appending)
652
+ return 'skipped';
653
+ if (occupied)
654
+ return 'refused';
655
+ changes.push(change);
656
+ return 'appended';
657
+ }
658
+ if ('content' in held) {
659
+ const parts = deferred.get(held);
660
+ if (parts === undefined)
661
+ deferred.set(held, [change]);
662
+ else
663
+ parts.push(change);
664
+ return 'folded';
665
+ }
666
+ if (change.kind === 'preamble')
667
+ return 'refused';
668
+ changes[whole] =
669
+ change.kind === 'field'
670
+ ? { ...held, fields: { ...held.fields, [change.name]: change.value } }
671
+ : { ...held, sections: { ...held.sections, [change.name]: change.value } };
672
+ return 'folded';
673
+ }
674
+ if (change.kind === 'replace') {
675
+ if (held !== undefined) {
676
+ if (!('content' in held))
677
+ return 'refused';
678
+ changes[whole] = { ...held, content: change.content };
679
+ return 'folded';
680
+ }
681
+ const parts = changes.filter((item) => isPart(item) && item.path === change.path);
682
+ if (parts.length === 0 && !appending)
683
+ return 'skipped';
684
+ if (occupied)
685
+ return 'refused';
686
+ changes.splice(0, changes.length, ...changes.filter((item) => !parts.includes(item)), change);
687
+ return 'appended';
688
+ }
689
+ if (changes.some((item) => touched(change).some((path) => touched(item).includes(path))))
690
+ return 'refused';
691
+ changes.push(change);
692
+ return 'appended';
693
+ }
694
+ function* relinked(batch, move, before, after, appending) {
695
+ const derived = yield* rewrites([move], after);
696
+ if (derived.kind === 'refused')
697
+ return derived;
698
+ const changes = [...batch.changes];
699
+ const appended = [];
700
+ let changed = false;
701
+ const deferred = new Map();
702
+ for (const change of derived.changes) {
703
+ if (change === move)
704
+ continue;
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);
711
+ if (outcome === 'refused')
712
+ return unqueueable(change.path);
713
+ if (outcome === 'appended')
714
+ appended.push(change);
715
+ if (outcome !== 'skipped')
716
+ changed = true;
717
+ }
718
+ const unsettled = settledParts(changes, deferred, after.collection.composition);
719
+ if (unsettled !== null)
720
+ return unqueueable(unsettled);
721
+ if (!changed)
722
+ return { kind: 'relinked', batch };
723
+ const held = new Set(batch.preconditions.map(identity));
724
+ const added = yield* preconditionsOf(appended, before, []);
725
+ return {
726
+ kind: 'relinked',
727
+ batch: immutable({
728
+ ...batch,
729
+ id: changeBatchIdentity(),
730
+ changes,
731
+ preconditions: [
732
+ ...batch.preconditions,
733
+ ...added.filter((precondition) => !held.has(identity(precondition))),
734
+ ],
735
+ }),
736
+ };
737
+ }
738
+ function leading(changes, at) {
739
+ const from = departure(changes, at);
740
+ return new Set(changes.filter((change, index) => index < at && change.kind === 'move' && departure(changes, index) === from));
741
+ }
742
+ function created(batch, at) {
743
+ const file = batch.changes[at];
744
+ return (leading(batch.changes, at).size === 0 &&
745
+ batch.preconditions.some((precondition) => precondition.kind === 'absent' && precondition.path === file.path));
746
+ }
747
+ function withdrawnFile(batch, change, at) {
748
+ const file = batch.changes[at];
749
+ const from = departure(batch.changes, at);
750
+ const moves = leading(batch.changes, at);
751
+ const fresh = created(batch, at);
752
+ const vacated = new Set([...moves].map((move) => move.to));
753
+ if (fresh)
754
+ vacated.add(file.path);
755
+ const replaced = fresh
756
+ ? change.kind === 'remove'
757
+ ? []
758
+ : [{ ...file, path: change.to }]
759
+ : change.kind === 'remove'
760
+ ? [{ kind: 'remove', path: from }]
761
+ : change.to === from
762
+ ? [{ ...file, path: from }]
763
+ : [
764
+ { kind: 'move', path: from, to: change.to },
765
+ { ...file, path: change.to },
766
+ ];
767
+ const added = change.kind === 'move' && (fresh || change.to !== from)
768
+ ? [{ kind: 'absent', path: change.to }]
769
+ : [];
770
+ const changes = batch.changes.flatMap((item) => item === file ? replaced : item.kind === 'move' && moves.has(item) ? [] : [item]);
771
+ if (changes.length === 0)
772
+ return null;
773
+ const preconditions = [
774
+ ...added,
775
+ ...batch.preconditions.filter((precondition) => precondition.kind !== 'absent' || !vacated.has(precondition.path)),
776
+ ];
777
+ return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
778
+ }
779
+ export function* requeuing(queue, changes, visible, changeable, state) {
780
+ const next = [...queue];
781
+ const origins = new Map();
782
+ const withdrawn = new Map();
783
+ const owners = new Set();
784
+ const queued = new Set();
785
+ const kept = [];
786
+ const originOf = (batch) => origins.get(batch) ?? batch.id;
787
+ const open = (batch) => origins.has(batch) || changeable(batch);
788
+ for (const change of changes) {
789
+ const held = visible.binaries[change.path];
790
+ if ((change.kind !== 'move' && change.kind !== 'remove') || held?.state !== 'pending') {
791
+ kept.push(change);
792
+ continue;
793
+ }
794
+ let index = next.length - 1;
795
+ while (index >= 0 && queuedFile(next[index], change.path, held.localId) === -1)
796
+ index -= 1;
797
+ const owner = next[index];
798
+ if (owner === undefined || !open(owner))
799
+ return unqueueable(change.path);
800
+ const following = next.slice(index);
801
+ const rebuilt = [];
802
+ let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
803
+ for (const [offset, batch] of following.entries()) {
804
+ let current = batch;
805
+ if (change.kind === 'move') {
806
+ const after = yield* state(next.slice(0, index + offset + 1));
807
+ const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
808
+ const relinking = yield* relinked(batch, change, before, after, appending);
809
+ if (relinking.kind === 'refused')
810
+ return relinking;
811
+ current = relinking.batch;
812
+ before = after;
813
+ }
814
+ if (current !== batch && (!open(batch) || batch.between !== undefined))
815
+ return unqueueable(change.path);
816
+ rebuilt.push(offset === 0
817
+ ? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
818
+ : current);
819
+ }
820
+ for (const [offset, batch] of following.entries()) {
821
+ const replaced = rebuilt[offset];
822
+ if (replaced === batch)
823
+ continue;
824
+ const origin = originOf(batch);
825
+ withdrawn.set(origin, replaced);
826
+ if (replaced !== null)
827
+ origins.set(replaced, origin);
828
+ }
829
+ owners.add(originOf(owner));
830
+ next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
831
+ queued.add(change.path);
832
+ }
833
+ return {
834
+ kind: 'requeued',
835
+ queue: withdrawn.size === 0 ? queue : next,
836
+ changes: kept,
837
+ queued,
838
+ withdrawn,
839
+ owners,
840
+ };
841
+ }
842
+ function covering(precondition) {
843
+ return precondition.kind === 'field' ||
844
+ precondition.kind === 'section' ||
845
+ precondition.kind === 'preamble'
846
+ ? identity(precondition)
847
+ : `path ${precondition.path}`;
848
+ }
849
+ export function joined(queue, owner, extra, composition) {
850
+ const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
851
+ const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
852
+ if (clash !== undefined)
853
+ return unqueueable(clash);
854
+ const changes = [...owner.changes];
855
+ const deferred = new Map();
856
+ for (const change of extra.changes) {
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')
861
+ return unqueueable(change.path);
862
+ }
863
+ const unsettled = settledParts(changes, deferred, composition);
864
+ if (unsettled !== null)
865
+ return unqueueable(unsettled);
866
+ const held = new Set(owner.preconditions.map(covering));
867
+ const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
868
+ const preconditions = [
869
+ ...owner.preconditions,
870
+ ...extra.preconditions.filter((precondition) => {
871
+ const key = covering(precondition);
872
+ return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
873
+ }),
874
+ ];
875
+ const batch = immutable({
876
+ ...owner,
877
+ ...(extra.validation === undefined ? {} : { validation: extra.validation }),
878
+ id: changeBatchIdentity(),
879
+ changes,
880
+ preconditions,
881
+ });
882
+ return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
883
+ }
884
+ export function* limiting(queue, batch, limits, state) {
885
+ const at = queue.indexOf(batch);
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;
889
+ }
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) {
390
924
  if (removed.size > 0) {
391
- const referenced = incoming(parseCollection(resulting.documents, input.composition), removed);
925
+ const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
392
926
  if (referenced !== null)
393
927
  return referenced;
394
928
  }
395
- const typed = (content) => {
396
- if (content === undefined)
397
- return false;
398
- const parsed = parse(content);
399
- return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
400
- };
401
- const typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
402
- const needsModel = typedPaths.length > 0;
403
- 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');
404
939
  if (withoutModel !== undefined)
405
940
  return refuse('model-unavailable', withoutModel);
406
- const validationText = needsModel
407
- ? resulting.documents
408
- : Object.fromEntries(Object.entries(resulting.documents).filter(([path]) => predicted.changes.some((change) => affected(change).includes(path))));
409
- const verdict = validateCollection(validationText, resulting.binaryTargets, input.composition);
941
+ const verdict = typedPaths.length > 0
942
+ ? await validate(resulting, touched)
943
+ : await touchedLibrary(resulting, touched, composition);
410
944
  if (verdict.verdict === 'refuse') {
411
945
  return {
412
946
  kind: 'refused',
413
947
  refusal: {
414
- rule: verdict.rule,
948
+ code: verdict.rule,
949
+ retryable: false,
415
950
  file: verdict.file,
416
951
  ...(verdict.location === undefined ? {} : { location: verdict.location }),
417
952
  ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
418
953
  },
419
954
  };
420
955
  }
421
- const limited = exceeded(input.providerLimits, 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));
422
1041
  if (limited !== null)
423
1042
  return limited;
424
- const validated = typedPaths
425
- .map((path) => governing(input.composition, path))
426
- .find((model) => model.status !== 'unavailable');
427
1043
  return {
428
1044
  kind: 'batch',
429
1045
  batch: immutable({
@@ -432,15 +1048,125 @@ export function buildBatch(input) {
432
1048
  changes,
433
1049
  preconditions,
434
1050
  ...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
435
- ...(validated === undefined
436
- ? {}
437
- : {
438
- validation: {
439
- identity: validated.snapshot.identity,
440
- version: validated.snapshot.version,
441
- source: validated.status,
442
- },
443
- }),
1051
+ ...validationOf(judged.validated),
444
1052
  }),
445
1053
  };
446
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
+ }