@agentshouse/mdruntime 0.1.0-ce131cb435f8b → 0.1.0-ce41fec66d91b

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