@agentshouse/mdruntime 0.1.0-ca1eff92bbe73 → 0.1.0-ca4dc698bd0c4

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 (193) hide show
  1. package/AGENTS.md +265 -209
  2. package/README.md +13 -3
  3. package/dist/acquisition.d.ts +14 -0
  4. package/dist/acquisition.d.ts.map +1 -0
  5. package/dist/acquisition.js +24 -0
  6. package/dist/adapter.d.ts +76 -17
  7. package/dist/adapter.d.ts.map +1 -1
  8. package/dist/attempt.d.ts +16 -0
  9. package/dist/attempt.d.ts.map +1 -0
  10. package/dist/attempt.js +70 -0
  11. package/dist/binary.d.ts +23 -0
  12. package/dist/binary.d.ts.map +1 -0
  13. package/dist/binary.js +8 -0
  14. package/dist/bootstrap.d.ts +17 -5
  15. package/dist/bootstrap.d.ts.map +1 -1
  16. package/dist/bootstrap.js +109 -37
  17. package/dist/change-set.d.ts +2 -0
  18. package/dist/change-set.d.ts.map +1 -1
  19. package/dist/change-set.js +48 -16
  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 +33 -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 +620 -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 +23 -11
  87. package/dist/client.d.ts.map +1 -1
  88. package/dist/client.js +67 -16
  89. package/dist/collection.d.ts +21 -12
  90. package/dist/collection.d.ts.map +1 -1
  91. package/dist/collection.js +271 -23
  92. package/dist/connection.d.ts +41 -15
  93. package/dist/connection.d.ts.map +1 -1
  94. package/dist/connection.js +1631 -378
  95. package/dist/conversation.d.ts +24 -10
  96. package/dist/conversation.d.ts.map +1 -1
  97. package/dist/conversation.js +307 -136
  98. package/dist/durability.d.ts +7 -1
  99. package/dist/durability.d.ts.map +1 -1
  100. package/dist/entry.d.ts +17 -0
  101. package/dist/entry.d.ts.map +1 -0
  102. package/dist/entry.js +156 -0
  103. package/dist/expectation.d.ts +21 -0
  104. package/dist/expectation.d.ts.map +1 -0
  105. package/dist/expectation.js +70 -0
  106. package/dist/failure.d.ts +13 -0
  107. package/dist/failure.d.ts.map +1 -0
  108. package/dist/failure.js +19 -0
  109. package/dist/fence.d.ts +6 -0
  110. package/dist/fence.d.ts.map +1 -0
  111. package/dist/fence.js +10 -0
  112. package/dist/host.d.ts +13 -2
  113. package/dist/host.d.ts.map +1 -1
  114. package/dist/host.js +48 -14
  115. package/dist/identity.d.ts +5 -0
  116. package/dist/identity.d.ts.map +1 -1
  117. package/dist/identity.js +19 -1
  118. package/dist/index.d.ts +14 -7
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +4 -0
  121. package/dist/memory.d.ts +6 -3
  122. package/dist/memory.d.ts.map +1 -1
  123. package/dist/memory.js +70 -22
  124. package/dist/model.d.ts +4 -2
  125. package/dist/model.d.ts.map +1 -1
  126. package/dist/model.js +9 -0
  127. package/dist/observation.d.ts +5 -2
  128. package/dist/observation.d.ts.map +1 -1
  129. package/dist/observation.js +22 -11
  130. package/dist/open.d.ts +2 -0
  131. package/dist/open.d.ts.map +1 -0
  132. package/dist/open.js +1 -0
  133. package/dist/outcome.d.ts +32 -3
  134. package/dist/outcome.d.ts.map +1 -1
  135. package/dist/outcome.js +42 -1
  136. package/dist/overlay.d.ts +15 -3
  137. package/dist/overlay.d.ts.map +1 -1
  138. package/dist/overlay.js +94 -77
  139. package/dist/query.d.ts +32 -3
  140. package/dist/query.d.ts.map +1 -1
  141. package/dist/query.js +5 -31
  142. package/dist/reach.d.ts +8 -2
  143. package/dist/reach.d.ts.map +1 -1
  144. package/dist/reach.js +60 -24
  145. package/dist/react.d.ts +28 -17
  146. package/dist/react.d.ts.map +1 -1
  147. package/dist/react.js +8 -6
  148. package/dist/read-side.d.ts +13 -6
  149. package/dist/read-side.d.ts.map +1 -1
  150. package/dist/read-side.js +291 -85
  151. package/dist/readiness.d.ts +53 -0
  152. package/dist/readiness.d.ts.map +1 -0
  153. package/dist/readiness.js +33 -0
  154. package/dist/recovery.d.ts +25 -0
  155. package/dist/recovery.d.ts.map +1 -0
  156. package/dist/recovery.js +197 -0
  157. package/dist/refresh.d.ts +10 -0
  158. package/dist/refresh.d.ts.map +1 -1
  159. package/dist/refresh.js +43 -38
  160. package/dist/replica.d.ts +8 -5
  161. package/dist/replica.d.ts.map +1 -1
  162. package/dist/replica.js +115 -67
  163. package/dist/room-draft.d.ts +26 -9
  164. package/dist/room-draft.d.ts.map +1 -1
  165. package/dist/room-draft.js +132 -23
  166. package/dist/rows.d.ts +8 -13
  167. package/dist/rows.d.ts.map +1 -1
  168. package/dist/rows.js +12 -14
  169. package/dist/save.d.ts +31 -1
  170. package/dist/save.d.ts.map +1 -1
  171. package/dist/save.js +376 -45
  172. package/dist/search.d.ts +31 -8
  173. package/dist/search.d.ts.map +1 -1
  174. package/dist/search.js +108 -45
  175. package/dist/shared.d.ts +11 -0
  176. package/dist/shared.d.ts.map +1 -0
  177. package/dist/shared.js +368 -0
  178. package/dist/status.d.ts +6 -2
  179. package/dist/status.d.ts.map +1 -1
  180. package/dist/status.js +0 -8
  181. package/dist/store.d.ts +73 -17
  182. package/dist/store.d.ts.map +1 -1
  183. package/dist/store.js +531 -198
  184. package/dist/turn.d.ts +7 -0
  185. package/dist/turn.d.ts.map +1 -0
  186. package/dist/turn.js +45 -0
  187. package/dist/wire.d.ts +9 -3
  188. package/dist/wire.d.ts.map +1 -1
  189. package/dist/wire.js +17 -2
  190. package/package.json +26 -14
  191. package/dist/attachment.d.ts +0 -3
  192. package/dist/attachment.d.ts.map +0 -1
  193. package/dist/attachment.js +0 -8
package/dist/read-side.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { and, BasicIndex, configureIndexDevMode, count, createCollection, createLiveQueryCollection, deepEquals, eq, inArray, or, toArray, } from '@tanstack/db';
2
- import { ARRAY_INDEX, pointerSegments } from '@agentshouse/mdmodel';
3
- import { rowsOf } from './observation.js';
2
+ import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
4
3
  import { documentKey } from './rows.js';
4
+ import { after, inUnits, ROW_WEIGHT } from './turn.js';
5
5
  function every(expressions) {
6
6
  const [first, second, ...rest] = expressions;
7
7
  return second === undefined ? first : and(first, second, ...rest);
@@ -101,14 +101,11 @@ function filtered(q, rows, plan) {
101
101
  }
102
102
  return query;
103
103
  }
104
- function named(prefix, names, source) {
105
- return Object.fromEntries(names.map((name, index) => [`${prefix}${index}`, source[name]]));
104
+ function bodied(shape) {
105
+ return shape.sections !== null || shape.preamble || shape.markdown;
106
106
  }
107
107
  function documents(q, rows, plan, shape) {
108
108
  let query = filtered(q, rows, plan);
109
- if (shape.sections !== null || shape.preamble || shape.markdown) {
110
- query = query.leftJoin({ b: rows }, ({ d, b }) => eq(d.body, b.key));
111
- }
112
109
  for (const { key, options } of shape.orderBy) {
113
110
  query = query.orderBy(({ d }) => keyed(key, d), options);
114
111
  }
@@ -117,15 +114,13 @@ function documents(q, rows, plan, shape) {
117
114
  }
118
115
  if (plan.limit !== null)
119
116
  query = query.limit(plan.limit);
120
- return query.select(({ d, b }) => ({
117
+ return query.select(({ d }) => ({
121
118
  room: d.room,
122
119
  path: d.path,
123
120
  type: d.type,
121
+ binary: d.binary,
124
122
  ...(shape.fields === null ? {} : { fields: d.fields }),
125
- ...(shape.sections === 'all' ? { sections: b.sections } : {}),
126
- ...(Array.isArray(shape.sections) ? named('section', shape.sections, b.sections) : {}),
127
- ...(shape.preamble ? { preamble: b.preamble } : {}),
128
- ...(shape.markdown ? { markdown: b.markdown } : {}),
123
+ ...(bodied(shape) ? { text: d.text } : {}),
129
124
  ...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
130
125
  ...(shape.change ? { change: d.change } : {}),
131
126
  }));
@@ -171,13 +166,22 @@ function built(rows, plan) {
171
166
  },
172
167
  };
173
168
  }
174
- function present(entries) {
175
- return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
176
- }
177
- function part(row, prefix, names, whole) {
169
+ function sectionsOf(sections, names) {
178
170
  if (names === 'all')
179
- return whole;
180
- return present(names.map((name, index) => [name, row[`${prefix}${index}`]]));
171
+ return sections;
172
+ return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
173
+ }
174
+ function bodyOf(row, key, bodies) {
175
+ const text = row.text;
176
+ if (text === null)
177
+ return null;
178
+ const held = bodies?.get(key);
179
+ if (held !== undefined && held.text === text)
180
+ return held.document;
181
+ const read = parse(text, row.path);
182
+ const document = read.ok ? read.document : null;
183
+ bodies?.set(key, { text, document });
184
+ return document;
181
185
  }
182
186
  function masked(pointers) {
183
187
  const root = { whole: false, members: new Map() };
@@ -228,9 +232,16 @@ function cut(value, mask) {
228
232
  function selected(fields, pointers) {
229
233
  return (cut(fields, masked(pointers)) ?? {});
230
234
  }
231
- function documentOf(row, shape) {
232
- if (row.type === null)
233
- return { room: row.room, path: row.path };
235
+ function documentOf(key, row, shape, bodies) {
236
+ if (row.type === null) {
237
+ const binary = row.binary;
238
+ return {
239
+ room: row.room,
240
+ path: row.path,
241
+ ...(binary === null ? {} : { binaryFile: binary }),
242
+ };
243
+ }
244
+ const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
234
245
  return {
235
246
  room: row.room,
236
247
  path: row.path,
@@ -242,13 +253,11 @@ function documentOf(row, shape) {
242
253
  ? row.fields
243
254
  : selected(row.fields, shape.fields),
244
255
  }),
245
- ...(shape.sections === null
256
+ ...(shape.sections === null || body === null
246
257
  ? {}
247
- : {
248
- sections: part(row, 'section', shape.sections, row.sections),
249
- }),
250
- ...(shape.preamble ? { preamble: row.preamble } : {}),
251
- ...(shape.markdown ? { markdown: row.markdown } : {}),
258
+ : { sections: sectionsOf(body.sections, shape.sections) }),
259
+ ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
260
+ ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
252
261
  ...(shape.backlinks
253
262
  ? {
254
263
  backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
@@ -257,30 +266,40 @@ function documentOf(row, shape) {
257
266
  ...(shape.change ? { change: row.change } : {}),
258
267
  };
259
268
  }
260
- function keyedOf(plan, entries) {
269
+ function weight(row) {
270
+ return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
271
+ }
272
+ function rowsOfPlan(plan, entries, bodies, each = (row) => row) {
261
273
  const shape = plan.shape;
262
274
  if (shape.kind === 'documents') {
263
- return {
264
- result: structuredClone({ documents: entries.map(([, row]) => documentOf(row, shape)) }),
265
- keys: entries.map(([key]) => key),
266
- };
275
+ const documents = [];
276
+ return after(inUnits(entries, ([key, row]) => {
277
+ documents.push(each(documentOf(key, row, shape, bodies)));
278
+ return weight(row);
279
+ }), () => ({ rows: { documents }, keys: entries.map(([key]) => key) }));
267
280
  }
268
281
  const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
269
- return {
270
- result: structuredClone({
271
- groups: bounded.map(([, row]) => ({
272
- key: shape.groupBy.map((_, index) => row[`key${index}`]),
273
- count: row.count,
274
- })),
275
- }),
276
- keys: bounded.map(([key]) => key),
277
- };
282
+ const groups = [];
283
+ return after(inUnits(bounded, ([, row]) => {
284
+ groups.push(each({
285
+ key: shape.groupBy.map((_, index) => row[`key${index}`]),
286
+ count: row.count,
287
+ }));
288
+ return ROW_WEIGHT;
289
+ }), () => ({ rows: { groups }, keys: bounded.map(([key]) => key) }));
278
290
  }
279
291
  export function reconciled(previous, next) {
280
- const before = rowsOf(previous.result);
281
- const held = new Map(previous.keys.map((key, index) => [key, before[index]]));
292
+ const before = previous.result;
293
+ const after = next.result;
294
+ if ('pending' in before || 'pending' in after) {
295
+ if (deepEquals(before, after))
296
+ return null;
297
+ return { keyed: next, message: next };
298
+ }
299
+ const earlier = 'groups' in before ? before.groups : before.documents;
300
+ const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
282
301
  const changed = [];
283
- const rows = rowsOf(next.result).map((row, index) => {
302
+ const rows = ('groups' in after ? after.groups : after.documents).map((row, index) => {
284
303
  const key = next.keys[index];
285
304
  const kept = held.get(key);
286
305
  if (kept !== undefined && deepEquals(kept, row))
@@ -289,22 +308,37 @@ export function reconciled(previous, next) {
289
308
  return row;
290
309
  });
291
310
  const present = new Set(next.keys);
292
- const survivors = previous.keys.filter((key) => present.has(key));
293
311
  const removed = previous.keys.filter((key) => !present.has(key));
294
- const moved = survivors.length !== next.keys.length ||
295
- survivors.some((key, index) => key !== next.keys[index]);
296
- if (changed.length === 0 && removed.length === 0 && !moved)
312
+ const moved = reordered(previous.keys, next.keys, present);
313
+ const covered = !deepEquals(before.coverage, after.coverage);
314
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered)
297
315
  return null;
298
316
  return {
299
317
  keyed: {
300
- result: 'groups' in next.result
301
- ? { groups: rows }
302
- : { documents: rows },
318
+ result: 'groups' in after
319
+ ? { groups: rows, coverage: after.coverage }
320
+ : { documents: rows, coverage: after.coverage },
303
321
  keys: next.keys,
304
322
  },
305
- change: { changed, removed, order: moved ? next.keys : null },
323
+ message: {
324
+ changed,
325
+ removed,
326
+ order: moved ? next.keys : null,
327
+ ...(covered ? { coverage: after.coverage } : {}),
328
+ },
306
329
  };
307
330
  }
331
+ function reordered(previous, next, present) {
332
+ let at = 0;
333
+ for (const key of previous) {
334
+ if (!present.has(key))
335
+ continue;
336
+ if (next[at] !== key)
337
+ return true;
338
+ at += 1;
339
+ }
340
+ return at !== next.length;
341
+ }
308
342
  export function createReadSide() {
309
343
  configureIndexDevMode({ enabled: false });
310
344
  let sync;
@@ -324,23 +358,139 @@ export function createReadSide() {
324
358
  });
325
359
  const shared = new Map();
326
360
  function searched(row) {
327
- const body = rows.get(row.body);
361
+ const read = row.text === null ? null : parse(row.text, row.path);
362
+ if (read === null || !read.ok)
363
+ return null;
328
364
  return {
329
365
  room: row.room,
330
366
  path: row.path,
331
367
  type: row.type,
332
368
  fields: row.fields,
333
- preamble: body.preamble,
334
- sections: body.sections,
369
+ preamble: read.document.preamble,
370
+ sections: read.document.sections,
335
371
  };
336
372
  }
337
- function publish(entry) {
338
- const next = reconciled(entry.result, keyedOf(entry.plan, [...entry.built.live.entries()]));
339
- if (next === null)
373
+ function kept(entry, key, row) {
374
+ const held = entry.rows.get(key);
375
+ if (held !== undefined && deepEquals(held, row))
340
376
  return;
341
- entry.result = next.keyed;
342
- for (const listener of entry.listeners)
343
- listener(next.change);
377
+ entry.rows.set(key, row);
378
+ }
379
+ function rebuilt(entry) {
380
+ return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], entry.bodies), ({ rows: found, keys }) => {
381
+ const present = new Set(keys);
382
+ for (const key of [...entry.rows.keys()])
383
+ if (!present.has(key))
384
+ entry.rows.delete(key);
385
+ for (const key of [...entry.bodies.keys()]) {
386
+ if (!present.has(key))
387
+ entry.bodies.delete(key);
388
+ }
389
+ keys.forEach((key, index) => kept(entry, key, rowAt(found, index)));
390
+ return keys;
391
+ });
392
+ }
393
+ function refreshed(entry, dirty) {
394
+ const shape = entry.plan.shape;
395
+ const keys = [...entry.built.live.keys()];
396
+ const named = new Set([...dirty, ...keys.filter((key) => !entry.rows.has(key))]);
397
+ const values = [...named].map((key) => [key, entry.built.live.get(key)]);
398
+ const done = inUnits(values, ([key, value]) => {
399
+ if (value !== undefined)
400
+ kept(entry, key, documentOf(key, value, shape, entry.bodies));
401
+ else {
402
+ entry.rows.delete(key);
403
+ entry.bodies.delete(key);
404
+ }
405
+ return weight(value);
406
+ });
407
+ return after(done, () => keys);
408
+ }
409
+ function current(entry, keys, coverage) {
410
+ const found = keys.map((key) => entry.rows.get(key));
411
+ return {
412
+ result: entry.plan.shape.kind === 'groups'
413
+ ? { groups: found, coverage }
414
+ : { documents: found, coverage },
415
+ keys,
416
+ };
417
+ }
418
+ function delivered(entry, next) {
419
+ const previous = entry.result;
420
+ entry.result = next;
421
+ const message = previous === undefined ? null : changedFrom(previous, next);
422
+ if (message !== null)
423
+ for (const listener of entry.ready)
424
+ listener(message);
425
+ for (const listener of entry.fresh) {
426
+ entry.ready.add(listener);
427
+ listener(next);
428
+ }
429
+ entry.fresh.clear();
430
+ }
431
+ function run(entry) {
432
+ while (entry.listeners.size > 0 &&
433
+ (entry.whole || entry.dirty.size > 0 || entry.stale || entry.fresh.size > 0)) {
434
+ entry.stale = false;
435
+ const cover = entry.cover();
436
+ if ('pending' in cover) {
437
+ entry.whole = true;
438
+ entry.dirty.clear();
439
+ entry.rows.clear();
440
+ entry.bodies.clear();
441
+ delivered(entry, { result: cover, keys: [] });
442
+ break;
443
+ }
444
+ const whole = entry.whole ||
445
+ entry.plan.shape.kind === 'groups' ||
446
+ entry.result === undefined ||
447
+ 'pending' in entry.result.result;
448
+ const dirty = entry.dirty;
449
+ entry.dirty = new Set();
450
+ entry.whole = false;
451
+ const keys = whole ? rebuilt(entry) : refreshed(entry, dirty);
452
+ if (keys instanceof Promise) {
453
+ void keys
454
+ .then((found) => {
455
+ if (entry.listeners.size > 0)
456
+ delivered(entry, current(entry, found, cover.coverage));
457
+ run(entry);
458
+ })
459
+ .catch(() => {
460
+ entry.running = false;
461
+ });
462
+ return;
463
+ }
464
+ delivered(entry, current(entry, keys, cover.coverage));
465
+ }
466
+ entry.running = false;
467
+ }
468
+ function wake(entry) {
469
+ if (entry.subscription === null || entry.running)
470
+ return;
471
+ entry.running = true;
472
+ try {
473
+ run(entry);
474
+ }
475
+ catch (error) {
476
+ entry.running = false;
477
+ throw error;
478
+ }
479
+ }
480
+ function recover() {
481
+ for (const entry of shared.values()) {
482
+ if (entry.result === undefined || entry.subscription === null)
483
+ continue;
484
+ const cover = entry.cover();
485
+ const before = entry.result.result;
486
+ const held = 'pending' in cover
487
+ ? deepEquals(before, cover)
488
+ : 'coverage' in before && deepEquals(before.coverage, cover.coverage);
489
+ if (held)
490
+ continue;
491
+ entry.stale = true;
492
+ wake(entry);
493
+ }
344
494
  }
345
495
  return {
346
496
  apply(set) {
@@ -351,38 +501,52 @@ export function createReadSide() {
351
501
  for (const row of change.after)
352
502
  next.set(row.key, row);
353
503
  }
354
- if (next.size === 0)
355
- return;
356
- sync.begin();
357
- for (const [key, row] of next) {
358
- if (row === null) {
359
- if (rows.has(key))
360
- sync.write({ type: 'delete', key });
361
- continue;
504
+ if (next.size > 0) {
505
+ sync.begin();
506
+ for (const [key, row] of next) {
507
+ if (row === null) {
508
+ if (rows.has(key))
509
+ sync.write({ type: 'delete', key });
510
+ continue;
511
+ }
512
+ sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
362
513
  }
363
- sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
514
+ sync.commit();
364
515
  }
365
- sync.commit();
516
+ recover();
366
517
  },
367
- async query(plan) {
518
+ recover,
519
+ async query(plan, at) {
368
520
  const { live: collection, dispose } = built(rows, plan);
369
521
  try {
370
522
  await collection.toArrayWhenReady();
371
- return keyedOf(plan, [...collection.entries()]).result;
523
+ const entries = [...collection.entries()];
524
+ const held = at();
525
+ const found = await rowsOfPlan(plan, entries, null, structuredClone);
526
+ return { rows: found.rows, at: held };
372
527
  }
373
528
  finally {
374
529
  dispose();
375
530
  }
376
531
  },
377
- watch(plan, identity, deliver) {
532
+ watch(plan, identity, cover, deliverTo) {
378
533
  let entry = shared.get(identity);
379
534
  if (entry === undefined) {
380
535
  const created = {
381
536
  plan,
382
537
  built: built(rows, plan),
538
+ cover,
383
539
  result: undefined,
540
+ rows: new Map(),
541
+ bodies: new Map(),
384
542
  listeners: new Set(),
543
+ fresh: new Set(),
544
+ ready: new Set(),
385
545
  subscription: null,
546
+ whole: true,
547
+ dirty: new Set(),
548
+ stale: false,
549
+ running: false,
386
550
  };
387
551
  created.built.live.on('status:change', ({ status }) => {
388
552
  if (status === 'cleaned-up' && created.listeners.size === 0)
@@ -392,29 +556,40 @@ export function createReadSide() {
392
556
  entry = created;
393
557
  }
394
558
  const observed = entry;
395
- observed.listeners.add(deliver);
559
+ observed.listeners.add(deliverTo);
396
560
  observed.built.live.onFirstReady(() => {
397
- if (!observed.listeners.has(deliver))
561
+ if (!observed.listeners.has(deliverTo))
398
562
  return;
563
+ observed.fresh.add(deliverTo);
399
564
  if (observed.subscription === null) {
400
- observed.result = keyedOf(observed.plan, [...observed.built.live.entries()]);
401
- observed.subscription = observed.built.live.subscribeChanges(() => {
402
- publish(observed);
565
+ observed.whole = true;
566
+ observed.subscription = observed.built.live.subscribeChanges((changes) => {
567
+ if (observed.plan.shape.kind === 'groups')
568
+ observed.whole = true;
569
+ else
570
+ for (const { key } of changes)
571
+ observed.dirty.add(key);
572
+ wake(observed);
403
573
  }, { includeInitialState: false });
404
574
  }
405
- deliver(observed.result);
575
+ wake(observed);
406
576
  });
407
577
  return () => {
408
- if (!observed.listeners.delete(deliver) || observed.listeners.size > 0)
578
+ observed.fresh.delete(deliverTo);
579
+ observed.ready.delete(deliverTo);
580
+ if (!observed.listeners.delete(deliverTo) || observed.listeners.size > 0)
409
581
  return;
410
582
  observed.subscription?.unsubscribe();
411
583
  observed.subscription = null;
584
+ observed.result = undefined;
585
+ observed.rows.clear();
586
+ observed.bodies.clear();
587
+ observed.dirty.clear();
588
+ observed.whole = true;
412
589
  };
413
590
  },
414
- documents() {
415
- return rows.toArray
416
- .filter((row) => row.kind === 'document' && row.type !== null)
417
- .map(searched);
591
+ room(room) {
592
+ return rows.toArray.flatMap((row) => row.kind !== 'document' || row.type === null || row.room !== room ? [] : [row.path]);
418
593
  },
419
594
  document(room, path) {
420
595
  const row = rows.get(documentKey(room, path));
@@ -423,6 +598,8 @@ export function createReadSide() {
423
598
  close() {
424
599
  for (const entry of shared.values()) {
425
600
  entry.listeners.clear();
601
+ entry.fresh.clear();
602
+ entry.ready.clear();
426
603
  entry.subscription?.unsubscribe();
427
604
  entry.built.dispose();
428
605
  }
@@ -431,3 +608,32 @@ export function createReadSide() {
431
608
  },
432
609
  };
433
610
  }
611
+ function changedFrom(previous, next) {
612
+ const before = previous.result;
613
+ const after = next.result;
614
+ if ('pending' in before || 'pending' in after)
615
+ return deepEquals(before, after) ? null : next;
616
+ const earlier = 'groups' in before ? before.groups : before.documents;
617
+ const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
618
+ const later = 'groups' in after ? after.groups : after.documents;
619
+ const changed = [];
620
+ next.keys.forEach((key, index) => {
621
+ if (held.get(key) !== later[index])
622
+ changed.push([key, later[index]]);
623
+ });
624
+ const present = new Set(next.keys);
625
+ const removed = previous.keys.filter((key) => !present.has(key));
626
+ const moved = reordered(previous.keys, next.keys, present);
627
+ const covered = !deepEquals(before.coverage, after.coverage);
628
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered)
629
+ return null;
630
+ return {
631
+ changed,
632
+ removed,
633
+ order: moved ? next.keys : null,
634
+ ...(covered ? { coverage: after.coverage } : {}),
635
+ };
636
+ }
637
+ function rowAt(rows, index) {
638
+ return 'groups' in rows ? rows.groups[index] : rows.documents[index];
639
+ }
@@ -0,0 +1,53 @@
1
+ import type { Open } from './open.js';
2
+ export type CodeAvailability = Open<'stored' | 'absent'>;
3
+ export type RoomPreparation = Open<'complete' | 'acquiring' | 'preparing' | 'indexing' | 'refused' | 'unadmitted'>;
4
+ export type RoomCoverage = {
5
+ readonly room: string;
6
+ readonly state: RoomPreparation;
7
+ };
8
+ export type ConversationCoverage = Open<'complete' | 'acquiring' | 'indexing' | 'undeclared'>;
9
+ export type SearchObligation = Open<'required' | 'unused'>;
10
+ export type TargetCoverage = {
11
+ readonly complete: boolean;
12
+ readonly listed: boolean;
13
+ readonly rooms: readonly RoomCoverage[];
14
+ readonly conversations: ConversationCoverage;
15
+ readonly search: SearchObligation;
16
+ };
17
+ export type Persistence = Open<'granted' | 'denied' | 'unavailable'>;
18
+ export type Durability = Open<'persistent' | 'best-effort'>;
19
+ export type ConnectionAvailability = Open<'connected' | 'connecting' | 'unreachable'>;
20
+ export type HouseOutcome = Open<'open' | 'write-halted' | 'refused'>;
21
+ export type CapacityCause = {
22
+ readonly cause: Open<'insufficient-space' | 'quota-exceeded'>;
23
+ readonly needed: number | null;
24
+ readonly available: number | null;
25
+ };
26
+ export type RefusedRoom = {
27
+ readonly room: string;
28
+ readonly file: string;
29
+ readonly rule: string;
30
+ readonly location: string | null;
31
+ };
32
+ export type ReadinessStatus = Open<'preparing' | 'paused' | 'refused' | 'ready' | 'lost'>;
33
+ export type Readiness = {
34
+ readonly status: ReadinessStatus;
35
+ readonly ready: boolean;
36
+ readonly usable: boolean;
37
+ readonly code: CodeAvailability;
38
+ readonly coverage: TargetCoverage;
39
+ readonly durability: Durability;
40
+ readonly persistence: Persistence;
41
+ readonly connection: ConnectionAvailability;
42
+ readonly house: HouseOutcome;
43
+ readonly capacity: CapacityCause | null;
44
+ readonly refused: readonly RefusedRoom[];
45
+ readonly lost: boolean;
46
+ };
47
+ export type ReadinessFacts = Omit<Readiness, 'status' | 'ready' | 'usable' | 'durability' | 'coverage'> & {
48
+ readonly coverage: Omit<TargetCoverage, 'complete'>;
49
+ readonly established: boolean;
50
+ };
51
+ export declare const SETTLED_ROOMS: ReadonlySet<string>;
52
+ export declare function readinessOf(facts: ReadinessFacts): Readiness;
53
+ //# sourceMappingURL=readiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ const COMPLETE_ROOMS = new Set(['complete', 'unadmitted']);
2
+ export const SETTLED_ROOMS = new Set([...COMPLETE_ROOMS, 'refused']);
3
+ export function readinessOf(facts) {
4
+ const { established, coverage: held, ...reported } = facts;
5
+ const settledConversations = held.conversations === 'complete' || held.conversations === 'undeclared';
6
+ const complete = held.listed &&
7
+ settledConversations &&
8
+ held.rooms.every((room) => COMPLETE_ROOMS.has(room.state));
9
+ const settled = held.listed &&
10
+ settledConversations &&
11
+ held.rooms.every((room) => SETTLED_ROOMS.has(room.state));
12
+ const coverage = { ...held, complete };
13
+ const stored = facts.code === 'stored';
14
+ const ready = stored && complete;
15
+ const usable = stored && (ready || established);
16
+ const status = ready
17
+ ? 'ready'
18
+ : facts.capacity !== null
19
+ ? 'paused'
20
+ : facts.lost && !established
21
+ ? 'lost'
22
+ : settled && !complete
23
+ ? 'refused'
24
+ : 'preparing';
25
+ return {
26
+ ...reported,
27
+ coverage,
28
+ status,
29
+ ready,
30
+ usable,
31
+ durability: facts.persistence === 'granted' ? 'persistent' : 'best-effort',
32
+ };
33
+ }
@@ -0,0 +1,25 @@
1
+ export type Attempted = 'done' | 'again' | {
2
+ readonly at: number;
3
+ };
4
+ export type Attempt = (signal: AbortSignal) => Promise<Attempted>;
5
+ export type AttemptOptions = {
6
+ readonly at?: number;
7
+ readonly again?: boolean;
8
+ };
9
+ export type RecoveryOptions = {
10
+ readonly firstMs?: number;
11
+ readonly ceilingMs?: number;
12
+ readonly attempts?: number;
13
+ readonly inFlight?: number;
14
+ };
15
+ export type Recovery = {
16
+ attempt(intent: string, run: Attempt, options?: AttemptOptions): Promise<boolean>;
17
+ pending(intent: string): boolean;
18
+ resume(): void;
19
+ cancel(intent: string): void;
20
+ scope(inFlight?: number): Recovery;
21
+ retire(): Promise<void>;
22
+ };
23
+ export declare function retryDelay(tries: number, firstMs?: number, ceilingMs?: number): number;
24
+ export declare function createRecovery(options?: RecoveryOptions): Recovery;
25
+ //# sourceMappingURL=recovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../src/recovery.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;AAElE,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClF,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,MAAM,IAAI,IAAI,CAAC;IACf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACnC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB,CAAC;AAsBF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,SAAW,EAAE,SAAS,SAAa,GAAG,MAAM,CAG5F;AAuKD,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAStE"}