@agentshouse/mdruntime 0.1.0-cd507e16e5fc0 → 0.1.0-cd574a610e9c8

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 (269) 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 +75 -165
  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 +90 -58
  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 +7 -2
  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 +13 -20
  32. package/dist/client.d.ts.map +1 -1
  33. package/dist/client.js +36 -20
  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 +31 -25
  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 +37 -12
  54. package/dist/conversation.d.ts.map +1 -1
  55. package/dist/conversation.js +320 -234
  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 +26 -13
  66. package/dist/expectation.d.ts.map +1 -1
  67. package/dist/expectation.js +72 -19
  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 +1 -1
  74. package/dist/file.d.ts.map +1 -1
  75. package/dist/file.js +3 -2
  76. package/dist/host.d.ts +18 -2
  77. package/dist/host.d.ts.map +1 -1
  78. package/dist/host.js +59 -15
  79. package/dist/index.d.ts +16 -11
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +12 -7
  82. package/dist/install.d.ts +10 -6
  83. package/dist/install.d.ts.map +1 -1
  84. package/dist/install.js +92 -111
  85. package/dist/lifecycle.d.ts +1 -1
  86. package/dist/lifecycle.d.ts.map +1 -1
  87. package/dist/lifecycle.js +54 -40
  88. package/dist/listing.d.ts +11 -12
  89. package/dist/listing.d.ts.map +1 -1
  90. package/dist/listing.js +48 -72
  91. package/dist/live.d.ts +5 -3
  92. package/dist/live.d.ts.map +1 -1
  93. package/dist/live.js +74 -33
  94. package/dist/loaded.d.ts +5 -0
  95. package/dist/loaded.d.ts.map +1 -0
  96. package/dist/loaded.js +40 -0
  97. package/dist/locks.d.ts +2 -4
  98. package/dist/locks.d.ts.map +1 -1
  99. package/dist/locks.js +10 -39
  100. package/dist/lost.d.ts.map +1 -1
  101. package/dist/lost.js +11 -23
  102. package/dist/media.d.ts +6 -1
  103. package/dist/media.d.ts.map +1 -1
  104. package/dist/media.js +68 -17
  105. package/dist/memory.d.ts +8 -3
  106. package/dist/memory.d.ts.map +1 -1
  107. package/dist/memory.js +92 -33
  108. package/dist/messages.d.ts +12 -6
  109. package/dist/messages.d.ts.map +1 -1
  110. package/dist/messages.js +30 -38
  111. package/dist/model.d.ts +0 -1
  112. package/dist/model.d.ts.map +1 -1
  113. package/dist/model.js +0 -5
  114. package/dist/models.d.ts +4 -8
  115. package/dist/models.d.ts.map +1 -1
  116. package/dist/models.js +97 -139
  117. package/dist/observation.d.ts +27 -12
  118. package/dist/observation.d.ts.map +1 -1
  119. package/dist/observation.js +184 -85
  120. package/dist/opened.d.ts +1 -1
  121. package/dist/opened.d.ts.map +1 -1
  122. package/dist/opened.js +5 -5
  123. package/dist/opening.d.ts.map +1 -1
  124. package/dist/opening.js +5 -9
  125. package/dist/outcome.d.ts +13 -1
  126. package/dist/outcome.d.ts.map +1 -1
  127. package/dist/outcome.js +32 -2
  128. package/dist/overlay.d.ts +6 -2
  129. package/dist/overlay.d.ts.map +1 -1
  130. package/dist/overlay.js +37 -24
  131. package/dist/page-metadata.d.ts +8 -0
  132. package/dist/page-metadata.d.ts.map +1 -0
  133. package/dist/page-metadata.js +19 -0
  134. package/dist/parts.d.ts +3 -58
  135. package/dist/parts.d.ts.map +1 -1
  136. package/dist/parts.js +10 -115
  137. package/dist/preparation.d.ts +27 -6
  138. package/dist/preparation.d.ts.map +1 -1
  139. package/dist/preparation.js +156 -68
  140. package/dist/presence.d.ts +58 -0
  141. package/dist/presence.d.ts.map +1 -0
  142. package/dist/presence.js +231 -0
  143. package/dist/queries.d.ts +2 -2
  144. package/dist/queries.d.ts.map +1 -1
  145. package/dist/queries.js +67 -31
  146. package/dist/query.d.ts +0 -1
  147. package/dist/query.d.ts.map +1 -1
  148. package/dist/questions.d.ts +1 -5
  149. package/dist/questions.d.ts.map +1 -1
  150. package/dist/questions.js +1 -7
  151. package/dist/queue.d.ts +40 -0
  152. package/dist/queue.d.ts.map +1 -0
  153. package/dist/queue.js +480 -0
  154. package/dist/reach.d.ts +2 -3
  155. package/dist/reach.d.ts.map +1 -1
  156. package/dist/reach.js +88 -26
  157. package/dist/react.d.ts +8 -1
  158. package/dist/react.d.ts.map +1 -1
  159. package/dist/react.js +33 -1
  160. package/dist/readiness.d.ts +9 -3
  161. package/dist/readiness.d.ts.map +1 -1
  162. package/dist/readiness.js +3 -5
  163. package/dist/reads.d.ts +4 -2
  164. package/dist/reads.d.ts.map +1 -1
  165. package/dist/reads.js +11 -4
  166. package/dist/recovery.d.ts +3 -1
  167. package/dist/recovery.d.ts.map +1 -1
  168. package/dist/recovery.js +29 -7
  169. package/dist/refresh.js +2 -2
  170. package/dist/refusal.d.ts.map +1 -1
  171. package/dist/refusal.js +5 -6
  172. package/dist/replica.d.ts +5 -11
  173. package/dist/replica.d.ts.map +1 -1
  174. package/dist/replica.js +15 -17
  175. package/dist/retried.d.ts +30 -0
  176. package/dist/retried.d.ts.map +1 -0
  177. package/dist/retried.js +72 -0
  178. package/dist/room-draft.d.ts +31 -8
  179. package/dist/room-draft.d.ts.map +1 -1
  180. package/dist/room-draft.js +273 -66
  181. package/dist/room-drafts.d.ts.map +1 -1
  182. package/dist/room-drafts.js +22 -18
  183. package/dist/room-list.d.ts +10 -0
  184. package/dist/room-list.d.ts.map +1 -0
  185. package/dist/room-list.js +45 -0
  186. package/dist/rows.d.ts +2 -1
  187. package/dist/rows.d.ts.map +1 -1
  188. package/dist/rows.js +9 -10
  189. package/dist/runtime.d.ts +9 -18
  190. package/dist/runtime.d.ts.map +1 -1
  191. package/dist/save.d.ts +42 -90
  192. package/dist/save.d.ts.map +1 -1
  193. package/dist/save.js +327 -229
  194. package/dist/saves.d.ts +4 -40
  195. package/dist/saves.d.ts.map +1 -1
  196. package/dist/saves.js +143 -714
  197. package/dist/seed.d.ts.map +1 -1
  198. package/dist/seed.js +4 -5
  199. package/dist/shared.d.ts +0 -5
  200. package/dist/shared.d.ts.map +1 -1
  201. package/dist/shared.js +4 -90
  202. package/dist/shown.d.ts +7 -5
  203. package/dist/shown.d.ts.map +1 -1
  204. package/dist/shown.js +77 -63
  205. package/dist/socket/lanes.d.ts +12 -4
  206. package/dist/socket/lanes.d.ts.map +1 -1
  207. package/dist/socket/lanes.js +34 -8
  208. package/dist/socket/session.d.ts +0 -1
  209. package/dist/socket/session.d.ts.map +1 -1
  210. package/dist/socket.d.ts +3 -6
  211. package/dist/socket.d.ts.map +1 -1
  212. package/dist/socket.js +5 -16
  213. package/dist/source.d.ts +5 -7
  214. package/dist/source.d.ts.map +1 -1
  215. package/dist/source.js +18 -6
  216. package/dist/store/installs.d.ts +2 -2
  217. package/dist/store/installs.d.ts.map +1 -1
  218. package/dist/store/installs.js +10 -4
  219. package/dist/store/readiness.d.ts +5 -4
  220. package/dist/store/readiness.d.ts.map +1 -1
  221. package/dist/store/readiness.js +28 -12
  222. package/dist/store/records.d.ts +18 -7
  223. package/dist/store/records.d.ts.map +1 -1
  224. package/dist/store/records.js +58 -24
  225. package/dist/store/rooms-shown.d.ts +8 -0
  226. package/dist/store/rooms-shown.d.ts.map +1 -0
  227. package/dist/store/rooms-shown.js +13 -0
  228. package/dist/store/space.d.ts +1 -1
  229. package/dist/store/space.d.ts.map +1 -1
  230. package/dist/store/space.js +0 -2
  231. package/dist/store/watchers.js +1 -2
  232. package/dist/store.d.ts +23 -14
  233. package/dist/store.d.ts.map +1 -1
  234. package/dist/store.js +46 -17
  235. package/dist/stored.d.ts +25 -14
  236. package/dist/stored.d.ts.map +1 -1
  237. package/dist/stored.js +83 -32
  238. package/dist/surface.d.ts +5 -3
  239. package/dist/surface.d.ts.map +1 -1
  240. package/dist/surface.js +25 -23
  241. package/dist/sync.d.ts +1 -1
  242. package/dist/sync.d.ts.map +1 -1
  243. package/dist/sync.js +96 -92
  244. package/dist/tables.d.ts +16 -57
  245. package/dist/tables.d.ts.map +1 -1
  246. package/dist/tables.js +91 -332
  247. package/dist/tallies.d.ts.map +1 -1
  248. package/dist/tallies.js +6 -7
  249. package/dist/validation.d.ts.map +1 -1
  250. package/dist/validation.js +4 -10
  251. package/dist/wire.d.ts +53 -7
  252. package/dist/wire.d.ts.map +1 -1
  253. package/dist/wire.js +17 -4
  254. package/package.json +3 -2
  255. package/dist/attempt.d.ts +0 -16
  256. package/dist/attempt.d.ts.map +0 -1
  257. package/dist/attempt.js +0 -70
  258. package/dist/job.d.ts +0 -33
  259. package/dist/job.d.ts.map +0 -1
  260. package/dist/job.js +0 -54
  261. package/dist/log.d.ts +0 -6
  262. package/dist/log.d.ts.map +0 -1
  263. package/dist/log.js +0 -14
  264. package/dist/socket/rooms.d.ts +0 -44
  265. package/dist/socket/rooms.d.ts.map +0 -1
  266. package/dist/socket/rooms.js +0 -90
  267. package/dist/store/fences.d.ts +0 -16
  268. package/dist/store/fences.d.ts.map +0 -1
  269. 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,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,43 +46,13 @@ 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
- const parts = new Map();
56
- for (const change of changes) {
57
- if (change.kind === 'preamble')
58
- parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
59
- if (change.kind === 'section')
60
- parts.set(`section ${change.name}`, {
61
- kind: 'section',
62
- name: change.name,
63
- value: change.value,
64
- prior: document.sections[change.name] ?? '',
65
- });
66
- }
67
- if (parts.size === 0)
68
- return document;
69
- const applied = apply(document, [...parts.values()]);
70
- return applied.outcome === 'applied' ? applied.document : document;
71
- }
72
- function bodies(path, before, after) {
73
- const changes = [];
74
- if (after.preamble !== before.preamble) {
75
- changes.push({ kind: 'preamble', path, value: after.preamble });
76
- }
77
- for (const [name, value] of Object.entries(after.sections)) {
78
- if (before.sections[name] === value)
79
- continue;
80
- changes.push({ kind: 'section', path, name, value });
81
- }
82
- return changes;
83
- }
84
- function relinks(path, moved, before, moves) {
55
+ function relinks(path, moved, before, moves, definition) {
85
56
  const changes = [];
86
57
  for (const link of links(before, path)) {
87
58
  const destination = moves.get(link.target) ?? link.target;
@@ -94,28 +65,30 @@ function relinks(path, moved, before, moves) {
94
65
  }
95
66
  if (changes.length === 0)
96
67
  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
- }
68
+ const applied = apply(before, changes, definition);
69
+ if (applied.outcome === 'refuse')
70
+ return refusedBy(applied.refusal, path);
109
71
  const after = applied.document;
110
72
  if (after === before)
111
73
  return { kind: 'changes', changes: [] };
112
74
  if (before.type === undefined) {
113
75
  return { kind: 'changes', changes: [{ kind: 'replace', path, content: serialize(after) }] };
114
76
  }
115
- return { kind: 'changes', changes: bodies(path, before, after) };
77
+ return { kind: 'changes', changes: partsBetween(path, before, after) };
116
78
  }
117
79
  function partKey(change) {
118
- 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;
83
+ }
84
+ const NAMES_A_SKIP_TESTS = 64;
85
+ const ENCODED = /[%&\\]/u;
86
+ function movedNames(moves) {
87
+ const names = new Set([...moves.keys()].map((path) => path.slice(path.lastIndexOf('/') + 1)));
88
+ return names.size > NAMES_A_SKIP_TESTS ? null : [...names];
89
+ }
90
+ function cannotLink(text, names) {
91
+ return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
119
92
  }
120
93
  function* rewrites(changes, visible) {
121
94
  const moves = new Map();
@@ -142,17 +115,21 @@ function* rewrites(changes, visible) {
142
115
  }
143
116
  if (moves.size === 0)
144
117
  return { kind: 'changes', changes };
118
+ const names = movedNames(moves);
145
119
  const merged = [...changes];
146
120
  const added = [];
147
121
  for (const [path, document] of yield* documentsOf(visible.collection)) {
148
122
  if (settled.has(path))
149
123
  continue;
150
124
  const moved = moves.get(path) ?? path;
151
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
125
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
152
126
  const repointing = repointed(document, definition, path, moved, moves, settled);
153
127
  added.push(...repointing);
154
128
  const own = parts.get(path) ?? [];
155
- const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
129
+ const edited = applyParts(document, own.map((index) => merged[index]), definition);
130
+ const relinked = moved === path && own.length === 0 && cannotLink(document.content, names)
131
+ ? { kind: 'changes', changes: [] }
132
+ : relinks(path, moved, edited.outcome === 'applied' ? edited.document : document, moves, definition);
156
133
  if (relinked.kind === 'refused')
157
134
  return relinked;
158
135
  const keyed = new Map();
@@ -165,7 +142,7 @@ function* rewrites(changes, visible) {
165
142
  if (change.kind === 'replace') {
166
143
  for (const index of own) {
167
144
  const kind = merged[index]?.kind;
168
- if (kind === 'section' || kind === 'preamble')
145
+ if (kind === 'field' || kind === 'section' || kind === 'preamble')
169
146
  merged[index] = null;
170
147
  }
171
148
  added.push(change);
@@ -221,25 +198,21 @@ function crossed(document, definition, path, to) {
221
198
  }
222
199
  return changes;
223
200
  }
224
- function rewritten(path, document, changes) {
201
+ function rewritten(path, document, changes, definition) {
225
202
  if (changes.length === 0)
226
203
  return [];
227
- const applied = apply(document, changes);
204
+ const applied = apply(document, changes, definition);
228
205
  if (applied.outcome !== 'applied')
229
206
  return [];
230
207
  const after = applied.document;
231
208
  if (document.type === undefined)
232
209
  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
- ];
210
+ return partsBetween(path, document, after);
238
211
  }
239
212
  function roomPathOf(handle, path) {
240
213
  return `/rooms/${handle}/${path}`;
241
214
  }
242
- export function* leavingRewritten(batch, visible) {
215
+ function* leavingRewritten(batch, visible) {
243
216
  const between = batch.between;
244
217
  if (between === undefined)
245
218
  return batch;
@@ -248,8 +221,8 @@ export function* leavingRewritten(batch, visible) {
248
221
  for (const [path, document] of yield* documentsOf(visible.collection)) {
249
222
  if (moved.has(path))
250
223
  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))));
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));
253
226
  yield ROW_WEIGHT + visible.collection.text[path].length;
254
227
  }
255
228
  if (derived.length === 0)
@@ -291,7 +264,7 @@ function* incoming(collection, removed, candidates) {
291
264
  for (const [path, document] of candidates === null
292
265
  ? yield* documentsOf(collection)
293
266
  : read(candidates)) {
294
- const definition = definitionOf(governing(collection.composition, path), document.type);
267
+ const definition = definitionsAt(collection.composition, path)(document.type);
295
268
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
296
269
  if (to.includes(ATTACHMENT))
297
270
  continue;
@@ -334,18 +307,9 @@ function guard(changes, input, removing) {
334
307
  const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
335
308
  for (const [index, change] of changes.entries()) {
336
309
  for (const path of affected(change)) {
337
- const normalized = normalize(path);
338
- if (normalized === null || normalized === '')
339
- return refuse('room-root', path);
340
- if (covered(input.providerOwnedPaths, normalized) ||
341
- (input.authoredPaths !== undefined && !authoredAt(input.authoredPaths, normalized))) {
342
- return refuse('provider-owned-path', normalized);
343
- }
344
- if (!input.pathRule(normalized))
345
- return refuse('invalid-path', normalized);
346
- for (const operation of change.kind === 'remove' ? removing : WRITE)
347
- if (!input.local && !covered(input.operations[operation], normalized))
348
- 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);
349
313
  }
350
314
  if (change.kind === 'create' || change.kind === 'arrival') {
351
315
  const taken = existing.has(change.path) ? change.path : occupied(change.path);
@@ -406,9 +370,9 @@ function guard(changes, input, removing) {
406
370
  return null;
407
371
  }
408
372
  function unrevised(input, path) {
409
- 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;
410
374
  }
411
- export function queuedDocuments(queue) {
375
+ function queuedDocuments(queue) {
412
376
  const paths = new Set();
413
377
  for (const batch of queue) {
414
378
  for (const change of batch.changes) {
@@ -453,7 +417,7 @@ function* preconditionsOf(changes, visible, bases) {
453
417
  };
454
418
  for (const change of changes) {
455
419
  yield ROW_WEIGHT;
456
- if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
420
+ if (isPart(change)) {
457
421
  const based = bases.find((base) => sameTarget(base, change));
458
422
  if (based !== undefined) {
459
423
  add(based);
@@ -544,93 +508,101 @@ function* exceeding(limits, changes, effective, resulting) {
544
508
  function shellRelative(text, path, handle, definition) {
545
509
  if (!text.includes(`/rooms/${handle}/`))
546
510
  return null;
547
- const parsed = parse(text, path);
511
+ const parsed = parse(text, path, definition);
548
512
  if (!parsed.ok)
549
513
  return null;
550
514
  const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
551
515
  return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
552
516
  }
553
517
  function relativeField(path, name, value, handle, definition) {
554
- const opened = parse('---\n---\n');
555
- if (definition === undefined ||
556
- !opened.ok ||
557
- !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
518
+ if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
558
519
  return value;
559
- 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);
560
521
  if (held.outcome !== 'applied')
561
522
  return value;
562
523
  const result = relativize(held.document, path, handle, definition);
563
524
  return result.outcome === 'applied' ? result.document.fields[name] : value;
564
525
  }
565
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) {
566
537
  const { handle, composition, visible } = input;
567
- const typed = (path) => (type) => definitionOf(governing(composition, path), type);
568
538
  const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
569
- return input.changes.map((change) => {
570
- const definition = typed(change.path);
571
- if (change.kind === 'field') {
572
- const type = visible.collection.document(change.path)?.type;
573
- const value = relativeField(change.path, change.name, change.value, handle, definition(type));
574
- return value === change.value ? change : { ...change, value };
575
- }
576
- if (change.kind === 'section' || change.kind === 'preamble') {
577
- const value = body(change.path, change.value);
578
- return value === change.value ? change : { ...change, value };
579
- }
580
- if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
581
- const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
582
- return content === change.content ? change : { ...change, content };
583
- }
584
- if (change.kind !== 'create')
585
- return change;
586
- const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
587
- const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
588
- name,
589
- relativeField(change.path, name, value, handle, definition(type)),
590
- ]));
591
- const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
592
- return { ...change, fields, sections };
593
- });
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
+ };
594
571
  }
595
572
  function unqueueable(file) {
596
- return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
597
- }
598
- function isPart(change) {
599
- return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
573
+ return refuse('unknown-revision', file);
600
574
  }
601
575
  function touched(change) {
602
576
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
603
577
  }
604
- function partsApplied(content, path, changes) {
605
- const parsed = parse(content, path);
606
- if (!parsed.ok)
607
- return null;
608
- const document = parsed.document;
609
- 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();
610
584
  for (const change of changes) {
611
- if (change.kind === 'field')
612
- parts.set(`field ${change.name}`, {
613
- kind: 'field',
614
- name: change.name,
615
- value: change.value,
616
- prior: document.fields[change.name],
617
- });
618
- else if (change.kind === 'section')
619
- parts.set(`section ${change.name}`, {
620
- kind: 'section',
621
- name: change.name,
622
- value: change.value,
623
- prior: document.sections[change.name] ?? '',
624
- });
625
- else
626
- 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]);
627
587
  }
628
- const applied = apply(document, [...parts.values()]);
629
- return applied.outcome === 'applied' ? serialize(applied.document) : null;
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);
595
+ }
596
+ return null;
630
597
  }
631
- function settledParts(changes, deferred) {
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;
602
+ }
603
+ function settledParts(changes, deferred, composition) {
632
604
  for (const [held, parts] of deferred) {
633
- const content = partsApplied(held.content, held.path, parts);
605
+ const content = partsApplied(held.content, held.path, parts, composition);
634
606
  if (content === null)
635
607
  return held.path;
636
608
  changes[changes.indexOf(held)] = { ...held, content };
@@ -705,7 +677,9 @@ function* relinked(batch, move, before, after, appending) {
705
677
  for (const change of derived.changes) {
706
678
  if (change === move)
707
679
  continue;
708
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
680
+ const unsettled = isPart(change)
681
+ ? null
682
+ : settledParts(changes, deferred, after.collection.composition);
709
683
  if (unsettled !== null)
710
684
  return unqueueable(unsettled);
711
685
  const outcome = folded(changes, change, appending, deferred);
@@ -716,7 +690,7 @@ function* relinked(batch, move, before, after, appending) {
716
690
  if (outcome !== 'skipped')
717
691
  changed = true;
718
692
  }
719
- const unsettled = settledParts(changes, deferred);
693
+ const unsettled = settledParts(changes, deferred, after.collection.composition);
720
694
  if (unsettled !== null)
721
695
  return unqueueable(unsettled);
722
696
  if (!changed)
@@ -777,11 +751,10 @@ function withdrawnFile(batch, change, at) {
777
751
  ];
778
752
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
779
753
  }
780
- export function* requeuing(queue, changes, visible, changeable, state) {
754
+ function* requeuing(queue, changes, visible, changeable, state) {
781
755
  const next = [...queue];
782
756
  const origins = new Map();
783
757
  const withdrawn = new Map();
784
- const owners = new Set();
785
758
  const queued = new Set();
786
759
  const kept = [];
787
760
  const originOf = (batch) => origins.get(batch) ?? batch.id;
@@ -827,7 +800,6 @@ export function* requeuing(queue, changes, visible, changeable, state) {
827
800
  if (replaced !== null)
828
801
  origins.set(replaced, origin);
829
802
  }
830
- owners.add(originOf(owner));
831
803
  next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
832
804
  queued.add(change.path);
833
805
  }
@@ -837,62 +809,16 @@ export function* requeuing(queue, changes, visible, changeable, state) {
837
809
  changes: kept,
838
810
  queued,
839
811
  withdrawn,
840
- owners,
841
812
  };
842
813
  }
843
- function covering(precondition) {
844
- return precondition.kind === 'field' ||
845
- precondition.kind === 'section' ||
846
- precondition.kind === 'preamble'
847
- ? identity(precondition)
848
- : `path ${precondition.path}`;
849
- }
850
- export function joined(queue, owner, extra) {
851
- const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
852
- const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
853
- if (clash !== undefined)
854
- return unqueueable(clash);
855
- const changes = [...owner.changes];
856
- const deferred = new Map();
857
- for (const change of extra.changes) {
858
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
859
- if (unsettled !== null)
860
- return unqueueable(unsettled);
861
- if (folded(changes, change, true, deferred) === 'refused')
862
- return unqueueable(change.path);
863
- }
864
- const unsettled = settledParts(changes, deferred);
865
- if (unsettled !== null)
866
- return unqueueable(unsettled);
867
- const held = new Set(owner.preconditions.map(covering));
868
- const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
869
- const preconditions = [
870
- ...owner.preconditions,
871
- ...extra.preconditions.filter((precondition) => {
872
- const key = covering(precondition);
873
- return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
874
- }),
875
- ];
876
- const batch = immutable({
877
- ...owner,
878
- ...(extra.validation === undefined ? {} : { validation: extra.validation }),
879
- id: changeBatchIdentity(),
880
- changes,
881
- preconditions,
882
- });
883
- return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
884
- }
885
- export function* limiting(queue, batch, limits, state) {
814
+ function* limiting(queue, batch, limits, state) {
886
815
  const at = queue.indexOf(batch);
887
816
  const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
888
817
  const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
889
- return limited?.kind === 'refused' ? limited.refusal : null;
818
+ return limited?.refusal ?? null;
890
819
  }
891
- function typedText(content) {
892
- if (content === undefined)
893
- return false;
894
- const parsed = parse(content);
895
- return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
820
+ function typedContent(content) {
821
+ return content !== undefined && typedText(parse(content));
896
822
  }
897
823
  function touchedLibrary(resulting, touched, composition) {
898
824
  const entries = touched
@@ -912,9 +838,9 @@ function touchedLibrary(resulting, touched, composition) {
912
838
  if (binaries.has(path))
913
839
  known.set(path, { type: undefined, binary: true });
914
840
  else if (content !== undefined && content !== null) {
915
- const read = parse(content, path);
916
- if (read.ok)
917
- 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 });
918
844
  await pace(content.length + ROW_WEIGHT);
919
845
  }
920
846
  }
@@ -932,7 +858,7 @@ async function judging(visible, resulting, composition, validate, removed, touch
932
858
  for (const path of touched) {
933
859
  const before = visible.documents[path];
934
860
  const after = resulting.documents[path];
935
- if (typedText(before) || typedText(after))
861
+ if (typedContent(before) || typedContent(after))
936
862
  typedPaths.push(path);
937
863
  await pace((before?.length ?? 0) + (after?.length ?? 0) + ROW_WEIGHT);
938
864
  }
@@ -950,6 +876,7 @@ async function judging(visible, resulting, composition, validate, removed, touch
950
876
  retryable: false,
951
877
  file: verdict.file,
952
878
  ...(verdict.location === undefined ? {} : { location: verdict.location }),
879
+ ...(verdict.line === undefined ? {} : { line: verdict.line }),
953
880
  ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
954
881
  },
955
882
  };
@@ -965,8 +892,6 @@ function placed(reference) {
965
892
  const key = roomPathOf(reference.handle, reference.path);
966
893
  if (reference.kind === 'link')
967
894
  return `link ${key}`;
968
- if (reference.kind === 'item')
969
- return `section ${reference.section} ${key}`;
970
895
  const pointer = reference.pointer
971
896
  .split('/')
972
897
  .map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
@@ -976,10 +901,11 @@ function placed(reference) {
976
901
  function roomReferencesIn(path, content, composition) {
977
902
  if (content === undefined)
978
903
  return [];
979
- const parsed = parse(content, path);
904
+ const definitions = definitionsAt(composition, path);
905
+ const parsed = parse(content, path, definitions);
980
906
  if (!parsed.ok)
981
907
  return [];
982
- return roomReferences(parsed.document, definitionOf(governing(composition, path), parsed.document.type));
908
+ return roomReferences(parsed.document, definitions(parsed.document.type));
983
909
  }
984
910
  function* admittedRooms(changes, visible, resulting, composition) {
985
911
  const from = new Map();
@@ -997,7 +923,7 @@ function* admittedRooms(changes, visible, resulting, composition) {
997
923
  continue;
998
924
  if (held.has(placed(reference)))
999
925
  continue;
1000
- return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
926
+ return refuse('unresolved-reference', path, reference.pointer);
1001
927
  }
1002
928
  yield ROW_WEIGHT + now.length;
1003
929
  }
@@ -1015,7 +941,10 @@ function validationOf(validated) {
1015
941
  };
1016
942
  }
1017
943
  async function batching(input) {
1018
- const changes = relativeChanges(input);
944
+ const relative = relativeChanges(input);
945
+ if (relative.kind === 'refused')
946
+ return relative;
947
+ const { changes } = relative;
1019
948
  const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
1020
949
  if (structural !== undefined && input.unmodeled) {
1021
950
  return refuse('model-unavailable', structural.path);
@@ -1025,12 +954,12 @@ async function batching(input) {
1025
954
  const rewritten = await stepped(rewrites(changes, input.visible));
1026
955
  if (rewritten.kind === 'refused')
1027
956
  return rewritten;
1028
- const refusal = guard(changes, input, WRITE);
957
+ const refusal = guard(changes, input, WRITE) ?? unapplied(changes, input.visible, input.composition);
1029
958
  if (refusal !== null)
1030
959
  return refusal;
1031
960
  const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
1032
961
  const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
1033
- 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));
1034
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))]);
1035
964
  if (judged.kind === 'refused')
1036
965
  return judged;
@@ -1052,7 +981,7 @@ async function batching(input) {
1052
981
  }),
1053
982
  };
1054
983
  }
1055
- export const MOVE_DOCUMENTS = 'move-documents';
984
+ const MOVE_DOCUMENTS = 'move-documents';
1056
985
  function heldAt(visible, path) {
1057
986
  return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
1058
987
  }
@@ -1061,7 +990,7 @@ function arrivingText(path, to, source, moved) {
1061
990
  const document = source.visible.collection.document(path);
1062
991
  if (document === undefined)
1063
992
  return content;
1064
- const definition = definitionOf(governing(source.composition, path), document.type);
993
+ const definition = definitionsAt(source.composition, path)(document.type);
1065
994
  const changes = crossed(document, definition, path, (named, href) => {
1066
995
  const at = moved.get(named);
1067
996
  if (at !== undefined)
@@ -1070,7 +999,7 @@ function arrivingText(path, to, source, moved) {
1070
999
  });
1071
1000
  if (changes.length === 0)
1072
1001
  return content;
1073
- const applied = apply(document, changes);
1002
+ const applied = apply(document, changes, definition);
1074
1003
  return applied.outcome === 'applied' ? serialize(applied.document) : content;
1075
1004
  }
1076
1005
  function arrivalOf({ path, to }, source, moved) {
@@ -1103,11 +1032,9 @@ function arrivalOf({ path, to }, source, moved) {
1103
1032
  };
1104
1033
  }
1105
1034
  async function moving(input) {
1106
- const { source, destination, moves, pathRule } = input;
1107
- if (moves.length === 0)
1108
- return refuse('no-change');
1035
+ const { source, destination, moves } = input;
1109
1036
  const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1110
- const leaves = guard(removals, { ...source, local: input.local, bases: [], pathRule }, MOVE_OUT);
1037
+ const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
1111
1038
  if (leaves !== null)
1112
1039
  return leaves;
1113
1040
  const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
@@ -1115,7 +1042,7 @@ async function moving(input) {
1115
1042
  return refuse('unknown-revision', uploading.path);
1116
1043
  const moved = new Map(moves.map(({ path, to }) => [path, to]));
1117
1044
  const arrivals = moves.map((move) => arrivalOf(move, source, moved));
1118
- const arrives = guard(arrivals, { ...destination, local: input.local, bases: [], pathRule }, WRITE);
1045
+ const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, WRITE);
1119
1046
  if (arrives !== null)
1120
1047
  return arrives;
1121
1048
  const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
@@ -1144,7 +1071,7 @@ async function moving(input) {
1144
1071
  between,
1145
1072
  };
1146
1073
  const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1147
- const left = await stepped(overlaying(source.visible, [effective]));
1074
+ const left = await stepped(overlaying(source.visible, [effective], source.composition));
1148
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))]);
1149
1076
  if (sourceJudged.kind === 'refused')
1150
1077
  return sourceJudged;
@@ -1156,7 +1083,7 @@ async function moving(input) {
1156
1083
  ...written,
1157
1084
  between,
1158
1085
  };
1159
- const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1086
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch], destination.composition));
1160
1087
  const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1161
1088
  if (destinationJudged.kind === 'refused')
1162
1089
  return destinationJudged;
@@ -1166,9 +1093,180 @@ async function moving(input) {
1166
1093
  return limited;
1167
1094
  return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1168
1095
  }
1169
- export function buildMove(input) {
1170
- 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;
1171
1269
  }
1172
- export function buildBatch(input) {
1173
- return batching(input);
1270
+ export function planSave(ask, room, into) {
1271
+ return into === undefined ? plannedWithin(ask, room) : plannedMove(ask, room, into);
1174
1272
  }