@agentshouse/mdruntime 0.1.0-cfb2ae0107054 → 0.1.0-cfcbb238a5fa0

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 (124) hide show
  1. package/AGENTS.md +262 -262
  2. package/dist/acquisition.d.ts +14 -0
  3. package/dist/acquisition.d.ts.map +1 -0
  4. package/dist/acquisition.js +24 -0
  5. package/dist/adapter.d.ts +14 -7
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/bootstrap.d.ts +2 -1
  8. package/dist/bootstrap.d.ts.map +1 -1
  9. package/dist/bootstrap.js +4 -0
  10. package/dist/change-set.d.ts +2 -0
  11. package/dist/change-set.d.ts.map +1 -1
  12. package/dist/change-set.js +36 -10
  13. package/dist/chat/kit.d.ts +2 -1
  14. package/dist/chat/kit.d.ts.map +1 -1
  15. package/dist/chat/kit.js +3 -3
  16. package/dist/chat/list.d.ts +2 -1
  17. package/dist/chat/list.d.ts.map +1 -1
  18. package/dist/chat/list.js +4 -3
  19. package/dist/chat/message.d.ts.map +1 -1
  20. package/dist/chat/message.js +2 -2
  21. package/dist/chat/messages.d.ts +1 -0
  22. package/dist/chat/messages.d.ts.map +1 -1
  23. package/dist/chat/messages.js +4 -0
  24. package/dist/chat/provider.d.ts +1 -1
  25. package/dist/chat/provider.d.ts.map +1 -1
  26. package/dist/chat/provider.js +26 -17
  27. package/dist/chat/ready.d.ts +1 -0
  28. package/dist/chat/ready.d.ts.map +1 -1
  29. package/dist/chat/ready.js +2 -2
  30. package/dist/client.d.ts +10 -6
  31. package/dist/client.d.ts.map +1 -1
  32. package/dist/client.js +11 -2
  33. package/dist/collection.d.ts +17 -3
  34. package/dist/collection.d.ts.map +1 -1
  35. package/dist/collection.js +230 -19
  36. package/dist/connection.d.ts +23 -7
  37. package/dist/connection.d.ts.map +1 -1
  38. package/dist/connection.js +1065 -192
  39. package/dist/conversation.d.ts +11 -5
  40. package/dist/conversation.d.ts.map +1 -1
  41. package/dist/conversation.js +241 -100
  42. package/dist/durability.d.ts +7 -1
  43. package/dist/durability.d.ts.map +1 -1
  44. package/dist/entry.d.ts +2 -2
  45. package/dist/entry.d.ts.map +1 -1
  46. package/dist/entry.js +3 -3
  47. package/dist/host.d.ts +16 -2
  48. package/dist/host.d.ts.map +1 -1
  49. package/dist/host.js +82 -8
  50. package/dist/identity.d.ts +2 -0
  51. package/dist/identity.d.ts.map +1 -1
  52. package/dist/identity.js +6 -0
  53. package/dist/index.d.ts +8 -5
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +2 -0
  56. package/dist/memory.d.ts +7 -2
  57. package/dist/memory.d.ts.map +1 -1
  58. package/dist/memory.js +20 -7
  59. package/dist/model.d.ts +4 -2
  60. package/dist/model.d.ts.map +1 -1
  61. package/dist/model.js +10 -1
  62. package/dist/observation.d.ts +5 -2
  63. package/dist/observation.d.ts.map +1 -1
  64. package/dist/observation.js +22 -11
  65. package/dist/overlay.d.ts +2 -0
  66. package/dist/overlay.d.ts.map +1 -1
  67. package/dist/overlay.js +58 -55
  68. package/dist/query.d.ts +35 -3
  69. package/dist/query.d.ts.map +1 -1
  70. package/dist/query.js +8 -2
  71. package/dist/reach.d.ts +6 -3
  72. package/dist/reach.d.ts.map +1 -1
  73. package/dist/reach.js +16 -4
  74. package/dist/react.d.ts +7 -4
  75. package/dist/react.d.ts.map +1 -1
  76. package/dist/react.js +6 -4
  77. package/dist/read-side.d.ts +13 -6
  78. package/dist/read-side.d.ts.map +1 -1
  79. package/dist/read-side.js +285 -83
  80. package/dist/readiness.d.ts +53 -0
  81. package/dist/readiness.d.ts.map +1 -0
  82. package/dist/readiness.js +33 -0
  83. package/dist/reads.d.ts +11 -0
  84. package/dist/reads.d.ts.map +1 -0
  85. package/dist/reads.js +98 -0
  86. package/dist/recovery.d.ts +25 -0
  87. package/dist/recovery.d.ts.map +1 -0
  88. package/dist/recovery.js +197 -0
  89. package/dist/refresh.d.ts +4 -4
  90. package/dist/refresh.d.ts.map +1 -1
  91. package/dist/refresh.js +17 -10
  92. package/dist/replica.d.ts +1 -1
  93. package/dist/replica.d.ts.map +1 -1
  94. package/dist/replica.js +57 -20
  95. package/dist/room-draft.d.ts +13 -1
  96. package/dist/room-draft.d.ts.map +1 -1
  97. package/dist/room-draft.js +2 -2
  98. package/dist/rows.d.ts +5 -13
  99. package/dist/rows.d.ts.map +1 -1
  100. package/dist/rows.js +5 -13
  101. package/dist/save.d.ts +5 -0
  102. package/dist/save.d.ts.map +1 -1
  103. package/dist/save.js +36 -13
  104. package/dist/search.d.ts +36 -6
  105. package/dist/search.d.ts.map +1 -1
  106. package/dist/search.js +132 -46
  107. package/dist/seed.d.ts +16 -0
  108. package/dist/seed.d.ts.map +1 -0
  109. package/dist/seed.js +61 -0
  110. package/dist/shared.d.ts +11 -0
  111. package/dist/shared.d.ts.map +1 -0
  112. package/dist/shared.js +368 -0
  113. package/dist/status.d.ts +4 -0
  114. package/dist/status.d.ts.map +1 -1
  115. package/dist/store.d.ts +21 -5
  116. package/dist/store.d.ts.map +1 -1
  117. package/dist/store.js +157 -53
  118. package/dist/turn.d.ts +7 -0
  119. package/dist/turn.d.ts.map +1 -0
  120. package/dist/turn.js +45 -0
  121. package/dist/wire.d.ts +136 -32
  122. package/dist/wire.d.ts.map +1 -1
  123. package/dist/wire.js +48 -31
  124. package/package.json +2 -2
package/dist/react.js CHANGED
@@ -47,10 +47,10 @@ export async function saveRoomDrafts(client, room, roomDrafts) {
47
47
  }
48
48
  export function useSave(client) {
49
49
  const [state, setState] = useState(IDLE);
50
- const save = useCallback(async (room, roomDrafts) => {
50
+ const settled = useCallback(async (saving) => {
51
51
  setState({ busy: true, outcome: null, failure: null });
52
52
  try {
53
- const outcome = await saveRoomDrafts(client, room, roomDrafts);
53
+ const outcome = await saving();
54
54
  setState({ busy: false, outcome, failure: null });
55
55
  return outcome;
56
56
  }
@@ -59,6 +59,8 @@ export function useSave(client) {
59
59
  setState({ busy: false, outcome: null, failure });
60
60
  throw failure;
61
61
  }
62
- }, [client]);
63
- return { ...state, save };
62
+ }, []);
63
+ const save = useCallback((room, roomDrafts) => settled(() => saveRoomDrafts(client, room, roomDrafts)), [client, settled]);
64
+ const saveWritten = useCallback((room, roomDrafts) => settled(() => client.save(room, roomDrafts)), [client, settled]);
65
+ return { ...state, save, saveWritten };
64
66
  }
@@ -1,18 +1,25 @@
1
1
  import type { ChangeSet } from './change-set.js';
2
- import { type KeyedResult, type QueryChange } from './observation.js';
3
- import type { Plan, QueryResult } from './query.js';
2
+ import { type KeyedResult, type QueryMessage } from './observation.js';
3
+ import type { PendingResult, Plan, QueryCoverage, QueryRows } from './query.js';
4
4
  import type { SearchedDocument } from './search.js';
5
+ export type Cover = {
6
+ coverage: QueryCoverage;
7
+ } | PendingResult;
5
8
  export type ReadSide = {
6
9
  apply(set: ChangeSet): void;
7
- query(plan: Plan): Promise<QueryResult>;
8
- watch(plan: Plan, identity: string, deliver: (message: KeyedResult | QueryChange) => void): () => void;
9
- documents(): readonly SearchedDocument[];
10
+ recover(): void;
11
+ query<T>(plan: Plan, at: () => T): Promise<{
12
+ rows: QueryRows;
13
+ at: T;
14
+ }>;
15
+ watch(plan: Plan, identity: string, cover: () => Cover, deliver: (message: QueryMessage) => void): () => void;
16
+ room(room: string): readonly string[];
10
17
  document(room: string, path: string): SearchedDocument | null;
11
18
  close(): void;
12
19
  };
13
20
  export declare function reconciled(previous: KeyedResult, next: KeyedResult): {
14
21
  keyed: KeyedResult;
15
- change: QueryChange;
22
+ message: QueryMessage;
16
23
  } | null;
17
24
  export declare function createReadSide(): ReadSide;
18
25
  //# sourceMappingURL=read-side.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"read-side.d.ts","sourceRoot":"","sources":["../src/read-side.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAU,KAAK,WAAW,EAAE,KAAK,WAAW,EAAe,MAAM,kBAAkB,CAAC;AAC3F,OAAO,KAAK,EAOV,IAAI,EAIJ,WAAW,EAIZ,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAuDpD,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACxC,KAAK,CACH,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,KAAK,IAAI,GACpD,MAAM,IAAI,CAAC;IACd,SAAS,IAAI,SAAS,gBAAgB,EAAE,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAC9D,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAuVF,wBAAgB,UAAU,CACxB,QAAQ,EAAE,WAAW,EACrB,IAAI,EAAE,WAAW,GAChB;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAAG,IAAI,CA4BpD;AAED,wBAAgB,cAAc,IAAI,QAAQ,CA4HzC"}
1
+ {"version":3,"file":"read-side.d.ts","sourceRoot":"","sources":["../src/read-side.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAe,MAAM,kBAAkB,CAAC;AACpF,OAAO,KAAK,EAOV,aAAa,EACb,IAAI,EAEJ,aAAa,EAGb,SAAS,EAIV,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA4CpD,MAAM,MAAM,KAAK,GAAG;IAAE,QAAQ,EAAE,aAAa,CAAA;CAAE,GAAG,aAAa,CAAC;AAuBhE,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,OAAO,IAAI,IAAI,CAAC;IAChB,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,EAAE,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IACvE,KAAK,CACH,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,KAAK,EAClB,OAAO,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,GACvC,MAAM,IAAI,CAAC;IACd,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAC9D,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAwWF,wBAAgB,UAAU,CACxB,QAAQ,EAAE,WAAW,EACrB,IAAI,EAAE,WAAW,GAChB;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAAG,IAAI,CAuCtD;AAgBD,wBAAgB,cAAc,IAAI,QAAQ,CA+QzC"}
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,18 +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,
124
121
  binary: d.binary,
125
122
  ...(shape.fields === null ? {} : { fields: d.fields }),
126
- ...(shape.sections === 'all' ? { sections: b.sections } : {}),
127
- ...(Array.isArray(shape.sections) ? named('section', shape.sections, b.sections) : {}),
128
- ...(shape.preamble ? { preamble: b.preamble } : {}),
129
- ...(shape.markdown ? { markdown: b.markdown } : {}),
123
+ ...(bodied(shape) ? { text: d.text } : {}),
130
124
  ...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
131
125
  ...(shape.change ? { change: d.change } : {}),
126
+ ...(shape.fileId ? { fileId: d.fileId } : {}),
132
127
  }));
133
128
  }
134
129
  function backlinked(q, rows, document, reach) {
@@ -172,13 +167,22 @@ function built(rows, plan) {
172
167
  },
173
168
  };
174
169
  }
175
- function present(entries) {
176
- return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
177
- }
178
- function part(row, prefix, names, whole) {
170
+ function sectionsOf(sections, names) {
179
171
  if (names === 'all')
180
- return whole;
181
- 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;
182
186
  }
183
187
  function masked(pointers) {
184
188
  const root = { whole: false, members: new Map() };
@@ -229,15 +233,17 @@ function cut(value, mask) {
229
233
  function selected(fields, pointers) {
230
234
  return (cut(fields, masked(pointers)) ?? {});
231
235
  }
232
- function documentOf(row, shape) {
236
+ function documentOf(key, row, shape, bodies) {
233
237
  if (row.type === null) {
234
238
  const binary = row.binary;
235
239
  return {
236
240
  room: row.room,
237
241
  path: row.path,
238
242
  ...(binary === null ? {} : { binaryFile: binary }),
243
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
239
244
  };
240
245
  }
246
+ const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
241
247
  return {
242
248
  room: row.room,
243
249
  path: row.path,
@@ -249,45 +255,54 @@ function documentOf(row, shape) {
249
255
  ? row.fields
250
256
  : selected(row.fields, shape.fields),
251
257
  }),
252
- ...(shape.sections === null
258
+ ...(shape.sections === null || body === null
253
259
  ? {}
254
- : {
255
- sections: part(row, 'section', shape.sections, row.sections),
256
- }),
257
- ...(shape.preamble ? { preamble: row.preamble } : {}),
258
- ...(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 } : {}),
259
263
  ...(shape.backlinks
260
264
  ? {
261
265
  backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
262
266
  }
263
267
  : {}),
264
268
  ...(shape.change ? { change: row.change } : {}),
269
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
265
270
  };
266
271
  }
267
- 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) {
268
276
  const shape = plan.shape;
269
277
  if (shape.kind === 'documents') {
270
- return {
271
- result: structuredClone({ documents: entries.map(([, row]) => documentOf(row, shape)) }),
272
- keys: entries.map(([key]) => key),
273
- };
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) }));
274
283
  }
275
284
  const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
276
- return {
277
- result: structuredClone({
278
- groups: bounded.map(([, row]) => ({
279
- key: shape.groupBy.map((_, index) => row[`key${index}`]),
280
- count: row.count,
281
- })),
282
- }),
283
- keys: bounded.map(([key]) => key),
284
- };
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) }));
285
293
  }
286
294
  export function reconciled(previous, next) {
287
- const before = rowsOf(previous.result);
288
- 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]]));
289
304
  const changed = [];
290
- const rows = rowsOf(next.result).map((row, index) => {
305
+ const rows = ('groups' in after ? after.groups : after.documents).map((row, index) => {
291
306
  const key = next.keys[index];
292
307
  const kept = held.get(key);
293
308
  if (kept !== undefined && deepEquals(kept, row))
@@ -296,22 +311,37 @@ export function reconciled(previous, next) {
296
311
  return row;
297
312
  });
298
313
  const present = new Set(next.keys);
299
- const survivors = previous.keys.filter((key) => present.has(key));
300
314
  const removed = previous.keys.filter((key) => !present.has(key));
301
- const moved = survivors.length !== next.keys.length ||
302
- survivors.some((key, index) => key !== next.keys[index]);
303
- 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)
304
318
  return null;
305
319
  return {
306
320
  keyed: {
307
- result: 'groups' in next.result
308
- ? { groups: rows }
309
- : { documents: rows },
321
+ result: 'groups' in after
322
+ ? { groups: rows, coverage: after.coverage }
323
+ : { documents: rows, coverage: after.coverage },
310
324
  keys: next.keys,
311
325
  },
312
- 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
+ },
313
332
  };
314
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
+ }
315
345
  export function createReadSide() {
316
346
  configureIndexDevMode({ enabled: false });
317
347
  let sync;
@@ -331,23 +361,139 @@ export function createReadSide() {
331
361
  });
332
362
  const shared = new Map();
333
363
  function searched(row) {
334
- 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;
335
367
  return {
336
368
  room: row.room,
337
369
  path: row.path,
338
370
  type: row.type,
339
371
  fields: row.fields,
340
- preamble: body.preamble,
341
- sections: body.sections,
372
+ preamble: read.document.preamble,
373
+ sections: read.document.sections,
374
+ };
375
+ }
376
+ function kept(entry, key, row) {
377
+ const held = entry.rows.get(key);
378
+ if (held !== undefined && deepEquals(held, row))
379
+ return;
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,
342
419
  };
343
420
  }
344
- function publish(entry) {
345
- const next = reconciled(entry.result, keyedOf(entry.plan, [...entry.built.live.entries()]));
346
- if (next === null)
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)
347
473
  return;
348
- entry.result = next.keyed;
349
- for (const listener of entry.listeners)
350
- listener(next.change);
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
+ }
351
497
  }
352
498
  return {
353
499
  apply(set) {
@@ -358,38 +504,52 @@ export function createReadSide() {
358
504
  for (const row of change.after)
359
505
  next.set(row.key, row);
360
506
  }
361
- if (next.size === 0)
362
- return;
363
- sync.begin();
364
- for (const [key, row] of next) {
365
- if (row === null) {
366
- if (rows.has(key))
367
- sync.write({ type: 'delete', key });
368
- 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 });
369
516
  }
370
- sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
517
+ sync.commit();
371
518
  }
372
- sync.commit();
519
+ recover();
373
520
  },
374
- async query(plan) {
521
+ recover,
522
+ async query(plan, at) {
375
523
  const { live: collection, dispose } = built(rows, plan);
376
524
  try {
377
525
  await collection.toArrayWhenReady();
378
- 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 };
379
530
  }
380
531
  finally {
381
532
  dispose();
382
533
  }
383
534
  },
384
- watch(plan, identity, deliver) {
535
+ watch(plan, identity, cover, deliverTo) {
385
536
  let entry = shared.get(identity);
386
537
  if (entry === undefined) {
387
538
  const created = {
388
539
  plan,
389
540
  built: built(rows, plan),
541
+ cover,
390
542
  result: undefined,
543
+ rows: new Map(),
544
+ bodies: new Map(),
391
545
  listeners: new Set(),
546
+ fresh: new Set(),
547
+ ready: new Set(),
392
548
  subscription: null,
549
+ whole: true,
550
+ dirty: new Set(),
551
+ stale: false,
552
+ running: false,
393
553
  };
394
554
  created.built.live.on('status:change', ({ status }) => {
395
555
  if (status === 'cleaned-up' && created.listeners.size === 0)
@@ -399,29 +559,40 @@ export function createReadSide() {
399
559
  entry = created;
400
560
  }
401
561
  const observed = entry;
402
- observed.listeners.add(deliver);
562
+ observed.listeners.add(deliverTo);
403
563
  observed.built.live.onFirstReady(() => {
404
- if (!observed.listeners.has(deliver))
564
+ if (!observed.listeners.has(deliverTo))
405
565
  return;
566
+ observed.fresh.add(deliverTo);
406
567
  if (observed.subscription === null) {
407
- observed.result = keyedOf(observed.plan, [...observed.built.live.entries()]);
408
- observed.subscription = observed.built.live.subscribeChanges(() => {
409
- 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);
410
576
  }, { includeInitialState: false });
411
577
  }
412
- deliver(observed.result);
578
+ wake(observed);
413
579
  });
414
580
  return () => {
415
- 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)
416
584
  return;
417
585
  observed.subscription?.unsubscribe();
418
586
  observed.subscription = null;
587
+ observed.result = undefined;
588
+ observed.rows.clear();
589
+ observed.bodies.clear();
590
+ observed.dirty.clear();
591
+ observed.whole = true;
419
592
  };
420
593
  },
421
- documents() {
422
- return rows.toArray
423
- .filter((row) => row.kind === 'document' && row.type !== null)
424
- .map(searched);
594
+ room(room) {
595
+ return rows.toArray.flatMap((row) => row.kind !== 'document' || row.type === null || row.room !== room ? [] : [row.path]);
425
596
  },
426
597
  document(room, path) {
427
598
  const row = rows.get(documentKey(room, path));
@@ -430,6 +601,8 @@ export function createReadSide() {
430
601
  close() {
431
602
  for (const entry of shared.values()) {
432
603
  entry.listeners.clear();
604
+ entry.fresh.clear();
605
+ entry.ready.clear();
433
606
  entry.subscription?.unsubscribe();
434
607
  entry.built.dispose();
435
608
  }
@@ -438,3 +611,32 @@ export function createReadSide() {
438
611
  },
439
612
  };
440
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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"}