@agentshouse/mdruntime 0.1.0-cec6690d5712e → 0.1.0-cedb03eaa48b6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/AGENTS.md +277 -271
  2. package/README.md +8 -8
  3. package/dist/adapter.d.ts +85 -20
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/attempt.d.ts.map +1 -1
  6. package/dist/attempt.js +14 -1
  7. package/dist/binary.d.ts +23 -0
  8. package/dist/binary.d.ts.map +1 -0
  9. package/dist/binary.js +8 -0
  10. package/dist/bootstrap.d.ts +3 -2
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +37 -14
  13. package/dist/change-set.d.ts +2 -0
  14. package/dist/change-set.d.ts.map +1 -1
  15. package/dist/change-set.js +40 -13
  16. package/dist/change.d.ts +11 -0
  17. package/dist/change.d.ts.map +1 -1
  18. package/dist/change.js +26 -1
  19. package/dist/chat/acts.d.ts +3 -4
  20. package/dist/chat/acts.d.ts.map +1 -1
  21. package/dist/chat/acts.js +10 -10
  22. package/dist/chat/earlier.d.ts +10 -0
  23. package/dist/chat/earlier.d.ts.map +1 -0
  24. package/dist/chat/earlier.js +54 -0
  25. package/dist/chat/kit.d.ts +2 -1
  26. package/dist/chat/kit.d.ts.map +1 -1
  27. package/dist/chat/kit.js +3 -3
  28. package/dist/chat/list.d.ts +8 -1
  29. package/dist/chat/list.d.ts.map +1 -1
  30. package/dist/chat/list.js +48 -8
  31. package/dist/chat/markdown.d.ts +2 -1
  32. package/dist/chat/markdown.d.ts.map +1 -1
  33. package/dist/chat/markdown.js +7 -6
  34. package/dist/chat/message.d.ts.map +1 -1
  35. package/dist/chat/message.js +2 -2
  36. package/dist/chat/messages.d.ts +1 -0
  37. package/dist/chat/messages.d.ts.map +1 -1
  38. package/dist/chat/messages.js +4 -0
  39. package/dist/chat/provider.d.ts +1 -1
  40. package/dist/chat/provider.d.ts.map +1 -1
  41. package/dist/chat/provider.js +62 -79
  42. package/dist/chat/question.d.ts +1 -1
  43. package/dist/chat/question.d.ts.map +1 -1
  44. package/dist/chat/question.js +7 -18
  45. package/dist/chat/ready.d.ts +1 -0
  46. package/dist/chat/ready.d.ts.map +1 -1
  47. package/dist/chat/ready.js +4 -4
  48. package/dist/chat/style.js +5 -5
  49. package/dist/client.d.ts +22 -9
  50. package/dist/client.d.ts.map +1 -1
  51. package/dist/client.js +23 -6
  52. package/dist/collection.d.ts +17 -3
  53. package/dist/collection.d.ts.map +1 -1
  54. package/dist/collection.js +260 -19
  55. package/dist/connection.d.ts +39 -12
  56. package/dist/connection.d.ts.map +1 -1
  57. package/dist/connection.js +1769 -385
  58. package/dist/conversation.d.ts +20 -7
  59. package/dist/conversation.d.ts.map +1 -1
  60. package/dist/conversation.js +474 -163
  61. package/dist/durability.d.ts +7 -1
  62. package/dist/durability.d.ts.map +1 -1
  63. package/dist/entry.d.ts +17 -0
  64. package/dist/entry.d.ts.map +1 -0
  65. package/dist/entry.js +157 -0
  66. package/dist/fence.d.ts +6 -0
  67. package/dist/fence.d.ts.map +1 -0
  68. package/dist/fence.js +10 -0
  69. package/dist/host.d.ts +15 -3
  70. package/dist/host.d.ts.map +1 -1
  71. package/dist/host.js +98 -12
  72. package/dist/identity.d.ts +3 -0
  73. package/dist/identity.d.ts.map +1 -1
  74. package/dist/identity.js +9 -0
  75. package/dist/index.d.ts +12 -7
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +3 -0
  78. package/dist/memory.d.ts +10 -4
  79. package/dist/memory.d.ts.map +1 -1
  80. package/dist/memory.js +64 -17
  81. package/dist/model.d.ts +4 -2
  82. package/dist/model.d.ts.map +1 -1
  83. package/dist/model.js +10 -1
  84. package/dist/observation.d.ts +7 -2
  85. package/dist/observation.d.ts.map +1 -1
  86. package/dist/observation.js +29 -12
  87. package/dist/outcome.d.ts +17 -1
  88. package/dist/outcome.d.ts.map +1 -1
  89. package/dist/outcome.js +10 -0
  90. package/dist/overlay.d.ts +15 -2
  91. package/dist/overlay.d.ts.map +1 -1
  92. package/dist/overlay.js +99 -62
  93. package/dist/query.d.ts +58 -4
  94. package/dist/query.d.ts.map +1 -1
  95. package/dist/query.js +36 -7
  96. package/dist/reach.d.ts +8 -3
  97. package/dist/reach.d.ts.map +1 -1
  98. package/dist/reach.js +53 -6
  99. package/dist/react.d.ts +8 -6
  100. package/dist/react.d.ts.map +1 -1
  101. package/dist/react.js +7 -17
  102. package/dist/read-side.d.ts +16 -7
  103. package/dist/read-side.d.ts.map +1 -1
  104. package/dist/read-side.js +493 -102
  105. package/dist/readiness.d.ts +53 -0
  106. package/dist/readiness.d.ts.map +1 -0
  107. package/dist/readiness.js +33 -0
  108. package/dist/reads.d.ts +11 -0
  109. package/dist/reads.d.ts.map +1 -0
  110. package/dist/reads.js +98 -0
  111. package/dist/recovery.d.ts +25 -0
  112. package/dist/recovery.d.ts.map +1 -0
  113. package/dist/recovery.js +197 -0
  114. package/dist/refresh.d.ts +4 -4
  115. package/dist/refresh.d.ts.map +1 -1
  116. package/dist/refresh.js +27 -25
  117. package/dist/replica.d.ts +11 -4
  118. package/dist/replica.d.ts.map +1 -1
  119. package/dist/replica.js +106 -48
  120. package/dist/room-draft.d.ts +32 -3
  121. package/dist/room-draft.d.ts.map +1 -1
  122. package/dist/room-draft.js +121 -3
  123. package/dist/rows.d.ts +8 -13
  124. package/dist/rows.d.ts.map +1 -1
  125. package/dist/rows.js +12 -13
  126. package/dist/save.d.ts +29 -0
  127. package/dist/save.d.ts.map +1 -1
  128. package/dist/save.js +358 -30
  129. package/dist/search.d.ts +49 -6
  130. package/dist/search.d.ts.map +1 -1
  131. package/dist/search.js +192 -47
  132. package/dist/seed.d.ts +16 -0
  133. package/dist/seed.d.ts.map +1 -0
  134. package/dist/seed.js +75 -0
  135. package/dist/shared.d.ts +11 -0
  136. package/dist/shared.d.ts.map +1 -0
  137. package/dist/shared.js +368 -0
  138. package/dist/status.d.ts +4 -0
  139. package/dist/status.d.ts.map +1 -1
  140. package/dist/store.d.ts +68 -21
  141. package/dist/store.d.ts.map +1 -1
  142. package/dist/store.js +463 -206
  143. package/dist/turn.d.ts +7 -0
  144. package/dist/turn.d.ts.map +1 -0
  145. package/dist/turn.js +51 -0
  146. package/dist/windows.d.ts +26 -0
  147. package/dist/windows.d.ts.map +1 -0
  148. package/dist/windows.js +93 -0
  149. package/dist/wire.d.ts +152 -30
  150. package/dist/wire.d.ts.map +1 -1
  151. package/dist/wire.js +52 -29
  152. package/package.json +12 -8
  153. package/dist/attachment.d.ts +0 -3
  154. package/dist/attachment.d.ts.map +0 -1
  155. package/dist/attachment.js +0 -8
package/dist/save.d.ts CHANGED
@@ -22,7 +22,13 @@ export type SaveInput = {
22
22
  handle: string;
23
23
  changes: readonly Change[];
24
24
  bases: readonly Precondition[];
25
+ queued?: ReadonlySet<string>;
25
26
  writtenBy?: WrittenBy;
27
+ clean?: {
28
+ text: Readonly<Record<string, string>>;
29
+ binaries: Readonly<Record<string, unknown>>;
30
+ composition: Composition;
31
+ };
26
32
  };
27
33
  export type BatchResult = {
28
34
  kind: 'batch';
@@ -32,5 +38,28 @@ export type BatchResult = {
32
38
  refusal: SaveRefusal;
33
39
  };
34
40
  export declare function providerRewritten(batch: ChangeBatch, visible: VisibleLayer): ChangeBatch;
41
+ export declare function queuedDocuments(queue: readonly ChangeBatch[]): ReadonlySet<string>;
42
+ export type Requeued = {
43
+ kind: 'requeued';
44
+ queue: readonly ChangeBatch[];
45
+ changes: readonly Change[];
46
+ queued: ReadonlySet<string>;
47
+ withdrawn: ReadonlyMap<string, ChangeBatch | null>;
48
+ owners: ReadonlySet<string>;
49
+ } | {
50
+ kind: 'refused';
51
+ refusal: SaveRefusal;
52
+ };
53
+ export type Joining = {
54
+ kind: 'joined';
55
+ batch: ChangeBatch;
56
+ queue: readonly ChangeBatch[];
57
+ } | {
58
+ kind: 'refused';
59
+ refusal: SaveRefusal;
60
+ };
61
+ export declare function requeued(queue: readonly ChangeBatch[], changes: readonly Change[], visible: VisibleLayer, changeable: (batch: ChangeBatch) => boolean, state: (queue: readonly ChangeBatch[]) => VisibleLayer): Requeued;
62
+ export declare function joined(queue: readonly ChangeBatch[], owner: ChangeBatch, extra: ChangeBatch): Joining;
63
+ export declare function limitedIn(queue: readonly ChangeBatch[], batch: ChangeBatch, limits: readonly ProviderLimit[], state: (queue: readonly ChangeBatch[]) => VisibleLayer): SaveRefusal | null;
35
64
  export declare function buildBatch(input: SaveInput): BatchResult;
36
65
  //# sourceMappingURL=save.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../src/save.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAEL,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,OAAO,cAAc,EAAE,eAAe,CAAC;IACnD,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,cAAc,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAiL9C,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,WAAW,CAOxF;AAyRD,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,CAkFxD"}
1
+ {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../src/save.ts"],"names":[],"mappings":"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"}
package/dist/save.js CHANGED
@@ -1,10 +1,11 @@
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 { collectionOf, documentsOf, governing, 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
+ import { differing } from './shared.js';
8
9
  function refuse(code, file, location) {
9
10
  return {
10
11
  kind: 'refused',
@@ -122,7 +123,10 @@ function rewrites(changes, visible) {
122
123
  for (const change of changes) {
123
124
  if (change.kind === 'move')
124
125
  moves.set(change.path, change.to);
125
- if (change.kind === 'remove' || change.kind === 'replace' || change.kind === 'create') {
126
+ if (change.kind === 'remove' ||
127
+ change.kind === 'replace' ||
128
+ change.kind === 'create' ||
129
+ change.kind === 'binary') {
126
130
  settled.add(change.path);
127
131
  }
128
132
  if (change.kind === 'field')
@@ -174,7 +178,7 @@ export function providerRewritten(batch, visible) {
174
178
  ...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
175
179
  };
176
180
  }
177
- function incoming(collection, removed) {
181
+ function incoming(collection, removed, candidates) {
178
182
  const found = new Map();
179
183
  const note = (target, file) => {
180
184
  if (!removed.has(target))
@@ -183,7 +187,13 @@ function incoming(collection, removed) {
183
187
  referrers.add(file);
184
188
  found.set(target, referrers);
185
189
  };
186
- for (const [path, document] of documentsOf(collection)) {
190
+ const read = candidates === null
191
+ ? documentsOf(collection)
192
+ : [...new Set(candidates)].sort().flatMap((path) => {
193
+ const document = collection.document(path);
194
+ return document === undefined ? [] : [[path, document]];
195
+ });
196
+ for (const [path, document] of read) {
187
197
  const definition = definitionOf(governing(collection.composition, path), document.type);
188
198
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
189
199
  if (to.includes(ATTACHMENT))
@@ -214,11 +224,14 @@ function affected(change) {
214
224
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
215
225
  }
216
226
  function guard(changes, input) {
217
- const existing = new Set([
218
- ...Object.keys(input.visible.documents),
219
- ...input.visible.binaryTargets,
220
- ]);
221
- for (const change of changes) {
227
+ const overridden = new Map();
228
+ const existing = {
229
+ has: (path) => overridden.get(path) ??
230
+ (Object.hasOwn(input.visible.documents, path) || Object.hasOwn(input.visible.binaries, path)),
231
+ add: (path) => void overridden.set(path, true),
232
+ delete: (path) => void overridden.set(path, false),
233
+ };
234
+ for (const [index, change] of changes.entries()) {
222
235
  for (const path of affected(change)) {
223
236
  const normalized = normalize(path);
224
237
  if (normalized === null || normalized === '')
@@ -236,12 +249,26 @@ function guard(changes, input) {
236
249
  existing.add(change.path);
237
250
  continue;
238
251
  }
252
+ if (change.kind === 'binary') {
253
+ if (input.visible.documents[change.path] !== undefined ||
254
+ (existing.has(change.path) &&
255
+ input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
256
+ return refuse('path-collision', change.path);
257
+ }
258
+ if (!input.local &&
259
+ existing.has(change.path) &&
260
+ input.visible.revisions[departure(changes, index)] === undefined) {
261
+ return refuse('unknown-revision', change.path);
262
+ }
263
+ existing.add(change.path);
264
+ continue;
265
+ }
239
266
  if (change.kind === 'move') {
240
267
  if (!existing.has(change.path))
241
268
  return refuse('path-absent', change.path);
242
269
  if (existing.has(change.to))
243
270
  return refuse('path-collision', change.to);
244
- if (!input.local && unrevisionedDocument(input.visible, change.path)) {
271
+ if (unrevised(input, change.path)) {
245
272
  return refuse('unknown-revision', change.path);
246
273
  }
247
274
  existing.delete(change.path);
@@ -251,14 +278,14 @@ function guard(changes, input) {
251
278
  if (!existing.has(change.path))
252
279
  return refuse('path-absent', change.path);
253
280
  if (change.kind === 'remove') {
254
- if (!input.local && unrevisionedDocument(input.visible, change.path)) {
281
+ if (unrevised(input, change.path)) {
255
282
  return refuse('unknown-revision', change.path);
256
283
  }
257
284
  existing.delete(change.path);
258
285
  continue;
259
286
  }
260
287
  if (change.kind === 'replace') {
261
- if (!input.local && input.visible.revisions[change.path] === undefined) {
288
+ if (unrevised(input, change.path)) {
262
289
  return refuse('unknown-revision', change.path);
263
290
  }
264
291
  continue;
@@ -269,8 +296,28 @@ function guard(changes, input) {
269
296
  }
270
297
  return null;
271
298
  }
272
- function unrevisionedDocument(visible, path) {
273
- return visible.revisions[path] === undefined && !visible.binaryTargets.includes(path);
299
+ function unrevised(input, path) {
300
+ return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
301
+ }
302
+ export function queuedDocuments(queue) {
303
+ const paths = new Set();
304
+ for (const batch of queue) {
305
+ for (const change of batch.changes) {
306
+ if (change.kind === 'binary')
307
+ continue;
308
+ if (change.kind === 'remove')
309
+ paths.delete(change.path);
310
+ else if (change.kind !== 'move')
311
+ paths.add(change.path);
312
+ else if (paths.delete(change.path))
313
+ paths.add(change.to);
314
+ }
315
+ }
316
+ return paths;
317
+ }
318
+ function identity(precondition) {
319
+ const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
320
+ return `${precondition.kind} ${precondition.path} ${part}`;
274
321
  }
275
322
  function existence(visible, path) {
276
323
  const revision = visible.revisions[path];
@@ -289,11 +336,10 @@ function preconditionsOf(changes, visible, bases) {
289
336
  const preconditions = [];
290
337
  const seen = new Set();
291
338
  const add = (precondition) => {
292
- const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
293
- const identity = `${precondition.kind} ${precondition.path} ${part}`;
294
- if (seen.has(identity))
339
+ const held = identity(precondition);
340
+ if (seen.has(held))
295
341
  return;
296
- seen.add(identity);
342
+ seen.add(held);
297
343
  preconditions.push(precondition);
298
344
  };
299
345
  for (const change of changes) {
@@ -328,6 +374,13 @@ function preconditionsOf(changes, visible, bases) {
328
374
  add({ kind: 'absent', path: change.path });
329
375
  continue;
330
376
  }
377
+ if (change.kind === 'binary') {
378
+ const revision = visible.revisions[change.path];
379
+ add(visible.binaries[change.path] === undefined || revision === undefined
380
+ ? { kind: 'absent', path: change.path }
381
+ : { kind: 'revision', path: change.path, revision });
382
+ continue;
383
+ }
331
384
  add(existence(visible, change.path));
332
385
  if (change.kind === 'move')
333
386
  add({ kind: 'absent', path: change.to });
@@ -337,10 +390,10 @@ function preconditionsOf(changes, visible, bases) {
337
390
  function depth(path) {
338
391
  return path.split('/').length;
339
392
  }
340
- function exceeded(limits, changes, resulting) {
341
- const written = changes.map((change) => (change.kind === 'move' ? change.to : change.path));
393
+ function exceeded(limits, changes, effective, resulting) {
394
+ const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
342
395
  const created = changes
343
- .filter((change) => change.kind === 'create' || change.kind === 'move')
396
+ .filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
344
397
  .map((change) => (change.kind === 'move' ? change.to : change.path));
345
398
  for (const limit of limits) {
346
399
  const value = limit.value;
@@ -354,7 +407,7 @@ function exceeded(limits, changes, resulting) {
354
407
  return refuse('provider-limit', undefined, limit.name);
355
408
  }
356
409
  if (limit.name === 'document-size') {
357
- const large = written.find((path) => (resulting.documents[path] ?? '').length > value);
410
+ const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
358
411
  if (large !== undefined)
359
412
  return refuse('provider-limit', large, limit.name);
360
413
  }
@@ -423,6 +476,8 @@ function relativeChanges(input) {
423
476
  });
424
477
  }
425
478
  function unmodeledType(collection, composition) {
479
+ if (available(composition))
480
+ return false;
426
481
  for (const [path, document] of documentsOf(collection)) {
427
482
  if (document.type !== undefined &&
428
483
  document.type !== 'any' &&
@@ -431,6 +486,270 @@ function unmodeledType(collection, composition) {
431
486
  }
432
487
  return false;
433
488
  }
489
+ function unqueueable(file) {
490
+ return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
491
+ }
492
+ function isPart(change) {
493
+ return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
494
+ }
495
+ function touched(change) {
496
+ return change.kind === 'move' ? [change.path, change.to] : [change.path];
497
+ }
498
+ function partApplied(content, path, change) {
499
+ const parsed = parse(content, path);
500
+ if (!parsed.ok)
501
+ return null;
502
+ const document = parsed.document;
503
+ const applied = apply(document, change.kind === 'field'
504
+ ? {
505
+ kind: 'field',
506
+ name: change.name,
507
+ value: change.value,
508
+ prior: document.fields[change.name],
509
+ }
510
+ : change.kind === 'section'
511
+ ? {
512
+ kind: 'section',
513
+ name: change.name,
514
+ value: change.value,
515
+ prior: document.sections[change.name],
516
+ }
517
+ : { kind: 'preamble', value: change.value, prior: document.preamble });
518
+ return applied.outcome === 'applied' ? serialize(applied.document) : null;
519
+ }
520
+ function folded(changes, change, appending) {
521
+ const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
522
+ const whole = held === undefined ? -1 : changes.indexOf(held);
523
+ const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
524
+ if (isPart(change)) {
525
+ const same = changes.findIndex((item) => item.kind === change.kind &&
526
+ item.path === change.path &&
527
+ (change.kind === 'preamble' || ('name' in item && item.name === change.name)));
528
+ if (same !== -1) {
529
+ changes[same] = change;
530
+ return 'folded';
531
+ }
532
+ if (held === undefined) {
533
+ if (!appending)
534
+ return 'skipped';
535
+ if (occupied)
536
+ return 'refused';
537
+ changes.push(change);
538
+ return 'appended';
539
+ }
540
+ if ('content' in held) {
541
+ const content = partApplied(held.content, held.path, change);
542
+ if (content === null)
543
+ return 'refused';
544
+ changes[whole] = { ...held, content };
545
+ return 'folded';
546
+ }
547
+ if (change.kind === 'preamble')
548
+ return 'refused';
549
+ changes[whole] =
550
+ change.kind === 'field'
551
+ ? { ...held, fields: { ...held.fields, [change.name]: change.value } }
552
+ : { ...held, sections: { ...held.sections, [change.name]: change.value } };
553
+ return 'folded';
554
+ }
555
+ if (change.kind === 'replace') {
556
+ if (held !== undefined) {
557
+ if (!('content' in held))
558
+ return 'refused';
559
+ changes[whole] = { ...held, content: change.content };
560
+ return 'folded';
561
+ }
562
+ const parts = changes.filter((item) => isPart(item) && item.path === change.path);
563
+ if (parts.length === 0 && !appending)
564
+ return 'skipped';
565
+ if (occupied)
566
+ return 'refused';
567
+ changes.splice(0, changes.length, ...changes.filter((item) => !parts.includes(item)), change);
568
+ return 'appended';
569
+ }
570
+ if (changes.some((item) => touched(change).some((path) => touched(item).includes(path))))
571
+ return 'refused';
572
+ changes.push(change);
573
+ return 'appended';
574
+ }
575
+ function relinked(batch, move, before, after, appending) {
576
+ const derived = rewrites([move], after);
577
+ if (derived.kind === 'refused')
578
+ return derived;
579
+ const changes = [...batch.changes];
580
+ const appended = [];
581
+ let changed = false;
582
+ for (const change of derived.changes) {
583
+ if (change === move)
584
+ continue;
585
+ const outcome = folded(changes, change, appending);
586
+ if (outcome === 'refused')
587
+ return unqueueable(change.path);
588
+ if (outcome === 'appended')
589
+ appended.push(change);
590
+ if (outcome !== 'skipped')
591
+ changed = true;
592
+ }
593
+ if (!changed)
594
+ return { kind: 'relinked', batch };
595
+ const held = new Set(batch.preconditions.map(identity));
596
+ return {
597
+ kind: 'relinked',
598
+ batch: immutable({
599
+ ...batch,
600
+ id: changeBatchIdentity(),
601
+ changes,
602
+ preconditions: [
603
+ ...batch.preconditions,
604
+ ...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
605
+ ],
606
+ }),
607
+ };
608
+ }
609
+ function leading(changes, at) {
610
+ const from = departure(changes, at);
611
+ return new Set(changes.filter((change, index) => index < at && change.kind === 'move' && departure(changes, index) === from));
612
+ }
613
+ function created(batch, at) {
614
+ const file = batch.changes[at];
615
+ return (leading(batch.changes, at).size === 0 &&
616
+ batch.preconditions.some((precondition) => precondition.kind === 'absent' && precondition.path === file.path));
617
+ }
618
+ function withdrawnFile(batch, change, at) {
619
+ const file = batch.changes[at];
620
+ const from = departure(batch.changes, at);
621
+ const moves = leading(batch.changes, at);
622
+ const fresh = created(batch, at);
623
+ const vacated = new Set([...moves].map((move) => move.to));
624
+ if (fresh)
625
+ vacated.add(file.path);
626
+ const replaced = fresh
627
+ ? change.kind === 'remove'
628
+ ? []
629
+ : [{ ...file, path: change.to }]
630
+ : change.kind === 'remove'
631
+ ? [{ kind: 'remove', path: from }]
632
+ : change.to === from
633
+ ? [{ ...file, path: from }]
634
+ : [
635
+ { kind: 'move', path: from, to: change.to },
636
+ { ...file, path: change.to },
637
+ ];
638
+ const added = change.kind === 'move' && (fresh || change.to !== from)
639
+ ? [{ kind: 'absent', path: change.to }]
640
+ : [];
641
+ const changes = batch.changes.flatMap((item) => item === file ? replaced : item.kind === 'move' && moves.has(item) ? [] : [item]);
642
+ if (changes.length === 0)
643
+ return null;
644
+ const preconditions = [
645
+ ...added,
646
+ ...batch.preconditions.filter((precondition) => precondition.kind !== 'absent' || !vacated.has(precondition.path)),
647
+ ];
648
+ return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
649
+ }
650
+ export function requeued(queue, changes, visible, changeable, state) {
651
+ const next = [...queue];
652
+ const origins = new Map();
653
+ const withdrawn = new Map();
654
+ const owners = new Set();
655
+ const queued = new Set();
656
+ const kept = [];
657
+ const originOf = (batch) => origins.get(batch) ?? batch.id;
658
+ const open = (batch) => origins.has(batch) || changeable(batch);
659
+ for (const change of changes) {
660
+ const held = visible.binaries[change.path];
661
+ if ((change.kind !== 'move' && change.kind !== 'remove') || held?.state !== 'pending') {
662
+ kept.push(change);
663
+ continue;
664
+ }
665
+ let index = next.length - 1;
666
+ while (index >= 0 && queuedFile(next[index], change.path, held.localId) === -1)
667
+ index -= 1;
668
+ const owner = next[index];
669
+ if (owner === undefined || !open(owner))
670
+ return unqueueable(change.path);
671
+ const following = next.slice(index);
672
+ const rebuilt = [];
673
+ let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
674
+ for (const [offset, batch] of following.entries()) {
675
+ let current = batch;
676
+ if (change.kind === 'move') {
677
+ const after = state(next.slice(0, index + offset + 1));
678
+ const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
679
+ const relinking = relinked(batch, change, before, after, appending);
680
+ if (relinking.kind === 'refused')
681
+ return relinking;
682
+ current = relinking.batch;
683
+ before = after;
684
+ }
685
+ if (current !== batch && !open(batch))
686
+ return unqueueable(change.path);
687
+ rebuilt.push(offset === 0
688
+ ? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
689
+ : current);
690
+ }
691
+ for (const [offset, batch] of following.entries()) {
692
+ const replaced = rebuilt[offset];
693
+ if (replaced === batch)
694
+ continue;
695
+ const origin = originOf(batch);
696
+ withdrawn.set(origin, replaced);
697
+ if (replaced !== null)
698
+ origins.set(replaced, origin);
699
+ }
700
+ owners.add(originOf(owner));
701
+ next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
702
+ queued.add(change.path);
703
+ }
704
+ return {
705
+ kind: 'requeued',
706
+ queue: withdrawn.size === 0 ? queue : next,
707
+ changes: kept,
708
+ queued,
709
+ withdrawn,
710
+ owners,
711
+ };
712
+ }
713
+ function covering(precondition) {
714
+ return precondition.kind === 'field' ||
715
+ precondition.kind === 'section' ||
716
+ precondition.kind === 'preamble'
717
+ ? identity(precondition)
718
+ : `path ${precondition.path}`;
719
+ }
720
+ export function joined(queue, owner, extra) {
721
+ const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
722
+ const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
723
+ if (clash !== undefined)
724
+ return unqueueable(clash);
725
+ const changes = [...owner.changes];
726
+ for (const change of extra.changes) {
727
+ if (folded(changes, change, true) === 'refused')
728
+ return unqueueable(change.path);
729
+ }
730
+ const held = new Set(owner.preconditions.map(covering));
731
+ const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
732
+ const preconditions = [
733
+ ...owner.preconditions,
734
+ ...extra.preconditions.filter((precondition) => {
735
+ const key = covering(precondition);
736
+ return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
737
+ }),
738
+ ];
739
+ const batch = immutable({
740
+ ...owner,
741
+ ...(extra.validation === undefined ? {} : { validation: extra.validation }),
742
+ id: changeBatchIdentity(),
743
+ changes,
744
+ preconditions,
745
+ });
746
+ return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
747
+ }
748
+ export function limitedIn(queue, batch, limits, state) {
749
+ const at = queue.indexOf(batch);
750
+ const limited = exceeded(limits, batch.changes, providerRewritten(batch, state(queue.slice(0, at))).changes, state(queue.slice(0, at + 1)));
751
+ return limited?.kind === 'refused' ? limited.refusal : null;
752
+ }
434
753
  export function buildBatch(input) {
435
754
  const changes = relativeChanges(input);
436
755
  const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
@@ -449,9 +768,13 @@ export function buildBatch(input) {
449
768
  const resulting = overlay(input.visible, [
450
769
  { id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
451
770
  ]);
771
+ const clean = input.clean !== undefined && available(input.composition) ? input.clean : null;
452
772
  const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
453
773
  if (removed.size > 0) {
454
- const referenced = incoming(collectionOf(resulting.documents, input.composition), removed);
774
+ const referrers = clean === null ? null : referring(clean, input.composition, removed);
775
+ const referenced = incoming(collectionOf(resulting.documents, input.composition), removed, clean === null || referrers === null
776
+ ? null
777
+ : [...referrers, ...differing(clean.text, resulting.documents)]);
455
778
  if (referenced !== null)
456
779
  return referenced;
457
780
  }
@@ -461,15 +784,20 @@ export function buildBatch(input) {
461
784
  const parsed = parse(content);
462
785
  return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
463
786
  };
464
- const typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
787
+ const touched = [...new Set(predicted.changes.flatMap(affected))];
788
+ const typedPaths = touched.filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
465
789
  const needsModel = typedPaths.length > 0;
466
790
  const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
467
791
  if (withoutModel !== undefined)
468
792
  return refuse('model-unavailable', withoutModel);
469
- const validationText = needsModel
470
- ? resulting.documents
471
- : Object.fromEntries(Object.entries(resulting.documents).filter(([path]) => predicted.changes.some((change) => affected(change).includes(path))));
472
- const verdict = validateCollection(validationText, resulting.binaryTargets, input.composition);
793
+ const verdict = clean !== null &&
794
+ changedCleanly({ text: resulting.documents, binaries: resulting.binaries }, clean, input.composition)
795
+ ? { verdict: 'accept' }
796
+ : validateCollection(needsModel
797
+ ? resulting.documents
798
+ : Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
799
+ ? []
800
+ : [[path, resulting.documents[path]]])), Object.keys(resulting.binaries), input.composition);
473
801
  if (verdict.verdict === 'refuse') {
474
802
  return {
475
803
  kind: 'refused',
@@ -482,7 +810,7 @@ export function buildBatch(input) {
482
810
  },
483
811
  };
484
812
  }
485
- const limited = exceeded(input.providerLimits, changes, resulting);
813
+ const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
486
814
  if (limited !== null)
487
815
  return limited;
488
816
  const validated = typedPaths
package/dist/search.d.ts CHANGED
@@ -1,12 +1,16 @@
1
+ import type { ProviderRefusal } from './adapter.js';
1
2
  import type { ChangeSet } from './change-set.js';
2
- import { type Filter } from './query.js';
3
+ import { type CoveredRoom, type Filter, type PendingRoom } from './query.js';
3
4
  import type { Open } from './open.js';
4
5
  export type DocumentPart = Open<'fields' | 'sections' | 'preamble' | 'path'>;
5
6
  export type SearchRequest = {
6
7
  text: string;
7
8
  documents?: Filter;
9
+ collection?: true;
8
10
  conversations?: readonly string[];
9
11
  limit?: number;
12
+ after?: string;
13
+ total?: true;
10
14
  };
11
15
  export type DocumentHit = {
12
16
  room: string;
@@ -21,10 +25,38 @@ export type MessageHit = {
21
25
  score: number;
22
26
  matches: Readonly<Record<string, readonly Open<'text'>[]>>;
23
27
  };
24
- export type SearchResult = {
28
+ export type SearchCoverage = {
29
+ rooms: readonly CoveredRoom[];
30
+ conversations: readonly string[];
31
+ index: string;
32
+ overlay: readonly string[];
33
+ };
34
+ export type PendingConversation = {
35
+ conversation: string;
36
+ awaits: Open<'index'>;
37
+ };
38
+ export type SearchHits = {
25
39
  documents: readonly DocumentHit[];
26
40
  messages: readonly MessageHit[];
27
41
  };
42
+ export type SearchWindow = SearchHits & {
43
+ next?: string;
44
+ total?: {
45
+ documents: number;
46
+ messages: number;
47
+ };
48
+ };
49
+ export type SearchPending = SearchHits & {
50
+ pending: readonly (PendingRoom | PendingConversation)[];
51
+ coverage: SearchCoverage;
52
+ };
53
+ export type SearchUndeclared = ProviderRefusal & {
54
+ kind: 'undeclared';
55
+ };
56
+ export type SearchResult = (SearchWindow & {
57
+ coverage: SearchCoverage;
58
+ }) | SearchPending | SearchUndeclared;
59
+ export declare function undeclaredSearch(): SearchUndeclared;
28
60
  export type SearchedDocument = {
29
61
  room: string;
30
62
  path: string;
@@ -39,24 +71,35 @@ export type SearchedMessage = {
39
71
  text: string;
40
72
  };
41
73
  export type SearchSource = {
42
- documents(): readonly SearchedDocument[];
74
+ room(room: string): readonly string[];
43
75
  document(room: string, path: string): SearchedDocument | null;
44
- messages(): readonly SearchedMessage[];
45
- message(conversation: string, identity: string): SearchedMessage | null;
46
76
  };
77
+ type HitCursor = readonly [number, string, string] | readonly [] | null;
47
78
  export type SearchPlan = {
48
79
  text: string;
49
80
  documents: unknown;
81
+ collection: boolean;
50
82
  conversations: readonly string[] | null;
51
83
  limit: number | null;
84
+ after: readonly [HitCursor, HitCursor] | null;
85
+ total: boolean;
52
86
  };
53
87
  export type SearchIndex = {
88
+ generation(): string;
89
+ rooms(): ReadonlySet<string>;
90
+ conversations(): ReadonlySet<string>;
91
+ indexRoom(room: string): Promise<void>;
92
+ indexMessages(conversation: string, messages: readonly SearchedMessage[], whole: boolean): void;
93
+ forget(rooms: readonly string[]): void;
54
94
  changed(set: ChangeSet): void;
95
+ changes(): number;
96
+ caughtUp(): Promise<void>;
55
97
  search(plan: SearchPlan, documents: readonly {
56
98
  room: string;
57
99
  path: string;
58
- }[] | null): SearchResult;
100
+ }[] | null, conversations: readonly string[], rooms: ReadonlySet<string> | null, selections: ReadonlyMap<string, readonly string[]> | null): SearchWindow;
59
101
  };
60
102
  export declare function searchPlanOf(input: unknown): SearchPlan;
61
103
  export declare function createSearchIndex(source: SearchSource): SearchIndex;
104
+ export {};
62
105
  //# sourceMappingURL=search.d.ts.map