@agentshouse/mdruntime 0.1.0-cacb4c3f6734e → 0.1.0-cb0eb0d87ff49

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