@agentshouse/mdruntime 0.1.0-c44866799613e → 0.1.0-c44d5cc00aee5

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 (314) hide show
  1. package/README.md +18 -17
  2. package/dist/acceptance.d.ts +3 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +299 -0
  5. package/dist/act-lanes.d.ts +30 -0
  6. package/dist/act-lanes.d.ts.map +1 -0
  7. package/dist/act-lanes.js +225 -0
  8. package/dist/actions.d.ts +26 -0
  9. package/dist/actions.d.ts.map +1 -0
  10. package/dist/actions.js +302 -0
  11. package/dist/adapter.d.ts +110 -112
  12. package/dist/adapter.d.ts.map +1 -1
  13. package/dist/attached-model.d.ts +3 -11
  14. package/dist/attached-model.d.ts.map +1 -1
  15. package/dist/attached-model.js +28 -51
  16. package/dist/bootstrap.d.ts +15 -18
  17. package/dist/bootstrap.d.ts.map +1 -1
  18. package/dist/bootstrap.js +252 -158
  19. package/dist/cell.d.ts +6 -0
  20. package/dist/cell.d.ts.map +1 -0
  21. package/dist/cell.js +9 -0
  22. package/dist/chain.d.ts +2 -1
  23. package/dist/chain.d.ts.map +1 -1
  24. package/dist/change-set.d.ts +4 -0
  25. package/dist/change-set.d.ts.map +1 -1
  26. package/dist/change-set.js +6 -0
  27. package/dist/change.d.ts +1 -0
  28. package/dist/change.d.ts.map +1 -1
  29. package/dist/change.js +4 -2
  30. package/dist/chat-types.d.ts +3 -0
  31. package/dist/chat-types.d.ts.map +1 -0
  32. package/dist/chat-types.js +11 -0
  33. package/dist/client.d.ts +27 -21
  34. package/dist/client.d.ts.map +1 -1
  35. package/dist/client.js +36 -17
  36. package/dist/collection.d.ts +23 -7
  37. package/dist/collection.d.ts.map +1 -1
  38. package/dist/collection.js +13 -19
  39. package/dist/compile.d.ts +8 -6
  40. package/dist/compile.d.ts.map +1 -1
  41. package/dist/compile.js +288 -329
  42. package/dist/connection.d.ts +5 -143
  43. package/dist/connection.d.ts.map +1 -1
  44. package/dist/connection.js +109 -5104
  45. package/dist/context.d.ts +2 -0
  46. package/dist/context.d.ts.map +1 -0
  47. package/dist/context.js +7 -0
  48. package/dist/conversation-live.d.ts +143 -0
  49. package/dist/conversation-live.d.ts.map +1 -0
  50. package/dist/conversation-live.js +254 -0
  51. package/dist/conversation-row.d.ts +33 -0
  52. package/dist/conversation-row.d.ts.map +1 -0
  53. package/dist/conversation-row.js +53 -0
  54. package/dist/conversation.d.ts +65 -48
  55. package/dist/conversation.d.ts.map +1 -1
  56. package/dist/conversation.js +987 -838
  57. package/dist/declaration.d.ts +10 -0
  58. package/dist/declaration.d.ts.map +1 -0
  59. package/dist/declaration.js +24 -0
  60. package/dist/discovery.d.ts +10 -0
  61. package/dist/discovery.d.ts.map +1 -0
  62. package/dist/discovery.js +150 -0
  63. package/dist/durability.d.ts +19 -1
  64. package/dist/durability.d.ts.map +1 -1
  65. package/dist/durability.js +4 -0
  66. package/dist/entry.d.ts +3 -2
  67. package/dist/entry.d.ts.map +1 -1
  68. package/dist/entry.js +19 -9
  69. package/dist/expectation.d.ts +26 -13
  70. package/dist/expectation.d.ts.map +1 -1
  71. package/dist/expectation.js +72 -19
  72. package/dist/failure.d.ts +3 -0
  73. package/dist/failure.d.ts.map +1 -1
  74. package/dist/failure.js +6 -0
  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/holdings.d.ts +29 -0
  79. package/dist/holdings.d.ts.map +1 -0
  80. package/dist/holdings.js +95 -0
  81. package/dist/host.d.ts +21 -8
  82. package/dist/host.d.ts.map +1 -1
  83. package/dist/host.js +73 -40
  84. package/dist/identity.d.ts +0 -1
  85. package/dist/identity.d.ts.map +1 -1
  86. package/dist/identity.js +0 -3
  87. package/dist/index.d.ts +34 -12
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +16 -5
  90. package/dist/install.d.ts +54 -0
  91. package/dist/install.d.ts.map +1 -0
  92. package/dist/install.js +530 -0
  93. package/dist/lifecycle.d.ts +7 -0
  94. package/dist/lifecycle.d.ts.map +1 -0
  95. package/dist/lifecycle.js +277 -0
  96. package/dist/listing.d.ts +48 -0
  97. package/dist/listing.d.ts.map +1 -0
  98. package/dist/listing.js +139 -0
  99. package/dist/live.d.ts +5 -4
  100. package/dist/live.d.ts.map +1 -1
  101. package/dist/live.js +64 -23
  102. package/dist/locks.d.ts +7 -0
  103. package/dist/locks.d.ts.map +1 -0
  104. package/dist/locks.js +32 -0
  105. package/dist/lost.d.ts +5 -0
  106. package/dist/lost.d.ts.map +1 -0
  107. package/dist/lost.js +49 -0
  108. package/dist/media.d.ts +2 -2
  109. package/dist/media.d.ts.map +1 -1
  110. package/dist/media.js +23 -13
  111. package/dist/memory.d.ts +17 -7
  112. package/dist/memory.d.ts.map +1 -1
  113. package/dist/memory.js +210 -97
  114. package/dist/messages.d.ts +33 -5
  115. package/dist/messages.d.ts.map +1 -1
  116. package/dist/messages.js +104 -36
  117. package/dist/model.d.ts +2 -11
  118. package/dist/model.d.ts.map +1 -1
  119. package/dist/model.js +0 -23
  120. package/dist/models.d.ts +10 -0
  121. package/dist/models.d.ts.map +1 -0
  122. package/dist/models.js +153 -0
  123. package/dist/observation.d.ts +4 -3
  124. package/dist/observation.d.ts.map +1 -1
  125. package/dist/observation.js +23 -17
  126. package/dist/opened.d.ts +1 -1
  127. package/dist/opened.d.ts.map +1 -1
  128. package/dist/opened.js +5 -5
  129. package/dist/opening.d.ts +4 -0
  130. package/dist/opening.d.ts.map +1 -0
  131. package/dist/opening.js +53 -0
  132. package/dist/outcome.d.ts +7 -4
  133. package/dist/outcome.d.ts.map +1 -1
  134. package/dist/outcome.js +18 -0
  135. package/dist/overlay.d.ts +6 -1
  136. package/dist/overlay.d.ts.map +1 -1
  137. package/dist/overlay.js +47 -21
  138. package/dist/page-metadata.d.ts +8 -0
  139. package/dist/page-metadata.d.ts.map +1 -0
  140. package/dist/page-metadata.js +19 -0
  141. package/dist/parts.d.ts +4 -0
  142. package/dist/parts.d.ts.map +1 -0
  143. package/dist/parts.js +13 -0
  144. package/dist/paths.d.ts +2 -0
  145. package/dist/paths.d.ts.map +1 -1
  146. package/dist/paths.js +26 -0
  147. package/dist/preparation.d.ts +20 -0
  148. package/dist/preparation.d.ts.map +1 -0
  149. package/dist/preparation.js +227 -0
  150. package/dist/presence.d.ts +52 -0
  151. package/dist/presence.d.ts.map +1 -0
  152. package/dist/presence.js +220 -0
  153. package/dist/queries.d.ts +16 -0
  154. package/dist/queries.d.ts.map +1 -0
  155. package/dist/queries.js +282 -0
  156. package/dist/query.d.ts +14 -210
  157. package/dist/query.d.ts.map +1 -1
  158. package/dist/query.js +3 -505
  159. package/dist/questions.d.ts +29 -0
  160. package/dist/questions.d.ts.map +1 -0
  161. package/dist/questions.js +115 -0
  162. package/dist/queue.d.ts +40 -0
  163. package/dist/queue.d.ts.map +1 -0
  164. package/dist/queue.js +450 -0
  165. package/dist/reach.d.ts +15 -12
  166. package/dist/reach.d.ts.map +1 -1
  167. package/dist/reach.js +240 -149
  168. package/dist/react.d.ts +15 -2
  169. package/dist/react.d.ts.map +1 -1
  170. package/dist/react.js +33 -1
  171. package/dist/read.d.ts +7 -0
  172. package/dist/read.d.ts.map +1 -0
  173. package/dist/read.js +17 -0
  174. package/dist/readiness.d.ts +4 -19
  175. package/dist/readiness.d.ts.map +1 -1
  176. package/dist/readiness.js +4 -10
  177. package/dist/reads.d.ts +38 -10
  178. package/dist/reads.d.ts.map +1 -1
  179. package/dist/reads.js +163 -85
  180. package/dist/recovery.d.ts +3 -0
  181. package/dist/recovery.d.ts.map +1 -1
  182. package/dist/recovery.js +57 -1
  183. package/dist/refresh.d.ts +7 -5
  184. package/dist/refresh.d.ts.map +1 -1
  185. package/dist/refresh.js +35 -7
  186. package/dist/refusal.d.ts +5 -0
  187. package/dist/refusal.d.ts.map +1 -0
  188. package/dist/refusal.js +55 -0
  189. package/dist/replica.d.ts +12 -18
  190. package/dist/replica.d.ts.map +1 -1
  191. package/dist/replica.js +64 -34
  192. package/dist/retried.d.ts +30 -0
  193. package/dist/retried.d.ts.map +1 -0
  194. package/dist/retried.js +72 -0
  195. package/dist/room-draft.d.ts +24 -10
  196. package/dist/room-draft.d.ts.map +1 -1
  197. package/dist/room-draft.js +142 -106
  198. package/dist/room-drafts.d.ts +24 -0
  199. package/dist/room-drafts.d.ts.map +1 -0
  200. package/dist/room-drafts.js +217 -0
  201. package/dist/rows.d.ts +14 -21
  202. package/dist/rows.d.ts.map +1 -1
  203. package/dist/rows.js +49 -186
  204. package/dist/runtime.d.ts +34 -0
  205. package/dist/runtime.d.ts.map +1 -0
  206. package/dist/runtime.js +1 -0
  207. package/dist/save.d.ts +42 -84
  208. package/dist/save.d.ts.map +1 -1
  209. package/dist/save.js +349 -170
  210. package/dist/saves.d.ts +32 -0
  211. package/dist/saves.d.ts.map +1 -0
  212. package/dist/saves.js +410 -0
  213. package/dist/search.d.ts +12 -63
  214. package/dist/search.d.ts.map +1 -1
  215. package/dist/search.js +9 -52
  216. package/dist/seed.d.ts +11 -2
  217. package/dist/seed.d.ts.map +1 -1
  218. package/dist/seed.js +71 -1
  219. package/dist/shared.d.ts +1 -7
  220. package/dist/shared.d.ts.map +1 -1
  221. package/dist/shared.js +13 -105
  222. package/dist/shown.d.ts +33 -0
  223. package/dist/shown.d.ts.map +1 -0
  224. package/dist/shown.js +302 -0
  225. package/dist/socket/admission.d.ts +7 -0
  226. package/dist/socket/admission.d.ts.map +1 -0
  227. package/dist/socket/admission.js +39 -0
  228. package/dist/socket/answers.d.ts +6 -0
  229. package/dist/socket/answers.d.ts.map +1 -0
  230. package/dist/socket/answers.js +20 -0
  231. package/dist/socket/lanes.d.ts +28 -0
  232. package/dist/socket/lanes.d.ts.map +1 -0
  233. package/dist/socket/lanes.js +103 -0
  234. package/dist/socket/reads.d.ts +10 -0
  235. package/dist/socket/reads.d.ts.map +1 -0
  236. package/dist/socket/reads.js +31 -0
  237. package/dist/socket/session.d.ts +38 -0
  238. package/dist/socket/session.d.ts.map +1 -0
  239. package/dist/socket/session.js +23 -0
  240. package/dist/socket.d.ts +67 -0
  241. package/dist/socket.d.ts.map +1 -0
  242. package/dist/socket.js +164 -0
  243. package/dist/source.d.ts +54 -0
  244. package/dist/source.d.ts.map +1 -0
  245. package/dist/source.js +82 -0
  246. package/dist/status.d.ts +4 -5
  247. package/dist/status.d.ts.map +1 -1
  248. package/dist/status.js +3 -7
  249. package/dist/store/installs.d.ts +30 -0
  250. package/dist/store/installs.d.ts.map +1 -0
  251. package/dist/store/installs.js +34 -0
  252. package/dist/store/readiness.d.ts +10 -0
  253. package/dist/store/readiness.d.ts.map +1 -0
  254. package/dist/store/readiness.js +52 -0
  255. package/dist/store/records.d.ts +214 -0
  256. package/dist/store/records.d.ts.map +1 -0
  257. package/dist/store/records.js +438 -0
  258. package/dist/store/room-held.d.ts +8 -0
  259. package/dist/store/room-held.d.ts.map +1 -0
  260. package/dist/store/room-held.js +17 -0
  261. package/dist/store/space.d.ts +11 -0
  262. package/dist/store/space.d.ts.map +1 -0
  263. package/dist/store/space.js +41 -0
  264. package/dist/store/watchers.d.ts +17 -0
  265. package/dist/store/watchers.d.ts.map +1 -0
  266. package/dist/store/watchers.js +56 -0
  267. package/dist/store/writes.d.ts +24 -0
  268. package/dist/store/writes.d.ts.map +1 -0
  269. package/dist/store/writes.js +125 -0
  270. package/dist/store.d.ts +120 -171
  271. package/dist/store.d.ts.map +1 -1
  272. package/dist/store.js +166 -414
  273. package/dist/stored.d.ts +62 -57
  274. package/dist/stored.d.ts.map +1 -1
  275. package/dist/stored.js +317 -239
  276. package/dist/surface.d.ts +88 -0
  277. package/dist/surface.d.ts.map +1 -0
  278. package/dist/surface.js +220 -0
  279. package/dist/sync.d.ts +19 -0
  280. package/dist/sync.d.ts.map +1 -0
  281. package/dist/sync.js +535 -0
  282. package/dist/tables.d.ts +126 -65
  283. package/dist/tables.d.ts.map +1 -1
  284. package/dist/tables.js +250 -113
  285. package/dist/tallies.d.ts +18 -0
  286. package/dist/tallies.d.ts.map +1 -0
  287. package/dist/tallies.js +128 -0
  288. package/dist/transfer.d.ts +0 -5
  289. package/dist/transfer.d.ts.map +1 -1
  290. package/dist/validation.d.ts +3 -4
  291. package/dist/validation.d.ts.map +1 -1
  292. package/dist/validation.js +42 -100
  293. package/dist/windows.d.ts +1 -12
  294. package/dist/windows.d.ts.map +1 -1
  295. package/dist/windows.js +2 -75
  296. package/dist/wire.d.ts +65 -9
  297. package/dist/wire.d.ts.map +1 -1
  298. package/dist/wire.js +22 -5
  299. package/package.json +3 -2
  300. package/dist/attempt.d.ts +0 -16
  301. package/dist/attempt.d.ts.map +0 -1
  302. package/dist/attempt.js +0 -70
  303. package/dist/fence.d.ts +0 -6
  304. package/dist/fence.d.ts.map +0 -1
  305. package/dist/fence.js +0 -10
  306. package/dist/fulltext.d.ts +0 -27
  307. package/dist/fulltext.d.ts.map +0 -1
  308. package/dist/fulltext.js +0 -368
  309. package/dist/log.d.ts +0 -5
  310. package/dist/log.d.ts.map +0 -1
  311. package/dist/log.js +0 -14
  312. package/dist/words.d.ts +0 -19
  313. package/dist/words.d.ts.map +0 -1
  314. package/dist/words.js +0 -150
package/dist/save.js CHANGED
@@ -1,14 +1,14 @@
1
- import { definitionOf } from './model.js';
2
- import { apply, ARRAY_INDEX, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
1
+ import { apply, ARRAY_INDEX, ATTACHMENT, create, equalValue, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
3
2
  import { immutable, leaving, queuedFile, departure, } from './change.js';
4
- import { collectionOf, documentsOf, governing, } from './collection.js';
5
- import { changeBatchIdentity } from './identity.js';
6
- import { overlaying } from './overlay.js';
7
- import { covered, normalize } from './paths.js';
8
- import { sorting } from './shared.js';
3
+ import { collectionOf, definitionsAt, documentsOf, governing, } from './collection.js';
4
+ import { changeBatchIdentity, roomDraftRevision } from './identity.js';
5
+ import { composed, overlaying } from './overlay.js';
6
+ import { ancestors } from './paths.js';
7
+ import { entriesIn, sorting } from './shared.js';
8
+ import { changedParts, changesOf, moveTarget, withdrawnDrafts, } from './room-draft.js';
9
9
  import { paced, ROW_WEIGHT, stepped } from './turn.js';
10
10
  import { validatedCollection } from './validation.js';
11
- function refuse(code, file, location) {
11
+ function refuse(code, file, location, line) {
12
12
  return {
13
13
  kind: 'refused',
14
14
  refusal: {
@@ -16,13 +16,14 @@ function refuse(code, file, location) {
16
16
  retryable: false,
17
17
  ...(file === undefined ? {} : { file }),
18
18
  ...(location === undefined ? {} : { location }),
19
+ ...(line === undefined ? {} : { line }),
19
20
  },
20
21
  };
21
22
  }
23
+ function refusedBy(refusal, file) {
24
+ return refuse(refusal.rule, file, refusal.location, refusal.line);
25
+ }
22
26
  function repointed(document, definition, from, to, moves, settled) {
23
- const parsed = parse(document.content, from);
24
- if (!parsed.ok)
25
- return [];
26
27
  const changes = [];
27
28
  const names = new Set();
28
29
  for (const { pointer, value } of definition === undefined
@@ -45,15 +46,22 @@ function repointed(document, definition, from, to, moves, settled) {
45
46
  }
46
47
  if (changes.length === 0)
47
48
  return [];
48
- const applied = apply(parsed.document, changes);
49
+ const applied = apply(document, changes, definition);
49
50
  if (applied.outcome !== 'applied')
50
51
  return [];
51
52
  const next = applied.document;
52
53
  return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
53
54
  }
54
- function edited(document, changes) {
55
+ function edited(document, changes, definition) {
55
56
  const parts = new Map();
56
57
  for (const change of changes) {
58
+ if (change.kind === 'field')
59
+ parts.set(`field ${change.name}`, {
60
+ kind: 'field',
61
+ name: change.name,
62
+ value: change.value,
63
+ prior: document.fields[change.name],
64
+ });
57
65
  if (change.kind === 'preamble')
58
66
  parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
59
67
  if (change.kind === 'section')
@@ -66,11 +74,16 @@ function edited(document, changes) {
66
74
  }
67
75
  if (parts.size === 0)
68
76
  return document;
69
- const applied = apply(document, [...parts.values()]);
77
+ const applied = apply(document, [...parts.values()], definition);
70
78
  return applied.outcome === 'applied' ? applied.document : document;
71
79
  }
72
80
  function bodies(path, before, after) {
73
81
  const changes = [];
82
+ for (const [name, value] of Object.entries(after.fields)) {
83
+ if (equalValue(before.fields[name], value))
84
+ continue;
85
+ changes.push({ kind: 'field', path, name, value });
86
+ }
74
87
  if (after.preamble !== before.preamble) {
75
88
  changes.push({ kind: 'preamble', path, value: after.preamble });
76
89
  }
@@ -81,7 +94,7 @@ function bodies(path, before, after) {
81
94
  }
82
95
  return changes;
83
96
  }
84
- function relinks(path, moved, before, moves) {
97
+ function relinks(path, moved, before, moves, definition) {
85
98
  const changes = [];
86
99
  for (const link of links(before, path)) {
87
100
  const destination = moves.get(link.target) ?? link.target;
@@ -94,18 +107,9 @@ function relinks(path, moved, before, moves) {
94
107
  }
95
108
  if (changes.length === 0)
96
109
  return { kind: 'changes', changes: [] };
97
- const applied = apply(before, changes);
98
- if (applied.outcome === 'refuse') {
99
- return {
100
- kind: 'refused',
101
- refusal: {
102
- code: applied.refusal.rule,
103
- retryable: false,
104
- file: path,
105
- ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
106
- },
107
- };
108
- }
110
+ const applied = apply(before, changes, definition);
111
+ if (applied.outcome === 'refuse')
112
+ return refusedBy(applied.refusal, path);
109
113
  const after = applied.document;
110
114
  if (after === before)
111
115
  return { kind: 'changes', changes: [] };
@@ -115,7 +119,18 @@ function relinks(path, moved, before, moves) {
115
119
  return { kind: 'changes', changes: bodies(path, before, after) };
116
120
  }
117
121
  function partKey(change) {
118
- return change.kind === 'section' ? `section ${change.name}` : change.kind;
122
+ return change.kind === 'section' || change.kind === 'field'
123
+ ? `${change.kind} ${change.name}`
124
+ : change.kind;
125
+ }
126
+ const NAMES_A_SKIP_TESTS = 64;
127
+ const ENCODED = /[%&\\]/u;
128
+ function movedNames(moves) {
129
+ const names = new Set([...moves.keys()].map((path) => path.slice(path.lastIndexOf('/') + 1)));
130
+ return names.size > NAMES_A_SKIP_TESTS ? null : [...names];
131
+ }
132
+ function cannotLink(text, names) {
133
+ return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
119
134
  }
120
135
  function* rewrites(changes, visible) {
121
136
  const moves = new Map();
@@ -142,17 +157,20 @@ function* rewrites(changes, visible) {
142
157
  }
143
158
  if (moves.size === 0)
144
159
  return { kind: 'changes', changes };
160
+ const names = movedNames(moves);
145
161
  const merged = [...changes];
146
162
  const added = [];
147
163
  for (const [path, document] of yield* documentsOf(visible.collection)) {
148
164
  if (settled.has(path))
149
165
  continue;
150
166
  const moved = moves.get(path) ?? path;
151
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
167
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
152
168
  const repointing = repointed(document, definition, path, moved, moves, settled);
153
169
  added.push(...repointing);
154
170
  const own = parts.get(path) ?? [];
155
- const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
171
+ const relinked = moved === path && own.length === 0 && cannotLink(document.content, names)
172
+ ? { kind: 'changes', changes: [] }
173
+ : relinks(path, moved, edited(document, own.map((index) => merged[index]), definition), moves, definition);
156
174
  if (relinked.kind === 'refused')
157
175
  return relinked;
158
176
  const keyed = new Map();
@@ -165,7 +183,7 @@ function* rewrites(changes, visible) {
165
183
  if (change.kind === 'replace') {
166
184
  for (const index of own) {
167
185
  const kind = merged[index]?.kind;
168
- if (kind === 'section' || kind === 'preamble')
186
+ if (kind === 'field' || kind === 'section' || kind === 'preamble')
169
187
  merged[index] = null;
170
188
  }
171
189
  added.push(change);
@@ -221,25 +239,21 @@ function crossed(document, definition, path, to) {
221
239
  }
222
240
  return changes;
223
241
  }
224
- function rewritten(path, document, changes) {
242
+ function rewritten(path, document, changes, definition) {
225
243
  if (changes.length === 0)
226
244
  return [];
227
- const applied = apply(document, changes);
245
+ const applied = apply(document, changes, definition);
228
246
  if (applied.outcome !== 'applied')
229
247
  return [];
230
248
  const after = applied.document;
231
249
  if (document.type === undefined)
232
250
  return [{ kind: 'replace', path, content: serialize(after) }];
233
- const names = new Set(changes.flatMap((change) => change.kind === 'reference' ? [pointerSegments(change.pointer)[0] ?? ''] : []));
234
- return [
235
- ...[...names].map((name) => ({ kind: 'field', path, name, value: after.fields[name] })),
236
- ...bodies(path, document, after),
237
- ];
251
+ return bodies(path, document, after);
238
252
  }
239
253
  function roomPathOf(handle, path) {
240
254
  return `/rooms/${handle}/${path}`;
241
255
  }
242
- export function* leavingRewritten(batch, visible) {
256
+ function* leavingRewritten(batch, visible) {
243
257
  const between = batch.between;
244
258
  if (between === undefined)
245
259
  return batch;
@@ -248,8 +262,8 @@ export function* leavingRewritten(batch, visible) {
248
262
  for (const [path, document] of yield* documentsOf(visible.collection)) {
249
263
  if (moved.has(path))
250
264
  continue;
251
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
252
- derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named))));
265
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
266
+ derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named)), definition));
253
267
  yield ROW_WEIGHT + visible.collection.text[path].length;
254
268
  }
255
269
  if (derived.length === 0)
@@ -291,7 +305,7 @@ function* incoming(collection, removed, candidates) {
291
305
  for (const [path, document] of candidates === null
292
306
  ? yield* documentsOf(collection)
293
307
  : read(candidates)) {
294
- const definition = definitionOf(governing(collection.composition, path), document.type);
308
+ const definition = definitionsAt(collection.composition, path)(document.type);
295
309
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
296
310
  if (to.includes(ATTACHMENT))
297
311
  continue;
@@ -321,6 +335,8 @@ function* incoming(collection, removed, candidates) {
321
335
  function affected(change) {
322
336
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
323
337
  }
338
+ const WRITE = ['write'];
339
+ const MOVE_OUT = ['write', 'erase'];
324
340
  function guard(changes, input, removing) {
325
341
  const overridden = new Map();
326
342
  const existing = {
@@ -329,21 +345,17 @@ function guard(changes, input, removing) {
329
345
  add: (path) => void overridden.set(path, true),
330
346
  delete: (path) => void overridden.set(path, false),
331
347
  };
348
+ const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
332
349
  for (const [index, change] of changes.entries()) {
333
350
  for (const path of affected(change)) {
334
- const normalized = normalize(path);
335
- if (normalized === null || normalized === '')
336
- return refuse('room-root', path);
337
- if (covered(input.providerOwnedPaths, normalized)) {
338
- return refuse('provider-owned-path', normalized);
339
- }
340
- const operation = change.kind === 'remove' ? removing : 'write';
341
- if (!input.local && !covered(input.operations[operation], normalized))
342
- return refuse(`${operation}-grant`, normalized);
351
+ const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
352
+ if (refused !== null)
353
+ return refuse(refused.code, refused.path);
343
354
  }
344
355
  if (change.kind === 'create' || change.kind === 'arrival') {
345
- if (existing.has(change.path))
346
- return refuse('path-collision', change.path);
356
+ const taken = existing.has(change.path) ? change.path : occupied(change.path);
357
+ if (taken !== null)
358
+ return refuse('path-collision', taken);
347
359
  existing.add(change.path);
348
360
  continue;
349
361
  }
@@ -353,6 +365,9 @@ function guard(changes, input, removing) {
353
365
  input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
354
366
  return refuse('path-collision', change.path);
355
367
  }
368
+ const taken = occupied(change.path);
369
+ if (taken !== null)
370
+ return refuse('path-collision', taken);
356
371
  if (!input.local &&
357
372
  existing.has(change.path) &&
358
373
  input.visible.revisions[departure(changes, index)] === undefined) {
@@ -364,8 +379,9 @@ function guard(changes, input, removing) {
364
379
  if (change.kind === 'move') {
365
380
  if (!existing.has(change.path))
366
381
  return refuse('path-absent', change.path);
367
- if (existing.has(change.to))
368
- return refuse('path-collision', change.to);
382
+ const taken = existing.has(change.to) ? change.to : occupied(change.to);
383
+ if (taken !== null)
384
+ return refuse('path-collision', taken);
369
385
  if (unrevised(input, change.path)) {
370
386
  return refuse('unknown-revision', change.path);
371
387
  }
@@ -395,9 +411,9 @@ function guard(changes, input, removing) {
395
411
  return null;
396
412
  }
397
413
  function unrevised(input, path) {
398
- return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
414
+ return !input.local && !input.queued.has(path) && input.visible.revisions[path] === undefined;
399
415
  }
400
- export function queuedDocuments(queue) {
416
+ function queuedDocuments(queue) {
401
417
  const paths = new Set();
402
418
  for (const batch of queue) {
403
419
  for (const change of batch.changes) {
@@ -533,56 +549,69 @@ function* exceeding(limits, changes, effective, resulting) {
533
549
  function shellRelative(text, path, handle, definition) {
534
550
  if (!text.includes(`/rooms/${handle}/`))
535
551
  return null;
536
- const parsed = parse(text, path);
552
+ const parsed = parse(text, path, definition);
537
553
  if (!parsed.ok)
538
554
  return null;
539
555
  const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
540
556
  return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
541
557
  }
542
558
  function relativeField(path, name, value, handle, definition) {
543
- const opened = parse('---\n---\n');
544
- if (definition === undefined ||
545
- !opened.ok ||
546
- !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
559
+ if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
547
560
  return value;
548
- const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
561
+ const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
549
562
  if (held.outcome !== 'applied')
550
563
  return value;
551
564
  const result = relativize(held.document, path, handle, definition);
552
565
  return result.outcome === 'applied' ? result.document.fields[name] : value;
553
566
  }
554
567
  function relativeChanges(input) {
568
+ const changes = [];
569
+ for (const change of input.changes) {
570
+ const next = relativeChange(change, input);
571
+ if ('refusal' in next)
572
+ return next;
573
+ changes.push(next.change);
574
+ }
575
+ return { kind: 'changes', changes };
576
+ }
577
+ function relativeChange(change, input) {
555
578
  const { handle, composition, visible } = input;
556
- const typed = (path) => (type) => definitionOf(governing(composition, path), type);
557
579
  const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
558
- return input.changes.map((change) => {
559
- const definition = typed(change.path);
560
- if (change.kind === 'field') {
561
- const type = visible.collection.document(change.path)?.type;
562
- const value = relativeField(change.path, change.name, change.value, handle, definition(type));
563
- return value === change.value ? change : { ...change, value };
564
- }
565
- if (change.kind === 'section' || change.kind === 'preamble') {
566
- const value = body(change.path, change.value);
567
- return value === change.value ? change : { ...change, value };
568
- }
569
- if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
570
- const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
571
- return content === change.content ? change : { ...change, content };
572
- }
573
- if (change.kind !== 'create')
574
- return change;
575
- const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
576
- const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
577
- name,
578
- relativeField(change.path, name, value, handle, definition(type)),
579
- ]));
580
- const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
581
- return { ...change, fields, sections };
582
- });
580
+ const definition = definitionsAt(composition, change.path);
581
+ if (change.kind === 'field') {
582
+ const type = visible.collection.document(change.path)?.type;
583
+ const value = relativeField(change.path, change.name, change.value, handle, definition(type));
584
+ return { change: value === change.value ? change : { ...change, value } };
585
+ }
586
+ if (change.kind === 'section' || change.kind === 'preamble') {
587
+ const value = body(change.path, change.value);
588
+ return { change: value === change.value ? change : { ...change, value } };
589
+ }
590
+ if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
591
+ const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
592
+ return { change: content === change.content ? change : { ...change, content } };
593
+ }
594
+ if (change.kind !== 'create')
595
+ return { change };
596
+ const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
597
+ const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
598
+ name,
599
+ relativeField(change.path, name, value, handle, definition(type)),
600
+ ]));
601
+ const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
602
+ const written = composed(change.path, fields, sections, composition);
603
+ if (written.outcome === 'refuse')
604
+ return refusedBy(written.refusal, change.path);
605
+ if (written.outcome === 'stale')
606
+ throw new Error(`the creation of ${change.path} is stale`);
607
+ if (Object.keys(sections).length === 0)
608
+ return { change: { ...change, fields, sections } };
609
+ return {
610
+ change: { kind: 'create', path: change.path, content: serialize(written.document) },
611
+ };
583
612
  }
584
613
  function unqueueable(file) {
585
- return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
614
+ return refuse('unknown-revision', file);
586
615
  }
587
616
  function isPart(change) {
588
617
  return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
@@ -590,8 +619,29 @@ function isPart(change) {
590
619
  function touched(change) {
591
620
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
592
621
  }
593
- function partsApplied(content, path, changes) {
594
- const parsed = parse(content, path);
622
+ function partsApplied(content, path, changes, composition) {
623
+ const applied = partsResult(content, path, changes, composition);
624
+ return applied?.outcome === 'applied' ? serialize(applied.document) : null;
625
+ }
626
+ function unapplied(changes, visible, composition) {
627
+ const parted = new Map();
628
+ for (const change of changes) {
629
+ if (isPart(change))
630
+ parted.set(change.path, [...(parted.get(change.path) ?? []), change]);
631
+ }
632
+ for (const [path, parts] of parted) {
633
+ const content = visible.documents[path];
634
+ if (content === undefined)
635
+ continue;
636
+ const applied = partsResult(content, path, parts, composition);
637
+ if (applied?.outcome === 'refuse')
638
+ return refusedBy(applied.refusal, path);
639
+ }
640
+ return null;
641
+ }
642
+ function partsResult(content, path, changes, composition) {
643
+ const definitions = definitionsAt(composition, path);
644
+ const parsed = parse(content, path, definitions);
595
645
  if (!parsed.ok)
596
646
  return null;
597
647
  const document = parsed.document;
@@ -614,12 +664,11 @@ function partsApplied(content, path, changes) {
614
664
  else
615
665
  parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
616
666
  }
617
- const applied = apply(document, [...parts.values()]);
618
- return applied.outcome === 'applied' ? serialize(applied.document) : null;
667
+ return apply(document, [...parts.values()], definitions(document.type));
619
668
  }
620
- function settledParts(changes, deferred) {
669
+ function settledParts(changes, deferred, composition) {
621
670
  for (const [held, parts] of deferred) {
622
- const content = partsApplied(held.content, held.path, parts);
671
+ const content = partsApplied(held.content, held.path, parts, composition);
623
672
  if (content === null)
624
673
  return held.path;
625
674
  changes[changes.indexOf(held)] = { ...held, content };
@@ -694,7 +743,9 @@ function* relinked(batch, move, before, after, appending) {
694
743
  for (const change of derived.changes) {
695
744
  if (change === move)
696
745
  continue;
697
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
746
+ const unsettled = isPart(change)
747
+ ? null
748
+ : settledParts(changes, deferred, after.collection.composition);
698
749
  if (unsettled !== null)
699
750
  return unqueueable(unsettled);
700
751
  const outcome = folded(changes, change, appending, deferred);
@@ -705,7 +756,7 @@ function* relinked(batch, move, before, after, appending) {
705
756
  if (outcome !== 'skipped')
706
757
  changed = true;
707
758
  }
708
- const unsettled = settledParts(changes, deferred);
759
+ const unsettled = settledParts(changes, deferred, after.collection.composition);
709
760
  if (unsettled !== null)
710
761
  return unqueueable(unsettled);
711
762
  if (!changed)
@@ -766,11 +817,10 @@ function withdrawnFile(batch, change, at) {
766
817
  ];
767
818
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
768
819
  }
769
- export function* requeuing(queue, changes, visible, changeable, state) {
820
+ function* requeuing(queue, changes, visible, changeable, state) {
770
821
  const next = [...queue];
771
822
  const origins = new Map();
772
823
  const withdrawn = new Map();
773
- const owners = new Set();
774
824
  const queued = new Set();
775
825
  const kept = [];
776
826
  const originOf = (batch) => origins.get(batch) ?? batch.id;
@@ -816,7 +866,6 @@ export function* requeuing(queue, changes, visible, changeable, state) {
816
866
  if (replaced !== null)
817
867
  origins.set(replaced, origin);
818
868
  }
819
- owners.add(originOf(owner));
820
869
  next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
821
870
  queued.add(change.path);
822
871
  }
@@ -826,56 +875,13 @@ export function* requeuing(queue, changes, visible, changeable, state) {
826
875
  changes: kept,
827
876
  queued,
828
877
  withdrawn,
829
- owners,
830
878
  };
831
879
  }
832
- function covering(precondition) {
833
- return precondition.kind === 'field' ||
834
- precondition.kind === 'section' ||
835
- precondition.kind === 'preamble'
836
- ? identity(precondition)
837
- : `path ${precondition.path}`;
838
- }
839
- export function joined(queue, owner, extra) {
840
- const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
841
- const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
842
- if (clash !== undefined)
843
- return unqueueable(clash);
844
- const changes = [...owner.changes];
845
- const deferred = new Map();
846
- for (const change of extra.changes) {
847
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
848
- if (unsettled !== null)
849
- return unqueueable(unsettled);
850
- if (folded(changes, change, true, deferred) === 'refused')
851
- return unqueueable(change.path);
852
- }
853
- const unsettled = settledParts(changes, deferred);
854
- if (unsettled !== null)
855
- return unqueueable(unsettled);
856
- const held = new Set(owner.preconditions.map(covering));
857
- const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
858
- const preconditions = [
859
- ...owner.preconditions,
860
- ...extra.preconditions.filter((precondition) => {
861
- const key = covering(precondition);
862
- return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
863
- }),
864
- ];
865
- const batch = immutable({
866
- ...owner,
867
- ...(extra.validation === undefined ? {} : { validation: extra.validation }),
868
- id: changeBatchIdentity(),
869
- changes,
870
- preconditions,
871
- });
872
- return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
873
- }
874
- export function* limiting(queue, batch, limits, state) {
880
+ function* limiting(queue, batch, limits, state) {
875
881
  const at = queue.indexOf(batch);
876
882
  const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
877
883
  const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
878
- return limited?.kind === 'refused' ? limited.refusal : null;
884
+ return limited?.refusal ?? null;
879
885
  }
880
886
  function typedText(content) {
881
887
  if (content === undefined)
@@ -892,7 +898,7 @@ function touchedLibrary(resulting, touched, composition) {
892
898
  })
893
899
  .sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
894
900
  const contents = new Map(entries.map(({ path, content }) => [path, content]));
895
- const binaries = new Set(Object.keys(resulting.binaries).filter((path) => resulting.binaries[path] !== undefined));
901
+ const binaries = new Set(Array.from(entriesIn(resulting.binaries), ([path]) => path));
896
902
  return validatedCollection(() => Promise.resolve({ entries, next: null }), async (paths) => {
897
903
  const known = new Map();
898
904
  const pace = paced();
@@ -901,7 +907,7 @@ function touchedLibrary(resulting, touched, composition) {
901
907
  if (binaries.has(path))
902
908
  known.set(path, { type: undefined, binary: true });
903
909
  else if (content !== undefined && content !== null) {
904
- const read = parse(content, path);
910
+ const read = parse(content, path, definitionsAt(composition, path));
905
911
  if (read.ok)
906
912
  known.set(path, { type: read.document.type ?? 'any', binary: false });
907
913
  await pace(content.length + ROW_WEIGHT);
@@ -939,6 +945,7 @@ async function judging(visible, resulting, composition, validate, removed, touch
939
945
  retryable: false,
940
946
  file: verdict.file,
941
947
  ...(verdict.location === undefined ? {} : { location: verdict.location }),
948
+ ...(verdict.line === undefined ? {} : { line: verdict.line }),
942
949
  ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
943
950
  },
944
951
  };
@@ -954,8 +961,6 @@ function placed(reference) {
954
961
  const key = roomPathOf(reference.handle, reference.path);
955
962
  if (reference.kind === 'link')
956
963
  return `link ${key}`;
957
- if (reference.kind === 'item')
958
- return `section ${reference.section} ${key}`;
959
964
  const pointer = reference.pointer
960
965
  .split('/')
961
966
  .map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
@@ -965,10 +970,11 @@ function placed(reference) {
965
970
  function roomReferencesIn(path, content, composition) {
966
971
  if (content === undefined)
967
972
  return [];
968
- const parsed = parse(content, path);
973
+ const definitions = definitionsAt(composition, path);
974
+ const parsed = parse(content, path, definitions);
969
975
  if (!parsed.ok)
970
976
  return [];
971
- return roomReferences(path, content, definitionOf(governing(composition, path), parsed.document.type));
977
+ return roomReferences(parsed.document, definitions(parsed.document.type));
972
978
  }
973
979
  function* admittedRooms(changes, visible, resulting, composition) {
974
980
  const from = new Map();
@@ -986,7 +992,7 @@ function* admittedRooms(changes, visible, resulting, composition) {
986
992
  continue;
987
993
  if (held.has(placed(reference)))
988
994
  continue;
989
- return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
995
+ return refuse('unresolved-reference', path, reference.pointer);
990
996
  }
991
997
  yield ROW_WEIGHT + now.length;
992
998
  }
@@ -1004,7 +1010,10 @@ function validationOf(validated) {
1004
1010
  };
1005
1011
  }
1006
1012
  async function batching(input) {
1007
- const changes = relativeChanges(input);
1013
+ const relative = relativeChanges(input);
1014
+ if (relative.kind === 'refused')
1015
+ return relative;
1016
+ const { changes } = relative;
1008
1017
  const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
1009
1018
  if (structural !== undefined && input.unmodeled) {
1010
1019
  return refuse('model-unavailable', structural.path);
@@ -1014,12 +1023,12 @@ async function batching(input) {
1014
1023
  const rewritten = await stepped(rewrites(changes, input.visible));
1015
1024
  if (rewritten.kind === 'refused')
1016
1025
  return rewritten;
1017
- const refusal = guard(changes, input, 'delete');
1026
+ const refusal = guard(changes, input, WRITE) ?? unapplied(changes, input.visible, input.composition);
1018
1027
  if (refusal !== null)
1019
1028
  return refusal;
1020
1029
  const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
1021
1030
  const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
1022
- const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }]));
1031
+ const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }], input.composition));
1023
1032
  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))]);
1024
1033
  if (judged.kind === 'refused')
1025
1034
  return judged;
@@ -1041,7 +1050,7 @@ async function batching(input) {
1041
1050
  }),
1042
1051
  };
1043
1052
  }
1044
- export const MOVE_DOCUMENTS = 'move-documents';
1053
+ const MOVE_DOCUMENTS = 'move-documents';
1045
1054
  function heldAt(visible, path) {
1046
1055
  return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
1047
1056
  }
@@ -1050,7 +1059,7 @@ function arrivingText(path, to, source, moved) {
1050
1059
  const document = source.visible.collection.document(path);
1051
1060
  if (document === undefined)
1052
1061
  return content;
1053
- const definition = definitionOf(governing(source.composition, path), document.type);
1062
+ const definition = definitionsAt(source.composition, path)(document.type);
1054
1063
  const changes = crossed(document, definition, path, (named, href) => {
1055
1064
  const at = moved.get(named);
1056
1065
  if (at !== undefined)
@@ -1059,13 +1068,14 @@ function arrivingText(path, to, source, moved) {
1059
1068
  });
1060
1069
  if (changes.length === 0)
1061
1070
  return content;
1062
- const applied = apply(document, changes);
1071
+ const applied = apply(document, changes, definition);
1063
1072
  return applied.outcome === 'applied' ? serialize(applied.document) : content;
1064
1073
  }
1065
1074
  function arrivalOf({ path, to }, source, moved) {
1066
- const { documents, revisions, fileIds, changes, binaries } = source.visible;
1075
+ const { documents, revisions, fileIds, created, changes, binaries } = source.visible;
1067
1076
  const kept = {
1068
1077
  ...(fileIds[path] === undefined ? {} : { fileId: fileIds[path] }),
1078
+ ...(created[path] === undefined ? {} : { created: created[path] }),
1069
1079
  ...(changes[path] === undefined ? {} : { change: changes[path] }),
1070
1080
  };
1071
1081
  const heads = {
@@ -1092,10 +1102,8 @@ function arrivalOf({ path, to }, source, moved) {
1092
1102
  }
1093
1103
  async function moving(input) {
1094
1104
  const { source, destination, moves } = input;
1095
- if (moves.length === 0)
1096
- return refuse('no-change');
1097
1105
  const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1098
- const leaves = guard(removals, { ...source, local: input.local, bases: [] }, 'write');
1106
+ const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
1099
1107
  if (leaves !== null)
1100
1108
  return leaves;
1101
1109
  const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
@@ -1103,7 +1111,7 @@ async function moving(input) {
1103
1111
  return refuse('unknown-revision', uploading.path);
1104
1112
  const moved = new Map(moves.map(({ path, to }) => [path, to]));
1105
1113
  const arrivals = moves.map((move) => arrivalOf(move, source, moved));
1106
- const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, 'write');
1114
+ const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, WRITE);
1107
1115
  if (arrives !== null)
1108
1116
  return arrives;
1109
1117
  const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
@@ -1132,7 +1140,7 @@ async function moving(input) {
1132
1140
  between,
1133
1141
  };
1134
1142
  const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1135
- const left = await stepped(overlaying(source.visible, [effective]));
1143
+ const left = await stepped(overlaying(source.visible, [effective], source.composition));
1136
1144
  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))]);
1137
1145
  if (sourceJudged.kind === 'refused')
1138
1146
  return sourceJudged;
@@ -1144,7 +1152,7 @@ async function moving(input) {
1144
1152
  ...written,
1145
1153
  between,
1146
1154
  };
1147
- const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1155
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch], destination.composition));
1148
1156
  const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1149
1157
  if (destinationJudged.kind === 'refused')
1150
1158
  return destinationJudged;
@@ -1154,9 +1162,180 @@ async function moving(input) {
1154
1162
  return limited;
1155
1163
  return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1156
1164
  }
1157
- export function buildMove(input) {
1158
- return moving(input);
1165
+ function unreadable(saved, visible, composition) {
1166
+ const path = saved.find(({ path: at, parts }) => (parts.fields !== undefined ||
1167
+ parts.sections !== undefined ||
1168
+ parts.preamble !== undefined) &&
1169
+ typedText(visible.documents[at]) &&
1170
+ governing(composition, at).status === 'unavailable')?.path;
1171
+ return path === undefined ? null : refuse('model-unavailable', path);
1172
+ }
1173
+ function changedUnderneath(saved, visible) {
1174
+ const parts = saved.flatMap(({ path, parts: held }) => changedParts(path, held, visible));
1175
+ return parts.length === 0
1176
+ ? null
1177
+ : { kind: 'refused', refusal: { code: 'changed-underneath', retryable: false, parts } };
1178
+ }
1179
+ function sideOf(room, visible) {
1180
+ const { record } = room;
1181
+ return {
1182
+ room: record.locator,
1183
+ handle: record.declaration.handle,
1184
+ baseRoomVersion: record.roomVersion,
1185
+ composition: room.composition,
1186
+ rule: room.rule,
1187
+ visible,
1188
+ folders: room.folders,
1189
+ queued: queuedDocuments(room.queue),
1190
+ unmodeled: !room.modelled,
1191
+ validate: room.validate,
1192
+ };
1193
+ }
1194
+ function carriedDrafts(ask, from, into) {
1195
+ return new Map(ask.saved.flatMap(({ path, parts }) => {
1196
+ const record = from.drafts.get(path);
1197
+ if (parts.move === undefined || record === undefined || ask.consumed.has(path))
1198
+ return [];
1199
+ const to = moveTarget(parts.move);
1200
+ if (into.drafts.has(to))
1201
+ return [];
1202
+ const locator = into.record.locator;
1203
+ return [[path, { ...record, locator, path: to, revision: roomDraftRevision() }]];
1204
+ }));
1205
+ }
1206
+ async function plannedMove(ask, source, into) {
1207
+ const destination = into.record.locator;
1208
+ const mixed = ask.saved.find(({ parts }) => Object.keys(parts).length !== 1 || parts.move?.room !== destination);
1209
+ if (mixed !== undefined)
1210
+ return refuse('mixed-room-move', mixed.path);
1211
+ const leavingVisible = await stepped(source.replay(source.queue));
1212
+ const changed = changedUnderneath(ask.saved, leavingVisible);
1213
+ if (changed !== null)
1214
+ return changed;
1215
+ const moves = ask.saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
1216
+ const built = await moving({
1217
+ source: sideOf(source, leavingVisible),
1218
+ destination: sideOf(into, await stepped(into.replay(into.queue))),
1219
+ moves,
1220
+ providerLimits: ask.providerLimits,
1221
+ local: ask.local,
1222
+ ...(ask.writtenBy === undefined ? {} : { writtenBy: ask.writtenBy }),
1223
+ });
1224
+ if (built.kind === 'refused')
1225
+ return built;
1226
+ const { id } = built.leaving;
1227
+ const carried = carriedDrafts(ask, source, into);
1228
+ return {
1229
+ kind: 'planned',
1230
+ identifier: id,
1231
+ rooms: [
1232
+ {
1233
+ locator: source.record.locator,
1234
+ queue: [...source.queue, built.leaving],
1235
+ saved: new Map([[id, ask.saved]]),
1236
+ consumed: [...ask.consumed, ...carried.keys()],
1237
+ carried: [],
1238
+ },
1239
+ {
1240
+ locator: destination,
1241
+ queue: [...into.queue, built.arriving],
1242
+ saved: new Map([
1243
+ [id, [...carried.values()].map(({ path }) => ({ path, parts: {} }))],
1244
+ ]),
1245
+ consumed: [],
1246
+ carried: [...carried.values()],
1247
+ },
1248
+ ],
1249
+ };
1250
+ }
1251
+ async function plannedWithin(ask, room) {
1252
+ const { record } = room;
1253
+ const visible = await stepped(room.replay(room.queue));
1254
+ const changed = unreadable(ask.saved, visible, room.composition) ?? changedUnderneath(ask.saved, visible);
1255
+ if (changed !== null)
1256
+ return changed;
1257
+ const parts = ask.saved.map(({ path, parts: held }) => changesOf(path, held));
1258
+ const changes = parts.flatMap(({ changes: made }) => made);
1259
+ const requeue = await stepped(requeuing(room.queue, changes, visible, (batch) => room.changeable.has(batch.id), room.replay));
1260
+ if (requeue.kind === 'refused')
1261
+ return requeue;
1262
+ const foreign = [...requeue.withdrawn.keys()].some((origin) => {
1263
+ const writer = room.queue.find((batch) => batch.id === origin)?.writtenBy;
1264
+ return writer?.app !== ask.writtenBy?.app || writer?.release !== ask.writtenBy?.release;
1265
+ });
1266
+ if (foreign)
1267
+ return unqueueable([...requeue.queued].at(-1));
1268
+ const input = {
1269
+ local: ask.local,
1270
+ baseRoomVersion: record.roomVersion,
1271
+ composition: room.composition,
1272
+ rule: room.rule,
1273
+ providerLimits: ask.providerLimits,
1274
+ handle: record.declaration.handle,
1275
+ visible,
1276
+ folders: room.folders,
1277
+ changes,
1278
+ bases: parts.flatMap(({ bases }) => bases),
1279
+ queued: queuedDocuments(room.queue),
1280
+ writtenBy: ask.writtenBy,
1281
+ unmodeled: !room.modelled,
1282
+ validate: room.validate,
1283
+ };
1284
+ const rewritten = requeue.withdrawn.size > 0;
1285
+ if (rewritten) {
1286
+ const whole = await batching({
1287
+ ...input,
1288
+ providerLimits: [],
1289
+ queued: new Set([...input.queued, ...requeue.queued]),
1290
+ });
1291
+ if (whole.kind === 'refused')
1292
+ return whole;
1293
+ }
1294
+ const own = !rewritten
1295
+ ? await batching(input)
1296
+ : requeue.changes.length === 0
1297
+ ? null
1298
+ : await batching({
1299
+ ...input,
1300
+ visible: await stepped(room.replay(requeue.queue)),
1301
+ changes: requeue.changes,
1302
+ queued: queuedDocuments(requeue.queue),
1303
+ });
1304
+ if (own?.kind === 'refused')
1305
+ return own;
1306
+ const queue = own === null ? requeue.queue : [...requeue.queue, own.batch];
1307
+ const saved = new Map();
1308
+ for (const [origin, rebuilt] of requeue.withdrawn) {
1309
+ if (rebuilt === null)
1310
+ continue;
1311
+ const limited = await stepped(limiting(queue, rebuilt, ask.providerLimits, room.replay));
1312
+ if (limited !== null)
1313
+ return { kind: 'refused', refusal: limited };
1314
+ saved.set(rebuilt.id, withdrawnDrafts(room.saved.get(origin) ?? [], changes));
1315
+ }
1316
+ if (own !== null) {
1317
+ saved.set(own.batch.id, ask.saved.filter(({ path }) => !requeue.queued.has(path)));
1318
+ }
1319
+ const carried = carriedDrafts(ask, room, room);
1320
+ return {
1321
+ kind: 'planned',
1322
+ identifier: own?.batch.id ??
1323
+ [...requeue.withdrawn].map(([origin, rebuilt]) => rebuilt?.id ?? origin).at(-1),
1324
+ rooms: [
1325
+ {
1326
+ locator: record.locator,
1327
+ queue,
1328
+ saved,
1329
+ consumed: [...ask.consumed, ...carried.keys()],
1330
+ carried: [...carried.values()],
1331
+ },
1332
+ ],
1333
+ };
1334
+ }
1335
+ export function destination(locator, saved) {
1336
+ const into = saved.find(({ parts }) => parts.move?.room !== undefined && parts.move.room !== locator);
1337
+ return into?.parts.move?.room ?? null;
1159
1338
  }
1160
- export function buildBatch(input) {
1161
- return batching(input);
1339
+ export function planSave(ask, room, into) {
1340
+ return into === undefined ? plannedWithin(ask, room) : plannedMove(ask, room, into);
1162
1341
  }