@agentshouse/mdruntime 0.1.0-c3903a68dcbbf → 0.1.0-c56daf494ecb4

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 (178) hide show
  1. package/AGENTS.md +184 -92
  2. package/README.md +22 -12
  3. package/dist/adapter.d.ts +125 -63
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js +1 -9
  6. package/dist/attached-model.d.ts +11 -0
  7. package/dist/attached-model.d.ts.map +1 -0
  8. package/dist/attached-model.js +47 -0
  9. package/dist/attempt.d.ts +16 -0
  10. package/dist/attempt.d.ts.map +1 -0
  11. package/dist/attempt.js +70 -0
  12. package/dist/binary.d.ts +23 -0
  13. package/dist/binary.d.ts.map +1 -0
  14. package/dist/binary.js +8 -0
  15. package/dist/bootstrap.d.ts +20 -6
  16. package/dist/bootstrap.d.ts.map +1 -1
  17. package/dist/bootstrap.js +93 -39
  18. package/dist/change-set.d.ts.map +1 -1
  19. package/dist/change-set.js +20 -17
  20. package/dist/change.d.ts +15 -0
  21. package/dist/change.d.ts.map +1 -1
  22. package/dist/change.js +26 -1
  23. package/dist/chat/acts.d.ts +34 -0
  24. package/dist/chat/acts.d.ts.map +1 -0
  25. package/dist/chat/acts.js +36 -0
  26. package/dist/chat/context.d.ts +3 -0
  27. package/dist/chat/context.d.ts.map +1 -0
  28. package/dist/chat/context.js +10 -0
  29. package/dist/chat/input.d.ts +9 -0
  30. package/dist/chat/input.d.ts.map +1 -0
  31. package/dist/chat/input.js +99 -0
  32. package/dist/chat/kit.d.ts +60 -0
  33. package/dist/chat/kit.d.ts.map +1 -0
  34. package/dist/chat/kit.js +123 -0
  35. package/dist/chat/lasting.d.ts +6 -0
  36. package/dist/chat/lasting.d.ts.map +1 -0
  37. package/dist/chat/lasting.js +19 -0
  38. package/dist/chat/list.d.ts +26 -0
  39. package/dist/chat/list.d.ts.map +1 -0
  40. package/dist/chat/list.js +62 -0
  41. package/dist/chat/markdown.d.ts +20 -0
  42. package/dist/chat/markdown.d.ts.map +1 -0
  43. package/dist/chat/markdown.js +188 -0
  44. package/dist/chat/message.d.ts +6 -0
  45. package/dist/chat/message.d.ts.map +1 -0
  46. package/dist/chat/message.js +192 -0
  47. package/dist/chat/messages.d.ts +21 -0
  48. package/dist/chat/messages.d.ts.map +1 -0
  49. package/dist/chat/messages.js +151 -0
  50. package/dist/chat/name.d.ts +6 -0
  51. package/dist/chat/name.d.ts.map +1 -0
  52. package/dist/chat/name.js +37 -0
  53. package/dist/chat/opened.d.ts +11 -0
  54. package/dist/chat/opened.d.ts.map +1 -0
  55. package/dist/chat/opened.js +17 -0
  56. package/dist/chat/overlay.d.ts +31 -0
  57. package/dist/chat/overlay.d.ts.map +1 -0
  58. package/dist/chat/overlay.js +97 -0
  59. package/dist/chat/parts.d.ts +57 -0
  60. package/dist/chat/parts.d.ts.map +1 -0
  61. package/dist/chat/parts.js +119 -0
  62. package/dist/chat/process.d.ts +20 -0
  63. package/dist/chat/process.d.ts.map +1 -0
  64. package/dist/chat/process.js +32 -0
  65. package/dist/chat/provider.d.ts +63 -0
  66. package/dist/chat/provider.d.ts.map +1 -0
  67. package/dist/chat/provider.js +645 -0
  68. package/dist/chat/question.d.ts +9 -0
  69. package/dist/chat/question.d.ts.map +1 -0
  70. package/dist/chat/question.js +80 -0
  71. package/dist/chat/questions.d.ts +31 -0
  72. package/dist/chat/questions.d.ts.map +1 -0
  73. package/dist/chat/questions.js +111 -0
  74. package/dist/chat/read.d.ts +5 -0
  75. package/dist/chat/read.d.ts.map +1 -0
  76. package/dist/chat/read.js +13 -0
  77. package/dist/chat/ready.d.ts +33 -0
  78. package/dist/chat/ready.d.ts.map +1 -0
  79. package/dist/chat/ready.js +91 -0
  80. package/dist/chat/style.d.ts +4 -0
  81. package/dist/chat/style.d.ts.map +1 -0
  82. package/dist/chat/style.js +302 -0
  83. package/dist/chat.d.ts +14 -0
  84. package/dist/chat.d.ts.map +1 -0
  85. package/dist/chat.js +8 -0
  86. package/dist/client.d.ts +17 -13
  87. package/dist/client.d.ts.map +1 -1
  88. package/dist/client.js +68 -31
  89. package/dist/collection.d.ts +6 -13
  90. package/dist/collection.d.ts.map +1 -1
  91. package/dist/collection.js +56 -29
  92. package/dist/connection.d.ts +28 -33
  93. package/dist/connection.d.ts.map +1 -1
  94. package/dist/connection.js +951 -745
  95. package/dist/conversation.d.ts +32 -13
  96. package/dist/conversation.d.ts.map +1 -1
  97. package/dist/conversation.js +293 -108
  98. package/dist/entry.d.ts +17 -0
  99. package/dist/entry.d.ts.map +1 -0
  100. package/dist/entry.js +156 -0
  101. package/dist/expectation.d.ts +21 -0
  102. package/dist/expectation.d.ts.map +1 -0
  103. package/dist/expectation.js +70 -0
  104. package/dist/failure.d.ts +13 -0
  105. package/dist/failure.d.ts.map +1 -0
  106. package/dist/failure.js +19 -0
  107. package/dist/host.d.ts +9 -3
  108. package/dist/host.d.ts.map +1 -1
  109. package/dist/host.js +49 -68
  110. package/dist/identity.d.ts +3 -0
  111. package/dist/identity.d.ts.map +1 -1
  112. package/dist/identity.js +13 -1
  113. package/dist/index.d.ts +9 -5
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +3 -1
  116. package/dist/memory.d.ts +14 -7
  117. package/dist/memory.d.ts.map +1 -1
  118. package/dist/memory.js +137 -46
  119. package/dist/model.d.ts +13 -12
  120. package/dist/model.d.ts.map +1 -1
  121. package/dist/model.js +6 -3
  122. package/dist/observation.d.ts +2 -0
  123. package/dist/observation.d.ts.map +1 -1
  124. package/dist/observation.js +24 -14
  125. package/dist/open.d.ts +2 -0
  126. package/dist/open.d.ts.map +1 -0
  127. package/dist/open.js +1 -0
  128. package/dist/outcome.d.ts +32 -23
  129. package/dist/outcome.d.ts.map +1 -1
  130. package/dist/outcome.js +41 -1
  131. package/dist/overlay.d.ts +14 -3
  132. package/dist/overlay.d.ts.map +1 -1
  133. package/dist/overlay.js +75 -37
  134. package/dist/paths.d.ts +0 -1
  135. package/dist/paths.d.ts.map +1 -1
  136. package/dist/paths.js +0 -16
  137. package/dist/query.d.ts +12 -3
  138. package/dist/query.d.ts.map +1 -1
  139. package/dist/query.js +78 -36
  140. package/dist/reach.d.ts +42 -0
  141. package/dist/reach.d.ts.map +1 -0
  142. package/dist/reach.js +294 -0
  143. package/dist/react.d.ts +41 -0
  144. package/dist/react.d.ts.map +1 -0
  145. package/dist/react.js +64 -0
  146. package/dist/read-side.d.ts +4 -0
  147. package/dist/read-side.d.ts.map +1 -1
  148. package/dist/read-side.js +125 -56
  149. package/dist/refresh.d.ts +12 -1
  150. package/dist/refresh.d.ts.map +1 -1
  151. package/dist/refresh.js +44 -36
  152. package/dist/replica.d.ts +9 -6
  153. package/dist/replica.d.ts.map +1 -1
  154. package/dist/replica.js +83 -68
  155. package/dist/room-draft.d.ts +29 -9
  156. package/dist/room-draft.d.ts.map +1 -1
  157. package/dist/room-draft.js +137 -26
  158. package/dist/rows.d.ts +7 -2
  159. package/dist/rows.d.ts.map +1 -1
  160. package/dist/rows.js +85 -40
  161. package/dist/save.d.ts +27 -3
  162. package/dist/save.d.ts.map +1 -1
  163. package/dist/save.js +435 -86
  164. package/dist/search.d.ts +3 -2
  165. package/dist/search.d.ts.map +1 -1
  166. package/dist/status.d.ts +2 -2
  167. package/dist/status.d.ts.map +1 -1
  168. package/dist/status.js +0 -8
  169. package/dist/store.d.ts +41 -25
  170. package/dist/store.d.ts.map +1 -1
  171. package/dist/store.js +315 -191
  172. package/dist/wire.d.ts +58 -19
  173. package/dist/wire.d.ts.map +1 -1
  174. package/dist/wire.js +65 -67
  175. package/package.json +31 -7
  176. package/dist/attachment.d.ts +0 -3
  177. package/dist/attachment.d.ts.map +0 -1
  178. package/dist/attachment.js +0 -8
package/dist/save.js CHANGED
@@ -1,35 +1,49 @@
1
1
  import { definitionOf } from './model.js';
2
- import { apply, links, parse, retarget, serialize, target, } from '@agentshouse/mdmodel';
3
- import { immutable, } from './change.js';
4
- import { governing, untyped, parseCollection, validateCollection, } from './collection.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';
5
5
  import { changeBatchIdentity } from './identity.js';
6
6
  import { overlay } from './overlay.js';
7
- import { covered, normalize, relative } from './paths.js';
8
- function refuse(rule, file, location) {
7
+ import { covered, normalize } from './paths.js';
8
+ function refuse(code, file, location) {
9
9
  return {
10
10
  kind: 'refused',
11
11
  refusal: {
12
- rule,
12
+ code,
13
+ retryable: false,
13
14
  ...(file === undefined ? {} : { file }),
14
15
  ...(location === undefined ? {} : { location }),
15
16
  },
16
17
  };
17
18
  }
18
- function referenceField(spec) {
19
- return spec.kind === 'reference' || (spec.kind === 'list' && spec.of.kind === 'reference');
20
- }
21
- function repointed(value, from, to, moves) {
22
- if (Array.isArray(value)) {
23
- const next = value.map((item) => repointed(item, from, to, moves) ?? item);
24
- return next.some((item, index) => item !== value[index]) ? next : undefined;
19
+ function repointed(document, definition, from, to, moves, settled) {
20
+ const parsed = parse(document.content, from);
21
+ if (!parsed.ok)
22
+ return [];
23
+ let next = parsed.document;
24
+ const names = new Set();
25
+ for (const { pointer, value } of definition === undefined
26
+ ? []
27
+ : references(document, definition)) {
28
+ const [name = ''] = pointerSegments(pointer);
29
+ const named = target(from, value);
30
+ if (named === null || settled.has(`${from} ${name}`))
31
+ continue;
32
+ const destination = moves.get(named) ?? named;
33
+ if (target(to, value) === destination)
34
+ continue;
35
+ const applied = apply(next, {
36
+ kind: 'reference',
37
+ pointer,
38
+ value: retarget(value, relative(to, destination)),
39
+ prior: value,
40
+ });
41
+ if (applied.outcome !== 'applied')
42
+ continue;
43
+ next = applied.document;
44
+ names.add(name);
25
45
  }
26
- if (typeof value !== 'string')
27
- return undefined;
28
- const named = target(from, value);
29
- if (named === null)
30
- return undefined;
31
- const destination = moves.get(named) ?? named;
32
- return target(to, value) === destination ? undefined : retarget(value, relative(to, destination));
46
+ return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
33
47
  }
34
48
  function edited(document, path, changes) {
35
49
  let next = document;
@@ -77,7 +91,8 @@ function relinks(path, moved, before, moves) {
77
91
  return {
78
92
  kind: 'refused',
79
93
  refusal: {
80
- rule: applied.refusal.rule,
94
+ code: applied.refusal.rule,
95
+ retryable: false,
81
96
  file: path,
82
97
  ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
83
98
  },
@@ -107,7 +122,10 @@ function rewrites(changes, visible) {
107
122
  for (const change of changes) {
108
123
  if (change.kind === 'move')
109
124
  moves.set(change.path, change.to);
110
- if (change.kind === 'remove' || change.kind === 'replace' || change.kind === 'create') {
125
+ if (change.kind === 'remove' ||
126
+ change.kind === 'replace' ||
127
+ change.kind === 'create' ||
128
+ change.kind === 'binary') {
111
129
  settled.add(change.path);
112
130
  }
113
131
  if (change.kind === 'field')
@@ -117,19 +135,12 @@ function rewrites(changes, visible) {
117
135
  return { kind: 'changes', changes };
118
136
  const merged = [...changes];
119
137
  const added = [];
120
- for (const [path, document] of Object.entries(visible.collection.documents)) {
138
+ for (const [path, document] of documentsOf(visible.collection)) {
121
139
  if (settled.has(path))
122
140
  continue;
123
141
  const moved = moves.get(path) ?? path;
124
142
  const definition = definitionOf(governing(visible.collection.composition, path), document.type);
125
- for (const [name, spec] of Object.entries(definition?.fields ?? {})) {
126
- if (!referenceField(spec) || settled.has(`${path} ${name}`))
127
- continue;
128
- const value = repointed(document.fields[name], path, moved, moves);
129
- if (value === undefined)
130
- continue;
131
- added.push({ kind: 'field', path, name, value });
132
- }
143
+ added.push(...repointed(document, definition, path, moved, moves, settled));
133
144
  const relinked = relinks(path, moved, edited(document, path, merged), moves);
134
145
  if (relinked.kind === 'refused')
135
146
  return relinked;
@@ -166,18 +177,6 @@ export function providerRewritten(batch, visible) {
166
177
  ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
167
178
  };
168
179
  }
169
- function targets(value, from) {
170
- const values = Array.isArray(value) ? value : [value];
171
- const resolved = [];
172
- for (const item of values) {
173
- if (typeof item !== 'string')
174
- continue;
175
- const named = target(from, item);
176
- if (named !== null)
177
- resolved.push(named);
178
- }
179
- return resolved;
180
- }
181
180
  function incoming(collection, removed) {
182
181
  const found = new Map();
183
182
  const note = (target, file) => {
@@ -187,16 +186,15 @@ function incoming(collection, removed) {
187
186
  referrers.add(file);
188
187
  found.set(target, referrers);
189
188
  };
190
- for (const [path, document] of Object.entries(collection.documents)) {
189
+ for (const [path, document] of documentsOf(collection)) {
191
190
  const definition = definitionOf(governing(collection.composition, path), document.type);
192
- for (const [name, spec] of Object.entries(definition?.fields ?? {})) {
193
- if (!referenceField(spec))
191
+ for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
192
+ if (to.includes(ATTACHMENT))
194
193
  continue;
195
- for (const named of targets(document.fields[name], path))
194
+ const named = target(path, value);
195
+ if (named !== null)
196
196
  note(named, path);
197
197
  }
198
- for (const link of links(document, path))
199
- note(link.target, path);
200
198
  }
201
199
  const [first] = [...found.keys()].sort();
202
200
  if (first === undefined)
@@ -207,7 +205,12 @@ function incoming(collection, removed) {
207
205
  every.add(file);
208
206
  return {
209
207
  kind: 'refused',
210
- refusal: { rule: 'referenced-missing-file', file: first, referrers: [...every].sort() },
208
+ refusal: {
209
+ code: 'referenced-missing-file',
210
+ retryable: false,
211
+ file: first,
212
+ referrers: [...every].sort(),
213
+ },
211
214
  };
212
215
  }
213
216
  function affected(change) {
@@ -216,9 +219,9 @@ function affected(change) {
216
219
  function guard(changes, input) {
217
220
  const existing = new Set([
218
221
  ...Object.keys(input.visible.documents),
219
- ...input.visible.binaryTargets,
222
+ ...Object.keys(input.visible.binaries),
220
223
  ]);
221
- for (const change of changes) {
224
+ for (const [index, change] of changes.entries()) {
222
225
  for (const path of affected(change)) {
223
226
  const normalized = normalize(path);
224
227
  if (normalized === null || normalized === '')
@@ -227,7 +230,7 @@ function guard(changes, input) {
227
230
  return refuse('provider-owned-path', normalized);
228
231
  }
229
232
  const operation = change.kind === 'remove' ? 'delete' : 'write';
230
- if (!covered(input.operations[operation], normalized))
233
+ if (!input.local && !covered(input.operations[operation], normalized))
231
234
  return refuse(`${operation}-grant`, normalized);
232
235
  }
233
236
  if (change.kind === 'create') {
@@ -236,12 +239,26 @@ function guard(changes, input) {
236
239
  existing.add(change.path);
237
240
  continue;
238
241
  }
242
+ if (change.kind === 'binary') {
243
+ if (input.visible.documents[change.path] !== undefined ||
244
+ (existing.has(change.path) &&
245
+ input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
246
+ return refuse('path-collision', change.path);
247
+ }
248
+ if (!input.local &&
249
+ existing.has(change.path) &&
250
+ input.visible.revisions[departure(changes, index)] === undefined) {
251
+ return refuse('unknown-revision', change.path);
252
+ }
253
+ existing.add(change.path);
254
+ continue;
255
+ }
239
256
  if (change.kind === 'move') {
240
257
  if (!existing.has(change.path))
241
258
  return refuse('path-absent', change.path);
242
259
  if (existing.has(change.to))
243
260
  return refuse('path-collision', change.to);
244
- if (unrevisionedDocument(input.visible, change.path)) {
261
+ if (unrevised(input, change.path)) {
245
262
  return refuse('unknown-revision', change.path);
246
263
  }
247
264
  existing.delete(change.path);
@@ -251,30 +268,37 @@ function guard(changes, input) {
251
268
  if (!existing.has(change.path))
252
269
  return refuse('path-absent', change.path);
253
270
  if (change.kind === 'remove') {
254
- if (unrevisionedDocument(input.visible, change.path)) {
271
+ if (unrevised(input, change.path)) {
255
272
  return refuse('unknown-revision', change.path);
256
273
  }
257
274
  existing.delete(change.path);
258
275
  continue;
259
276
  }
260
277
  if (change.kind === 'replace') {
261
- if (input.visible.revisions[change.path] === undefined) {
278
+ if (!input.local && input.visible.revisions[change.path] === undefined) {
262
279
  return refuse('unknown-revision', change.path);
263
280
  }
264
281
  continue;
265
282
  }
266
- if (input.visible.collection.documents[change.path] === undefined) {
283
+ if (input.visible.collection.document(change.path) === undefined) {
267
284
  return refuse('path-absent', change.path);
268
285
  }
269
286
  }
270
287
  return null;
271
288
  }
272
- function unrevisionedDocument(visible, path) {
273
- return visible.revisions[path] === undefined && !visible.binaryTargets.includes(path);
289
+ function unrevised(input, path) {
290
+ return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
291
+ }
292
+ function identity(precondition) {
293
+ const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
294
+ return `${precondition.kind} ${precondition.path} ${part}`;
274
295
  }
275
296
  function existence(visible, path) {
276
297
  const revision = visible.revisions[path];
277
- return revision === undefined ? { kind: 'present', path } : { kind: 'revision', path, revision };
298
+ if (revision !== undefined)
299
+ return { kind: 'revision', path, revision };
300
+ const content = visible.documents[path];
301
+ return content === undefined ? { kind: 'present', path } : { kind: 'content', path, content };
278
302
  }
279
303
  function sameTarget(precondition, change) {
280
304
  if (precondition.kind !== change.kind || precondition.path !== change.path)
@@ -286,11 +310,10 @@ function preconditionsOf(changes, visible, bases) {
286
310
  const preconditions = [];
287
311
  const seen = new Set();
288
312
  const add = (precondition) => {
289
- const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
290
- const identity = `${precondition.kind} ${precondition.path} ${part}`;
291
- if (seen.has(identity))
313
+ const held = identity(precondition);
314
+ if (seen.has(held))
292
315
  return;
293
- seen.add(identity);
316
+ seen.add(held);
294
317
  preconditions.push(precondition);
295
318
  };
296
319
  for (const change of changes) {
@@ -300,7 +323,7 @@ function preconditionsOf(changes, visible, bases) {
300
323
  add(based);
301
324
  continue;
302
325
  }
303
- const document = visible.collection.documents[change.path];
326
+ const document = visible.collection.document(change.path);
304
327
  if (change.kind === 'field') {
305
328
  add({
306
329
  kind: 'field',
@@ -325,6 +348,13 @@ function preconditionsOf(changes, visible, bases) {
325
348
  add({ kind: 'absent', path: change.path });
326
349
  continue;
327
350
  }
351
+ if (change.kind === 'binary') {
352
+ const revision = visible.revisions[change.path];
353
+ add(visible.binaries[change.path] === undefined || revision === undefined
354
+ ? { kind: 'absent', path: change.path }
355
+ : { kind: 'revision', path: change.path, revision });
356
+ continue;
357
+ }
328
358
  add(existence(visible, change.path));
329
359
  if (change.kind === 'move')
330
360
  add({ kind: 'absent', path: change.to });
@@ -334,10 +364,10 @@ function preconditionsOf(changes, visible, bases) {
334
364
  function depth(path) {
335
365
  return path.split('/').length;
336
366
  }
337
- function exceeded(limits, changes, resulting) {
338
- const written = changes.map((change) => (change.kind === 'move' ? change.to : change.path));
367
+ function exceeded(limits, changes, effective, resulting) {
368
+ const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
339
369
  const created = changes
340
- .filter((change) => change.kind === 'create' || change.kind === 'move')
370
+ .filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
341
371
  .map((change) => (change.kind === 'move' ? change.to : change.path));
342
372
  for (const limit of limits) {
343
373
  const value = limit.value;
@@ -351,7 +381,7 @@ function exceeded(limits, changes, resulting) {
351
381
  return refuse('provider-limit', undefined, limit.name);
352
382
  }
353
383
  if (limit.name === 'document-size') {
354
- const large = written.find((path) => (resulting.documents[path] ?? '').length > value);
384
+ const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
355
385
  if (large !== undefined)
356
386
  return refuse('provider-limit', large, limit.name);
357
387
  }
@@ -368,32 +398,351 @@ function exceeded(limits, changes, resulting) {
368
398
  }
369
399
  return null;
370
400
  }
371
- export function buildBatch(input) {
372
- const structural = input.changes.find((change) => change.kind === 'move' || change.kind === 'remove');
373
- if (structural !== undefined &&
374
- Object.entries(input.visible.collection.documents).some(([path, document]) => document.type !== undefined &&
401
+ function shellRelative(text, path, handle, definition) {
402
+ if (!text.includes(`/rooms/${handle}/`))
403
+ return null;
404
+ const parsed = parse(text, path);
405
+ if (!parsed.ok)
406
+ return null;
407
+ const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
408
+ return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
409
+ }
410
+ function relativeField(path, name, value, handle, definition) {
411
+ const opened = parse('---\n---\n');
412
+ if (definition === undefined ||
413
+ !opened.ok ||
414
+ !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
415
+ return value;
416
+ const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
417
+ if (held.outcome !== 'applied')
418
+ return value;
419
+ const result = relativize(held.document, path, handle, definition);
420
+ return result.outcome === 'applied' ? result.document.fields[name] : value;
421
+ }
422
+ function relativeChanges(input) {
423
+ const { handle, composition, visible } = input;
424
+ const typed = (path) => (type) => definitionOf(governing(composition, path), type);
425
+ const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
426
+ return input.changes.map((change) => {
427
+ const definition = typed(change.path);
428
+ if (change.kind === 'field') {
429
+ const type = visible.collection.document(change.path)?.type;
430
+ const value = relativeField(change.path, change.name, change.value, handle, definition(type));
431
+ return value === change.value ? change : { ...change, value };
432
+ }
433
+ if (change.kind === 'section' || change.kind === 'preamble') {
434
+ const value = body(change.path, change.value);
435
+ return value === change.value ? change : { ...change, value };
436
+ }
437
+ if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
438
+ const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
439
+ return content === change.content ? change : { ...change, content };
440
+ }
441
+ if (change.kind !== 'create')
442
+ return change;
443
+ const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
444
+ const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
445
+ name,
446
+ relativeField(change.path, name, value, handle, definition(type)),
447
+ ]));
448
+ const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
449
+ return { ...change, fields, sections };
450
+ });
451
+ }
452
+ function unmodeledType(collection, composition) {
453
+ for (const [path, document] of documentsOf(collection)) {
454
+ if (document.type !== undefined &&
375
455
  document.type !== 'any' &&
376
- governing(input.composition, path).status === 'unavailable'))
456
+ governing(composition, path).status === 'unavailable')
457
+ return true;
458
+ }
459
+ return false;
460
+ }
461
+ function unqueueable(file) {
462
+ return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
463
+ }
464
+ function isPart(change) {
465
+ return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
466
+ }
467
+ function touched(change) {
468
+ return change.kind === 'move' ? [change.path, change.to] : [change.path];
469
+ }
470
+ function partApplied(content, path, change) {
471
+ const parsed = parse(content, path);
472
+ if (!parsed.ok)
473
+ return null;
474
+ 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
+ ? {
484
+ kind: 'section',
485
+ name: change.name,
486
+ value: change.value,
487
+ prior: document.sections[change.name],
488
+ }
489
+ : { kind: 'preamble', value: change.value, prior: document.preamble });
490
+ return applied.outcome === 'applied' ? serialize(applied.document) : null;
491
+ }
492
+ function folded(changes, change, appending) {
493
+ const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
494
+ const whole = held === undefined ? -1 : changes.indexOf(held);
495
+ const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
496
+ if (isPart(change)) {
497
+ const same = changes.findIndex((item) => item.kind === change.kind &&
498
+ item.path === change.path &&
499
+ (change.kind === 'preamble' || ('name' in item && item.name === change.name)));
500
+ if (same !== -1) {
501
+ changes[same] = change;
502
+ return 'folded';
503
+ }
504
+ if (held === undefined) {
505
+ if (!appending)
506
+ return 'skipped';
507
+ if (occupied)
508
+ return 'refused';
509
+ changes.push(change);
510
+ return 'appended';
511
+ }
512
+ 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 };
517
+ return 'folded';
518
+ }
519
+ if (change.kind === 'preamble')
520
+ return 'refused';
521
+ changes[whole] =
522
+ change.kind === 'field'
523
+ ? { ...held, fields: { ...held.fields, [change.name]: change.value } }
524
+ : { ...held, sections: { ...held.sections, [change.name]: change.value } };
525
+ return 'folded';
526
+ }
527
+ if (change.kind === 'replace') {
528
+ if (held !== undefined) {
529
+ if (!('content' in held))
530
+ return 'refused';
531
+ changes[whole] = { ...held, content: change.content };
532
+ return 'folded';
533
+ }
534
+ const parts = changes.filter((item) => isPart(item) && item.path === change.path);
535
+ if (parts.length === 0 && !appending)
536
+ return 'skipped';
537
+ if (occupied)
538
+ return 'refused';
539
+ changes.splice(0, changes.length, ...changes.filter((item) => !parts.includes(item)), change);
540
+ return 'appended';
541
+ }
542
+ if (changes.some((item) => touched(change).some((path) => touched(item).includes(path))))
543
+ return 'refused';
544
+ changes.push(change);
545
+ return 'appended';
546
+ }
547
+ function relinked(batch, move, before, after, appending) {
548
+ const derived = rewrites([move], after);
549
+ if (derived.kind === 'refused')
550
+ return derived;
551
+ const changes = [...batch.changes];
552
+ const appended = [];
553
+ let changed = false;
554
+ for (const change of derived.changes) {
555
+ if (change === move)
556
+ continue;
557
+ const outcome = folded(changes, change, appending);
558
+ if (outcome === 'refused')
559
+ return unqueueable(change.path);
560
+ if (outcome === 'appended')
561
+ appended.push(change);
562
+ if (outcome !== 'skipped')
563
+ changed = true;
564
+ }
565
+ if (!changed)
566
+ return { kind: 'relinked', batch };
567
+ const held = new Set(batch.preconditions.map(identity));
568
+ return {
569
+ kind: 'relinked',
570
+ batch: immutable({
571
+ ...batch,
572
+ id: changeBatchIdentity(),
573
+ changes,
574
+ preconditions: [
575
+ ...batch.preconditions,
576
+ ...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
577
+ ],
578
+ }),
579
+ };
580
+ }
581
+ function leading(changes, at) {
582
+ const from = departure(changes, at);
583
+ return new Set(changes.filter((change, index) => index < at && change.kind === 'move' && departure(changes, index) === from));
584
+ }
585
+ function created(batch, at) {
586
+ const file = batch.changes[at];
587
+ return (leading(batch.changes, at).size === 0 &&
588
+ batch.preconditions.some((precondition) => precondition.kind === 'absent' && precondition.path === file.path));
589
+ }
590
+ function withdrawnFile(batch, change, at) {
591
+ const file = batch.changes[at];
592
+ const from = departure(batch.changes, at);
593
+ const moves = leading(batch.changes, at);
594
+ const fresh = created(batch, at);
595
+ const vacated = new Set([...moves].map((move) => move.to));
596
+ if (fresh)
597
+ vacated.add(file.path);
598
+ const replaced = fresh
599
+ ? change.kind === 'remove'
600
+ ? []
601
+ : [{ ...file, path: change.to }]
602
+ : change.kind === 'remove'
603
+ ? [{ kind: 'remove', path: from }]
604
+ : change.to === from
605
+ ? [{ ...file, path: from }]
606
+ : [
607
+ { kind: 'move', path: from, to: change.to },
608
+ { ...file, path: change.to },
609
+ ];
610
+ const added = change.kind === 'move' && (fresh || change.to !== from)
611
+ ? [{ kind: 'absent', path: change.to }]
612
+ : [];
613
+ const changes = batch.changes.flatMap((item) => item === file ? replaced : item.kind === 'move' && moves.has(item) ? [] : [item]);
614
+ if (changes.length === 0)
615
+ return null;
616
+ const preconditions = [
617
+ ...added,
618
+ ...batch.preconditions.filter((precondition) => precondition.kind !== 'absent' || !vacated.has(precondition.path)),
619
+ ];
620
+ return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
621
+ }
622
+ export function requeued(queue, changes, visible, changeable, state) {
623
+ const next = [...queue];
624
+ const origins = new Map();
625
+ const withdrawn = new Map();
626
+ const owners = new Set();
627
+ const queued = new Set();
628
+ const kept = [];
629
+ const originOf = (batch) => origins.get(batch) ?? batch.id;
630
+ const open = (batch) => origins.has(batch) || changeable(batch);
631
+ for (const change of changes) {
632
+ const held = visible.binaries[change.path];
633
+ if ((change.kind !== 'move' && change.kind !== 'remove') || held?.state !== 'pending') {
634
+ kept.push(change);
635
+ continue;
636
+ }
637
+ let index = next.length - 1;
638
+ while (index >= 0 && queuedFile(next[index], change.path, held.localId) === -1)
639
+ index -= 1;
640
+ const owner = next[index];
641
+ if (owner === undefined || !open(owner))
642
+ return unqueueable(change.path);
643
+ const following = next.slice(index);
644
+ const rebuilt = [];
645
+ let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
646
+ for (const [offset, batch] of following.entries()) {
647
+ let current = batch;
648
+ if (change.kind === 'move') {
649
+ const after = state(next.slice(0, index + offset + 1));
650
+ const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
651
+ const relinking = relinked(batch, change, before, after, appending);
652
+ if (relinking.kind === 'refused')
653
+ return relinking;
654
+ current = relinking.batch;
655
+ before = after;
656
+ }
657
+ if (current !== batch && !open(batch))
658
+ return unqueueable(change.path);
659
+ rebuilt.push(offset === 0
660
+ ? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
661
+ : current);
662
+ }
663
+ for (const [offset, batch] of following.entries()) {
664
+ const replaced = rebuilt[offset];
665
+ if (replaced === batch)
666
+ continue;
667
+ const origin = originOf(batch);
668
+ withdrawn.set(origin, replaced);
669
+ if (replaced !== null)
670
+ origins.set(replaced, origin);
671
+ }
672
+ owners.add(originOf(owner));
673
+ next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
674
+ queued.add(change.path);
675
+ }
676
+ return {
677
+ kind: 'requeued',
678
+ queue: withdrawn.size === 0 ? queue : next,
679
+ changes: kept,
680
+ queued,
681
+ withdrawn,
682
+ owners,
683
+ };
684
+ }
685
+ function covering(precondition) {
686
+ return precondition.kind === 'field' ||
687
+ precondition.kind === 'section' ||
688
+ precondition.kind === 'preamble'
689
+ ? identity(precondition)
690
+ : `path ${precondition.path}`;
691
+ }
692
+ export function joined(queue, owner, extra) {
693
+ const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
694
+ const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
695
+ if (clash !== undefined)
696
+ return unqueueable(clash);
697
+ const changes = [...owner.changes];
698
+ for (const change of extra.changes) {
699
+ if (folded(changes, change, true) === 'refused')
700
+ return unqueueable(change.path);
701
+ }
702
+ const held = new Set(owner.preconditions.map(covering));
703
+ const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
704
+ const preconditions = [
705
+ ...owner.preconditions,
706
+ ...extra.preconditions.filter((precondition) => {
707
+ const key = covering(precondition);
708
+ return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
709
+ }),
710
+ ];
711
+ const batch = immutable({
712
+ ...owner,
713
+ ...(extra.validation === undefined ? {} : { validation: extra.validation }),
714
+ id: changeBatchIdentity(),
715
+ changes,
716
+ preconditions,
717
+ });
718
+ return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
719
+ }
720
+ export function limitedIn(queue, batch, limits, state) {
721
+ const at = queue.indexOf(batch);
722
+ const limited = exceeded(limits, batch.changes, providerRewritten(batch, state(queue.slice(0, at))).changes, state(queue.slice(0, at + 1)));
723
+ return limited?.kind === 'refused' ? limited.refusal : null;
724
+ }
725
+ export function buildBatch(input) {
726
+ const changes = relativeChanges(input);
727
+ const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
728
+ if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
377
729
  return refuse('model-unavailable', structural.path);
378
- if (input.changes.length === 0)
730
+ if (changes.length === 0)
379
731
  return refuse('no-change');
380
- const rewritten = rewrites(input.changes, input.visible);
732
+ const rewritten = rewrites(changes, input.visible);
381
733
  if (rewritten.kind === 'refused')
382
734
  return rewritten;
383
- const changes = input.rewriting === 'provider' ? input.changes : rewritten.changes;
384
735
  const refusal = guard(changes, input);
385
736
  if (refusal !== null)
386
737
  return refusal;
387
738
  const preconditions = preconditionsOf(changes, input.visible, input.bases);
388
- const predicted = input.rewriting === 'provider'
389
- ? anticipated(changes, preconditions, rewritten.changes, input.visible)
390
- : { changes, preconditions };
739
+ const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
391
740
  const resulting = overlay(input.visible, [
392
741
  { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
393
742
  ]);
394
743
  const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
395
744
  if (removed.size > 0) {
396
- const referenced = incoming(parseCollection(resulting.documents, input.composition), removed);
745
+ const referenced = incoming(collectionOf(resulting.documents, input.composition), removed);
397
746
  if (referenced !== null)
398
747
  return referenced;
399
748
  }
@@ -403,8 +752,7 @@ export function buildBatch(input) {
403
752
  const parsed = parse(content);
404
753
  return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
405
754
  };
406
- const typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) => !untyped(input.composition.untypedFolders, path) &&
407
- (typed(input.visible.documents[path]) || typed(resulting.documents[path])));
755
+ const typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
408
756
  const needsModel = typedPaths.length > 0;
409
757
  const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
410
758
  if (withoutModel !== undefined)
@@ -412,19 +760,20 @@ export function buildBatch(input) {
412
760
  const validationText = needsModel
413
761
  ? resulting.documents
414
762
  : Object.fromEntries(Object.entries(resulting.documents).filter(([path]) => predicted.changes.some((change) => affected(change).includes(path))));
415
- const verdict = validateCollection(validationText, resulting.binaryTargets, input.composition);
763
+ const verdict = validateCollection(validationText, Object.keys(resulting.binaries), input.composition);
416
764
  if (verdict.verdict === 'refuse') {
417
765
  return {
418
766
  kind: 'refused',
419
767
  refusal: {
420
- rule: verdict.rule,
768
+ code: verdict.rule,
769
+ retryable: false,
421
770
  file: verdict.file,
422
771
  ...(verdict.location === undefined ? {} : { location: verdict.location }),
423
772
  ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
424
773
  },
425
774
  };
426
775
  }
427
- const limited = exceeded(input.providerLimits, changes, resulting);
776
+ const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
428
777
  if (limited !== null)
429
778
  return limited;
430
779
  const validated = typedPaths