@agentshouse/mdruntime 0.1.0-cd26b960b7262 → 0.1.0-cd30853a1b15e

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 (257) hide show
  1. package/README.md +21 -19
  2. package/dist/acceptance.d.ts +0 -3
  3. package/dist/acceptance.d.ts.map +1 -1
  4. package/dist/acceptance.js +72 -158
  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 +8 -2
  9. package/dist/actions.d.ts.map +1 -1
  10. package/dist/actions.js +118 -67
  11. package/dist/adapter.d.ts +81 -56
  12. package/dist/adapter.d.ts.map +1 -1
  13. package/dist/attached-model.d.ts +0 -1
  14. package/dist/attached-model.d.ts.map +1 -1
  15. package/dist/attached-model.js +0 -5
  16. package/dist/authored.d.ts +88 -0
  17. package/dist/authored.d.ts.map +1 -0
  18. package/dist/authored.js +345 -0
  19. package/dist/bootstrap.d.ts +1 -1
  20. package/dist/bootstrap.d.ts.map +1 -1
  21. package/dist/bootstrap.js +6 -19
  22. package/dist/change-set.d.ts +4 -0
  23. package/dist/change-set.d.ts.map +1 -1
  24. package/dist/change-set.js +6 -0
  25. package/dist/change.d.ts +4 -0
  26. package/dist/change.d.ts.map +1 -1
  27. package/dist/change.js +3 -0
  28. package/dist/chat-types.d.ts +0 -2
  29. package/dist/chat-types.d.ts.map +1 -1
  30. package/dist/chat-types.js +0 -2
  31. package/dist/client.d.ts +10 -19
  32. package/dist/client.d.ts.map +1 -1
  33. package/dist/client.js +30 -18
  34. package/dist/collection.d.ts +25 -4
  35. package/dist/collection.d.ts.map +1 -1
  36. package/dist/collection.js +19 -2
  37. package/dist/column.d.ts +5 -0
  38. package/dist/column.d.ts.map +1 -0
  39. package/dist/column.js +9 -0
  40. package/dist/compile.d.ts +2 -0
  41. package/dist/compile.d.ts.map +1 -1
  42. package/dist/compile.js +17 -5
  43. package/dist/connection.d.ts.map +1 -1
  44. package/dist/connection.js +31 -24
  45. package/dist/context.d.ts.map +1 -1
  46. package/dist/context.js +1 -1
  47. package/dist/conversation-live.d.ts +26 -12
  48. package/dist/conversation-live.d.ts.map +1 -1
  49. package/dist/conversation-live.js +23 -12
  50. package/dist/conversation-row.d.ts +8 -1
  51. package/dist/conversation-row.d.ts.map +1 -1
  52. package/dist/conversation-row.js +20 -7
  53. package/dist/conversation.d.ts +32 -10
  54. package/dist/conversation.d.ts.map +1 -1
  55. package/dist/conversation.js +317 -232
  56. package/dist/declaration.d.ts +10 -0
  57. package/dist/declaration.d.ts.map +1 -0
  58. package/dist/declaration.js +24 -0
  59. package/dist/discovery.d.ts.map +1 -1
  60. package/dist/discovery.js +19 -44
  61. package/dist/durability.d.ts +4 -0
  62. package/dist/durability.d.ts.map +1 -1
  63. package/dist/durability.js +4 -0
  64. package/dist/entry.js +1 -1
  65. package/dist/expectation.d.ts +25 -14
  66. package/dist/expectation.d.ts.map +1 -1
  67. package/dist/expectation.js +51 -20
  68. package/dist/failure.d.ts +3 -0
  69. package/dist/failure.d.ts.map +1 -1
  70. package/dist/failure.js +6 -0
  71. package/dist/figures.d.ts.map +1 -1
  72. package/dist/figures.js +4 -2
  73. package/dist/file.d.ts.map +1 -1
  74. package/dist/file.js +2 -1
  75. package/dist/host.d.ts +18 -2
  76. package/dist/host.d.ts.map +1 -1
  77. package/dist/host.js +55 -15
  78. package/dist/index.d.ts +15 -10
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +12 -7
  81. package/dist/install.d.ts +10 -6
  82. package/dist/install.d.ts.map +1 -1
  83. package/dist/install.js +92 -109
  84. package/dist/lifecycle.d.ts +1 -1
  85. package/dist/lifecycle.d.ts.map +1 -1
  86. package/dist/lifecycle.js +54 -40
  87. package/dist/listing.d.ts +11 -12
  88. package/dist/listing.d.ts.map +1 -1
  89. package/dist/listing.js +48 -72
  90. package/dist/live.d.ts +5 -3
  91. package/dist/live.d.ts.map +1 -1
  92. package/dist/live.js +74 -31
  93. package/dist/loaded.d.ts +5 -0
  94. package/dist/loaded.d.ts.map +1 -0
  95. package/dist/loaded.js +40 -0
  96. package/dist/locks.d.ts +2 -4
  97. package/dist/locks.d.ts.map +1 -1
  98. package/dist/locks.js +10 -39
  99. package/dist/lost.d.ts.map +1 -1
  100. package/dist/lost.js +11 -23
  101. package/dist/media.d.ts +5 -0
  102. package/dist/media.d.ts.map +1 -1
  103. package/dist/media.js +65 -14
  104. package/dist/memory.d.ts +7 -3
  105. package/dist/memory.d.ts.map +1 -1
  106. package/dist/memory.js +56 -26
  107. package/dist/messages.d.ts +12 -6
  108. package/dist/messages.d.ts.map +1 -1
  109. package/dist/messages.js +25 -27
  110. package/dist/model.d.ts +0 -1
  111. package/dist/model.d.ts.map +1 -1
  112. package/dist/model.js +0 -5
  113. package/dist/models.d.ts +4 -8
  114. package/dist/models.d.ts.map +1 -1
  115. package/dist/models.js +97 -139
  116. package/dist/observation.d.ts +27 -12
  117. package/dist/observation.d.ts.map +1 -1
  118. package/dist/observation.js +173 -69
  119. package/dist/opened.d.ts +1 -1
  120. package/dist/opened.d.ts.map +1 -1
  121. package/dist/opened.js +2 -2
  122. package/dist/opening.d.ts.map +1 -1
  123. package/dist/opening.js +5 -9
  124. package/dist/outcome.d.ts +7 -0
  125. package/dist/outcome.d.ts.map +1 -1
  126. package/dist/outcome.js +23 -2
  127. package/dist/overlay.d.ts +6 -2
  128. package/dist/overlay.d.ts.map +1 -1
  129. package/dist/overlay.js +37 -24
  130. package/dist/page-metadata.d.ts +8 -0
  131. package/dist/page-metadata.d.ts.map +1 -0
  132. package/dist/page-metadata.js +19 -0
  133. package/dist/parts.d.ts +3 -59
  134. package/dist/parts.d.ts.map +1 -1
  135. package/dist/parts.js +10 -123
  136. package/dist/preparation.d.ts +27 -6
  137. package/dist/preparation.d.ts.map +1 -1
  138. package/dist/preparation.js +154 -66
  139. package/dist/presence.d.ts +58 -0
  140. package/dist/presence.d.ts.map +1 -0
  141. package/dist/presence.js +231 -0
  142. package/dist/queries.d.ts +2 -2
  143. package/dist/queries.d.ts.map +1 -1
  144. package/dist/queries.js +66 -30
  145. package/dist/questions.d.ts +1 -5
  146. package/dist/questions.d.ts.map +1 -1
  147. package/dist/questions.js +1 -7
  148. package/dist/queue.d.ts +40 -0
  149. package/dist/queue.d.ts.map +1 -0
  150. package/dist/queue.js +480 -0
  151. package/dist/reach.d.ts +2 -3
  152. package/dist/reach.d.ts.map +1 -1
  153. package/dist/reach.js +83 -25
  154. package/dist/react.d.ts +8 -5
  155. package/dist/react.d.ts.map +1 -1
  156. package/dist/react.js +28 -5
  157. package/dist/readiness.d.ts +9 -3
  158. package/dist/readiness.d.ts.map +1 -1
  159. package/dist/readiness.js +3 -5
  160. package/dist/reads.d.ts +4 -2
  161. package/dist/reads.d.ts.map +1 -1
  162. package/dist/reads.js +11 -4
  163. package/dist/recovery.d.ts +3 -1
  164. package/dist/recovery.d.ts.map +1 -1
  165. package/dist/recovery.js +29 -7
  166. package/dist/refresh.js +2 -2
  167. package/dist/refusal.d.ts.map +1 -1
  168. package/dist/refusal.js +5 -5
  169. package/dist/replica.d.ts +5 -11
  170. package/dist/replica.d.ts.map +1 -1
  171. package/dist/replica.js +14 -16
  172. package/dist/retried.d.ts +30 -0
  173. package/dist/retried.d.ts.map +1 -0
  174. package/dist/retried.js +72 -0
  175. package/dist/room-draft.d.ts +30 -5
  176. package/dist/room-draft.d.ts.map +1 -1
  177. package/dist/room-draft.js +270 -63
  178. package/dist/room-drafts.d.ts.map +1 -1
  179. package/dist/room-drafts.js +8 -5
  180. package/dist/room-list.d.ts +10 -0
  181. package/dist/room-list.d.ts.map +1 -0
  182. package/dist/room-list.js +45 -0
  183. package/dist/rows.d.ts +2 -1
  184. package/dist/rows.d.ts.map +1 -1
  185. package/dist/rows.js +9 -10
  186. package/dist/runtime.d.ts +9 -18
  187. package/dist/runtime.d.ts.map +1 -1
  188. package/dist/save.d.ts +42 -90
  189. package/dist/save.d.ts.map +1 -1
  190. package/dist/save.js +314 -223
  191. package/dist/saves.d.ts +2 -40
  192. package/dist/saves.d.ts.map +1 -1
  193. package/dist/saves.js +124 -703
  194. package/dist/seed.d.ts.map +1 -1
  195. package/dist/seed.js +4 -4
  196. package/dist/shown.d.ts +7 -5
  197. package/dist/shown.d.ts.map +1 -1
  198. package/dist/shown.js +75 -58
  199. package/dist/socket/lanes.d.ts +12 -4
  200. package/dist/socket/lanes.d.ts.map +1 -1
  201. package/dist/socket/lanes.js +34 -8
  202. package/dist/socket.d.ts +3 -6
  203. package/dist/socket.d.ts.map +1 -1
  204. package/dist/socket.js +3 -14
  205. package/dist/source.d.ts +5 -7
  206. package/dist/source.d.ts.map +1 -1
  207. package/dist/source.js +13 -2
  208. package/dist/store/installs.d.ts +2 -2
  209. package/dist/store/installs.d.ts.map +1 -1
  210. package/dist/store/installs.js +10 -4
  211. package/dist/store/readiness.d.ts +5 -4
  212. package/dist/store/readiness.d.ts.map +1 -1
  213. package/dist/store/readiness.js +28 -12
  214. package/dist/store/records.d.ts +17 -6
  215. package/dist/store/records.d.ts.map +1 -1
  216. package/dist/store/records.js +56 -22
  217. package/dist/store/rooms-shown.d.ts +8 -0
  218. package/dist/store/rooms-shown.d.ts.map +1 -0
  219. package/dist/store/rooms-shown.js +13 -0
  220. package/dist/store.d.ts +23 -14
  221. package/dist/store.d.ts.map +1 -1
  222. package/dist/store.js +46 -17
  223. package/dist/stored.d.ts +24 -13
  224. package/dist/stored.d.ts.map +1 -1
  225. package/dist/stored.js +80 -29
  226. package/dist/surface.d.ts +5 -3
  227. package/dist/surface.d.ts.map +1 -1
  228. package/dist/surface.js +23 -21
  229. package/dist/sync.d.ts +1 -1
  230. package/dist/sync.d.ts.map +1 -1
  231. package/dist/sync.js +89 -85
  232. package/dist/tables.d.ts +16 -56
  233. package/dist/tables.d.ts.map +1 -1
  234. package/dist/tables.js +86 -327
  235. package/dist/tallies.d.ts.map +1 -1
  236. package/dist/tallies.js +1 -2
  237. package/dist/validation.d.ts.map +1 -1
  238. package/dist/validation.js +4 -10
  239. package/dist/wire.d.ts +45 -7
  240. package/dist/wire.d.ts.map +1 -1
  241. package/dist/wire.js +15 -4
  242. package/package.json +3 -2
  243. package/dist/attempt.d.ts +0 -16
  244. package/dist/attempt.d.ts.map +0 -1
  245. package/dist/attempt.js +0 -70
  246. package/dist/job.d.ts +0 -33
  247. package/dist/job.d.ts.map +0 -1
  248. package/dist/job.js +0 -54
  249. package/dist/log.d.ts +0 -6
  250. package/dist/log.d.ts.map +0 -1
  251. package/dist/log.js +0 -14
  252. package/dist/socket/rooms.d.ts +0 -44
  253. package/dist/socket/rooms.d.ts.map +0 -1
  254. package/dist/socket/rooms.js +0 -90
  255. package/dist/store/fences.d.ts +0 -16
  256. package/dist/store/fences.d.ts.map +0 -1
  257. package/dist/store/fences.js +0 -26
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';
3
- 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 { ancestors, authoredAt, covered, normalize } from './paths.js';
1
+ import { apply, ARRAY_INDEX, ATTACHMENT, create, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
2
+ import { immutable, isPart, leaving, queuedFile, departure, } from './change.js';
3
+ import { collectionOf, definitionsAt, documentsOf, governing, textType, typedText, } from './collection.js';
4
+ import { changeBatchIdentity, roomDraftRevision } from './identity.js';
5
+ import { composed, overlaying } from './overlay.js';
6
+ import { ancestors } from './paths.js';
8
7
  import { entriesIn, sorting } from './shared.js';
8
+ import { applyParts, changedParts, changesOf, moveTarget, partsBetween, 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,9 +16,13 @@ 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
27
  const changes = [];
24
28
  const names = new Set();
@@ -42,43 +46,13 @@ function repointed(document, definition, from, to, moves, settled) {
42
46
  }
43
47
  if (changes.length === 0)
44
48
  return [];
45
- const applied = apply(document, changes);
49
+ const applied = apply(document, changes, definition);
46
50
  if (applied.outcome !== 'applied')
47
51
  return [];
48
52
  const next = applied.document;
49
53
  return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
50
54
  }
51
- function edited(document, changes) {
52
- const parts = new Map();
53
- for (const change of changes) {
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
- kind: 'section',
59
- name: change.name,
60
- value: change.value,
61
- prior: document.sections[change.name] ?? '',
62
- });
63
- }
64
- if (parts.size === 0)
65
- return document;
66
- const applied = apply(document, [...parts.values()]);
67
- return applied.outcome === 'applied' ? applied.document : document;
68
- }
69
- function bodies(path, before, after) {
70
- const changes = [];
71
- if (after.preamble !== before.preamble) {
72
- changes.push({ kind: 'preamble', path, value: after.preamble });
73
- }
74
- for (const [name, value] of Object.entries(after.sections)) {
75
- if (before.sections[name] === value)
76
- continue;
77
- changes.push({ kind: 'section', path, name, value });
78
- }
79
- return changes;
80
- }
81
- function relinks(path, moved, before, moves) {
55
+ function relinks(path, moved, before, moves, definition) {
82
56
  const changes = [];
83
57
  for (const link of links(before, path)) {
84
58
  const destination = moves.get(link.target) ?? link.target;
@@ -91,28 +65,21 @@ function relinks(path, moved, before, moves) {
91
65
  }
92
66
  if (changes.length === 0)
93
67
  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
- }
68
+ const applied = apply(before, changes, definition);
69
+ if (applied.outcome === 'refuse')
70
+ return refusedBy(applied.refusal, path);
106
71
  const after = applied.document;
107
72
  if (after === before)
108
73
  return { kind: 'changes', changes: [] };
109
74
  if (before.type === undefined) {
110
75
  return { kind: 'changes', changes: [{ kind: 'replace', path, content: serialize(after) }] };
111
76
  }
112
- return { kind: 'changes', changes: bodies(path, before, after) };
77
+ return { kind: 'changes', changes: partsBetween(path, before, after) };
113
78
  }
114
79
  function partKey(change) {
115
- return change.kind === 'section' ? `section ${change.name}` : change.kind;
80
+ return change.kind === 'section' || change.kind === 'field'
81
+ ? `${change.kind} ${change.name}`
82
+ : change.kind;
116
83
  }
117
84
  const NAMES_A_SKIP_TESTS = 64;
118
85
  const ENCODED = /[%&\\]/u;
@@ -155,13 +122,14 @@ function* rewrites(changes, visible) {
155
122
  if (settled.has(path))
156
123
  continue;
157
124
  const moved = moves.get(path) ?? path;
158
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
125
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
159
126
  const repointing = repointed(document, definition, path, moved, moves, settled);
160
127
  added.push(...repointing);
161
128
  const own = parts.get(path) ?? [];
129
+ const edited = applyParts(document, own.map((index) => merged[index]), definition);
162
130
  const relinked = moved === path && own.length === 0 && cannotLink(document.content, names)
163
131
  ? { kind: 'changes', changes: [] }
164
- : relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
132
+ : relinks(path, moved, edited.outcome === 'applied' ? edited.document : document, moves, definition);
165
133
  if (relinked.kind === 'refused')
166
134
  return relinked;
167
135
  const keyed = new Map();
@@ -174,7 +142,7 @@ function* rewrites(changes, visible) {
174
142
  if (change.kind === 'replace') {
175
143
  for (const index of own) {
176
144
  const kind = merged[index]?.kind;
177
- if (kind === 'section' || kind === 'preamble')
145
+ if (kind === 'field' || kind === 'section' || kind === 'preamble')
178
146
  merged[index] = null;
179
147
  }
180
148
  added.push(change);
@@ -230,25 +198,21 @@ function crossed(document, definition, path, to) {
230
198
  }
231
199
  return changes;
232
200
  }
233
- function rewritten(path, document, changes) {
201
+ function rewritten(path, document, changes, definition) {
234
202
  if (changes.length === 0)
235
203
  return [];
236
- const applied = apply(document, changes);
204
+ const applied = apply(document, changes, definition);
237
205
  if (applied.outcome !== 'applied')
238
206
  return [];
239
207
  const after = applied.document;
240
208
  if (document.type === undefined)
241
209
  return [{ kind: 'replace', path, content: serialize(after) }];
242
- const names = new Set(changes.flatMap((change) => change.kind === 'reference' ? [pointerSegments(change.pointer)[0] ?? ''] : []));
243
- return [
244
- ...[...names].map((name) => ({ kind: 'field', path, name, value: after.fields[name] })),
245
- ...bodies(path, document, after),
246
- ];
210
+ return partsBetween(path, document, after);
247
211
  }
248
212
  function roomPathOf(handle, path) {
249
213
  return `/rooms/${handle}/${path}`;
250
214
  }
251
- export function* leavingRewritten(batch, visible) {
215
+ function* leavingRewritten(batch, visible) {
252
216
  const between = batch.between;
253
217
  if (between === undefined)
254
218
  return batch;
@@ -257,8 +221,8 @@ export function* leavingRewritten(batch, visible) {
257
221
  for (const [path, document] of yield* documentsOf(visible.collection)) {
258
222
  if (moved.has(path))
259
223
  continue;
260
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
261
- derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named))));
224
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
225
+ derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named)), definition));
262
226
  yield ROW_WEIGHT + visible.collection.text[path].length;
263
227
  }
264
228
  if (derived.length === 0)
@@ -300,7 +264,7 @@ function* incoming(collection, removed, candidates) {
300
264
  for (const [path, document] of candidates === null
301
265
  ? yield* documentsOf(collection)
302
266
  : read(candidates)) {
303
- const definition = definitionOf(governing(collection.composition, path), document.type);
267
+ const definition = definitionsAt(collection.composition, path)(document.type);
304
268
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
305
269
  if (to.includes(ATTACHMENT))
306
270
  continue;
@@ -343,18 +307,9 @@ function guard(changes, input, removing) {
343
307
  const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
344
308
  for (const [index, change] of changes.entries()) {
345
309
  for (const path of affected(change)) {
346
- const normalized = normalize(path);
347
- if (normalized === null || normalized === '')
348
- return refuse('room-root', path);
349
- if (covered(input.providerOwnedPaths, normalized) ||
350
- (input.authoredPaths !== undefined && !authoredAt(input.authoredPaths, normalized))) {
351
- return refuse('provider-owned-path', normalized);
352
- }
353
- if (!input.pathRule(normalized))
354
- return refuse('invalid-path', normalized);
355
- for (const operation of change.kind === 'remove' ? removing : WRITE)
356
- if (!input.local && !covered(input.operations[operation], normalized))
357
- return refuse(`${operation}-grant`, normalized);
310
+ const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
311
+ if (refused !== null)
312
+ return refuse(refused.code, refused.path);
358
313
  }
359
314
  if (change.kind === 'create' || change.kind === 'arrival') {
360
315
  const taken = existing.has(change.path) ? change.path : occupied(change.path);
@@ -415,9 +370,9 @@ function guard(changes, input, removing) {
415
370
  return null;
416
371
  }
417
372
  function unrevised(input, path) {
418
- return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
373
+ return !input.local && !input.queued.has(path) && input.visible.revisions[path] === undefined;
419
374
  }
420
- export function queuedDocuments(queue) {
375
+ function queuedDocuments(queue) {
421
376
  const paths = new Set();
422
377
  for (const batch of queue) {
423
378
  for (const change of batch.changes) {
@@ -462,7 +417,7 @@ function* preconditionsOf(changes, visible, bases) {
462
417
  };
463
418
  for (const change of changes) {
464
419
  yield ROW_WEIGHT;
465
- if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
420
+ if (isPart(change)) {
466
421
  const based = bases.find((base) => sameTarget(base, change));
467
422
  if (based !== undefined) {
468
423
  add(based);
@@ -553,93 +508,101 @@ function* exceeding(limits, changes, effective, resulting) {
553
508
  function shellRelative(text, path, handle, definition) {
554
509
  if (!text.includes(`/rooms/${handle}/`))
555
510
  return null;
556
- const parsed = parse(text, path);
511
+ const parsed = parse(text, path, definition);
557
512
  if (!parsed.ok)
558
513
  return null;
559
514
  const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
560
515
  return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
561
516
  }
562
517
  function relativeField(path, name, value, handle, definition) {
563
- const opened = parse('---\n---\n');
564
- if (definition === undefined ||
565
- !opened.ok ||
566
- !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
518
+ if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
567
519
  return value;
568
- const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
520
+ const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
569
521
  if (held.outcome !== 'applied')
570
522
  return value;
571
523
  const result = relativize(held.document, path, handle, definition);
572
524
  return result.outcome === 'applied' ? result.document.fields[name] : value;
573
525
  }
574
526
  function relativeChanges(input) {
527
+ const changes = [];
528
+ for (const change of input.changes) {
529
+ const next = relativeChange(change, input);
530
+ if ('refusal' in next)
531
+ return next;
532
+ changes.push(next.change);
533
+ }
534
+ return { kind: 'changes', changes };
535
+ }
536
+ function relativeChange(change, input) {
575
537
  const { handle, composition, visible } = input;
576
- const typed = (path) => (type) => definitionOf(governing(composition, path), type);
577
538
  const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
578
- return input.changes.map((change) => {
579
- const definition = typed(change.path);
580
- if (change.kind === 'field') {
581
- const type = visible.collection.document(change.path)?.type;
582
- const value = relativeField(change.path, change.name, change.value, handle, definition(type));
583
- return value === change.value ? change : { ...change, value };
584
- }
585
- if (change.kind === 'section' || change.kind === 'preamble') {
586
- const value = body(change.path, change.value);
587
- return value === change.value ? change : { ...change, value };
588
- }
589
- if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
590
- const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
591
- return content === change.content ? change : { ...change, content };
592
- }
593
- if (change.kind !== 'create')
594
- return change;
595
- const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
596
- const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
597
- name,
598
- relativeField(change.path, name, value, handle, definition(type)),
599
- ]));
600
- const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
601
- return { ...change, fields, sections };
602
- });
539
+ const definition = definitionsAt(composition, change.path);
540
+ if (change.kind === 'field') {
541
+ const type = visible.collection.document(change.path)?.type;
542
+ const value = relativeField(change.path, change.name, change.value, handle, definition(type));
543
+ return { change: value === change.value ? change : { ...change, value } };
544
+ }
545
+ if (change.kind === 'section' || change.kind === 'preamble') {
546
+ const value = body(change.path, change.value);
547
+ return { change: value === change.value ? change : { ...change, value } };
548
+ }
549
+ if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
550
+ const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
551
+ return { change: content === change.content ? change : { ...change, content } };
552
+ }
553
+ if (change.kind !== 'create')
554
+ return { change };
555
+ const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
556
+ const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
557
+ name,
558
+ relativeField(change.path, name, value, handle, definition(type)),
559
+ ]));
560
+ const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
561
+ const written = composed(change.path, fields, sections, composition);
562
+ if (written.outcome === 'refuse')
563
+ return refusedBy(written.refusal, change.path);
564
+ if (written.outcome === 'stale')
565
+ throw new Error(`the creation of ${change.path} is stale`);
566
+ if (Object.keys(sections).length === 0)
567
+ return { change: { ...change, fields, sections } };
568
+ return {
569
+ change: { kind: 'create', path: change.path, content: serialize(written.document) },
570
+ };
603
571
  }
604
572
  function unqueueable(file) {
605
- return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
606
- }
607
- function isPart(change) {
608
- return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
573
+ return refuse('unknown-revision', file);
609
574
  }
610
575
  function touched(change) {
611
576
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
612
577
  }
613
- function partsApplied(content, path, changes) {
614
- const parsed = parse(content, path);
615
- if (!parsed.ok)
616
- return null;
617
- const document = parsed.document;
618
- const parts = new Map();
578
+ function partsApplied(content, path, changes, composition) {
579
+ const applied = partsResult(content, path, changes, composition);
580
+ return applied?.outcome === 'applied' ? serialize(applied.document) : null;
581
+ }
582
+ function unapplied(changes, visible, composition) {
583
+ const parted = new Map();
619
584
  for (const change of changes) {
620
- if (change.kind === 'field')
621
- parts.set(`field ${change.name}`, {
622
- kind: 'field',
623
- name: change.name,
624
- value: change.value,
625
- prior: document.fields[change.name],
626
- });
627
- else if (change.kind === 'section')
628
- parts.set(`section ${change.name}`, {
629
- kind: 'section',
630
- name: change.name,
631
- value: change.value,
632
- prior: document.sections[change.name] ?? '',
633
- });
634
- else
635
- parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
585
+ if (isPart(change))
586
+ parted.set(change.path, [...(parted.get(change.path) ?? []), change]);
587
+ }
588
+ for (const [path, parts] of parted) {
589
+ const content = visible.documents[path];
590
+ if (content === undefined)
591
+ continue;
592
+ const applied = partsResult(content, path, parts, composition);
593
+ if (applied?.outcome === 'refuse')
594
+ return refusedBy(applied.refusal, path);
636
595
  }
637
- const applied = apply(document, [...parts.values()]);
638
- return applied.outcome === 'applied' ? serialize(applied.document) : null;
596
+ return null;
597
+ }
598
+ function partsResult(content, path, changes, composition) {
599
+ const definitions = definitionsAt(composition, path);
600
+ const parsed = parse(content, path, definitions);
601
+ return parsed.ok ? applyParts(parsed.document, changes, definitions(parsed.document.type)) : null;
639
602
  }
640
- function settledParts(changes, deferred) {
603
+ function settledParts(changes, deferred, composition) {
641
604
  for (const [held, parts] of deferred) {
642
- const content = partsApplied(held.content, held.path, parts);
605
+ const content = partsApplied(held.content, held.path, parts, composition);
643
606
  if (content === null)
644
607
  return held.path;
645
608
  changes[changes.indexOf(held)] = { ...held, content };
@@ -714,7 +677,9 @@ function* relinked(batch, move, before, after, appending) {
714
677
  for (const change of derived.changes) {
715
678
  if (change === move)
716
679
  continue;
717
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
680
+ const unsettled = isPart(change)
681
+ ? null
682
+ : settledParts(changes, deferred, after.collection.composition);
718
683
  if (unsettled !== null)
719
684
  return unqueueable(unsettled);
720
685
  const outcome = folded(changes, change, appending, deferred);
@@ -725,7 +690,7 @@ function* relinked(batch, move, before, after, appending) {
725
690
  if (outcome !== 'skipped')
726
691
  changed = true;
727
692
  }
728
- const unsettled = settledParts(changes, deferred);
693
+ const unsettled = settledParts(changes, deferred, after.collection.composition);
729
694
  if (unsettled !== null)
730
695
  return unqueueable(unsettled);
731
696
  if (!changed)
@@ -786,11 +751,10 @@ function withdrawnFile(batch, change, at) {
786
751
  ];
787
752
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
788
753
  }
789
- export function* requeuing(queue, changes, visible, changeable, state) {
754
+ function* requeuing(queue, changes, visible, changeable, state) {
790
755
  const next = [...queue];
791
756
  const origins = new Map();
792
757
  const withdrawn = new Map();
793
- const owners = new Set();
794
758
  const queued = new Set();
795
759
  const kept = [];
796
760
  const originOf = (batch) => origins.get(batch) ?? batch.id;
@@ -836,7 +800,6 @@ export function* requeuing(queue, changes, visible, changeable, state) {
836
800
  if (replaced !== null)
837
801
  origins.set(replaced, origin);
838
802
  }
839
- owners.add(originOf(owner));
840
803
  next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
841
804
  queued.add(change.path);
842
805
  }
@@ -846,62 +809,16 @@ export function* requeuing(queue, changes, visible, changeable, state) {
846
809
  changes: kept,
847
810
  queued,
848
811
  withdrawn,
849
- owners,
850
812
  };
851
813
  }
852
- function covering(precondition) {
853
- return precondition.kind === 'field' ||
854
- precondition.kind === 'section' ||
855
- precondition.kind === 'preamble'
856
- ? identity(precondition)
857
- : `path ${precondition.path}`;
858
- }
859
- export function joined(queue, owner, extra) {
860
- const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
861
- const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
862
- if (clash !== undefined)
863
- return unqueueable(clash);
864
- const changes = [...owner.changes];
865
- const deferred = new Map();
866
- for (const change of extra.changes) {
867
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
868
- if (unsettled !== null)
869
- return unqueueable(unsettled);
870
- if (folded(changes, change, true, deferred) === 'refused')
871
- return unqueueable(change.path);
872
- }
873
- const unsettled = settledParts(changes, deferred);
874
- if (unsettled !== null)
875
- return unqueueable(unsettled);
876
- const held = new Set(owner.preconditions.map(covering));
877
- const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
878
- const preconditions = [
879
- ...owner.preconditions,
880
- ...extra.preconditions.filter((precondition) => {
881
- const key = covering(precondition);
882
- return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
883
- }),
884
- ];
885
- const batch = immutable({
886
- ...owner,
887
- ...(extra.validation === undefined ? {} : { validation: extra.validation }),
888
- id: changeBatchIdentity(),
889
- changes,
890
- preconditions,
891
- });
892
- return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
893
- }
894
- export function* limiting(queue, batch, limits, state) {
814
+ function* limiting(queue, batch, limits, state) {
895
815
  const at = queue.indexOf(batch);
896
816
  const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
897
817
  const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
898
818
  return limited?.refusal ?? null;
899
819
  }
900
- function typedText(content) {
901
- if (content === undefined)
902
- return false;
903
- const parsed = parse(content);
904
- return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
820
+ function typedContent(content) {
821
+ return content !== undefined && typedText(parse(content));
905
822
  }
906
823
  function touchedLibrary(resulting, touched, composition) {
907
824
  const entries = touched
@@ -921,9 +838,9 @@ function touchedLibrary(resulting, touched, composition) {
921
838
  if (binaries.has(path))
922
839
  known.set(path, { type: undefined, binary: true });
923
840
  else if (content !== undefined && content !== null) {
924
- const read = parse(content, path);
925
- if (read.ok)
926
- known.set(path, { type: read.document.type ?? 'any', binary: false });
841
+ const type = textType(parse(content, path, definitionsAt(composition, path)));
842
+ if (type !== undefined)
843
+ known.set(path, { type, binary: false });
927
844
  await pace(content.length + ROW_WEIGHT);
928
845
  }
929
846
  }
@@ -941,7 +858,7 @@ async function judging(visible, resulting, composition, validate, removed, touch
941
858
  for (const path of touched) {
942
859
  const before = visible.documents[path];
943
860
  const after = resulting.documents[path];
944
- if (typedText(before) || typedText(after))
861
+ if (typedContent(before) || typedContent(after))
945
862
  typedPaths.push(path);
946
863
  await pace((before?.length ?? 0) + (after?.length ?? 0) + ROW_WEIGHT);
947
864
  }
@@ -959,6 +876,7 @@ async function judging(visible, resulting, composition, validate, removed, touch
959
876
  retryable: false,
960
877
  file: verdict.file,
961
878
  ...(verdict.location === undefined ? {} : { location: verdict.location }),
879
+ ...(verdict.line === undefined ? {} : { line: verdict.line }),
962
880
  ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
963
881
  },
964
882
  };
@@ -974,8 +892,6 @@ function placed(reference) {
974
892
  const key = roomPathOf(reference.handle, reference.path);
975
893
  if (reference.kind === 'link')
976
894
  return `link ${key}`;
977
- if (reference.kind === 'item')
978
- return `section ${reference.section} ${key}`;
979
895
  const pointer = reference.pointer
980
896
  .split('/')
981
897
  .map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
@@ -985,10 +901,11 @@ function placed(reference) {
985
901
  function roomReferencesIn(path, content, composition) {
986
902
  if (content === undefined)
987
903
  return [];
988
- const parsed = parse(content, path);
904
+ const definitions = definitionsAt(composition, path);
905
+ const parsed = parse(content, path, definitions);
989
906
  if (!parsed.ok)
990
907
  return [];
991
- return roomReferences(parsed.document, definitionOf(governing(composition, path), parsed.document.type));
908
+ return roomReferences(parsed.document, definitions(parsed.document.type));
992
909
  }
993
910
  function* admittedRooms(changes, visible, resulting, composition) {
994
911
  const from = new Map();
@@ -1006,7 +923,7 @@ function* admittedRooms(changes, visible, resulting, composition) {
1006
923
  continue;
1007
924
  if (held.has(placed(reference)))
1008
925
  continue;
1009
- return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
926
+ return refuse('unresolved-reference', path, reference.pointer);
1010
927
  }
1011
928
  yield ROW_WEIGHT + now.length;
1012
929
  }
@@ -1024,7 +941,10 @@ function validationOf(validated) {
1024
941
  };
1025
942
  }
1026
943
  async function batching(input) {
1027
- const changes = relativeChanges(input);
944
+ const relative = relativeChanges(input);
945
+ if (relative.kind === 'refused')
946
+ return relative;
947
+ const { changes } = relative;
1028
948
  const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
1029
949
  if (structural !== undefined && input.unmodeled) {
1030
950
  return refuse('model-unavailable', structural.path);
@@ -1034,12 +954,12 @@ async function batching(input) {
1034
954
  const rewritten = await stepped(rewrites(changes, input.visible));
1035
955
  if (rewritten.kind === 'refused')
1036
956
  return rewritten;
1037
- const refusal = guard(changes, input, WRITE);
957
+ const refusal = guard(changes, input, WRITE) ?? unapplied(changes, input.visible, input.composition);
1038
958
  if (refusal !== null)
1039
959
  return refusal;
1040
960
  const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
1041
961
  const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
1042
- const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }]));
962
+ const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }], input.composition));
1043
963
  const judged = await judging(input.visible, resulting, input.composition, input.validate, new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path)), [...new Set(predicted.changes.flatMap(affected))]);
1044
964
  if (judged.kind === 'refused')
1045
965
  return judged;
@@ -1061,7 +981,7 @@ async function batching(input) {
1061
981
  }),
1062
982
  };
1063
983
  }
1064
- export const MOVE_DOCUMENTS = 'move-documents';
984
+ const MOVE_DOCUMENTS = 'move-documents';
1065
985
  function heldAt(visible, path) {
1066
986
  return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
1067
987
  }
@@ -1070,7 +990,7 @@ function arrivingText(path, to, source, moved) {
1070
990
  const document = source.visible.collection.document(path);
1071
991
  if (document === undefined)
1072
992
  return content;
1073
- const definition = definitionOf(governing(source.composition, path), document.type);
993
+ const definition = definitionsAt(source.composition, path)(document.type);
1074
994
  const changes = crossed(document, definition, path, (named, href) => {
1075
995
  const at = moved.get(named);
1076
996
  if (at !== undefined)
@@ -1079,7 +999,7 @@ function arrivingText(path, to, source, moved) {
1079
999
  });
1080
1000
  if (changes.length === 0)
1081
1001
  return content;
1082
- const applied = apply(document, changes);
1002
+ const applied = apply(document, changes, definition);
1083
1003
  return applied.outcome === 'applied' ? serialize(applied.document) : content;
1084
1004
  }
1085
1005
  function arrivalOf({ path, to }, source, moved) {
@@ -1112,9 +1032,9 @@ function arrivalOf({ path, to }, source, moved) {
1112
1032
  };
1113
1033
  }
1114
1034
  async function moving(input) {
1115
- const { source, destination, moves, pathRule } = input;
1035
+ const { source, destination, moves } = input;
1116
1036
  const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1117
- const leaves = guard(removals, { ...source, local: input.local, bases: [], pathRule }, MOVE_OUT);
1037
+ const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
1118
1038
  if (leaves !== null)
1119
1039
  return leaves;
1120
1040
  const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
@@ -1122,7 +1042,7 @@ async function moving(input) {
1122
1042
  return refuse('unknown-revision', uploading.path);
1123
1043
  const moved = new Map(moves.map(({ path, to }) => [path, to]));
1124
1044
  const arrivals = moves.map((move) => arrivalOf(move, source, moved));
1125
- const arrives = guard(arrivals, { ...destination, local: input.local, bases: [], pathRule }, WRITE);
1045
+ const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, WRITE);
1126
1046
  if (arrives !== null)
1127
1047
  return arrives;
1128
1048
  const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
@@ -1151,7 +1071,7 @@ async function moving(input) {
1151
1071
  between,
1152
1072
  };
1153
1073
  const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1154
- const left = await stepped(overlaying(source.visible, [effective]));
1074
+ const left = await stepped(overlaying(source.visible, [effective], source.composition));
1155
1075
  const sourceJudged = await judging(source.visible, left, source.composition, source.validate, new Set(moves.map(({ path }) => path)), [...new Set(effective.changes.map((change) => change.path))]);
1156
1076
  if (sourceJudged.kind === 'refused')
1157
1077
  return sourceJudged;
@@ -1163,7 +1083,7 @@ async function moving(input) {
1163
1083
  ...written,
1164
1084
  between,
1165
1085
  };
1166
- const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1086
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch], destination.composition));
1167
1087
  const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1168
1088
  if (destinationJudged.kind === 'refused')
1169
1089
  return destinationJudged;
@@ -1173,9 +1093,180 @@ async function moving(input) {
1173
1093
  return limited;
1174
1094
  return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1175
1095
  }
1176
- export function buildMove(input) {
1177
- return moving(input);
1096
+ function unreadable(saved, visible, composition) {
1097
+ const path = saved.find(({ path: at, parts }) => (parts.fields !== undefined ||
1098
+ parts.sections !== undefined ||
1099
+ parts.preamble !== undefined) &&
1100
+ typedContent(visible.documents[at]) &&
1101
+ governing(composition, at).status === 'unavailable')?.path;
1102
+ return path === undefined ? null : refuse('model-unavailable', path);
1103
+ }
1104
+ function changedUnderneath(saved, visible) {
1105
+ const parts = saved.flatMap(({ path, parts: held }) => changedParts(path, held, visible));
1106
+ return parts.length === 0
1107
+ ? null
1108
+ : { kind: 'refused', refusal: { code: 'changed-underneath', retryable: false, parts } };
1109
+ }
1110
+ function sideOf(room, visible) {
1111
+ const { record } = room;
1112
+ return {
1113
+ room: record.locator,
1114
+ handle: record.declaration.handle,
1115
+ baseRoomVersion: record.roomVersion,
1116
+ composition: room.composition,
1117
+ rule: room.rule,
1118
+ visible,
1119
+ folders: room.folders,
1120
+ queued: queuedDocuments(room.queue),
1121
+ unmodeled: !room.modelled,
1122
+ validate: room.validate,
1123
+ };
1124
+ }
1125
+ function carriedDrafts(ask, from, into) {
1126
+ return new Map(ask.saved.flatMap(({ path, parts }) => {
1127
+ const record = from.drafts.get(path);
1128
+ if (parts.move === undefined || record === undefined || ask.consumed.has(path))
1129
+ return [];
1130
+ const to = moveTarget(parts.move);
1131
+ if (into.drafts.has(to))
1132
+ return [];
1133
+ const locator = into.record.locator;
1134
+ return [[path, { ...record, locator, path: to, revision: roomDraftRevision() }]];
1135
+ }));
1136
+ }
1137
+ async function plannedMove(ask, source, into) {
1138
+ const destination = into.record.locator;
1139
+ const mixed = ask.saved.find(({ parts }) => Object.keys(parts).length !== 1 || parts.move?.room !== destination);
1140
+ if (mixed !== undefined)
1141
+ return refuse('mixed-room-move', mixed.path);
1142
+ const leavingVisible = await stepped(source.replay(source.queue));
1143
+ const changed = changedUnderneath(ask.saved, leavingVisible);
1144
+ if (changed !== null)
1145
+ return changed;
1146
+ const moves = ask.saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
1147
+ const built = await moving({
1148
+ source: sideOf(source, leavingVisible),
1149
+ destination: sideOf(into, await stepped(into.replay(into.queue))),
1150
+ moves,
1151
+ providerLimits: ask.providerLimits,
1152
+ local: ask.local,
1153
+ ...(ask.writtenBy === undefined ? {} : { writtenBy: ask.writtenBy }),
1154
+ });
1155
+ if (built.kind === 'refused')
1156
+ return built;
1157
+ const { id } = built.leaving;
1158
+ const carried = carriedDrafts(ask, source, into);
1159
+ return {
1160
+ kind: 'planned',
1161
+ identifier: id,
1162
+ rooms: [
1163
+ {
1164
+ locator: source.record.locator,
1165
+ queue: [...source.queue, built.leaving],
1166
+ saved: new Map([[id, ask.saved]]),
1167
+ consumed: [...ask.consumed, ...carried.keys()],
1168
+ carried: [],
1169
+ },
1170
+ {
1171
+ locator: destination,
1172
+ queue: [...into.queue, built.arriving],
1173
+ saved: new Map([
1174
+ [id, [...carried.values()].map(({ path }) => ({ path, parts: {} }))],
1175
+ ]),
1176
+ consumed: [],
1177
+ carried: [...carried.values()],
1178
+ },
1179
+ ],
1180
+ };
1181
+ }
1182
+ async function plannedWithin(ask, room) {
1183
+ const { record } = room;
1184
+ const visible = await stepped(room.replay(room.queue));
1185
+ const changed = unreadable(ask.saved, visible, room.composition) ?? changedUnderneath(ask.saved, visible);
1186
+ if (changed !== null)
1187
+ return changed;
1188
+ const parts = ask.saved.map(({ path, parts: held }) => changesOf(path, held));
1189
+ const changes = parts.flatMap(({ changes: made }) => made);
1190
+ const requeue = await stepped(requeuing(room.queue, changes, visible, (batch) => room.changeable.has(batch.id), room.replay));
1191
+ if (requeue.kind === 'refused')
1192
+ return requeue;
1193
+ const foreign = [...requeue.withdrawn.keys()].some((origin) => {
1194
+ const writer = room.queue.find((batch) => batch.id === origin)?.writtenBy;
1195
+ return writer?.app !== ask.writtenBy?.app || writer?.release !== ask.writtenBy?.release;
1196
+ });
1197
+ if (foreign)
1198
+ return unqueueable([...requeue.queued].at(-1));
1199
+ const input = {
1200
+ local: ask.local,
1201
+ baseRoomVersion: record.roomVersion,
1202
+ composition: room.composition,
1203
+ rule: room.rule,
1204
+ providerLimits: ask.providerLimits,
1205
+ handle: record.declaration.handle,
1206
+ visible,
1207
+ folders: room.folders,
1208
+ changes,
1209
+ bases: parts.flatMap(({ bases }) => bases),
1210
+ queued: queuedDocuments(room.queue),
1211
+ writtenBy: ask.writtenBy,
1212
+ unmodeled: !room.modelled,
1213
+ validate: room.validate,
1214
+ };
1215
+ const rewritten = requeue.withdrawn.size > 0;
1216
+ if (rewritten) {
1217
+ const whole = await batching({
1218
+ ...input,
1219
+ providerLimits: [],
1220
+ queued: new Set([...input.queued, ...requeue.queued]),
1221
+ });
1222
+ if (whole.kind === 'refused')
1223
+ return whole;
1224
+ }
1225
+ const own = !rewritten
1226
+ ? await batching(input)
1227
+ : requeue.changes.length === 0
1228
+ ? null
1229
+ : await batching({
1230
+ ...input,
1231
+ visible: await stepped(room.replay(requeue.queue)),
1232
+ changes: requeue.changes,
1233
+ queued: queuedDocuments(requeue.queue),
1234
+ });
1235
+ if (own?.kind === 'refused')
1236
+ return own;
1237
+ const queue = own === null ? requeue.queue : [...requeue.queue, own.batch];
1238
+ const saved = new Map();
1239
+ for (const [origin, rebuilt] of requeue.withdrawn) {
1240
+ if (rebuilt === null)
1241
+ continue;
1242
+ const limited = await stepped(limiting(queue, rebuilt, ask.providerLimits, room.replay));
1243
+ if (limited !== null)
1244
+ return { kind: 'refused', refusal: limited };
1245
+ saved.set(rebuilt.id, withdrawnDrafts(room.saved.get(origin) ?? [], changes));
1246
+ }
1247
+ if (own !== null) {
1248
+ saved.set(own.batch.id, ask.saved.filter(({ path }) => !requeue.queued.has(path)));
1249
+ }
1250
+ const carried = carriedDrafts(ask, room, room);
1251
+ return {
1252
+ kind: 'planned',
1253
+ identifier: own?.batch.id ??
1254
+ [...requeue.withdrawn].map(([origin, rebuilt]) => rebuilt?.id ?? origin).at(-1),
1255
+ rooms: [
1256
+ {
1257
+ locator: record.locator,
1258
+ queue,
1259
+ saved,
1260
+ consumed: [...ask.consumed, ...carried.keys()],
1261
+ carried: [...carried.values()],
1262
+ },
1263
+ ],
1264
+ };
1265
+ }
1266
+ export function destination(locator, saved) {
1267
+ const into = saved.find(({ parts }) => parts.move?.room !== undefined && parts.move.room !== locator);
1268
+ return into?.parts.move?.room ?? null;
1178
1269
  }
1179
- export function buildBatch(input) {
1180
- return batching(input);
1270
+ export function planSave(ask, room, into) {
1271
+ return into === undefined ? plannedWithin(ask, room) : plannedMove(ask, room, into);
1181
1272
  }