@agentshouse/mdruntime 0.1.0-ca9afacd5aebd → 0.1.0-caee7332d2235

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 (155) hide show
  1. package/AGENTS.md +271 -290
  2. package/README.md +8 -8
  3. package/dist/adapter.d.ts +52 -10
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/attempt.d.ts.map +1 -1
  6. package/dist/attempt.js +14 -1
  7. package/dist/bootstrap.d.ts +3 -2
  8. package/dist/bootstrap.d.ts.map +1 -1
  9. package/dist/bootstrap.js +33 -7
  10. package/dist/change-set.d.ts +7 -2
  11. package/dist/change-set.d.ts.map +1 -1
  12. package/dist/change-set.js +41 -10
  13. package/dist/chat/acts.d.ts +3 -4
  14. package/dist/chat/acts.d.ts.map +1 -1
  15. package/dist/chat/acts.js +10 -10
  16. package/dist/chat/earlier.d.ts +10 -0
  17. package/dist/chat/earlier.d.ts.map +1 -0
  18. package/dist/chat/earlier.js +54 -0
  19. package/dist/chat/kit.d.ts +2 -1
  20. package/dist/chat/kit.d.ts.map +1 -1
  21. package/dist/chat/kit.js +3 -3
  22. package/dist/chat/list.d.ts +8 -1
  23. package/dist/chat/list.d.ts.map +1 -1
  24. package/dist/chat/list.js +48 -8
  25. package/dist/chat/markdown.d.ts +3 -1
  26. package/dist/chat/markdown.d.ts.map +1 -1
  27. package/dist/chat/markdown.js +85 -26
  28. package/dist/chat/message.d.ts.map +1 -1
  29. package/dist/chat/message.js +3 -3
  30. package/dist/chat/messages.d.ts +1 -0
  31. package/dist/chat/messages.d.ts.map +1 -1
  32. package/dist/chat/messages.js +35 -15
  33. package/dist/chat/pieces.d.ts +62 -0
  34. package/dist/chat/pieces.d.ts.map +1 -0
  35. package/dist/chat/pieces.js +1110 -0
  36. package/dist/chat/provider.d.ts +1 -1
  37. package/dist/chat/provider.d.ts.map +1 -1
  38. package/dist/chat/provider.js +65 -80
  39. package/dist/chat/question.d.ts +1 -1
  40. package/dist/chat/question.d.ts.map +1 -1
  41. package/dist/chat/question.js +7 -18
  42. package/dist/chat/ready.d.ts +1 -0
  43. package/dist/chat/ready.d.ts.map +1 -1
  44. package/dist/chat/ready.js +4 -4
  45. package/dist/chat/steady.d.ts +3 -0
  46. package/dist/chat/steady.d.ts.map +1 -0
  47. package/dist/chat/steady.js +26 -0
  48. package/dist/chat/style.js +4 -4
  49. package/dist/chat.d.ts +1 -1
  50. package/dist/chat.d.ts.map +1 -1
  51. package/dist/chat.js +1 -1
  52. package/dist/client.d.ts +17 -9
  53. package/dist/client.d.ts.map +1 -1
  54. package/dist/client.js +19 -6
  55. package/dist/collection.d.ts +20 -5
  56. package/dist/collection.d.ts.map +1 -1
  57. package/dist/collection.js +309 -33
  58. package/dist/connection.d.ts +31 -9
  59. package/dist/connection.d.ts.map +1 -1
  60. package/dist/connection.js +1602 -386
  61. package/dist/conversation.d.ts +20 -7
  62. package/dist/conversation.d.ts.map +1 -1
  63. package/dist/conversation.js +474 -163
  64. package/dist/durability.d.ts +7 -1
  65. package/dist/durability.d.ts.map +1 -1
  66. package/dist/entry.d.ts +17 -0
  67. package/dist/entry.d.ts.map +1 -0
  68. package/dist/entry.js +157 -0
  69. package/dist/fence.d.ts +6 -0
  70. package/dist/fence.d.ts.map +1 -0
  71. package/dist/fence.js +10 -0
  72. package/dist/host.d.ts +15 -3
  73. package/dist/host.d.ts.map +1 -1
  74. package/dist/host.js +94 -12
  75. package/dist/identity.d.ts +2 -0
  76. package/dist/identity.d.ts.map +1 -1
  77. package/dist/identity.js +6 -0
  78. package/dist/index.d.ts +10 -7
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +2 -0
  81. package/dist/memory.d.ts +8 -2
  82. package/dist/memory.d.ts.map +1 -1
  83. package/dist/memory.js +33 -6
  84. package/dist/model.d.ts +4 -2
  85. package/dist/model.d.ts.map +1 -1
  86. package/dist/model.js +10 -1
  87. package/dist/observation.d.ts +7 -2
  88. package/dist/observation.d.ts.map +1 -1
  89. package/dist/observation.js +29 -12
  90. package/dist/outcome.d.ts +12 -2
  91. package/dist/outcome.d.ts.map +1 -1
  92. package/dist/outcome.js +4 -0
  93. package/dist/overlay.d.ts +5 -3
  94. package/dist/overlay.d.ts.map +1 -1
  95. package/dist/overlay.js +203 -159
  96. package/dist/query.d.ts +56 -4
  97. package/dist/query.d.ts.map +1 -1
  98. package/dist/query.js +36 -7
  99. package/dist/reach.d.ts +6 -3
  100. package/dist/reach.d.ts.map +1 -1
  101. package/dist/reach.js +30 -5
  102. package/dist/react.d.ts +8 -6
  103. package/dist/react.d.ts.map +1 -1
  104. package/dist/react.js +7 -17
  105. package/dist/read-side.d.ts +16 -7
  106. package/dist/read-side.d.ts.map +1 -1
  107. package/dist/read-side.js +486 -100
  108. package/dist/readiness.d.ts +53 -0
  109. package/dist/readiness.d.ts.map +1 -0
  110. package/dist/readiness.js +33 -0
  111. package/dist/reads.d.ts +11 -0
  112. package/dist/reads.d.ts.map +1 -0
  113. package/dist/reads.js +98 -0
  114. package/dist/recovery.d.ts +25 -0
  115. package/dist/recovery.d.ts.map +1 -0
  116. package/dist/recovery.js +197 -0
  117. package/dist/refresh.d.ts +4 -4
  118. package/dist/refresh.d.ts.map +1 -1
  119. package/dist/refresh.js +44 -18
  120. package/dist/replica.d.ts +11 -5
  121. package/dist/replica.d.ts.map +1 -1
  122. package/dist/replica.js +111 -56
  123. package/dist/room-draft.d.ts +13 -1
  124. package/dist/room-draft.d.ts.map +1 -1
  125. package/dist/room-draft.js +11 -12
  126. package/dist/rows.d.ts +6 -13
  127. package/dist/rows.d.ts.map +1 -1
  128. package/dist/rows.js +10 -13
  129. package/dist/save.d.ts +10 -4
  130. package/dist/save.d.ts.map +1 -1
  131. package/dist/save.js +216 -108
  132. package/dist/search.d.ts +49 -6
  133. package/dist/search.d.ts.map +1 -1
  134. package/dist/search.js +192 -47
  135. package/dist/seed.d.ts +16 -0
  136. package/dist/seed.d.ts.map +1 -0
  137. package/dist/seed.js +75 -0
  138. package/dist/shared.d.ts +15 -0
  139. package/dist/shared.d.ts.map +1 -0
  140. package/dist/shared.js +398 -0
  141. package/dist/status.d.ts +4 -0
  142. package/dist/status.d.ts.map +1 -1
  143. package/dist/store.d.ts +61 -12
  144. package/dist/store.d.ts.map +1 -1
  145. package/dist/store.js +580 -268
  146. package/dist/turn.d.ts +9 -0
  147. package/dist/turn.d.ts.map +1 -0
  148. package/dist/turn.js +64 -0
  149. package/dist/windows.d.ts +26 -0
  150. package/dist/windows.d.ts.map +1 -0
  151. package/dist/windows.js +93 -0
  152. package/dist/wire.d.ts +152 -32
  153. package/dist/wire.d.ts.map +1 -1
  154. package/dist/wire.js +52 -31
  155. package/package.json +12 -8
package/dist/save.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { definitionOf } from './model.js';
2
2
  import { apply, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, serialize, target, } from '@agentshouse/mdmodel';
3
3
  import { immutable, queuedFile, departure, } from './change.js';
4
- import { collectionOf, documentsOf, governing, validateCollection, } from './collection.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 } from './shared.js';
9
+ import { finished, 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,6 +131,13 @@ 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 };
@@ -141,43 +149,57 @@ function rewrites(changes, visible) {
141
149
  const moved = moves.get(path) ?? path;
142
150
  const definition = definitionOf(governing(visible.collection.composition, path), document.type);
143
151
  added.push(...repointed(document, definition, path, moved, moves, settled));
144
- const relinked = relinks(path, moved, edited(document, path, merged), moves);
152
+ const own = parts.get(path) ?? [];
153
+ const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
145
154
  if (relinked.kind === 'refused')
146
155
  return relinked;
156
+ const keyed = new Map();
157
+ for (const index of own) {
158
+ const key = partKey(merged[index]);
159
+ if (!keyed.has(key))
160
+ keyed.set(key, index);
161
+ }
147
162
  for (const change of relinked.changes) {
148
163
  if (change.kind === 'replace') {
149
- subsumed(merged, path);
164
+ for (const index of own) {
165
+ const kind = merged[index]?.kind;
166
+ if (kind === 'section' || kind === 'preamble')
167
+ merged[index] = null;
168
+ }
150
169
  added.push(change);
151
170
  continue;
152
171
  }
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)
172
+ const index = keyed.get(partKey(change));
173
+ if (index === undefined)
157
174
  added.push(change);
158
175
  else
159
176
  merged[index] = change;
160
177
  }
178
+ yield ROW_WEIGHT + visible.collection.text[path].length;
161
179
  }
162
- return { kind: 'changes', changes: [...added, ...merged] };
180
+ return {
181
+ kind: 'changes',
182
+ changes: [...added, ...merged.filter((change) => change !== null)],
183
+ };
163
184
  }
164
- function anticipated(changes, preconditions, rewritten, visible) {
165
- const derived = rewritten.filter((change) => !changes.includes(change));
185
+ function* anticipated(changes, preconditions, rewritten, visible) {
186
+ const given = new Set(changes);
187
+ const derived = rewritten.filter((change) => !given.has(change));
166
188
  return {
167
189
  changes: rewritten,
168
- preconditions: [...preconditionsOf(derived, visible, []), ...preconditions],
190
+ preconditions: [...(yield* preconditionsOf(derived, visible, [])), ...preconditions],
169
191
  };
170
192
  }
171
- export function providerRewritten(batch, visible) {
172
- const rewritten = rewrites(batch.changes, visible);
193
+ export function* providerRewritten(batch, visible) {
194
+ const rewritten = yield* rewrites(batch.changes, visible);
173
195
  if (rewritten.kind === 'refused')
174
196
  return batch;
175
197
  return {
176
198
  ...batch,
177
- ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
199
+ ...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
178
200
  };
179
201
  }
180
- function incoming(collection, removed) {
202
+ function* incoming(collection, removed, candidates) {
181
203
  const found = new Map();
182
204
  const note = (target, file) => {
183
205
  if (!removed.has(target))
@@ -186,7 +208,14 @@ function incoming(collection, removed) {
186
208
  referrers.add(file);
187
209
  found.set(target, referrers);
188
210
  };
189
- for (const [path, document] of documentsOf(collection)) {
211
+ const read = function* (paths) {
212
+ for (const path of [...new Set(paths)].sort()) {
213
+ const document = collection.document(path);
214
+ if (document !== undefined)
215
+ yield [path, document];
216
+ }
217
+ };
218
+ for (const [path, document] of candidates === null ? documentsOf(collection) : read(candidates)) {
190
219
  const definition = definitionOf(governing(collection.composition, path), document.type);
191
220
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
192
221
  if (to.includes(ATTACHMENT))
@@ -195,6 +224,7 @@ function incoming(collection, removed) {
195
224
  if (named !== null)
196
225
  note(named, path);
197
226
  }
227
+ yield ROW_WEIGHT + collection.text[path].length;
198
228
  }
199
229
  const [first] = [...found.keys()].sort();
200
230
  if (first === undefined)
@@ -217,10 +247,13 @@ function affected(change) {
217
247
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
218
248
  }
219
249
  function guard(changes, input) {
220
- const existing = new Set([
221
- ...Object.keys(input.visible.documents),
222
- ...Object.keys(input.visible.binaries),
223
- ]);
250
+ const overridden = new Map();
251
+ const existing = {
252
+ has: (path) => overridden.get(path) ??
253
+ (Object.hasOwn(input.visible.documents, path) || Object.hasOwn(input.visible.binaries, path)),
254
+ add: (path) => void overridden.set(path, true),
255
+ delete: (path) => void overridden.set(path, false),
256
+ };
224
257
  for (const [index, change] of changes.entries()) {
225
258
  for (const path of affected(change)) {
226
259
  const normalized = normalize(path);
@@ -275,7 +308,7 @@ function guard(changes, input) {
275
308
  continue;
276
309
  }
277
310
  if (change.kind === 'replace') {
278
- if (!input.local && input.visible.revisions[change.path] === undefined) {
311
+ if (unrevised(input, change.path)) {
279
312
  return refuse('unknown-revision', change.path);
280
313
  }
281
314
  continue;
@@ -289,6 +322,22 @@ function guard(changes, input) {
289
322
  function unrevised(input, path) {
290
323
  return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
291
324
  }
325
+ export function queuedDocuments(queue) {
326
+ const paths = new Set();
327
+ for (const batch of queue) {
328
+ for (const change of batch.changes) {
329
+ if (change.kind === 'binary')
330
+ continue;
331
+ if (change.kind === 'remove')
332
+ paths.delete(change.path);
333
+ else if (change.kind !== 'move')
334
+ paths.add(change.path);
335
+ else if (paths.delete(change.path))
336
+ paths.add(change.to);
337
+ }
338
+ }
339
+ return paths;
340
+ }
292
341
  function identity(precondition) {
293
342
  const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
294
343
  return `${precondition.kind} ${precondition.path} ${part}`;
@@ -306,7 +355,7 @@ function sameTarget(precondition, change) {
306
355
  return ((precondition.kind !== 'field' && precondition.kind !== 'section') ||
307
356
  ((change.kind === 'field' || change.kind === 'section') && precondition.name === change.name));
308
357
  }
309
- function preconditionsOf(changes, visible, bases) {
358
+ function* preconditionsOf(changes, visible, bases) {
310
359
  const preconditions = [];
311
360
  const seen = new Set();
312
361
  const add = (precondition) => {
@@ -317,6 +366,7 @@ function preconditionsOf(changes, visible, bases) {
317
366
  preconditions.push(precondition);
318
367
  };
319
368
  for (const change of changes) {
369
+ yield ROW_WEIGHT;
320
370
  if (change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble') {
321
371
  const based = bases.find((base) => sameTarget(base, change));
322
372
  if (based !== undefined) {
@@ -364,8 +414,8 @@ function preconditionsOf(changes, visible, bases) {
364
414
  function depth(path) {
365
415
  return path.split('/').length;
366
416
  }
367
- function exceeded(limits, changes, resulting) {
368
- const written = changes.map((change) => (change.kind === 'move' ? change.to : change.path));
417
+ function* exceeding(limits, changes, effective, resulting) {
418
+ const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
369
419
  const created = changes
370
420
  .filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
371
421
  .map((change) => (change.kind === 'move' ? change.to : change.path));
@@ -381,9 +431,13 @@ function exceeded(limits, changes, resulting) {
381
431
  return refuse('provider-limit', undefined, limit.name);
382
432
  }
383
433
  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);
434
+ for (const path of written) {
435
+ const text = resulting.documents[path] ?? '';
436
+ if (new TextEncoder().encode(text).byteLength > value) {
437
+ return refuse('provider-limit', path, limit.name);
438
+ }
439
+ yield ROW_WEIGHT + text.length;
440
+ }
387
441
  }
388
442
  if (limit.name === 'path-length') {
389
443
  const long = created.find((path) => path.length > value);
@@ -413,7 +467,7 @@ function relativeField(path, name, value, handle, definition) {
413
467
  !opened.ok ||
414
468
  !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
415
469
  return value;
416
- const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
470
+ const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
417
471
  if (held.outcome !== 'applied')
418
472
  return value;
419
473
  const result = relativize(held.document, path, handle, definition);
@@ -449,12 +503,15 @@ function relativeChanges(input) {
449
503
  return { ...change, fields, sections };
450
504
  });
451
505
  }
452
- function unmodeledType(collection, composition) {
506
+ function* unmodeledType(collection, composition) {
507
+ if (available(composition))
508
+ return false;
453
509
  for (const [path, document] of documentsOf(collection)) {
454
510
  if (document.type !== undefined &&
455
511
  document.type !== 'any' &&
456
512
  governing(composition, path).status === 'unavailable')
457
513
  return true;
514
+ yield ROW_WEIGHT + collection.text[path].length;
458
515
  }
459
516
  return false;
460
517
  }
@@ -467,29 +524,44 @@ function isPart(change) {
467
524
  function touched(change) {
468
525
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
469
526
  }
470
- function partApplied(content, path, change) {
527
+ function partsApplied(content, path, changes) {
471
528
  const parsed = parse(content, path);
472
529
  if (!parsed.ok)
473
530
  return null;
474
531
  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
- ? {
532
+ const parts = new Map();
533
+ for (const change of changes) {
534
+ if (change.kind === 'field')
535
+ parts.set(`field ${change.name}`, {
536
+ kind: 'field',
537
+ name: change.name,
538
+ value: change.value,
539
+ prior: document.fields[change.name],
540
+ });
541
+ else if (change.kind === 'section')
542
+ parts.set(`section ${change.name}`, {
484
543
  kind: 'section',
485
544
  name: change.name,
486
545
  value: change.value,
487
- prior: document.sections[change.name],
488
- }
489
- : { kind: 'preamble', value: change.value, prior: document.preamble });
546
+ prior: document.sections[change.name] ?? '',
547
+ });
548
+ else
549
+ parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
550
+ }
551
+ const applied = apply(document, [...parts.values()]);
490
552
  return applied.outcome === 'applied' ? serialize(applied.document) : null;
491
553
  }
492
- function folded(changes, change, appending) {
554
+ function settledParts(changes, deferred) {
555
+ for (const [held, parts] of deferred) {
556
+ const content = partsApplied(held.content, held.path, parts);
557
+ if (content === null)
558
+ return held.path;
559
+ changes[changes.indexOf(held)] = { ...held, content };
560
+ }
561
+ deferred.clear();
562
+ return null;
563
+ }
564
+ function folded(changes, change, appending, deferred) {
493
565
  const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
494
566
  const whole = held === undefined ? -1 : changes.indexOf(held);
495
567
  const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
@@ -510,10 +582,11 @@ function folded(changes, change, appending) {
510
582
  return 'appended';
511
583
  }
512
584
  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 };
585
+ const parts = deferred.get(held);
586
+ if (parts === undefined)
587
+ deferred.set(held, [change]);
588
+ else
589
+ parts.push(change);
517
590
  return 'folded';
518
591
  }
519
592
  if (change.kind === 'preamble')
@@ -545,16 +618,20 @@ function folded(changes, change, appending) {
545
618
  return 'appended';
546
619
  }
547
620
  function relinked(batch, move, before, after, appending) {
548
- const derived = rewrites([move], after);
621
+ const derived = finished(rewrites([move], after));
549
622
  if (derived.kind === 'refused')
550
623
  return derived;
551
624
  const changes = [...batch.changes];
552
625
  const appended = [];
553
626
  let changed = false;
627
+ const deferred = new Map();
554
628
  for (const change of derived.changes) {
555
629
  if (change === move)
556
630
  continue;
557
- const outcome = folded(changes, change, appending);
631
+ const unsettled = isPart(change) ? null : settledParts(changes, deferred);
632
+ if (unsettled !== null)
633
+ return unqueueable(unsettled);
634
+ const outcome = folded(changes, change, appending, deferred);
558
635
  if (outcome === 'refused')
559
636
  return unqueueable(change.path);
560
637
  if (outcome === 'appended')
@@ -562,6 +639,9 @@ function relinked(batch, move, before, after, appending) {
562
639
  if (outcome !== 'skipped')
563
640
  changed = true;
564
641
  }
642
+ const unsettled = settledParts(changes, deferred);
643
+ if (unsettled !== null)
644
+ return unqueueable(unsettled);
565
645
  if (!changed)
566
646
  return { kind: 'relinked', batch };
567
647
  const held = new Set(batch.preconditions.map(identity));
@@ -573,7 +653,7 @@ function relinked(batch, move, before, after, appending) {
573
653
  changes,
574
654
  preconditions: [
575
655
  ...batch.preconditions,
576
- ...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
656
+ ...finished(preconditionsOf(appended, before, [])).filter((precondition) => !held.has(identity(precondition))),
577
657
  ],
578
658
  }),
579
659
  };
@@ -695,10 +775,17 @@ export function joined(queue, owner, extra) {
695
775
  if (clash !== undefined)
696
776
  return unqueueable(clash);
697
777
  const changes = [...owner.changes];
778
+ const deferred = new Map();
698
779
  for (const change of extra.changes) {
699
- if (folded(changes, change, true) === 'refused')
780
+ const unsettled = isPart(change) ? null : settledParts(changes, deferred);
781
+ if (unsettled !== null)
782
+ return unqueueable(unsettled);
783
+ if (folded(changes, change, true, deferred) === 'refused')
700
784
  return unqueueable(change.path);
701
785
  }
786
+ const unsettled = settledParts(changes, deferred);
787
+ if (unsettled !== null)
788
+ return unqueueable(unsettled);
702
789
  const held = new Set(owner.preconditions.map(covering));
703
790
  const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
704
791
  const preconditions = [
@@ -718,30 +805,36 @@ export function joined(queue, owner, extra) {
718
805
  return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
719
806
  }
720
807
  export function limitedIn(queue, batch, limits, state) {
721
- const limited = exceeded(limits, batch.changes, state(queue.slice(0, queue.indexOf(batch) + 1)));
808
+ const at = queue.indexOf(batch);
809
+ const limited = finished(exceeding(limits, batch.changes, finished(providerRewritten(batch, state(queue.slice(0, at)))).changes, state(queue.slice(0, at + 1))));
722
810
  return limited?.kind === 'refused' ? limited.refusal : null;
723
811
  }
724
- export function buildBatch(input) {
812
+ function* batching(input) {
725
813
  const changes = relativeChanges(input);
726
814
  const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
727
- if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
815
+ if (structural !== undefined &&
816
+ (yield* unmodeledType(input.visible.collection, input.composition)))
728
817
  return refuse('model-unavailable', structural.path);
729
818
  if (changes.length === 0)
730
819
  return refuse('no-change');
731
- const rewritten = rewrites(changes, input.visible);
820
+ const rewritten = yield* rewrites(changes, input.visible);
732
821
  if (rewritten.kind === 'refused')
733
822
  return rewritten;
734
823
  const refusal = guard(changes, input);
735
824
  if (refusal !== null)
736
825
  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, [
826
+ const preconditions = yield* preconditionsOf(changes, input.visible, input.bases);
827
+ const predicted = yield* anticipated(changes, preconditions, rewritten.changes, input.visible);
828
+ const resulting = yield* overlaying(input.visible, [
740
829
  { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
741
830
  ]);
831
+ const clean = input.clean !== undefined && available(input.composition) ? input.clean : null;
742
832
  const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
743
833
  if (removed.size > 0) {
744
- const referenced = incoming(collectionOf(resulting.documents, input.composition), removed);
834
+ const referrers = clean === null ? null : referring(clean, input.composition, removed);
835
+ const referenced = yield* incoming(collectionOf(resulting.documents, input.composition), removed, clean === null || referrers === null
836
+ ? null
837
+ : [...referrers, ...(yield* differences(clean.text, resulting.documents))]);
745
838
  if (referenced !== null)
746
839
  return referenced;
747
840
  }
@@ -751,15 +844,27 @@ export function buildBatch(input) {
751
844
  const parsed = parse(content);
752
845
  return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
753
846
  };
754
- const typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
847
+ const touched = [...new Set(predicted.changes.flatMap(affected))];
848
+ const typedPaths = [];
849
+ for (const path of touched) {
850
+ const before = input.visible.documents[path];
851
+ const after = resulting.documents[path];
852
+ if (typed(before) || typed(after))
853
+ typedPaths.push(path);
854
+ yield ROW_WEIGHT + (before?.length ?? 0) + (after?.length ?? 0);
855
+ }
755
856
  const needsModel = typedPaths.length > 0;
756
857
  const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
757
858
  if (withoutModel !== undefined)
758
859
  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);
860
+ const verdict = clean !== null &&
861
+ (yield* changedCleanly({ text: resulting.documents, binaries: resulting.binaries }, clean, input.composition))
862
+ ? { verdict: 'accept' }
863
+ : yield* validateCollection(needsModel
864
+ ? resulting.documents
865
+ : Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
866
+ ? []
867
+ : [[path, resulting.documents[path]]])), Object.keys(resulting.binaries), input.composition);
763
868
  if (verdict.verdict === 'refuse') {
764
869
  return {
765
870
  kind: 'refused',
@@ -772,7 +877,7 @@ export function buildBatch(input) {
772
877
  },
773
878
  };
774
879
  }
775
- const limited = exceeded(input.providerLimits, changes, resulting);
880
+ const limited = yield* exceeding(input.providerLimits, changes, predicted.changes, resulting);
776
881
  if (limited !== null)
777
882
  return limited;
778
883
  const validated = typedPaths
@@ -798,3 +903,6 @@ export function buildBatch(input) {
798
903
  }),
799
904
  };
800
905
  }
906
+ export function buildBatch(input) {
907
+ return stepped(batching(input));
908
+ }