@agentshouse/mdruntime 0.1.0-c2aaa9c0e3ca4 → 0.1.0-c2c52158b7381

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