@agentshouse/mdruntime 0.1.0-c7bd7ca3faee4 → 0.1.0-c7f51acc2629c

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 (199) hide show
  1. package/AGENTS.md +275 -221
  2. package/README.md +11 -1
  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 +91 -23
  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 +18 -5
  15. package/dist/bootstrap.d.ts.map +1 -1
  16. package/dist/bootstrap.js +113 -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 +49 -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 +61 -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 +27 -0
  39. package/dist/chat/list.d.ts.map +1 -0
  40. package/dist/chat/list.js +63 -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 +22 -0
  48. package/dist/chat/messages.d.ts.map +1 -0
  49. package/dist/chat/messages.js +155 -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 +629 -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 +69 -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 +34 -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 +25 -12
  87. package/dist/client.d.ts.map +1 -1
  88. package/dist/client.js +44 -18
  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 +45 -15
  93. package/dist/connection.d.ts.map +1 -1
  94. package/dist/connection.js +1884 -402
  95. package/dist/conversation.d.ts +26 -10
  96. package/dist/conversation.d.ts.map +1 -1
  97. package/dist/conversation.js +414 -148
  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 +18 -3
  113. package/dist/host.d.ts.map +1 -1
  114. package/dist/host.js +101 -20
  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 +16 -8
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +5 -0
  121. package/dist/memory.d.ts +11 -4
  122. package/dist/memory.d.ts.map +1 -1
  123. package/dist/memory.js +89 -27
  124. package/dist/model.d.ts +7 -4
  125. package/dist/model.d.ts.map +1 -1
  126. package/dist/model.js +10 -1
  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 +38 -3
  134. package/dist/outcome.d.ts.map +1 -1
  135. package/dist/outcome.js +45 -1
  136. package/dist/overlay.d.ts +16 -3
  137. package/dist/overlay.d.ts.map +1 -1
  138. package/dist/overlay.js +118 -77
  139. package/dist/query.d.ts +39 -3
  140. package/dist/query.d.ts.map +1 -1
  141. package/dist/query.js +11 -2
  142. package/dist/reach.d.ts +8 -3
  143. package/dist/reach.d.ts.map +1 -1
  144. package/dist/reach.js +63 -24
  145. package/dist/react.d.ts +11 -6
  146. package/dist/react.d.ts.map +1 -1
  147. package/dist/react.js +9 -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 +294 -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/reads.d.ts +11 -0
  155. package/dist/reads.d.ts.map +1 -0
  156. package/dist/reads.js +98 -0
  157. package/dist/recovery.d.ts +25 -0
  158. package/dist/recovery.d.ts.map +1 -0
  159. package/dist/recovery.js +197 -0
  160. package/dist/refresh.d.ts +13 -3
  161. package/dist/refresh.d.ts.map +1 -1
  162. package/dist/refresh.js +49 -38
  163. package/dist/replica.d.ts +8 -5
  164. package/dist/replica.d.ts.map +1 -1
  165. package/dist/replica.js +123 -67
  166. package/dist/room-draft.d.ts +38 -9
  167. package/dist/room-draft.d.ts.map +1 -1
  168. package/dist/room-draft.js +134 -25
  169. package/dist/rows.d.ts +9 -13
  170. package/dist/rows.d.ts.map +1 -1
  171. package/dist/rows.js +14 -14
  172. package/dist/save.d.ts +31 -1
  173. package/dist/save.d.ts.map +1 -1
  174. package/dist/save.js +376 -45
  175. package/dist/search.d.ts +39 -8
  176. package/dist/search.d.ts.map +1 -1
  177. package/dist/search.js +132 -46
  178. package/dist/seed.d.ts +16 -0
  179. package/dist/seed.d.ts.map +1 -0
  180. package/dist/seed.js +61 -0
  181. package/dist/shared.d.ts +11 -0
  182. package/dist/shared.d.ts.map +1 -0
  183. package/dist/shared.js +368 -0
  184. package/dist/status.d.ts +6 -2
  185. package/dist/status.d.ts.map +1 -1
  186. package/dist/status.js +0 -8
  187. package/dist/store.d.ts +81 -19
  188. package/dist/store.d.ts.map +1 -1
  189. package/dist/store.js +544 -200
  190. package/dist/turn.d.ts +7 -0
  191. package/dist/turn.d.ts.map +1 -0
  192. package/dist/turn.js +45 -0
  193. package/dist/wire.d.ts +148 -33
  194. package/dist/wire.d.ts.map +1 -1
  195. package/dist/wire.js +62 -31
  196. package/package.json +26 -14
  197. package/dist/attachment.d.ts +0 -3
  198. package/dist/attachment.d.ts.map +0 -1
  199. 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,17 +114,16 @@ 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 } : {}),
126
+ ...(shape.fileId ? { fileId: d.fileId } : {}),
131
127
  }));
132
128
  }
133
129
  function backlinked(q, rows, document, reach) {
@@ -171,13 +167,22 @@ function built(rows, plan) {
171
167
  },
172
168
  };
173
169
  }
174
- function present(entries) {
175
- return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
176
- }
177
- function part(row, prefix, names, whole) {
170
+ function sectionsOf(sections, names) {
178
171
  if (names === 'all')
179
- return whole;
180
- return present(names.map((name, index) => [name, row[`${prefix}${index}`]]));
172
+ return sections;
173
+ return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
174
+ }
175
+ function bodyOf(row, key, bodies) {
176
+ const text = row.text;
177
+ if (text === null)
178
+ return null;
179
+ const held = bodies?.get(key);
180
+ if (held !== undefined && held.text === text)
181
+ return held.document;
182
+ const read = parse(text, row.path);
183
+ const document = read.ok ? read.document : null;
184
+ bodies?.set(key, { text, document });
185
+ return document;
181
186
  }
182
187
  function masked(pointers) {
183
188
  const root = { whole: false, members: new Map() };
@@ -228,9 +233,17 @@ function cut(value, mask) {
228
233
  function selected(fields, pointers) {
229
234
  return (cut(fields, masked(pointers)) ?? {});
230
235
  }
231
- function documentOf(row, shape) {
232
- if (row.type === null)
233
- return { room: row.room, path: row.path };
236
+ function documentOf(key, row, shape, bodies) {
237
+ if (row.type === null) {
238
+ const binary = row.binary;
239
+ return {
240
+ room: row.room,
241
+ path: row.path,
242
+ ...(binary === null ? {} : { binaryFile: binary }),
243
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
244
+ };
245
+ }
246
+ const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
234
247
  return {
235
248
  room: row.room,
236
249
  path: row.path,
@@ -242,45 +255,54 @@ function documentOf(row, shape) {
242
255
  ? row.fields
243
256
  : selected(row.fields, shape.fields),
244
257
  }),
245
- ...(shape.sections === null
258
+ ...(shape.sections === null || body === null
246
259
  ? {}
247
- : {
248
- sections: part(row, 'section', shape.sections, row.sections),
249
- }),
250
- ...(shape.preamble ? { preamble: row.preamble } : {}),
251
- ...(shape.markdown ? { markdown: row.markdown } : {}),
260
+ : { sections: sectionsOf(body.sections, shape.sections) }),
261
+ ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
262
+ ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
252
263
  ...(shape.backlinks
253
264
  ? {
254
265
  backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
255
266
  }
256
267
  : {}),
257
268
  ...(shape.change ? { change: row.change } : {}),
269
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
258
270
  };
259
271
  }
260
- function keyedOf(plan, entries) {
272
+ function weight(row) {
273
+ return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
274
+ }
275
+ function rowsOfPlan(plan, entries, bodies, each = (row) => row) {
261
276
  const shape = plan.shape;
262
277
  if (shape.kind === 'documents') {
263
- return {
264
- result: structuredClone({ documents: entries.map(([, row]) => documentOf(row, shape)) }),
265
- keys: entries.map(([key]) => key),
266
- };
278
+ const documents = [];
279
+ return after(inUnits(entries, ([key, row]) => {
280
+ documents.push(each(documentOf(key, row, shape, bodies)));
281
+ return weight(row);
282
+ }), () => ({ rows: { documents }, keys: entries.map(([key]) => key) }));
267
283
  }
268
284
  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
- };
285
+ const groups = [];
286
+ return after(inUnits(bounded, ([, row]) => {
287
+ groups.push(each({
288
+ key: shape.groupBy.map((_, index) => row[`key${index}`]),
289
+ count: row.count,
290
+ }));
291
+ return ROW_WEIGHT;
292
+ }), () => ({ rows: { groups }, keys: bounded.map(([key]) => key) }));
278
293
  }
279
294
  export function reconciled(previous, next) {
280
- const before = rowsOf(previous.result);
281
- const held = new Map(previous.keys.map((key, index) => [key, before[index]]));
295
+ const before = previous.result;
296
+ const after = next.result;
297
+ if ('pending' in before || 'pending' in after) {
298
+ if (deepEquals(before, after))
299
+ return null;
300
+ return { keyed: next, message: next };
301
+ }
302
+ const earlier = 'groups' in before ? before.groups : before.documents;
303
+ const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
282
304
  const changed = [];
283
- const rows = rowsOf(next.result).map((row, index) => {
305
+ const rows = ('groups' in after ? after.groups : after.documents).map((row, index) => {
284
306
  const key = next.keys[index];
285
307
  const kept = held.get(key);
286
308
  if (kept !== undefined && deepEquals(kept, row))
@@ -289,22 +311,37 @@ export function reconciled(previous, next) {
289
311
  return row;
290
312
  });
291
313
  const present = new Set(next.keys);
292
- const survivors = previous.keys.filter((key) => present.has(key));
293
314
  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)
315
+ const moved = reordered(previous.keys, next.keys, present);
316
+ const covered = !deepEquals(before.coverage, after.coverage);
317
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered)
297
318
  return null;
298
319
  return {
299
320
  keyed: {
300
- result: 'groups' in next.result
301
- ? { groups: rows }
302
- : { documents: rows },
321
+ result: 'groups' in after
322
+ ? { groups: rows, coverage: after.coverage }
323
+ : { documents: rows, coverage: after.coverage },
303
324
  keys: next.keys,
304
325
  },
305
- change: { changed, removed, order: moved ? next.keys : null },
326
+ message: {
327
+ changed,
328
+ removed,
329
+ order: moved ? next.keys : null,
330
+ ...(covered ? { coverage: after.coverage } : {}),
331
+ },
306
332
  };
307
333
  }
334
+ function reordered(previous, next, present) {
335
+ let at = 0;
336
+ for (const key of previous) {
337
+ if (!present.has(key))
338
+ continue;
339
+ if (next[at] !== key)
340
+ return true;
341
+ at += 1;
342
+ }
343
+ return at !== next.length;
344
+ }
308
345
  export function createReadSide() {
309
346
  configureIndexDevMode({ enabled: false });
310
347
  let sync;
@@ -324,23 +361,139 @@ export function createReadSide() {
324
361
  });
325
362
  const shared = new Map();
326
363
  function searched(row) {
327
- const body = rows.get(row.body);
364
+ const read = row.text === null ? null : parse(row.text, row.path);
365
+ if (read === null || !read.ok)
366
+ return null;
328
367
  return {
329
368
  room: row.room,
330
369
  path: row.path,
331
370
  type: row.type,
332
371
  fields: row.fields,
333
- preamble: body.preamble,
334
- sections: body.sections,
372
+ preamble: read.document.preamble,
373
+ sections: read.document.sections,
335
374
  };
336
375
  }
337
- function publish(entry) {
338
- const next = reconciled(entry.result, keyedOf(entry.plan, [...entry.built.live.entries()]));
339
- if (next === null)
376
+ function kept(entry, key, row) {
377
+ const held = entry.rows.get(key);
378
+ if (held !== undefined && deepEquals(held, row))
340
379
  return;
341
- entry.result = next.keyed;
342
- for (const listener of entry.listeners)
343
- listener(next.change);
380
+ entry.rows.set(key, row);
381
+ }
382
+ function rebuilt(entry) {
383
+ return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], entry.bodies), ({ rows: found, keys }) => {
384
+ const present = new Set(keys);
385
+ for (const key of [...entry.rows.keys()])
386
+ if (!present.has(key))
387
+ entry.rows.delete(key);
388
+ for (const key of [...entry.bodies.keys()]) {
389
+ if (!present.has(key))
390
+ entry.bodies.delete(key);
391
+ }
392
+ keys.forEach((key, index) => kept(entry, key, rowAt(found, index)));
393
+ return keys;
394
+ });
395
+ }
396
+ function refreshed(entry, dirty) {
397
+ const shape = entry.plan.shape;
398
+ const keys = [...entry.built.live.keys()];
399
+ const named = new Set([...dirty, ...keys.filter((key) => !entry.rows.has(key))]);
400
+ const values = [...named].map((key) => [key, entry.built.live.get(key)]);
401
+ const done = inUnits(values, ([key, value]) => {
402
+ if (value !== undefined)
403
+ kept(entry, key, documentOf(key, value, shape, entry.bodies));
404
+ else {
405
+ entry.rows.delete(key);
406
+ entry.bodies.delete(key);
407
+ }
408
+ return weight(value);
409
+ });
410
+ return after(done, () => keys);
411
+ }
412
+ function current(entry, keys, coverage) {
413
+ const found = keys.map((key) => entry.rows.get(key));
414
+ return {
415
+ result: entry.plan.shape.kind === 'groups'
416
+ ? { groups: found, coverage }
417
+ : { documents: found, coverage },
418
+ keys,
419
+ };
420
+ }
421
+ function delivered(entry, next) {
422
+ const previous = entry.result;
423
+ entry.result = next;
424
+ const message = previous === undefined ? null : changedFrom(previous, next);
425
+ if (message !== null)
426
+ for (const listener of entry.ready)
427
+ listener(message);
428
+ for (const listener of entry.fresh) {
429
+ entry.ready.add(listener);
430
+ listener(next);
431
+ }
432
+ entry.fresh.clear();
433
+ }
434
+ function run(entry) {
435
+ while (entry.listeners.size > 0 &&
436
+ (entry.whole || entry.dirty.size > 0 || entry.stale || entry.fresh.size > 0)) {
437
+ entry.stale = false;
438
+ const cover = entry.cover();
439
+ if ('pending' in cover) {
440
+ entry.whole = true;
441
+ entry.dirty.clear();
442
+ entry.rows.clear();
443
+ entry.bodies.clear();
444
+ delivered(entry, { result: cover, keys: [] });
445
+ break;
446
+ }
447
+ const whole = entry.whole ||
448
+ entry.plan.shape.kind === 'groups' ||
449
+ entry.result === undefined ||
450
+ 'pending' in entry.result.result;
451
+ const dirty = entry.dirty;
452
+ entry.dirty = new Set();
453
+ entry.whole = false;
454
+ const keys = whole ? rebuilt(entry) : refreshed(entry, dirty);
455
+ if (keys instanceof Promise) {
456
+ void keys
457
+ .then((found) => {
458
+ if (entry.listeners.size > 0)
459
+ delivered(entry, current(entry, found, cover.coverage));
460
+ run(entry);
461
+ })
462
+ .catch(() => {
463
+ entry.running = false;
464
+ });
465
+ return;
466
+ }
467
+ delivered(entry, current(entry, keys, cover.coverage));
468
+ }
469
+ entry.running = false;
470
+ }
471
+ function wake(entry) {
472
+ if (entry.subscription === null || entry.running)
473
+ return;
474
+ entry.running = true;
475
+ try {
476
+ run(entry);
477
+ }
478
+ catch (error) {
479
+ entry.running = false;
480
+ throw error;
481
+ }
482
+ }
483
+ function recover() {
484
+ for (const entry of shared.values()) {
485
+ if (entry.result === undefined || entry.subscription === null)
486
+ continue;
487
+ const cover = entry.cover();
488
+ const before = entry.result.result;
489
+ const held = 'pending' in cover
490
+ ? deepEquals(before, cover)
491
+ : 'coverage' in before && deepEquals(before.coverage, cover.coverage);
492
+ if (held)
493
+ continue;
494
+ entry.stale = true;
495
+ wake(entry);
496
+ }
344
497
  }
345
498
  return {
346
499
  apply(set) {
@@ -351,38 +504,52 @@ export function createReadSide() {
351
504
  for (const row of change.after)
352
505
  next.set(row.key, row);
353
506
  }
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;
507
+ if (next.size > 0) {
508
+ sync.begin();
509
+ for (const [key, row] of next) {
510
+ if (row === null) {
511
+ if (rows.has(key))
512
+ sync.write({ type: 'delete', key });
513
+ continue;
514
+ }
515
+ sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
362
516
  }
363
- sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
517
+ sync.commit();
364
518
  }
365
- sync.commit();
519
+ recover();
366
520
  },
367
- async query(plan) {
521
+ recover,
522
+ async query(plan, at) {
368
523
  const { live: collection, dispose } = built(rows, plan);
369
524
  try {
370
525
  await collection.toArrayWhenReady();
371
- return keyedOf(plan, [...collection.entries()]).result;
526
+ const entries = [...collection.entries()];
527
+ const held = at();
528
+ const found = await rowsOfPlan(plan, entries, null, structuredClone);
529
+ return { rows: found.rows, at: held };
372
530
  }
373
531
  finally {
374
532
  dispose();
375
533
  }
376
534
  },
377
- watch(plan, identity, deliver) {
535
+ watch(plan, identity, cover, deliverTo) {
378
536
  let entry = shared.get(identity);
379
537
  if (entry === undefined) {
380
538
  const created = {
381
539
  plan,
382
540
  built: built(rows, plan),
541
+ cover,
383
542
  result: undefined,
543
+ rows: new Map(),
544
+ bodies: new Map(),
384
545
  listeners: new Set(),
546
+ fresh: new Set(),
547
+ ready: new Set(),
385
548
  subscription: null,
549
+ whole: true,
550
+ dirty: new Set(),
551
+ stale: false,
552
+ running: false,
386
553
  };
387
554
  created.built.live.on('status:change', ({ status }) => {
388
555
  if (status === 'cleaned-up' && created.listeners.size === 0)
@@ -392,29 +559,40 @@ export function createReadSide() {
392
559
  entry = created;
393
560
  }
394
561
  const observed = entry;
395
- observed.listeners.add(deliver);
562
+ observed.listeners.add(deliverTo);
396
563
  observed.built.live.onFirstReady(() => {
397
- if (!observed.listeners.has(deliver))
564
+ if (!observed.listeners.has(deliverTo))
398
565
  return;
566
+ observed.fresh.add(deliverTo);
399
567
  if (observed.subscription === null) {
400
- observed.result = keyedOf(observed.plan, [...observed.built.live.entries()]);
401
- observed.subscription = observed.built.live.subscribeChanges(() => {
402
- publish(observed);
568
+ observed.whole = true;
569
+ observed.subscription = observed.built.live.subscribeChanges((changes) => {
570
+ if (observed.plan.shape.kind === 'groups')
571
+ observed.whole = true;
572
+ else
573
+ for (const { key } of changes)
574
+ observed.dirty.add(key);
575
+ wake(observed);
403
576
  }, { includeInitialState: false });
404
577
  }
405
- deliver(observed.result);
578
+ wake(observed);
406
579
  });
407
580
  return () => {
408
- if (!observed.listeners.delete(deliver) || observed.listeners.size > 0)
581
+ observed.fresh.delete(deliverTo);
582
+ observed.ready.delete(deliverTo);
583
+ if (!observed.listeners.delete(deliverTo) || observed.listeners.size > 0)
409
584
  return;
410
585
  observed.subscription?.unsubscribe();
411
586
  observed.subscription = null;
587
+ observed.result = undefined;
588
+ observed.rows.clear();
589
+ observed.bodies.clear();
590
+ observed.dirty.clear();
591
+ observed.whole = true;
412
592
  };
413
593
  },
414
- documents() {
415
- return rows.toArray
416
- .filter((row) => row.kind === 'document' && row.type !== null)
417
- .map(searched);
594
+ room(room) {
595
+ return rows.toArray.flatMap((row) => row.kind !== 'document' || row.type === null || row.room !== room ? [] : [row.path]);
418
596
  },
419
597
  document(room, path) {
420
598
  const row = rows.get(documentKey(room, path));
@@ -423,6 +601,8 @@ export function createReadSide() {
423
601
  close() {
424
602
  for (const entry of shared.values()) {
425
603
  entry.listeners.clear();
604
+ entry.fresh.clear();
605
+ entry.ready.clear();
426
606
  entry.subscription?.unsubscribe();
427
607
  entry.built.dispose();
428
608
  }
@@ -431,3 +611,32 @@ export function createReadSide() {
431
611
  },
432
612
  };
433
613
  }
614
+ function changedFrom(previous, next) {
615
+ const before = previous.result;
616
+ const after = next.result;
617
+ if ('pending' in before || 'pending' in after)
618
+ return deepEquals(before, after) ? null : next;
619
+ const earlier = 'groups' in before ? before.groups : before.documents;
620
+ const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
621
+ const later = 'groups' in after ? after.groups : after.documents;
622
+ const changed = [];
623
+ next.keys.forEach((key, index) => {
624
+ if (held.get(key) !== later[index])
625
+ changed.push([key, later[index]]);
626
+ });
627
+ const present = new Set(next.keys);
628
+ const removed = previous.keys.filter((key) => !present.has(key));
629
+ const moved = reordered(previous.keys, next.keys, present);
630
+ const covered = !deepEquals(before.coverage, after.coverage);
631
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered)
632
+ return null;
633
+ return {
634
+ changed,
635
+ removed,
636
+ order: moved ? next.keys : null,
637
+ ...(covered ? { coverage: after.coverage } : {}),
638
+ };
639
+ }
640
+ function rowAt(rows, index) {
641
+ return 'groups' in rows ? rows.groups[index] : rows.documents[index];
642
+ }
@@ -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,11 @@
1
+ import type { Adapter } from './adapter.js';
2
+ import type { Reads } from './bootstrap.js';
3
+ export type DoorReads = Reads & {
4
+ carried(locator: string): number;
5
+ finished(locator: string, turn: number): void;
6
+ opened(): void;
7
+ applied(): void;
8
+ lost(): void;
9
+ };
10
+ export declare function doorReads(adapter: Adapter, declaration: (locator: string) => unknown, generation: () => number): DoorReads;
11
+ //# sourceMappingURL=reads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reads.d.ts","sourceRoot":"","sources":["../src/reads.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAOR,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG;IAC9B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9C,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;IAChB,IAAI,IAAI,IAAI,CAAC;CACd,CAAC;AAiCF,wBAAgB,SAAS,CACvB,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,EACzC,UAAU,EAAE,MAAM,MAAM,GACvB,SAAS,CA0FX"}