@agentshouse/mdruntime 0.1.0-cf24798931067 → 0.1.0-cf281e8224a5d

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