@agentshouse/mdruntime 0.1.0-cc5745cd9af96 → 0.1.0-ccbb89b0ca151

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 (225) hide show
  1. package/README.md +18 -15
  2. package/dist/adapter.d.ts +56 -32
  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 +21 -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 +30 -8
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +186 -85
  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 +6 -16
  17. package/dist/change-set.d.ts.map +1 -1
  18. package/dist/change-set.js +11 -94
  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 +9 -5
  23. package/dist/client.d.ts.map +1 -1
  24. package/dist/client.js +80 -6
  25. package/dist/collection.d.ts +6 -20
  26. package/dist/collection.d.ts.map +1 -1
  27. package/dist/collection.js +31 -354
  28. package/dist/compile.d.ts +16 -0
  29. package/dist/compile.d.ts.map +1 -0
  30. package/dist/compile.js +765 -0
  31. package/dist/connection.d.ts +16 -9
  32. package/dist/connection.d.ts.map +1 -1
  33. package/dist/connection.js +2399 -952
  34. package/dist/conversation.d.ts +3 -3
  35. package/dist/conversation.d.ts.map +1 -1
  36. package/dist/conversation.js +80 -68
  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 +3 -5
  41. package/dist/durability.d.ts.map +1 -1
  42. package/dist/durability.js +5 -0
  43. package/dist/entry.d.ts +3 -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 +368 -0
  52. package/dist/host.d.ts +4 -4
  53. package/dist/host.d.ts.map +1 -1
  54. package/dist/host.js +45 -15
  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 +11 -9
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +4 -1
  61. package/dist/live.d.ts +22 -0
  62. package/dist/live.d.ts.map +1 -0
  63. package/dist/live.js +124 -0
  64. package/dist/media.d.ts +8 -0
  65. package/dist/media.d.ts.map +1 -0
  66. package/dist/media.js +195 -0
  67. package/dist/memory.d.ts +2 -1
  68. package/dist/memory.d.ts.map +1 -1
  69. package/dist/memory.js +63 -22
  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.map +1 -1
  74. package/dist/model.js +1 -1
  75. package/dist/name.d.ts +4 -0
  76. package/dist/name.d.ts.map +1 -0
  77. package/dist/name.js +122 -0
  78. package/dist/observation.d.ts +12 -0
  79. package/dist/observation.d.ts.map +1 -1
  80. package/dist/observation.js +84 -0
  81. package/dist/opened.d.ts +22 -0
  82. package/dist/opened.d.ts.map +1 -0
  83. package/dist/opened.js +107 -0
  84. package/dist/outcome.d.ts +40 -5
  85. package/dist/outcome.d.ts.map +1 -1
  86. package/dist/outcome.js +36 -2
  87. package/dist/overlay.d.ts +1 -4
  88. package/dist/overlay.d.ts.map +1 -1
  89. package/dist/overlay.js +22 -148
  90. package/dist/query.d.ts +11 -1
  91. package/dist/query.d.ts.map +1 -1
  92. package/dist/query.js +13 -1
  93. package/dist/reach.d.ts +11 -3
  94. package/dist/reach.d.ts.map +1 -1
  95. package/dist/reach.js +198 -69
  96. package/dist/readiness.d.ts +20 -9
  97. package/dist/readiness.d.ts.map +1 -1
  98. package/dist/readiness.js +6 -6
  99. package/dist/reads.d.ts.map +1 -1
  100. package/dist/reads.js +4 -2
  101. package/dist/recovery.d.ts +2 -2
  102. package/dist/recovery.d.ts.map +1 -1
  103. package/dist/recovery.js +8 -7
  104. package/dist/refresh.d.ts +15 -14
  105. package/dist/refresh.d.ts.map +1 -1
  106. package/dist/refresh.js +113 -75
  107. package/dist/replica.d.ts +56 -39
  108. package/dist/replica.d.ts.map +1 -1
  109. package/dist/replica.js +277 -197
  110. package/dist/room-draft.d.ts +4 -1
  111. package/dist/room-draft.d.ts.map +1 -1
  112. package/dist/room-draft.js +54 -13
  113. package/dist/rows.d.ts +38 -31
  114. package/dist/rows.d.ts.map +1 -1
  115. package/dist/rows.js +173 -101
  116. package/dist/save.d.ts +49 -14
  117. package/dist/save.d.ts.map +1 -1
  118. package/dist/save.js +352 -98
  119. package/dist/search.d.ts +11 -43
  120. package/dist/search.d.ts.map +1 -1
  121. package/dist/search.js +0 -213
  122. package/dist/shared.d.ts +4 -3
  123. package/dist/shared.d.ts.map +1 -1
  124. package/dist/shared.js +67 -34
  125. package/dist/store.d.ts +83 -95
  126. package/dist/store.d.ts.map +1 -1
  127. package/dist/store.js +317 -720
  128. package/dist/stored.d.ts +152 -0
  129. package/dist/stored.d.ts.map +1 -0
  130. package/dist/stored.js +607 -0
  131. package/dist/tables.d.ts +352 -0
  132. package/dist/tables.d.ts.map +1 -0
  133. package/dist/tables.js +624 -0
  134. package/dist/turn.d.ts +1 -1
  135. package/dist/turn.d.ts.map +1 -1
  136. package/dist/turn.js +31 -20
  137. package/dist/validation.d.ts +30 -0
  138. package/dist/validation.d.ts.map +1 -0
  139. package/dist/validation.js +162 -0
  140. package/dist/windows.d.ts +2 -6
  141. package/dist/windows.d.ts.map +1 -1
  142. package/dist/windows.js +30 -34
  143. package/dist/wire.d.ts +17 -1
  144. package/dist/wire.d.ts.map +1 -1
  145. package/dist/wire.js +5 -1
  146. package/dist/words.d.ts +19 -0
  147. package/dist/words.d.ts.map +1 -0
  148. package/dist/words.js +150 -0
  149. package/package.json +18 -19
  150. package/AGENTS.md +0 -299
  151. package/dist/chat/acts.d.ts +0 -33
  152. package/dist/chat/acts.d.ts.map +0 -1
  153. package/dist/chat/acts.js +0 -36
  154. package/dist/chat/context.d.ts +0 -3
  155. package/dist/chat/context.d.ts.map +0 -1
  156. package/dist/chat/context.js +0 -10
  157. package/dist/chat/earlier.d.ts +0 -10
  158. package/dist/chat/earlier.d.ts.map +0 -1
  159. package/dist/chat/earlier.js +0 -54
  160. package/dist/chat/input.d.ts +0 -9
  161. package/dist/chat/input.d.ts.map +0 -1
  162. package/dist/chat/input.js +0 -99
  163. package/dist/chat/kit.d.ts +0 -61
  164. package/dist/chat/kit.d.ts.map +0 -1
  165. package/dist/chat/kit.js +0 -123
  166. package/dist/chat/lasting.d.ts +0 -6
  167. package/dist/chat/lasting.d.ts.map +0 -1
  168. package/dist/chat/lasting.js +0 -19
  169. package/dist/chat/list.d.ts +0 -28
  170. package/dist/chat/list.d.ts.map +0 -1
  171. package/dist/chat/list.js +0 -103
  172. package/dist/chat/markdown.d.ts +0 -21
  173. package/dist/chat/markdown.d.ts.map +0 -1
  174. package/dist/chat/markdown.js +0 -246
  175. package/dist/chat/message.d.ts +0 -7
  176. package/dist/chat/message.d.ts.map +0 -1
  177. package/dist/chat/message.js +0 -202
  178. package/dist/chat/messages.d.ts +0 -24
  179. package/dist/chat/messages.d.ts.map +0 -1
  180. package/dist/chat/messages.js +0 -187
  181. package/dist/chat/name.d.ts +0 -6
  182. package/dist/chat/name.d.ts.map +0 -1
  183. package/dist/chat/name.js +0 -37
  184. package/dist/chat/opened.d.ts +0 -11
  185. package/dist/chat/opened.d.ts.map +0 -1
  186. package/dist/chat/opened.js +0 -17
  187. package/dist/chat/overlay.d.ts +0 -32
  188. package/dist/chat/overlay.d.ts.map +0 -1
  189. package/dist/chat/overlay.js +0 -107
  190. package/dist/chat/parts.d.ts +0 -57
  191. package/dist/chat/parts.d.ts.map +0 -1
  192. package/dist/chat/parts.js +0 -119
  193. package/dist/chat/pieces.d.ts +0 -62
  194. package/dist/chat/pieces.d.ts.map +0 -1
  195. package/dist/chat/pieces.js +0 -1110
  196. package/dist/chat/process.d.ts +0 -20
  197. package/dist/chat/process.d.ts.map +0 -1
  198. package/dist/chat/process.js +0 -32
  199. package/dist/chat/provider.d.ts +0 -64
  200. package/dist/chat/provider.d.ts.map +0 -1
  201. package/dist/chat/provider.js +0 -638
  202. package/dist/chat/question.d.ts +0 -9
  203. package/dist/chat/question.d.ts.map +0 -1
  204. package/dist/chat/question.js +0 -69
  205. package/dist/chat/questions.d.ts +0 -31
  206. package/dist/chat/questions.d.ts.map +0 -1
  207. package/dist/chat/questions.js +0 -111
  208. package/dist/chat/read.d.ts +0 -5
  209. package/dist/chat/read.d.ts.map +0 -1
  210. package/dist/chat/read.js +0 -13
  211. package/dist/chat/ready.d.ts +0 -34
  212. package/dist/chat/ready.d.ts.map +0 -1
  213. package/dist/chat/ready.js +0 -91
  214. package/dist/chat/steady.d.ts +0 -3
  215. package/dist/chat/steady.d.ts.map +0 -1
  216. package/dist/chat/steady.js +0 -26
  217. package/dist/chat/style.d.ts +0 -4
  218. package/dist/chat/style.d.ts.map +0 -1
  219. package/dist/chat/style.js +0 -302
  220. package/dist/chat.d.ts +0 -14
  221. package/dist/chat.d.ts.map +0 -1
  222. package/dist/chat.js +0 -8
  223. package/dist/read-side.d.ts +0 -27
  224. package/dist/read-side.d.ts.map +0 -1
  225. package/dist/read-side.js +0 -826
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
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
+ 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',
@@ -143,12 +144,13 @@ function* rewrites(changes, visible) {
143
144
  return { kind: 'changes', changes };
144
145
  const merged = [...changes];
145
146
  const added = [];
146
- for (const [path, document] of documentsOf(visible.collection)) {
147
+ for (const [path, document] of yield* documentsOf(visible.collection)) {
147
148
  if (settled.has(path))
148
149
  continue;
149
150
  const moved = moves.get(path) ?? path;
150
151
  const definition = definitionOf(governing(visible.collection.composition, path), document.type);
151
- added.push(...repointed(document, definition, path, moved, moves, settled));
152
+ const repointing = repointed(document, definition, path, moved, moves, settled);
153
+ added.push(...repointing);
152
154
  const own = parts.get(path) ?? [];
153
155
  const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
154
156
  if (relinked.kind === 'refused')
@@ -175,7 +177,9 @@ function* rewrites(changes, visible) {
175
177
  else
176
178
  merged[index] = change;
177
179
  }
178
- yield ROW_WEIGHT + visible.collection.text[path].length;
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);
179
183
  }
180
184
  return {
181
185
  kind: 'changes',
@@ -199,6 +203,71 @@ export function* providerRewritten(batch, visible) {
199
203
  ...(yield* anticipated(batch.changes, batch.preconditions, rewritten.changes, visible)),
200
204
  };
201
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],
261
+ };
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
+ }
202
271
  function* incoming(collection, removed, candidates) {
203
272
  const found = new Map();
204
273
  const note = (target, file) => {
@@ -209,13 +278,19 @@ function* incoming(collection, removed, candidates) {
209
278
  found.set(target, referrers);
210
279
  };
211
280
  const read = function* (paths) {
212
- 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);
213
286
  const document = collection.document(path);
214
287
  if (document !== undefined)
215
288
  yield [path, document];
216
289
  }
217
290
  };
218
- 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)) {
219
294
  const definition = definitionOf(governing(collection.composition, path), document.type);
220
295
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
221
296
  if (to.includes(ATTACHMENT))
@@ -226,7 +301,7 @@ function* incoming(collection, removed, candidates) {
226
301
  }
227
302
  yield ROW_WEIGHT + collection.text[path].length;
228
303
  }
229
- const [first] = [...found.keys()].sort();
304
+ const [first] = yield* sorting(found.keys());
230
305
  if (first === undefined)
231
306
  return null;
232
307
  const every = new Set();
@@ -239,14 +314,14 @@ function* incoming(collection, removed, candidates) {
239
314
  code: 'referenced-missing-file',
240
315
  retryable: false,
241
316
  file: first,
242
- referrers: [...every].sort(),
317
+ referrers: yield* sorting(every),
243
318
  },
244
319
  };
245
320
  }
246
321
  function affected(change) {
247
322
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
248
323
  }
249
- function guard(changes, input) {
324
+ function guard(changes, input, removing) {
250
325
  const overridden = new Map();
251
326
  const existing = {
252
327
  has: (path) => overridden.get(path) ??
@@ -262,11 +337,11 @@ function guard(changes, input) {
262
337
  if (covered(input.providerOwnedPaths, normalized)) {
263
338
  return refuse('provider-owned-path', normalized);
264
339
  }
265
- const operation = change.kind === 'remove' ? 'delete' : 'write';
340
+ const operation = change.kind === 'remove' ? removing : 'write';
266
341
  if (!input.local && !covered(input.operations[operation], normalized))
267
342
  return refuse(`${operation}-grant`, normalized);
268
343
  }
269
- if (change.kind === 'create') {
344
+ if (change.kind === 'create' || change.kind === 'arrival') {
270
345
  if (existing.has(change.path))
271
346
  return refuse('path-collision', change.path);
272
347
  existing.add(change.path);
@@ -326,7 +401,7 @@ export function queuedDocuments(queue) {
326
401
  const paths = new Set();
327
402
  for (const batch of queue) {
328
403
  for (const change of batch.changes) {
329
- if (change.kind === 'binary')
404
+ if (change.kind === 'binary' || (change.kind === 'arrival' && !('content' in change)))
330
405
  continue;
331
406
  if (change.kind === 'remove')
332
407
  paths.delete(change.path);
@@ -417,7 +492,10 @@ function depth(path) {
417
492
  function* exceeding(limits, changes, effective, resulting) {
418
493
  const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
419
494
  const created = changes
420
- .filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
495
+ .filter((change) => change.kind === 'create' ||
496
+ change.kind === 'move' ||
497
+ change.kind === 'binary' ||
498
+ change.kind === 'arrival')
421
499
  .map((change) => (change.kind === 'move' ? change.to : change.path));
422
500
  for (const limit of limits) {
423
501
  const value = limit.value;
@@ -503,18 +581,6 @@ function relativeChanges(input) {
503
581
  return { ...change, fields, sections };
504
582
  });
505
583
  }
506
- function* unmodeledType(collection, composition) {
507
- if (available(composition))
508
- return false;
509
- for (const [path, document] of documentsOf(collection)) {
510
- if (document.type !== undefined &&
511
- document.type !== 'any' &&
512
- governing(composition, path).status === 'unavailable')
513
- return true;
514
- yield ROW_WEIGHT + collection.text[path].length;
515
- }
516
- return false;
517
- }
518
584
  function unqueueable(file) {
519
585
  return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
520
586
  }
@@ -617,8 +683,8 @@ function folded(changes, change, appending, deferred) {
617
683
  changes.push(change);
618
684
  return 'appended';
619
685
  }
620
- function relinked(batch, move, before, after, appending) {
621
- const derived = finished(rewrites([move], after));
686
+ function* relinked(batch, move, before, after, appending) {
687
+ const derived = yield* rewrites([move], after);
622
688
  if (derived.kind === 'refused')
623
689
  return derived;
624
690
  const changes = [...batch.changes];
@@ -645,6 +711,7 @@ function relinked(batch, move, before, after, appending) {
645
711
  if (!changed)
646
712
  return { kind: 'relinked', batch };
647
713
  const held = new Set(batch.preconditions.map(identity));
714
+ const added = yield* preconditionsOf(appended, before, []);
648
715
  return {
649
716
  kind: 'relinked',
650
717
  batch: immutable({
@@ -653,7 +720,7 @@ function relinked(batch, move, before, after, appending) {
653
720
  changes,
654
721
  preconditions: [
655
722
  ...batch.preconditions,
656
- ...finished(preconditionsOf(appended, before, [])).filter((precondition) => !held.has(identity(precondition))),
723
+ ...added.filter((precondition) => !held.has(identity(precondition))),
657
724
  ],
658
725
  }),
659
726
  };
@@ -699,7 +766,7 @@ function withdrawnFile(batch, change, at) {
699
766
  ];
700
767
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
701
768
  }
702
- export function requeued(queue, changes, visible, changeable, state) {
769
+ export function* requeuing(queue, changes, visible, changeable, state) {
703
770
  const next = [...queue];
704
771
  const origins = new Map();
705
772
  const withdrawn = new Map();
@@ -722,19 +789,19 @@ export function requeued(queue, changes, visible, changeable, state) {
722
789
  return unqueueable(change.path);
723
790
  const following = next.slice(index);
724
791
  const rebuilt = [];
725
- let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
792
+ let before = change.kind === 'move' ? yield* state(next.slice(0, index)) : visible;
726
793
  for (const [offset, batch] of following.entries()) {
727
794
  let current = batch;
728
795
  if (change.kind === 'move') {
729
- const after = state(next.slice(0, index + offset + 1));
796
+ const after = yield* state(next.slice(0, index + offset + 1));
730
797
  const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
731
- const relinking = relinked(batch, change, before, after, appending);
798
+ const relinking = yield* relinked(batch, change, before, after, appending);
732
799
  if (relinking.kind === 'refused')
733
800
  return relinking;
734
801
  current = relinking.batch;
735
802
  before = after;
736
803
  }
737
- if (current !== batch && !open(batch))
804
+ if (current !== batch && (!open(batch) || batch.between !== undefined))
738
805
  return unqueueable(change.path);
739
806
  rebuilt.push(offset === 0
740
807
  ? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
@@ -804,67 +871,66 @@ export function joined(queue, owner, extra) {
804
871
  });
805
872
  return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
806
873
  }
807
- export function limitedIn(queue, batch, limits, state) {
874
+ export function* limiting(queue, batch, limits, state) {
808
875
  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))));
876
+ const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
877
+ const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
810
878
  return limited?.kind === 'refused' ? limited.refusal : null;
811
879
  }
812
- function* batching(input) {
813
- const changes = relativeChanges(input);
814
- const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
815
- if (structural !== undefined &&
816
- (yield* unmodeledType(input.visible.collection, input.composition)))
817
- return refuse('model-unavailable', structural.path);
818
- if (changes.length === 0)
819
- return refuse('no-change');
820
- const rewritten = yield* rewrites(changes, input.visible);
821
- if (rewritten.kind === 'refused')
822
- return rewritten;
823
- const refusal = guard(changes, input);
824
- if (refusal !== null)
825
- return refusal;
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, [
829
- { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
830
- ]);
831
- const clean = input.clean !== undefined && available(input.composition) ? input.clean : null;
832
- const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
880
+ function typedText(content) {
881
+ if (content === undefined)
882
+ return false;
883
+ const parsed = parse(content);
884
+ return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
885
+ }
886
+ function touchedLibrary(resulting, touched, composition) {
887
+ const entries = touched
888
+ .flatMap((path) => {
889
+ const content = resulting.documents[path];
890
+ const binary = resulting.binaries[path] !== undefined;
891
+ return content === undefined && !binary ? [] : [{ path, content: content ?? null }];
892
+ })
893
+ .sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
894
+ const contents = new Map(entries.map(({ path, content }) => [path, content]));
895
+ const binaries = new Set(Object.keys(resulting.binaries).filter((path) => resulting.binaries[path] !== undefined));
896
+ return validatedCollection(() => Promise.resolve({ entries, next: null }), async (paths) => {
897
+ const known = new Map();
898
+ const pace = paced();
899
+ for (const path of paths) {
900
+ const content = contents.get(path);
901
+ if (binaries.has(path))
902
+ known.set(path, { type: undefined, binary: true });
903
+ else if (content !== undefined && content !== null) {
904
+ const read = parse(content, path);
905
+ if (read.ok)
906
+ known.set(path, { type: read.document.type ?? 'any', binary: false });
907
+ await pace(content.length + ROW_WEIGHT);
908
+ }
909
+ }
910
+ return known;
911
+ }, composition);
912
+ }
913
+ async function judging(visible, resulting, composition, validate, removed, touched) {
833
914
  if (removed.size > 0) {
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))]);
915
+ const referenced = await stepped(incoming(collectionOf(resulting.documents, composition), removed, null));
838
916
  if (referenced !== null)
839
917
  return referenced;
840
918
  }
841
- const typed = (content) => {
842
- if (content === undefined)
843
- return false;
844
- const parsed = parse(content);
845
- return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
846
- };
847
- const touched = [...new Set(predicted.changes.flatMap(affected))];
848
919
  const typedPaths = [];
920
+ const pace = paced();
849
921
  for (const path of touched) {
850
- const before = input.visible.documents[path];
922
+ const before = visible.documents[path];
851
923
  const after = resulting.documents[path];
852
- if (typed(before) || typed(after))
924
+ if (typedText(before) || typedText(after))
853
925
  typedPaths.push(path);
854
- yield ROW_WEIGHT + (before?.length ?? 0) + (after?.length ?? 0);
926
+ await pace((before?.length ?? 0) + (after?.length ?? 0) + ROW_WEIGHT);
855
927
  }
856
- const needsModel = typedPaths.length > 0;
857
- const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
928
+ const withoutModel = typedPaths.find((path) => governing(composition, path).status === 'unavailable');
858
929
  if (withoutModel !== undefined)
859
930
  return refuse('model-unavailable', withoutModel);
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);
931
+ const verdict = typedPaths.length > 0
932
+ ? await validate(resulting, touched)
933
+ : await touchedLibrary(resulting, touched, composition);
868
934
  if (verdict.verdict === 'refuse') {
869
935
  return {
870
936
  kind: 'refused',
@@ -877,12 +943,92 @@ function* batching(input) {
877
943
  },
878
944
  };
879
945
  }
880
- const limited = yield* exceeding(input.providerLimits, changes, predicted.changes, resulting);
946
+ return {
947
+ kind: 'judged',
948
+ validated: typedPaths
949
+ .map((path) => governing(composition, path))
950
+ .find((model) => model.status !== 'unavailable'),
951
+ };
952
+ }
953
+ function placed(reference) {
954
+ const key = roomPathOf(reference.handle, reference.path);
955
+ if (reference.kind === 'link')
956
+ return `link ${key}`;
957
+ if (reference.kind === 'item')
958
+ return `section ${reference.section} ${key}`;
959
+ const pointer = reference.pointer
960
+ .split('/')
961
+ .map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
962
+ .join('/');
963
+ return `field ${pointer} ${key}`;
964
+ }
965
+ function roomReferencesIn(path, content, composition) {
966
+ if (content === undefined)
967
+ return [];
968
+ const parsed = parse(content, path);
969
+ if (!parsed.ok)
970
+ return [];
971
+ return roomReferences(path, content, definitionOf(governing(composition, path), parsed.document.type));
972
+ }
973
+ function* admittedRooms(changes, visible, resulting, composition) {
974
+ const from = new Map();
975
+ for (const [index, change] of changes.entries()) {
976
+ if (change.kind === 'move')
977
+ from.set(change.to, departure(changes, index));
978
+ }
979
+ for (const path of [...new Set(changes.flatMap(affected))].sort()) {
980
+ const now = resulting.documents[path];
981
+ if (now === undefined)
982
+ continue;
983
+ const held = new Set(roomReferencesIn(path, visible.documents[from.get(path) ?? path], composition).map(placed));
984
+ for (const reference of roomReferencesIn(path, now, composition)) {
985
+ if (reference.kind === 'link' || reference.crossRoom === true)
986
+ continue;
987
+ if (held.has(placed(reference)))
988
+ continue;
989
+ return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
990
+ }
991
+ yield ROW_WEIGHT + now.length;
992
+ }
993
+ return null;
994
+ }
995
+ function validationOf(validated) {
996
+ return validated === undefined
997
+ ? {}
998
+ : {
999
+ validation: {
1000
+ identity: validated.snapshot.identity,
1001
+ version: validated.snapshot.version,
1002
+ source: validated.status,
1003
+ },
1004
+ };
1005
+ }
1006
+ async function batching(input) {
1007
+ const changes = relativeChanges(input);
1008
+ const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
1009
+ if (structural !== undefined && input.unmodeled) {
1010
+ return refuse('model-unavailable', structural.path);
1011
+ }
1012
+ if (changes.length === 0)
1013
+ return refuse('no-change');
1014
+ const rewritten = await stepped(rewrites(changes, input.visible));
1015
+ if (rewritten.kind === 'refused')
1016
+ return rewritten;
1017
+ const refusal = guard(changes, input, 'delete');
1018
+ if (refusal !== null)
1019
+ return refusal;
1020
+ const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
1021
+ const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
1022
+ const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }]));
1023
+ const judged = await judging(input.visible, resulting, input.composition, input.validate, new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path)), [...new Set(predicted.changes.flatMap(affected))]);
1024
+ if (judged.kind === 'refused')
1025
+ return judged;
1026
+ const crossing = await stepped(admittedRooms(changes, input.visible, resulting, input.composition));
1027
+ if (crossing !== null)
1028
+ return crossing;
1029
+ const limited = await stepped(exceeding(input.providerLimits, changes, predicted.changes, resulting));
881
1030
  if (limited !== null)
882
1031
  return limited;
883
- const validated = typedPaths
884
- .map((path) => governing(input.composition, path))
885
- .find((model) => model.status !== 'unavailable');
886
1032
  return {
887
1033
  kind: 'batch',
888
1034
  batch: immutable({
@@ -891,18 +1037,126 @@ function* batching(input) {
891
1037
  changes,
892
1038
  preconditions,
893
1039
  ...(input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy }),
894
- ...(validated === undefined
895
- ? {}
896
- : {
897
- validation: {
898
- identity: validated.snapshot.identity,
899
- version: validated.snapshot.version,
900
- source: validated.status,
901
- },
902
- }),
1040
+ ...validationOf(judged.validated),
903
1041
  }),
904
1042
  };
905
1043
  }
1044
+ export const MOVE_DOCUMENTS = 'move-documents';
1045
+ function heldAt(visible, path) {
1046
+ return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
1047
+ }
1048
+ function arrivingText(path, to, source, moved) {
1049
+ const content = source.visible.documents[path];
1050
+ const document = source.visible.collection.document(path);
1051
+ if (document === undefined)
1052
+ return content;
1053
+ const definition = definitionOf(governing(source.composition, path), document.type);
1054
+ const changes = crossed(document, definition, path, (named, href) => {
1055
+ const at = moved.get(named);
1056
+ if (at !== undefined)
1057
+ return target(to, href) === at ? undefined : relative(to, at);
1058
+ return heldAt(source.visible, named) ? roomPathOf(source.handle, named) : undefined;
1059
+ });
1060
+ if (changes.length === 0)
1061
+ return content;
1062
+ const applied = apply(document, changes);
1063
+ return applied.outcome === 'applied' ? serialize(applied.document) : content;
1064
+ }
1065
+ function arrivalOf({ path, to }, source, moved) {
1066
+ const { documents, revisions, fileIds, changes, binaries } = source.visible;
1067
+ const kept = {
1068
+ ...(fileIds[path] === undefined ? {} : { fileId: fileIds[path] }),
1069
+ ...(changes[path] === undefined ? {} : { change: changes[path] }),
1070
+ };
1071
+ const heads = {
1072
+ ...(revisions[path] === undefined ? {} : { revision: revisions[path] }),
1073
+ ...kept,
1074
+ };
1075
+ const binary = binaries[path];
1076
+ if (binary?.state === 'accepted') {
1077
+ const { byteLength, mediaType, sha256 } = binary;
1078
+ return {
1079
+ kind: 'arrival',
1080
+ path: to,
1081
+ binary: { byteLength, mediaType, sha256 },
1082
+ ...heads,
1083
+ };
1084
+ }
1085
+ const content = arrivingText(path, to, source, moved);
1086
+ return {
1087
+ kind: 'arrival',
1088
+ path: to,
1089
+ content,
1090
+ ...(content === documents[path] ? heads : kept),
1091
+ };
1092
+ }
1093
+ async function moving(input) {
1094
+ const { source, destination, moves } = input;
1095
+ if (moves.length === 0)
1096
+ return refuse('no-change');
1097
+ const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1098
+ const leaves = guard(removals, { ...source, local: input.local, bases: [] }, 'write');
1099
+ if (leaves !== null)
1100
+ return leaves;
1101
+ const uploading = moves.find(({ path }) => source.visible.binaries[path]?.state === 'pending');
1102
+ if (uploading !== undefined)
1103
+ return refuse('unknown-revision', uploading.path);
1104
+ const moved = new Map(moves.map(({ path, to }) => [path, to]));
1105
+ const arrivals = moves.map((move) => arrivalOf(move, source, moved));
1106
+ const arrives = guard(arrivals, { ...destination, local: input.local, bases: [] }, 'write');
1107
+ if (arrives !== null)
1108
+ return arrives;
1109
+ const bound = input.providerLimits.find((limit) => limit.name === MOVE_DOCUMENTS)?.value;
1110
+ if (typeof bound === 'number' && moves.length > bound) {
1111
+ return refuse('provider-limit', undefined, MOVE_DOCUMENTS);
1112
+ }
1113
+ if (source.unmodeled)
1114
+ return refuse('model-unavailable', moves[0].path);
1115
+ const id = changeBatchIdentity();
1116
+ const between = {
1117
+ source: { room: source.room, baseRoomVersion: source.baseRoomVersion },
1118
+ destination: {
1119
+ room: destination.room,
1120
+ baseRoomVersion: destination.baseRoomVersion,
1121
+ handle: destination.handle,
1122
+ },
1123
+ moves,
1124
+ };
1125
+ const written = input.writtenBy === undefined ? {} : { writtenBy: input.writtenBy };
1126
+ const leavingBatch = {
1127
+ id,
1128
+ baseRoomVersion: source.baseRoomVersion,
1129
+ changes: removals,
1130
+ preconditions: moves.map(({ path }) => existence(source.visible, path)),
1131
+ ...written,
1132
+ between,
1133
+ };
1134
+ const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1135
+ const left = await stepped(overlaying(source.visible, [effective]));
1136
+ const sourceJudged = await judging(source.visible, left, source.composition, source.validate, new Set(moves.map(({ path }) => path)), [...new Set(effective.changes.map((change) => change.path))]);
1137
+ if (sourceJudged.kind === 'refused')
1138
+ return sourceJudged;
1139
+ const arrivingBatch = {
1140
+ id,
1141
+ baseRoomVersion: destination.baseRoomVersion,
1142
+ changes: arrivals,
1143
+ preconditions: moves.map(({ to }) => ({ kind: 'absent', path: to })),
1144
+ ...written,
1145
+ between,
1146
+ };
1147
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1148
+ const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1149
+ if (destinationJudged.kind === 'refused')
1150
+ return destinationJudged;
1151
+ const limited = (await stepped(exceeding(input.providerLimits, removals, effective.changes, left))) ??
1152
+ (await stepped(exceeding(input.providerLimits, arrivals, arrivals, arrived)));
1153
+ if (limited !== null)
1154
+ return limited;
1155
+ return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1156
+ }
1157
+ export function buildMove(input) {
1158
+ return moving(input);
1159
+ }
906
1160
  export function buildBatch(input) {
907
- return stepped(batching(input));
1161
+ return batching(input);
908
1162
  }