@agentshouse/mdruntime 0.1.0-cedb03eaa48b6 → 0.1.0-cee9c8aa4cd86

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 (192) hide show
  1. package/AGENTS.md +86 -71
  2. package/README.md +9 -8
  3. package/dist/adapter.d.ts +44 -18
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/attached-model.d.ts +4 -1
  6. package/dist/attached-model.d.ts.map +1 -1
  7. package/dist/attached-model.js +13 -3
  8. package/dist/bootstrap.d.ts +13 -6
  9. package/dist/bootstrap.d.ts.map +1 -1
  10. package/dist/bootstrap.js +90 -61
  11. package/dist/chain.d.ts +11 -0
  12. package/dist/chain.d.ts.map +1 -0
  13. package/dist/chain.js +64 -0
  14. package/dist/change-set.d.ts +9 -16
  15. package/dist/change-set.d.ts.map +1 -1
  16. package/dist/change-set.js +11 -89
  17. package/dist/change.d.ts +31 -1
  18. package/dist/change.d.ts.map +1 -1
  19. package/dist/change.js +10 -1
  20. package/dist/chat/house.d.ts +25 -0
  21. package/dist/chat/house.d.ts.map +1 -0
  22. package/dist/chat/house.js +38 -0
  23. package/dist/chat/input.d.ts +3 -1
  24. package/dist/chat/input.d.ts.map +1 -1
  25. package/dist/chat/input.js +11 -18
  26. package/dist/chat/list.d.ts +2 -1
  27. package/dist/chat/list.d.ts.map +1 -1
  28. package/dist/chat/list.js +7 -3
  29. package/dist/chat/markdown.d.ts +1 -0
  30. package/dist/chat/markdown.d.ts.map +1 -1
  31. package/dist/chat/markdown.js +78 -20
  32. package/dist/chat/message.d.ts +1 -0
  33. package/dist/chat/message.d.ts.map +1 -1
  34. package/dist/chat/message.js +73 -11
  35. package/dist/chat/messages.d.ts +14 -1
  36. package/dist/chat/messages.d.ts.map +1 -1
  37. package/dist/chat/messages.js +79 -24
  38. package/dist/chat/name.d.ts +1 -0
  39. package/dist/chat/name.d.ts.map +1 -1
  40. package/dist/chat/name.js +1 -1
  41. package/dist/chat/opened.d.ts +1 -0
  42. package/dist/chat/opened.d.ts.map +1 -1
  43. package/dist/chat/opened.js +2 -0
  44. package/dist/chat/overlay.d.ts +1 -0
  45. package/dist/chat/overlay.d.ts.map +1 -1
  46. package/dist/chat/overlay.js +11 -5
  47. package/dist/chat/parts.d.ts +1 -0
  48. package/dist/chat/parts.d.ts.map +1 -1
  49. package/dist/chat/parts.js +3 -2
  50. package/dist/chat/pieces.d.ts +62 -0
  51. package/dist/chat/pieces.d.ts.map +1 -0
  52. package/dist/chat/pieces.js +1110 -0
  53. package/dist/chat/provider.d.ts +8 -1
  54. package/dist/chat/provider.d.ts.map +1 -1
  55. package/dist/chat/provider.js +88 -81
  56. package/dist/chat/ready.d.ts +5 -2
  57. package/dist/chat/ready.d.ts.map +1 -1
  58. package/dist/chat/ready.js +7 -5
  59. package/dist/chat/room-file.d.ts +8 -0
  60. package/dist/chat/room-file.d.ts.map +1 -0
  61. package/dist/chat/room-file.js +72 -0
  62. package/dist/chat/steady.d.ts +3 -0
  63. package/dist/chat/steady.d.ts.map +1 -0
  64. package/dist/chat/steady.js +26 -0
  65. package/dist/chat/style.d.ts.map +1 -1
  66. package/dist/chat/style.js +5 -0
  67. package/dist/chat.d.ts +3 -2
  68. package/dist/chat.d.ts.map +1 -1
  69. package/dist/chat.js +2 -2
  70. package/dist/client.d.ts +6 -1
  71. package/dist/client.d.ts.map +1 -1
  72. package/dist/client.js +29 -2
  73. package/dist/collection.d.ts +6 -19
  74. package/dist/collection.d.ts.map +1 -1
  75. package/dist/collection.js +30 -317
  76. package/dist/compile.d.ts +16 -0
  77. package/dist/compile.d.ts.map +1 -0
  78. package/dist/compile.js +718 -0
  79. package/dist/connection.d.ts +11 -5
  80. package/dist/connection.d.ts.map +1 -1
  81. package/dist/connection.js +1931 -706
  82. package/dist/conversation.d.ts +9 -3
  83. package/dist/conversation.d.ts.map +1 -1
  84. package/dist/conversation.js +85 -61
  85. package/dist/digest.d.ts +7 -0
  86. package/dist/digest.d.ts.map +1 -0
  87. package/dist/digest.js +113 -0
  88. package/dist/durability.d.ts +2 -1
  89. package/dist/durability.d.ts.map +1 -1
  90. package/dist/entry.d.ts +3 -1
  91. package/dist/entry.d.ts.map +1 -1
  92. package/dist/entry.js +18 -24
  93. package/dist/file.d.ts +2 -10
  94. package/dist/file.d.ts.map +1 -1
  95. package/dist/file.js +3 -120
  96. package/dist/fulltext.d.ts +27 -0
  97. package/dist/fulltext.d.ts.map +1 -0
  98. package/dist/fulltext.js +368 -0
  99. package/dist/host.d.ts +0 -1
  100. package/dist/host.d.ts.map +1 -1
  101. package/dist/host.js +10 -3
  102. package/dist/identity.d.ts +0 -1
  103. package/dist/identity.d.ts.map +1 -1
  104. package/dist/identity.js +0 -3
  105. package/dist/index.d.ts +8 -5
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +3 -0
  108. package/dist/live.d.ts +22 -0
  109. package/dist/live.d.ts.map +1 -0
  110. package/dist/live.js +123 -0
  111. package/dist/memory.d.ts +1 -0
  112. package/dist/memory.d.ts.map +1 -1
  113. package/dist/memory.js +22 -6
  114. package/dist/messages.d.ts +28 -0
  115. package/dist/messages.d.ts.map +1 -0
  116. package/dist/messages.js +90 -0
  117. package/dist/name.d.ts +4 -0
  118. package/dist/name.d.ts.map +1 -0
  119. package/dist/name.js +122 -0
  120. package/dist/observation.d.ts +12 -0
  121. package/dist/observation.d.ts.map +1 -1
  122. package/dist/observation.js +84 -0
  123. package/dist/opened.d.ts +22 -0
  124. package/dist/opened.d.ts.map +1 -0
  125. package/dist/opened.js +107 -0
  126. package/dist/outcome.d.ts +29 -4
  127. package/dist/outcome.d.ts.map +1 -1
  128. package/dist/outcome.js +25 -0
  129. package/dist/overlay.d.ts +2 -5
  130. package/dist/overlay.d.ts.map +1 -1
  131. package/dist/overlay.js +86 -171
  132. package/dist/query.d.ts +2 -1
  133. package/dist/query.d.ts.map +1 -1
  134. package/dist/query.js +1 -0
  135. package/dist/reach.d.ts +5 -1
  136. package/dist/reach.d.ts.map +1 -1
  137. package/dist/reach.js +158 -52
  138. package/dist/readiness.d.ts +3 -3
  139. package/dist/readiness.d.ts.map +1 -1
  140. package/dist/reads.js +1 -1
  141. package/dist/recovery.d.ts +2 -2
  142. package/dist/recovery.d.ts.map +1 -1
  143. package/dist/recovery.js +8 -7
  144. package/dist/refresh.d.ts +10 -13
  145. package/dist/refresh.d.ts.map +1 -1
  146. package/dist/refresh.js +74 -51
  147. package/dist/replica.d.ts +54 -36
  148. package/dist/replica.d.ts.map +1 -1
  149. package/dist/replica.js +277 -184
  150. package/dist/room-draft.d.ts +3 -1
  151. package/dist/room-draft.d.ts.map +1 -1
  152. package/dist/room-draft.js +29 -22
  153. package/dist/rows.d.ts +29 -31
  154. package/dist/rows.d.ts.map +1 -1
  155. package/dist/rows.js +113 -69
  156. package/dist/save.d.ts +50 -14
  157. package/dist/save.d.ts.map +1 -1
  158. package/dist/save.js +502 -178
  159. package/dist/search.d.ts +11 -43
  160. package/dist/search.d.ts.map +1 -1
  161. package/dist/search.js +0 -213
  162. package/dist/shared.d.ts +8 -3
  163. package/dist/shared.d.ts.map +1 -1
  164. package/dist/shared.js +129 -66
  165. package/dist/store.d.ts +79 -93
  166. package/dist/store.d.ts.map +1 -1
  167. package/dist/store.js +318 -687
  168. package/dist/stored.d.ts +138 -0
  169. package/dist/stored.d.ts.map +1 -0
  170. package/dist/stored.js +544 -0
  171. package/dist/tables.d.ts +332 -0
  172. package/dist/tables.d.ts.map +1 -0
  173. package/dist/tables.js +570 -0
  174. package/dist/turn.d.ts +4 -2
  175. package/dist/turn.d.ts.map +1 -1
  176. package/dist/turn.js +46 -22
  177. package/dist/validation.d.ts +30 -0
  178. package/dist/validation.d.ts.map +1 -0
  179. package/dist/validation.js +162 -0
  180. package/dist/windows.d.ts +2 -6
  181. package/dist/windows.d.ts.map +1 -1
  182. package/dist/windows.js +30 -34
  183. package/dist/wire.d.ts +16 -0
  184. package/dist/wire.d.ts.map +1 -1
  185. package/dist/wire.js +4 -0
  186. package/dist/words.d.ts +19 -0
  187. package/dist/words.d.ts.map +1 -0
  188. package/dist/words.js +150 -0
  189. package/package.json +10 -4
  190. package/dist/read-side.d.ts +0 -27
  191. package/dist/read-side.d.ts.map +0 -1
  192. package/dist/read-side.js +0 -824
package/dist/save.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import { definitionOf } from './model.js';
2
- import { apply, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, serialize, target, } from '@agentshouse/mdmodel';
3
- import { immutable, queuedFile, departure, } from './change.js';
4
- import { available, changedCleanly, collectionOf, documentsOf, governing, referring, validateCollection, } from './collection.js';
2
+ import { apply, ARRAY_INDEX, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
3
+ import { immutable, leaving, queuedFile, departure, } from './change.js';
4
+ import { collectionOf, documentsOf, governing, } from './collection.js';
5
5
  import { changeBatchIdentity } from './identity.js';
6
- import { overlay } from './overlay.js';
6
+ import { overlaying } from './overlay.js';
7
7
  import { covered, normalize } from './paths.js';
8
- import { differing } from './shared.js';
8
+ import { sorting } from './shared.js';
9
+ import { paced, ROW_WEIGHT, stepped } from './turn.js';
10
+ import { validatedCollection } from './validation.js';
9
11
  function refuse(code, file, location) {
10
12
  return {
11
13
  kind: 'refused',
@@ -21,7 +23,7 @@ function repointed(document, definition, from, to, moves, settled) {
21
23
  const parsed = parse(document.content, from);
22
24
  if (!parsed.ok)
23
25
  return [];
24
- let next = parsed.document;
26
+ const changes = [];
25
27
  const names = new Set();
26
28
  for (const { pointer, value } of definition === undefined
27
29
  ? []
@@ -33,38 +35,39 @@ function repointed(document, definition, from, to, moves, settled) {
33
35
  const destination = moves.get(named) ?? named;
34
36
  if (target(to, value) === destination)
35
37
  continue;
36
- const applied = apply(next, {
38
+ changes.push({
37
39
  kind: 'reference',
38
40
  pointer,
39
41
  value: retarget(value, relative(to, destination)),
40
42
  prior: value,
41
43
  });
42
- if (applied.outcome !== 'applied')
43
- continue;
44
- next = applied.document;
45
44
  names.add(name);
46
45
  }
46
+ if (changes.length === 0)
47
+ return [];
48
+ const applied = apply(parsed.document, changes);
49
+ if (applied.outcome !== 'applied')
50
+ return [];
51
+ const next = applied.document;
47
52
  return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
48
53
  }
49
- function edited(document, path, changes) {
50
- let next = document;
54
+ function edited(document, changes) {
55
+ const parts = new Map();
51
56
  for (const change of changes) {
52
- if (change.path !== path)
53
- continue;
54
- if (change.kind !== 'section' && change.kind !== 'preamble')
55
- continue;
56
- const applied = apply(next, change.kind === 'preamble'
57
- ? { kind: 'preamble', value: change.value, prior: next.preamble }
58
- : {
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}`, {
59
61
  kind: 'section',
60
62
  name: change.name,
61
63
  value: change.value,
62
- prior: next.sections[change.name],
64
+ prior: document.sections[change.name] ?? '',
63
65
  });
64
- if (applied.outcome === 'applied')
65
- next = applied.document;
66
66
  }
67
- return next;
67
+ if (parts.size === 0)
68
+ return document;
69
+ const applied = apply(document, [...parts.values()]);
70
+ return applied.outcome === 'applied' ? applied.document : document;
68
71
  }
69
72
  function bodies(path, before, after) {
70
73
  const changes = [];
@@ -79,7 +82,7 @@ function bodies(path, before, after) {
79
82
  return changes;
80
83
  }
81
84
  function relinks(path, moved, before, moves) {
82
- let after = before;
85
+ const changes = [];
83
86
  for (const link of links(before, path)) {
84
87
  const destination = moves.get(link.target) ?? link.target;
85
88
  if (destination === link.target && moved === path)
@@ -87,20 +90,23 @@ function relinks(path, moved, before, moves) {
87
90
  const to = relative(moved, destination);
88
91
  if (to === relative(path, link.target))
89
92
  continue;
90
- const applied = apply(after, { kind: 'link', href: link.href, to });
91
- if (applied.outcome === 'refuse') {
92
- return {
93
- kind: 'refused',
94
- refusal: {
95
- code: applied.refusal.rule,
96
- retryable: false,
97
- file: path,
98
- ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
99
- },
100
- };
101
- }
102
- after = applied.document;
93
+ changes.push({ kind: 'link', href: link.href, to });
103
94
  }
95
+ if (changes.length === 0)
96
+ 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
+ }
109
+ const after = applied.document;
104
110
  if (after === before)
105
111
  return { kind: 'changes', changes: [] };
106
112
  if (before.type === undefined) {
@@ -108,19 +114,14 @@ function relinks(path, moved, before, moves) {
108
114
  }
109
115
  return { kind: 'changes', changes: bodies(path, before, after) };
110
116
  }
111
- function subsumed(merged, path) {
112
- for (let index = merged.length - 1; index >= 0; index -= 1) {
113
- const item = merged[index];
114
- if (item.path !== path)
115
- continue;
116
- if (item.kind === 'section' || item.kind === 'preamble')
117
- merged.splice(index, 1);
118
- }
117
+ function partKey(change) {
118
+ return change.kind === 'section' ? `section ${change.name}` : change.kind;
119
119
  }
120
- function rewrites(changes, visible) {
120
+ function* rewrites(changes, visible) {
121
121
  const moves = new Map();
122
122
  const settled = new Set();
123
- for (const change of changes) {
123
+ const parts = new Map();
124
+ for (const [index, change] of changes.entries()) {
124
125
  if (change.kind === 'move')
125
126
  moves.set(change.path, change.to);
126
127
  if (change.kind === 'remove' ||
@@ -131,54 +132,143 @@ function rewrites(changes, visible) {
131
132
  }
132
133
  if (change.kind === 'field')
133
134
  settled.add(`${change.path} ${change.name}`);
135
+ if (isPart(change)) {
136
+ const held = parts.get(change.path);
137
+ if (held === undefined)
138
+ parts.set(change.path, [index]);
139
+ else
140
+ held.push(index);
141
+ }
134
142
  }
135
143
  if (moves.size === 0)
136
144
  return { kind: 'changes', changes };
137
145
  const merged = [...changes];
138
146
  const added = [];
139
- for (const [path, document] of documentsOf(visible.collection)) {
147
+ for (const [path, document] of yield* documentsOf(visible.collection)) {
140
148
  if (settled.has(path))
141
149
  continue;
142
150
  const moved = moves.get(path) ?? path;
143
151
  const definition = definitionOf(governing(visible.collection.composition, path), document.type);
144
- added.push(...repointed(document, definition, path, moved, moves, settled));
145
- const relinked = relinks(path, moved, edited(document, path, merged), moves);
152
+ const repointing = repointed(document, definition, path, moved, moves, settled);
153
+ added.push(...repointing);
154
+ const own = parts.get(path) ?? [];
155
+ const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
146
156
  if (relinked.kind === 'refused')
147
157
  return relinked;
158
+ const keyed = new Map();
159
+ for (const index of own) {
160
+ const key = partKey(merged[index]);
161
+ if (!keyed.has(key))
162
+ keyed.set(key, index);
163
+ }
148
164
  for (const change of relinked.changes) {
149
165
  if (change.kind === 'replace') {
150
- subsumed(merged, path);
166
+ for (const index of own) {
167
+ const kind = merged[index]?.kind;
168
+ if (kind === 'section' || kind === 'preamble')
169
+ merged[index] = null;
170
+ }
151
171
  added.push(change);
152
172
  continue;
153
173
  }
154
- const index = merged.findIndex((item) => item.kind === change.kind &&
155
- item.path === path &&
156
- (change.kind !== 'section' || (item.kind === 'section' && item.name === change.name)));
157
- if (index === -1)
174
+ const index = keyed.get(partKey(change));
175
+ if (index === undefined)
158
176
  added.push(change);
159
177
  else
160
178
  merged[index] = change;
161
179
  }
180
+ const text = visible.collection.text[path].length;
181
+ const rewritten = repointing.length > 0 || relinked.changes.length > 0;
182
+ yield ROW_WEIGHT + text + (rewritten ? text : 0);
162
183
  }
163
- return { kind: 'changes', changes: [...added, ...merged] };
184
+ return {
185
+ kind: 'changes',
186
+ changes: [...added, ...merged.filter((change) => change !== null)],
187
+ };
164
188
  }
165
- function anticipated(changes, preconditions, rewritten, visible) {
166
- const derived = rewritten.filter((change) => !changes.includes(change));
189
+ function* anticipated(changes, preconditions, rewritten, visible) {
190
+ const given = new Set(changes);
191
+ const derived = rewritten.filter((change) => !given.has(change));
167
192
  return {
168
193
  changes: rewritten,
169
- preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
194
+ preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
170
195
  };
171
196
  }
172
- export function providerRewritten(batch, visible) {
173
- const rewritten = rewrites(batch.changes, visible);
197
+ export function* providerRewritten(batch, visible) {
198
+ const rewritten = yield* rewrites(batch.changes, visible);
174
199
  if (rewritten.kind === 'refused')
175
200
  return batch;
176
201
  return {
177
202
  ...batch,
178
- ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
203
+ ...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
204
+ };
205
+ }
206
+ function crossed(document, definition, path, to) {
207
+ const changes = [];
208
+ for (const link of links(document, path)) {
209
+ const written = to(link.target, link.href);
210
+ if (written !== undefined)
211
+ changes.push({ kind: 'link', href: link.href, to: written });
212
+ }
213
+ for (const { pointer, value } of definition === undefined
214
+ ? []
215
+ : references(document, definition)) {
216
+ const named = target(path, value);
217
+ const written = named === null ? undefined : to(named, value);
218
+ if (written !== undefined) {
219
+ changes.push({ kind: 'reference', pointer, value: retarget(value, written), prior: value });
220
+ }
221
+ }
222
+ return changes;
223
+ }
224
+ function rewritten(path, document, changes) {
225
+ if (changes.length === 0)
226
+ return [];
227
+ const applied = apply(document, changes);
228
+ if (applied.outcome !== 'applied')
229
+ return [];
230
+ const after = applied.document;
231
+ if (document.type === undefined)
232
+ 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
+ ];
238
+ }
239
+ function roomPathOf(handle, path) {
240
+ return `/rooms/${handle}/${path}`;
241
+ }
242
+ export function* leavingRewritten(batch, visible) {
243
+ const between = batch.between;
244
+ if (between === undefined)
245
+ return batch;
246
+ const moved = new Map(between.moves.map(({ path, to }) => [path, roomPathOf(between.destination.handle, to)]));
247
+ const derived = [];
248
+ for (const [path, document] of yield* documentsOf(visible.collection)) {
249
+ if (moved.has(path))
250
+ 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))));
253
+ yield ROW_WEIGHT + visible.collection.text[path].length;
254
+ }
255
+ if (derived.length === 0)
256
+ return batch;
257
+ return {
258
+ ...batch,
259
+ changes: [...derived, ...batch.changes],
260
+ preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...batch.preconditions],
179
261
  };
180
262
  }
181
- function incoming(collection, removed, candidates) {
263
+ export function rewriting(batch, locator) {
264
+ return leaving(batch, locator) || batch.changes.some((change) => change.kind === 'move');
265
+ }
266
+ export function* effectiveIn(batch, locator, visible) {
267
+ return leaving(batch, locator)
268
+ ? yield* leavingRewritten(batch, visible)
269
+ : yield* providerRewritten(batch, visible);
270
+ }
271
+ function* incoming(collection, removed, candidates) {
182
272
  const found = new Map();
183
273
  const note = (target, file) => {
184
274
  if (!removed.has(target))
@@ -187,13 +277,20 @@ function incoming(collection, removed, candidates) {
187
277
  referrers.add(file);
188
278
  found.set(target, referrers);
189
279
  };
190
- const read = candidates === null
191
- ? documentsOf(collection)
192
- : [...new Set(candidates)].sort().flatMap((path) => {
280
+ const read = function* (paths) {
281
+ const seen = new Set();
282
+ for (const path of paths) {
283
+ if (seen.has(path))
284
+ continue;
285
+ seen.add(path);
193
286
  const document = collection.document(path);
194
- return document === undefined ? [] : [[path, document]];
195
- });
196
- for (const [path, document] of read) {
287
+ if (document !== undefined)
288
+ yield [path, document];
289
+ }
290
+ };
291
+ for (const [path, document] of candidates === null
292
+ ? yield* documentsOf(collection)
293
+ : read(candidates)) {
197
294
  const definition = definitionOf(governing(collection.composition, path), document.type);
198
295
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
199
296
  if (to.includes(ATTACHMENT))
@@ -202,8 +299,9 @@ function incoming(collection, removed, candidates) {
202
299
  if (named !== null)
203
300
  note(named, path);
204
301
  }
302
+ yield ROW_WEIGHT + collection.text[path].length;
205
303
  }
206
- const [first] = [...found.keys()].sort();
304
+ const [first] = yield* sorting(found.keys());
207
305
  if (first === undefined)
208
306
  return null;
209
307
  const every = new Set();
@@ -216,14 +314,14 @@ function incoming(collection, removed, candidates) {
216
314
  code: 'referenced-missing-file',
217
315
  retryable: false,
218
316
  file: first,
219
- referrers: [...every].sort(),
317
+ referrers: yield* sorting(every),
220
318
  },
221
319
  };
222
320
  }
223
321
  function affected(change) {
224
322
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
225
323
  }
226
- function guard(changes, input) {
324
+ function guard(changes, input, removing) {
227
325
  const overridden = new Map();
228
326
  const existing = {
229
327
  has: (path) => overridden.get(path) ??
@@ -239,11 +337,11 @@ function guard(changes, input) {
239
337
  if (covered(input.providerOwnedPaths, normalized)) {
240
338
  return refuse('provider-owned-path', normalized);
241
339
  }
242
- const operation = change.kind === 'remove' ? 'delete' : 'write';
340
+ const operation = change.kind === 'remove' ? removing : 'write';
243
341
  if (!input.local && !covered(input.operations[operation], normalized))
244
342
  return refuse(`${operation}-grant`, normalized);
245
343
  }
246
- if (change.kind === 'create') {
344
+ if (change.kind === 'create' || change.kind === 'arrival') {
247
345
  if (existing.has(change.path))
248
346
  return refuse('path-collision', change.path);
249
347
  existing.add(change.path);
@@ -303,7 +401,7 @@ export function queuedDocuments(queue) {
303
401
  const paths = new Set();
304
402
  for (const batch of queue) {
305
403
  for (const change of batch.changes) {
306
- if (change.kind === 'binary')
404
+ if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
307
405
  continue;
308
406
  if (change.kind === 'remove')
309
407
  paths.delete(change.path);
@@ -332,7 +430,7 @@ function sameTarget(precondition, change) {
332
430
  return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
333
431
  ((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
334
432
  }
335
- function preconditionsOf(changes, visible, bases) {
433
+ function* preconditionsOf(changes, visible, bases) {
336
434
  const preconditions = [];
337
435
  const seen = new Set();
338
436
  const add = (precondition) => {
@@ -343,6 +441,7 @@ function preconditionsOf(changes, visible, bases) {
343
441
  preconditions.push(precondition);
344
442
  };
345
443
  for (const change of changes) {
444
+ yield ROW_WEIGHT;
346
445
  if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
347
446
  const based = bases.find((base) => sameTarget(base, change));
348
447
  if (based !== undefined) {
@@ -390,10 +489,13 @@ function preconditionsOf(changes, visible, bases) {
390
489
  function depth(path) {
391
490
  return path.split('/').length;
392
491
  }
393
- function exceeded(limits, changes, effective, resulting) {
492
+ function* exceeding(limits, changes, effective, resulting) {
394
493
  const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
395
494
  const created = changes
396
- .filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
495
+ .filter((change) => change.kind === 'create' ||
496
+ change.kind === 'move' ||
497
+ change.kind === 'binary' ||
498
+ change.kind === 'arrival')
397
499
  .map((change) => (change.kind === 'move' ? change.to : change.path));
398
500
  for (const limit of limits) {
399
501
  const value = limit.value;
@@ -407,9 +509,13 @@ function exceeded(limits, changes, effective, resulting) {
407
509
  return refuse('provider-limit', undefined, limit.name);
408
510
  }
409
511
  if (limit.name === 'document-size') {
410
- const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
411
- if (large !== undefined)
412
- return refuse('provider-limit', large, limit.name);
512
+ for (const path of written) {
513
+ const text = resulting.documents[path] ?? '';
514
+ if (new TextEncoder().encode(text).byteLength > value) {
515
+ return refuse('provider-limit', path, limit.name);
516
+ }
517
+ yield ROW_WEIGHT + text.length;
518
+ }
413
519
  }
414
520
  if (limit.name === 'path-length') {
415
521
  const long = created.find((path) => path.length > value);
@@ -439,7 +545,7 @@ function relativeField(path, name, value, handle, definition) {
439
545
  !opened.ok ||
440
546
  !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
441
547
  return value;
442
- const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
548
+ const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
443
549
  if (held.outcome !== 'applied')
444
550
  return value;
445
551
  const result = relativize(held.document, path, handle, definition);
@@ -475,17 +581,6 @@ function relativeChanges(input) {
475
581
  return { ...change, fields, sections };
476
582
  });
477
583
  }
478
- function unmodeledType(collection, composition) {
479
- if (available(composition))
480
- return false;
481
- for (const [path, document] of documentsOf(collection)) {
482
- if (document.type !== undefined &&
483
- document.type !== 'any' &&
484
- governing(composition, path).status === 'unavailable')
485
- return true;
486
- }
487
- return false;
488
- }
489
584
  function unqueueable(file) {
490
585
  return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
491
586
  }
@@ -495,29 +590,44 @@ function isPart(change) {
495
590
  function touched(change) {
496
591
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
497
592
  }
498
- function partApplied(content, path, change) {
593
+ function partsApplied(content, path, changes) {
499
594
  const parsed = parse(content, path);
500
595
  if (!parsed.ok)
501
596
  return null;
502
597
  const document = parsed.document;
503
- const applied = apply(document, change.kind === 'field'
504
- ? {
505
- kind: 'field',
506
- name: change.name,
507
- value: change.value,
508
- prior: document.fields[change.name],
509
- }
510
- : change.kind === 'section'
511
- ? {
598
+ const parts = new Map();
599
+ for (const change of changes) {
600
+ if (change.kind === 'field')
601
+ parts.set(`field ${change.name}`, {
602
+ kind: 'field',
603
+ name: change.name,
604
+ value: change.value,
605
+ prior: document.fields[change.name],
606
+ });
607
+ else if (change.kind === 'section')
608
+ parts.set(`section ${change.name}`, {
512
609
  kind: 'section',
513
610
  name: change.name,
514
611
  value: change.value,
515
- prior: document.sections[change.name],
516
- }
517
- : { kind: 'preamble', value: change.value, prior: document.preamble });
612
+ prior: document.sections[change.name] ?? '',
613
+ });
614
+ else
615
+ parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
616
+ }
617
+ const applied = apply(document, [...parts.values()]);
518
618
  return applied.outcome === 'applied' ? serialize(applied.document) : null;
519
619
  }
520
- function folded(changes, change, appending) {
620
+ function settledParts(changes, deferred) {
621
+ for (const [held, parts] of deferred) {
622
+ const content = partsApplied(held.content, held.path, parts);
623
+ if (content === null)
624
+ return held.path;
625
+ changes[changes.indexOf(held)] = { ...held, content };
626
+ }
627
+ deferred.clear();
628
+ return null;
629
+ }
630
+ function folded(changes, change, appending, deferred) {
521
631
  const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
522
632
  const whole = held === undefined ? -1 : changes.indexOf(held);
523
633
  const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
@@ -538,10 +648,11 @@ function folded(changes, change, appending) {
538
648
  return 'appended';
539
649
  }
540
650
  if ('content' in held) {
541
- const content = partApplied(held.content, held.path, change);
542
- if (content === null)
543
- return 'refused';
544
- changes[whole] = { ...held, content };
651
+ const parts = deferred.get(held);
652
+ if (parts === undefined)
653
+ deferred.set(held, [change]);
654
+ else
655
+ parts.push(change);
545
656
  return 'folded';
546
657
  }
547
658
  if (change.kind === 'preamble')
@@ -572,17 +683,21 @@ function folded(changes, change, appending) {
572
683
  changes.push(change);
573
684
  return 'appended';
574
685
  }
575
- function relinked(batch, move, before, after, appending) {
576
- const derived = rewrites([move], after);
686
+ function* relinked(batch, move, before, after, appending) {
687
+ const derived = yield* rewrites([move], after);
577
688
  if (derived.kind === 'refused')
578
689
  return derived;
579
690
  const changes = [...batch.changes];
580
691
  const appended = [];
581
692
  let changed = false;
693
+ const deferred = new Map();
582
694
  for (const change of derived.changes) {
583
695
  if (change === move)
584
696
  continue;
585
- const outcome = folded(changes, change, appending);
697
+ const unsettled = isPart(change) ? null : settledParts(changes, deferred);
698
+ if (unsettled !== null)
699
+ return unqueueable(unsettled);
700
+ const outcome = folded(changes, change, appending, deferred);
586
701
  if (outcome === 'refused')
587
702
  return unqueueable(change.path);
588
703
  if (outcome === 'appended')
@@ -590,9 +705,13 @@ function relinked(batch, move, before, after, appending) {
590
705
  if (outcome !== 'skipped')
591
706
  changed = true;
592
707
  }
708
+ const unsettled = settledParts(changes, deferred);
709
+ if (unsettled !== null)
710
+ return unqueueable(unsettled);
593
711
  if (!changed)
594
712
  return { kind: 'relinked', batch };
595
713
  const held = new Set(batch.preconditions.map(identity));
714
+ const added = yield* preconditionsOf(appended, before, []);
596
715
  return {
597
716
  kind: 'relinked',
598
717
  batch: immutable({
@@ -601,7 +720,7 @@ function relinked(batch, move, before, after, appending) {
601
720
  changes,
602
721
  preconditions: [
603
722
  ...batch.preconditions,
604
- ...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
723
+ ...added.filter((precondition) => !held.has(identity(precondition))),
605
724
  ],
606
725
  }),
607
726
  };
@@ -647,7 +766,7 @@ function withdrawnFile(batch, change, at) {
647
766
  ];
648
767
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
649
768
  }
650
- export function requeued(queue, changes, visible, changeable, state) {
769
+ export function* requeuing(queue, changes, visible, changeable, state) {
651
770
  const next = [...queue];
652
771
  const origins = new Map();
653
772
  const withdrawn = new Map();
@@ -670,19 +789,19 @@ export function requeued(queue, changes, visible, changeable, state) {
670
789
  return unqueueable(change.path);
671
790
  const following = next.slice(index);
672
791
  const rebuilt = [];
673
- let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
792
+ let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
674
793
  for (const [offset, batch] of following.entries()) {
675
794
  let current = batch;
676
795
  if (change.kind === 'move') {
677
- const after = state(next.slice(0, index + offset + 1));
796
+ const after = yield* state(next.slice(0, index + offset + 1));
678
797
  const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
679
- const relinking = relinked(batch, change, before, after, appending);
798
+ const relinking = yield* relinked(batch, change, before, after, appending);
680
799
  if (relinking.kind === 'refused')
681
800
  return relinking;
682
801
  current = relinking.batch;
683
802
  before = after;
684
803
  }
685
- if (current !== batch && !open(batch))
804
+ if (current !== batch && (!open(batch) || batch.between !== undefined))
686
805
  return unqueueable(change.path);
687
806
  rebuilt.push(offset === 0
688
807
  ? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
@@ -723,10 +842,17 @@ export function joined(queue, owner, extra) {
723
842
  if (clash !== undefined)
724
843
  return unqueueable(clash);
725
844
  const changes = [...owner.changes];
845
+ const deferred = new Map();
726
846
  for (const change of extra.changes) {
727
- if (folded(changes, change, true) === 'refused')
847
+ const unsettled = isPart(change) ? null : settledParts(changes, deferred);
848
+ if (unsettled !== null)
849
+ return unqueueable(unsettled);
850
+ if (folded(changes, change, true, deferred) === 'refused')
728
851
  return unqueueable(change.path);
729
852
  }
853
+ const unsettled = settledParts(changes, deferred);
854
+ if (unsettled !== null)
855
+ return unqueueable(unsettled);
730
856
  const held = new Set(owner.preconditions.map(covering));
731
857
  const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
732
858
  const preconditions = [
@@ -745,59 +871,66 @@ export function joined(queue, owner, extra) {
745
871
  });
746
872
  return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
747
873
  }
748
- export function limitedIn(queue, batch, limits, state) {
874
+ export function* limiting(queue, batch, limits, state) {
749
875
  const at = queue.indexOf(batch);
750
- const limited = exceeded(limits, batch.changes, providerRewritten(batch, state(queue.slice(0, at))).changes, state(queue.slice(0, at + 1)));
876
+ const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
877
+ const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
751
878
  return limited?.kind === 'refused' ? limited.refusal : null;
752
879
  }
753
- export function buildBatch(input) {
754
- const changes = relativeChanges(input);
755
- const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
756
- if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
757
- return refuse('model-unavailable', structural.path);
758
- if (changes.length === 0)
759
- return refuse('no-change');
760
- const rewritten = rewrites(changes, input.visible);
761
- if (rewritten.kind === 'refused')
762
- return rewritten;
763
- const refusal = guard(changes, input);
764
- if (refusal !== null)
765
- return refusal;
766
- const preconditions = preconditionsOf(changes, input.visible, input.bases);
767
- const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
768
- const resulting = overlay(input.visible, [
769
- { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
770
- ]);
771
- const clean = input.clean !== undefined && available(input.composition) ? input.clean : null;
772
- const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
880
+ function typedText(content) {
881
+ if (content === undefined)
882
+ return false;
883
+ const parsed = parse(content);
884
+ return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
885
+ }
886
+ function touchedLibrary(resulting, touched, composition) {
887
+ const entries = touched
888
+ .flatMap((path) => {
889
+ const content = resulting.documents[path];
890
+ const binary = resulting.binaries[path] !== undefined;
891
+ return content === undefined && !binary ? [] : [{ path, content: content ?? null }];
892
+ })
893
+ .sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
894
+ const contents = new Map(entries.map(({ path, content }) => [path, content]));
895
+ const binaries = new Set(Object.keys(resulting.binaries).filter((path) => resulting.binaries[path] !== undefined));
896
+ return validatedCollection(() => Promise.resolve({ entries, next: null }), async (paths) => {
897
+ const known = new Map();
898
+ const pace = paced();
899
+ for (const path of paths) {
900
+ const content = contents.get(path);
901
+ if (binaries.has(path))
902
+ known.set(path, { type: undefined, binary: true });
903
+ else if (content !== undefined && content !== null) {
904
+ const read = parse(content, path);
905
+ if (read.ok)
906
+ known.set(path, { type: read.document.type ?? 'any', binary: false });
907
+ await pace(content.length + ROW_WEIGHT);
908
+ }
909
+ }
910
+ return known;
911
+ }, composition);
912
+ }
913
+ async function judging(visible, resulting, composition, validate, removed, touched) {
773
914
  if (removed.size > 0) {
774
- const referrers = clean === null ? null : referring(clean, input.composition, removed);
775
- const referenced = incoming(collectionOf(resulting.documents, input.composition), removed, clean === null || referrers === null
776
- ? null
777
- : [...referrers, ...differing(clean.text, resulting.documents)]);
915
+ const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
778
916
  if (referenced !== null)
779
917
  return referenced;
780
918
  }
781
- const typed = (content) => {
782
- if (content === undefined)
783
- return false;
784
- const parsed = parse(content);
785
- return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
786
- };
787
- const touched = [...new Set(predicted.changes.flatMap(affected))];
788
- const typedPaths = touched.filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
789
- const needsModel = typedPaths.length > 0;
790
- const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
919
+ const typedPaths = [];
920
+ const pace = paced();
921
+ for (const path of touched) {
922
+ const before = visible.documents[path];
923
+ const after = resulting.documents[path];
924
+ if (typedText(before) || typedText(after))
925
+ typedPaths.push(path);
926
+ await pace((before?.length ?? 0) + (after?.length ?? 0) + ROW_WEIGHT);
927
+ }
928
+ const withoutModel = typedPaths.find((path) => governing(composition, path).status === 'unavailable');
791
929
  if (withoutModel !== undefined)
792
930
  return refuse('model-unavailable', withoutModel);
793
- const verdict = clean !== null &&
794
- changedCleanly({ text: resulting.documents, binaries: resulting.binaries }, clean, input.composition)
795
- ? { verdict: 'accept' }
796
- : validateCollection(needsModel
797
- ? resulting.documents
798
- : Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
799
- ? []
800
- : [[path, resulting.documents[path]]])), Object.keys(resulting.binaries), input.composition);
931
+ const verdict = typedPaths.length > 0
932
+ ? await validate(resulting, touched)
933
+ : await touchedLibrary(resulting, touched, composition);
801
934
  if (verdict.verdict === 'refuse') {
802
935
  return {
803
936
  kind: 'refused',
@@ -810,12 +943,92 @@ export function buildBatch(input) {
810
943
  },
811
944
  };
812
945
  }
813
- const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
946
+ return {
947
+ kind: 'judged',
948
+ validated: typedPaths
949
+ .map((path) => governing(composition, path))
950
+ .find((model) => model.status !== 'unavailable'),
951
+ };
952
+ }
953
+ function placed(reference) {
954
+ const key = roomPathOf(reference.handle, reference.path);
955
+ if (reference.kind === 'link')
956
+ return `link ${key}`;
957
+ if (reference.kind === 'item')
958
+ return `section ${reference.section} ${key}`;
959
+ const pointer = reference.pointer
960
+ .split('/')
961
+ .map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
962
+ .join('/');
963
+ return `field ${pointer} ${key}`;
964
+ }
965
+ function roomReferencesIn(path, content, composition) {
966
+ if (content === undefined)
967
+ return [];
968
+ const parsed = parse(content, path);
969
+ if (!parsed.ok)
970
+ return [];
971
+ return roomReferences(path, content, definitionOf(governing(composition, path), parsed.document.type));
972
+ }
973
+ function* admittedRooms(changes, visible, resulting, composition) {
974
+ const from = new Map();
975
+ for (const [index, change] of changes.entries()) {
976
+ if (change.kind === 'move')
977
+ from.set(change.to, departure(changes, index));
978
+ }
979
+ for (const path of [...new Set(changes.flatMap(affected))].sort()) {
980
+ const now = resulting.documents[path];
981
+ if (now === undefined)
982
+ continue;
983
+ const held = new Set(roomReferencesIn(path, visible.documents[from.get(path) ?? path], composition).map(placed));
984
+ for (const reference of roomReferencesIn(path, now, composition)) {
985
+ if (reference.kind === 'link' || reference.crossRoom === true)
986
+ continue;
987
+ if (held.has(placed(reference)))
988
+ continue;
989
+ return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
990
+ }
991
+ yield ROW_WEIGHT + now.length;
992
+ }
993
+ return null;
994
+ }
995
+ function validationOf(validated) {
996
+ return validated === undefined
997
+ ? {}
998
+ : {
999
+ validation: {
1000
+ identity: validated.snapshot.identity,
1001
+ version: validated.snapshot.version,
1002
+ source: validated.status,
1003
+ },
1004
+ };
1005
+ }
1006
+ async function batching(input) {
1007
+ const changes = relativeChanges(input);
1008
+ const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
1009
+ if (structural !== undefined && input.unmodeled) {
1010
+ return refuse('model-unavailable', structural.path);
1011
+ }
1012
+ if (changes.length === 0)
1013
+ return refuse('no-change');
1014
+ const rewritten = await stepped(rewrites(changes, input.visible));
1015
+ if (rewritten.kind === 'refused')
1016
+ return rewritten;
1017
+ const refusal = guard(changes, input, 'delete');
1018
+ if (refusal !== null)
1019
+ return refusal;
1020
+ const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
1021
+ const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
1022
+ const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }]));
1023
+ const judged = await judging(input.visible, resulting, input.composition, input.validate, new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path)), [...new Set(predicted.changes.flatMap(affected))]);
1024
+ if (judged.kind === 'refused')
1025
+ return judged;
1026
+ const crossing = await stepped(admittedRooms(changes, input.visible, resulting, input.composition));
1027
+ if (crossing !== null)
1028
+ return crossing;
1029
+ const limited = await stepped(exceeding(input.providerLimits, changes, predicted.changes, resulting));
814
1030
  if (limited !== null)
815
1031
  return limited;
816
- const validated = typedPaths
817
- .map((path) => governing(input.composition, path))
818
- .find((model) => model.status !== 'unavailable');
819
1032
  return {
820
1033
  kind: 'batch',
821
1034
  batch: immutable({
@@ -824,15 +1037,126 @@ export function buildBatch(input) {
824
1037
  changes,
825
1038
  preconditions,
826
1039
  ...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
827
- ...(validated === undefined
828
- ? {}
829
- : {
830
- validation: {
831
- identity: validated.snapshot.identity,
832
- version: validated.snapshot.version,
833
- source: validated.status,
834
- },
835
- }),
1040
+ ...validationOf(judged.validated),
836
1041
  }),
837
1042
  };
838
1043
  }
1044
+ export const MOVE_DOCUMENTS = 'move-documents';
1045
+ function heldAt(visible, path) {
1046
+ return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
1047
+ }
1048
+ function arrivingText(path, to, source, moved) {
1049
+ const content = source.visible.documents[path];
1050
+ const document = source.visible.collection.document(path);
1051
+ if (document === undefined)
1052
+ return content;
1053
+ const definition = definitionOf(governing(source.composition, path), document.type);
1054
+ const changes = crossed(document, definition, path, (named, href) => {
1055
+ const at = moved.get(named);
1056
+ if (at !== undefined)
1057
+ return target(to, href) === at ? undefined : relative(to, at);
1058
+ return heldAt(source.visible, named) ? roomPathOf(source.handle, named) : undefined;
1059
+ });
1060
+ if (changes.length === 0)
1061
+ return content;
1062
+ const applied = apply(document, changes);
1063
+ return applied.outcome === 'applied' ? serialize(applied.document) : content;
1064
+ }
1065
+ function arrivalOf({ path, to }, source, moved) {
1066
+ const { documents, revisions, fileIds, changes, binaries } = source.visible;
1067
+ const kept = {
1068
+ ...(fileIds[path] === undefined ? {} : { fileId: fileIds[path] }),
1069
+ ...(changes[path] === undefined ? {} : { change: changes[path] }),
1070
+ };
1071
+ const heads = {
1072
+ ...(revisions[path] === undefined ? {} : { revision: revisions[path] }),
1073
+ ...kept,
1074
+ };
1075
+ const binary = binaries[path];
1076
+ if (binary?.state === 'accepted') {
1077
+ const { fileRef, byteLength, mediaType, sha256 } = binary;
1078
+ return {
1079
+ kind: 'arrival',
1080
+ path: to,
1081
+ binary: { fileRef, byteLength, mediaType, sha256 },
1082
+ ...heads,
1083
+ };
1084
+ }
1085
+ const content = arrivingText(path, to, source, moved);
1086
+ return {
1087
+ kind: 'arrival',
1088
+ path: to,
1089
+ content,
1090
+ ...(content === documents[path] ? heads : kept),
1091
+ };
1092
+ }
1093
+ async function moving(input) {
1094
+ const { source, destination, moves } = input;
1095
+ if (moves.length === 0)
1096
+ return refuse('no-change');
1097
+ const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1098
+ const leaves = guard(removals, { ...source, local: input.local, bases: [] }, 'write');
1099
+ if (leaves !== null)
1100
+ return leaves;
1101
+ const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
1102
+ if (uploading !== undefined)
1103
+ return refuse('unknown-revision', uploading.path);
1104
+ const moved = new Map(moves.map(({ path, to }) => [path, to]));
1105
+ const arrivals = moves.map((move) => arrivalOf(move, source, moved));
1106
+ const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, 'write');
1107
+ if (arrives !== null)
1108
+ return arrives;
1109
+ const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
1110
+ if (typeof bound === 'number' && moves.length > bound) {
1111
+ return refuse('provider-limit', undefined, MOVE_DOCUMENTS);
1112
+ }
1113
+ if (source.unmodeled)
1114
+ return refuse('model-unavailable', moves[0].path);
1115
+ const id = changeBatchIdentity();
1116
+ const between = {
1117
+ source: { room: source.room, baseRoomVersion: source.baseRoomVersion },
1118
+ destination: {
1119
+ room: destination.room,
1120
+ baseRoomVersion: destination.baseRoomVersion,
1121
+ handle: destination.handle,
1122
+ },
1123
+ moves,
1124
+ };
1125
+ const written = input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy };
1126
+ const leavingBatch = {
1127
+ id,
1128
+ baseRoomVersion: source.baseRoomVersion,
1129
+ changes: removals,
1130
+ preconditions: moves.map(({ path }) => existence(source.visible, path)),
1131
+ ...written,
1132
+ between,
1133
+ };
1134
+ const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1135
+ const left = await stepped(overlaying(source.visible, [effective]));
1136
+ const sourceJudged = await judging(source.visible, left, source.composition, source.validate, new Set(moves.map(({ path }) => path)), [...new Set(effective.changes.map((change) => change.path))]);
1137
+ if (sourceJudged.kind === 'refused')
1138
+ return sourceJudged;
1139
+ const arrivingBatch = {
1140
+ id,
1141
+ baseRoomVersion: destination.baseRoomVersion,
1142
+ changes: arrivals,
1143
+ preconditions: moves.map(({ to }) => ({ kind: 'absent', path: to })),
1144
+ ...written,
1145
+ between,
1146
+ };
1147
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1148
+ const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1149
+ if (destinationJudged.kind === 'refused')
1150
+ return destinationJudged;
1151
+ const limited = (await stepped(exceeding(input.providerLimits, removals, effective.changes, left))) ??
1152
+ (await stepped(exceeding(input.providerLimits, arrivals, arrivals, arrived)));
1153
+ if (limited !== null)
1154
+ return limited;
1155
+ return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1156
+ }
1157
+ export function buildMove(input) {
1158
+ return moving(input);
1159
+ }
1160
+ export function buildBatch(input) {
1161
+ return batching(input);
1162
+ }