@agentshouse/mdruntime 0.1.0-c6e4c19c13638 → 0.1.0-c6fd2cbbff187

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 (207) hide show
  1. package/AGENTS.md +277 -219
  2. package/README.md +20 -10
  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 +94 -20
  7. package/dist/adapter.d.ts.map +1 -1
  8. package/dist/attached-model.d.ts.map +1 -1
  9. package/dist/attached-model.js +15 -2
  10. package/dist/attempt.d.ts +16 -0
  11. package/dist/attempt.d.ts.map +1 -0
  12. package/dist/attempt.js +70 -0
  13. package/dist/binary.d.ts +23 -0
  14. package/dist/binary.d.ts.map +1 -0
  15. package/dist/binary.js +8 -0
  16. package/dist/bootstrap.d.ts +18 -5
  17. package/dist/bootstrap.d.ts.map +1 -1
  18. package/dist/bootstrap.js +113 -37
  19. package/dist/change-set.d.ts +2 -0
  20. package/dist/change-set.d.ts.map +1 -1
  21. package/dist/change-set.js +49 -16
  22. package/dist/change.d.ts +15 -0
  23. package/dist/change.d.ts.map +1 -1
  24. package/dist/change.js +26 -1
  25. package/dist/chat/acts.d.ts +33 -0
  26. package/dist/chat/acts.d.ts.map +1 -0
  27. package/dist/chat/acts.js +36 -0
  28. package/dist/chat/context.d.ts +3 -0
  29. package/dist/chat/context.d.ts.map +1 -0
  30. package/dist/chat/context.js +10 -0
  31. package/dist/chat/earlier.d.ts +10 -0
  32. package/dist/chat/earlier.d.ts.map +1 -0
  33. package/dist/chat/earlier.js +54 -0
  34. package/dist/chat/input.d.ts +9 -0
  35. package/dist/chat/input.d.ts.map +1 -0
  36. package/dist/chat/input.js +99 -0
  37. package/dist/chat/kit.d.ts +61 -0
  38. package/dist/chat/kit.d.ts.map +1 -0
  39. package/dist/chat/kit.js +123 -0
  40. package/dist/chat/lasting.d.ts +6 -0
  41. package/dist/chat/lasting.d.ts.map +1 -0
  42. package/dist/chat/lasting.js +19 -0
  43. package/dist/chat/list.d.ts +27 -0
  44. package/dist/chat/list.d.ts.map +1 -0
  45. package/dist/chat/list.js +99 -0
  46. package/dist/chat/markdown.d.ts +20 -0
  47. package/dist/chat/markdown.d.ts.map +1 -0
  48. package/dist/chat/markdown.js +188 -0
  49. package/dist/chat/message.d.ts +6 -0
  50. package/dist/chat/message.d.ts.map +1 -0
  51. package/dist/chat/message.js +192 -0
  52. package/dist/chat/messages.d.ts +22 -0
  53. package/dist/chat/messages.d.ts.map +1 -0
  54. package/dist/chat/messages.js +155 -0
  55. package/dist/chat/name.d.ts +6 -0
  56. package/dist/chat/name.d.ts.map +1 -0
  57. package/dist/chat/name.js +37 -0
  58. package/dist/chat/opened.d.ts +11 -0
  59. package/dist/chat/opened.d.ts.map +1 -0
  60. package/dist/chat/opened.js +17 -0
  61. package/dist/chat/overlay.d.ts +31 -0
  62. package/dist/chat/overlay.d.ts.map +1 -0
  63. package/dist/chat/overlay.js +97 -0
  64. package/dist/chat/parts.d.ts +57 -0
  65. package/dist/chat/parts.d.ts.map +1 -0
  66. package/dist/chat/parts.js +119 -0
  67. package/dist/chat/process.d.ts +20 -0
  68. package/dist/chat/process.d.ts.map +1 -0
  69. package/dist/chat/process.js +32 -0
  70. package/dist/chat/provider.d.ts +63 -0
  71. package/dist/chat/provider.d.ts.map +1 -0
  72. package/dist/chat/provider.js +635 -0
  73. package/dist/chat/question.d.ts +9 -0
  74. package/dist/chat/question.d.ts.map +1 -0
  75. package/dist/chat/question.js +69 -0
  76. package/dist/chat/questions.d.ts +31 -0
  77. package/dist/chat/questions.d.ts.map +1 -0
  78. package/dist/chat/questions.js +111 -0
  79. package/dist/chat/read.d.ts +5 -0
  80. package/dist/chat/read.d.ts.map +1 -0
  81. package/dist/chat/read.js +13 -0
  82. package/dist/chat/ready.d.ts +34 -0
  83. package/dist/chat/ready.d.ts.map +1 -0
  84. package/dist/chat/ready.js +91 -0
  85. package/dist/chat/style.d.ts +4 -0
  86. package/dist/chat/style.d.ts.map +1 -0
  87. package/dist/chat/style.js +302 -0
  88. package/dist/chat.d.ts +14 -0
  89. package/dist/chat.d.ts.map +1 -0
  90. package/dist/chat.js +8 -0
  91. package/dist/client.d.ts +27 -12
  92. package/dist/client.d.ts.map +1 -1
  93. package/dist/client.js +77 -16
  94. package/dist/collection.d.ts +21 -12
  95. package/dist/collection.d.ts.map +1 -1
  96. package/dist/collection.js +271 -23
  97. package/dist/connection.d.ts +46 -15
  98. package/dist/connection.d.ts.map +1 -1
  99. package/dist/connection.js +1946 -414
  100. package/dist/conversation.d.ts +30 -10
  101. package/dist/conversation.d.ts.map +1 -1
  102. package/dist/conversation.js +485 -159
  103. package/dist/durability.d.ts +7 -1
  104. package/dist/durability.d.ts.map +1 -1
  105. package/dist/entry.d.ts +17 -0
  106. package/dist/entry.d.ts.map +1 -0
  107. package/dist/entry.js +156 -0
  108. package/dist/expectation.d.ts +21 -0
  109. package/dist/expectation.d.ts.map +1 -0
  110. package/dist/expectation.js +70 -0
  111. package/dist/failure.d.ts +13 -0
  112. package/dist/failure.d.ts.map +1 -0
  113. package/dist/failure.js +19 -0
  114. package/dist/fence.d.ts +6 -0
  115. package/dist/fence.d.ts.map +1 -0
  116. package/dist/fence.js +10 -0
  117. package/dist/host.d.ts +18 -2
  118. package/dist/host.d.ts.map +1 -1
  119. package/dist/host.js +101 -19
  120. package/dist/identity.d.ts +5 -0
  121. package/dist/identity.d.ts.map +1 -1
  122. package/dist/identity.js +19 -1
  123. package/dist/index.d.ts +16 -8
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +5 -0
  126. package/dist/memory.d.ts +12 -4
  127. package/dist/memory.d.ts.map +1 -1
  128. package/dist/memory.js +94 -25
  129. package/dist/model.d.ts +14 -4
  130. package/dist/model.d.ts.map +1 -1
  131. package/dist/model.js +12 -1
  132. package/dist/observation.d.ts +7 -2
  133. package/dist/observation.d.ts.map +1 -1
  134. package/dist/observation.js +29 -12
  135. package/dist/open.d.ts +2 -0
  136. package/dist/open.d.ts.map +1 -0
  137. package/dist/open.js +1 -0
  138. package/dist/outcome.d.ts +38 -3
  139. package/dist/outcome.d.ts.map +1 -1
  140. package/dist/outcome.js +45 -1
  141. package/dist/overlay.d.ts +16 -3
  142. package/dist/overlay.d.ts.map +1 -1
  143. package/dist/overlay.js +118 -77
  144. package/dist/query.d.ts +60 -4
  145. package/dist/query.d.ts.map +1 -1
  146. package/dist/query.js +41 -38
  147. package/dist/reach.d.ts +8 -3
  148. package/dist/reach.d.ts.map +1 -1
  149. package/dist/reach.js +63 -24
  150. package/dist/react.d.ts +31 -18
  151. package/dist/react.d.ts.map +1 -1
  152. package/dist/react.js +15 -11
  153. package/dist/read-side.d.ts +13 -6
  154. package/dist/read-side.d.ts.map +1 -1
  155. package/dist/read-side.js +428 -94
  156. package/dist/readiness.d.ts +53 -0
  157. package/dist/readiness.d.ts.map +1 -0
  158. package/dist/readiness.js +33 -0
  159. package/dist/reads.d.ts +11 -0
  160. package/dist/reads.d.ts.map +1 -0
  161. package/dist/reads.js +98 -0
  162. package/dist/recovery.d.ts +25 -0
  163. package/dist/recovery.d.ts.map +1 -0
  164. package/dist/recovery.js +197 -0
  165. package/dist/refresh.d.ts +13 -3
  166. package/dist/refresh.d.ts.map +1 -1
  167. package/dist/refresh.js +49 -38
  168. package/dist/replica.d.ts +13 -6
  169. package/dist/replica.d.ts.map +1 -1
  170. package/dist/replica.js +143 -70
  171. package/dist/room-draft.d.ts +38 -9
  172. package/dist/room-draft.d.ts.map +1 -1
  173. package/dist/room-draft.js +134 -25
  174. package/dist/rows.d.ts +10 -13
  175. package/dist/rows.d.ts.map +1 -1
  176. package/dist/rows.js +19 -14
  177. package/dist/save.d.ts +32 -1
  178. package/dist/save.d.ts.map +1 -1
  179. package/dist/save.js +392 -45
  180. package/dist/search.d.ts +52 -8
  181. package/dist/search.d.ts.map +1 -1
  182. package/dist/search.js +192 -47
  183. package/dist/seed.d.ts +16 -0
  184. package/dist/seed.d.ts.map +1 -0
  185. package/dist/seed.js +75 -0
  186. package/dist/shared.d.ts +11 -0
  187. package/dist/shared.d.ts.map +1 -0
  188. package/dist/shared.js +368 -0
  189. package/dist/status.d.ts +6 -2
  190. package/dist/status.d.ts.map +1 -1
  191. package/dist/status.js +0 -8
  192. package/dist/store.d.ts +89 -18
  193. package/dist/store.d.ts.map +1 -1
  194. package/dist/store.js +573 -200
  195. package/dist/turn.d.ts +7 -0
  196. package/dist/turn.d.ts.map +1 -0
  197. package/dist/turn.js +45 -0
  198. package/dist/windows.d.ts +19 -0
  199. package/dist/windows.d.ts.map +1 -0
  200. package/dist/windows.js +83 -0
  201. package/dist/wire.d.ts +149 -33
  202. package/dist/wire.d.ts.map +1 -1
  203. package/dist/wire.js +63 -31
  204. package/package.json +26 -14
  205. package/dist/attachment.d.ts +0 -3
  206. package/dist/attachment.d.ts.map +0 -1
  207. package/dist/attachment.js +0 -8
package/dist/read-side.js CHANGED
@@ -1,7 +1,8 @@
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
+ import { beyond, cursorOf, MAXIMUM_WINDOW, windowed } from './windows.js';
5
6
  function every(expressions) {
6
7
  const [first, second, ...rest] = expressions;
7
8
  return second === undefined ? first : and(first, second, ...rest);
@@ -59,10 +60,12 @@ function reached(reach, room, document) {
59
60
  ? []
60
61
  : [or(inArray(room, [...reach.rooms]), inArray(document, [...reach.documents]))];
61
62
  }
62
- function scoped(scope, row, rooms, reach) {
63
+ function scoped(scope, row, rooms, reach, paths = null, folder = null) {
63
64
  return every([
64
65
  eq(row.kind, 'document'),
65
66
  ...(rooms === null ? [] : [inArray(row.room, [...rooms])]),
67
+ ...(paths === null ? [] : [inArray(row.path, [...paths])]),
68
+ ...(folder === null ? [] : [eq(row.folder, folder)]),
66
69
  ...reached(reach, row.room, row.key),
67
70
  ...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
68
71
  ...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
@@ -90,7 +93,7 @@ function related(q, rows, scope, field, kept, reach) {
90
93
  }
91
94
  function filtered(q, rows, plan) {
92
95
  const from = q.from({ d: rows });
93
- let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach)), plan.root.where, 'd');
96
+ let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach, plan.paths, plan.folder)), plan.root.where, 'd');
94
97
  if (plan.hop !== null) {
95
98
  const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach);
96
99
  query = query.innerJoin({ h: hop }, ({ d, h }) => eq(d.key, h.key));
@@ -101,33 +104,59 @@ function filtered(q, rows, plan) {
101
104
  }
102
105
  return query;
103
106
  }
104
- function named(prefix, names, source) {
105
- return Object.fromEntries(names.map((name, index) => [`${prefix}${index}`, source[name]]));
107
+ function bodied(shape) {
108
+ return shape.sections !== null || shape.preamble || shape.markdown;
109
+ }
110
+ function orderingOf(options) {
111
+ const collated = options.stringSort === 'locale';
112
+ return {
113
+ direction: options.direction,
114
+ nulls: options.nulls ?? 'first',
115
+ stringSort: options.stringSort ?? 'locale',
116
+ locale: collated ? options.locale : undefined,
117
+ localeOptions: collated
118
+ ? options.localeOptions
119
+ : undefined,
120
+ };
121
+ }
122
+ function storedValue(key, row) {
123
+ return key.of === 'field' ? row.values[key.field] : row[key.of];
124
+ }
125
+ function documentOrder(shape) {
126
+ return shape.orderBy.map(({ options }) => orderingOf(options));
127
+ }
128
+ function documentCursor(shape, row) {
129
+ return cursorOf([
130
+ ...shape.orderBy.map((_, index) => row[`order${index}`]),
131
+ documentKey(row.room, row.path),
132
+ ]);
106
133
  }
107
134
  function documents(q, rows, plan, shape) {
108
135
  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));
136
+ const cursor = plan.after?.values ?? null;
137
+ if (cursor !== null) {
138
+ const orderings = documentOrder(shape);
139
+ query = query.fn.where(({ d }) => beyond([...shape.orderBy.map(({ key }) => storedValue(key, d)), d.key], cursor, orderings));
111
140
  }
112
141
  for (const { key, options } of shape.orderBy) {
113
142
  query = query.orderBy(({ d }) => keyed(key, d), options);
114
143
  }
115
- if (shape.orderBy.length === 0 && plan.limit !== null) {
144
+ if (shape.orderBy.length > 0 || plan.limit !== null || plan.after !== null) {
116
145
  query = query.orderBy(({ d }) => d.key, { stringSort: 'lexical' });
117
146
  }
118
147
  if (plan.limit !== null)
119
- query = query.limit(plan.limit);
120
- return query.select(({ d, b }) => ({
148
+ query = query.limit(plan.limit + 1);
149
+ return query.select(({ d }) => ({
121
150
  room: d.room,
122
151
  path: d.path,
123
152
  type: d.type,
153
+ binary: d.binary,
154
+ ...Object.fromEntries(shape.orderBy.map(({ key }, index) => [`order${index}`, keyed(key, d)])),
124
155
  ...(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 } : {}),
156
+ ...(bodied(shape) ? { text: d.text } : {}),
129
157
  ...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
130
158
  ...(shape.change ? { change: d.change } : {}),
159
+ ...(shape.fileId ? { fileId: d.fileId } : {}),
131
160
  }));
132
161
  }
133
162
  function backlinked(q, rows, document, reach) {
@@ -138,15 +167,13 @@ function backlinked(q, rows, document, reach) {
138
167
  .orderBy(({ r }) => r.field, { stringSort: 'lexical' })
139
168
  .select(({ r }) => ({ path: r.path, type: r.type, field: r.field }));
140
169
  }
141
- function groups(q, stage, shape, limit) {
170
+ function groups(q, stage, shape) {
142
171
  const keys = shape.groupBy.map((_, index) => `key${index}`);
143
172
  const from = q.from({ m: stage });
144
173
  let query = from.groupBy(({ m }) => keys.map((key) => m[key]));
145
174
  for (const { by, direction } of shape.orderBy) {
146
175
  query = query.orderBy(({ m }) => (by === 'count' ? count(m.key) : m[keys[by]]), direction);
147
176
  }
148
- if (limit !== null && shape.orderBy.length > 0)
149
- query = query.limit(limit);
150
177
  return query.select(({ m }) => ({
151
178
  ...Object.fromEntries(keys.map((key) => [key, m[key]])),
152
179
  count: count(m.key),
@@ -156,28 +183,49 @@ function built(rows, plan) {
156
183
  const shape = plan.shape;
157
184
  if (shape.kind === 'documents') {
158
185
  const collection = live((q) => documents(q, rows, plan, shape));
159
- return { live: collection, dispose: () => void collection.cleanup() };
186
+ const count = plan.total
187
+ ? live((q) => filtered(q, rows, plan).select(({ d }) => ({ key: d.key })))
188
+ : null;
189
+ return {
190
+ live: collection,
191
+ count,
192
+ dispose: () => {
193
+ void collection.cleanup();
194
+ if (count !== null)
195
+ void count.cleanup();
196
+ },
197
+ };
160
198
  }
161
199
  const stage = live((q) => filtered(q, rows, plan).select(({ d }) => ({
162
200
  key: d.key,
163
201
  ...Object.fromEntries(shape.groupBy.map((key, index) => [`key${index}`, keyed(key, d)])),
164
202
  })));
165
- const collection = live((q) => groups(q, stage, shape, plan.limit));
203
+ const collection = live((q) => groups(q, stage, shape));
166
204
  return {
167
205
  live: collection,
206
+ count: null,
168
207
  dispose: () => {
169
208
  void collection.cleanup();
170
209
  void stage.cleanup();
171
210
  },
172
211
  };
173
212
  }
174
- function present(entries) {
175
- return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
176
- }
177
- function part(row, prefix, names, whole) {
213
+ function sectionsOf(sections, names) {
178
214
  if (names === 'all')
179
- return whole;
180
- return present(names.map((name, index) => [name, row[`${prefix}${index}`]]));
215
+ return sections;
216
+ return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
217
+ }
218
+ function bodyOf(row, key, bodies) {
219
+ const text = row.text;
220
+ if (text === null)
221
+ return null;
222
+ const held = bodies?.get(key);
223
+ if (held !== undefined && held.text === text)
224
+ return held.document;
225
+ const read = parse(text, row.path);
226
+ const document = read.ok ? read.document : null;
227
+ bodies?.set(key, { text, document });
228
+ return document;
181
229
  }
182
230
  function masked(pointers) {
183
231
  const root = { whole: false, members: new Map() };
@@ -228,9 +276,17 @@ function cut(value, mask) {
228
276
  function selected(fields, pointers) {
229
277
  return (cut(fields, masked(pointers)) ?? {});
230
278
  }
231
- function documentOf(row, shape) {
232
- if (row.type === null)
233
- return { room: row.room, path: row.path };
279
+ function documentOf(key, row, shape, bodies) {
280
+ if (row.type === null) {
281
+ const binary = row.binary;
282
+ return {
283
+ room: row.room,
284
+ path: row.path,
285
+ ...(binary === null ? {} : { binaryFile: binary }),
286
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
287
+ };
288
+ }
289
+ const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
234
290
  return {
235
291
  room: row.room,
236
292
  path: row.path,
@@ -242,45 +298,94 @@ function documentOf(row, shape) {
242
298
  ? row.fields
243
299
  : selected(row.fields, shape.fields),
244
300
  }),
245
- ...(shape.sections === null
301
+ ...(shape.sections === null || body === null
246
302
  ? {}
247
- : {
248
- sections: part(row, 'section', shape.sections, row.sections),
249
- }),
250
- ...(shape.preamble ? { preamble: row.preamble } : {}),
251
- ...(shape.markdown ? { markdown: row.markdown } : {}),
303
+ : { sections: sectionsOf(body.sections, shape.sections) }),
304
+ ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
305
+ ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
252
306
  ...(shape.backlinks
253
307
  ? {
254
- backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
308
+ backlinks: row.backlinks
309
+ .slice(0, MAXIMUM_WINDOW)
310
+ .map(({ path, type, field }) => ({ path, type, field })),
255
311
  }
256
312
  : {}),
257
313
  ...(shape.change ? { change: row.change } : {}),
314
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
258
315
  };
259
316
  }
260
- function keyedOf(plan, entries) {
317
+ function weight(row) {
318
+ return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
319
+ }
320
+ function groupValues(shape, [key, row]) {
321
+ return [...shape.orderBy.map(({ by }) => (by === 'count' ? row.count : row[`key${by}`])), key];
322
+ }
323
+ function groupOrder(shape) {
324
+ return shape.orderBy.map(({ direction }) => ({
325
+ direction,
326
+ nulls: 'first',
327
+ stringSort: 'locale',
328
+ locale: undefined,
329
+ localeOptions: undefined,
330
+ }));
331
+ }
332
+ function windowOf(plan, entries, total) {
261
333
  const shape = plan.shape;
262
334
  if (shape.kind === 'documents') {
263
335
  return {
264
- result: structuredClone({ documents: entries.map(([, row]) => documentOf(row, shape)) }),
265
- keys: entries.map(([key]) => key),
336
+ ...windowed(entries, plan.limit, plan.after?.text, ([, row]) => documentCursor(shape, row)),
337
+ total,
266
338
  };
267
339
  }
268
- const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
340
+ const cursor = plan.after?.values ?? null;
341
+ const orderings = groupOrder(shape);
342
+ const following = cursor === null
343
+ ? entries
344
+ : entries.filter((entry) => beyond(groupValues(shape, entry), cursor, orderings));
345
+ return {
346
+ ...windowed(following, plan.limit, plan.after?.text, (entry) => cursorOf(groupValues(shape, entry))),
347
+ total: plan.total ? entries.length : undefined,
348
+ };
349
+ }
350
+ function framed(rows, { next, total }) {
269
351
  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),
352
+ ...rows,
353
+ ...(next === undefined ? {} : { next }),
354
+ ...(total === undefined ? {} : { total }),
277
355
  };
278
356
  }
357
+ function rowsOfPlan(plan, entries, total, bodies, each = (row) => row) {
358
+ const shape = plan.shape;
359
+ const { kept, ...window } = windowOf(plan, entries, total);
360
+ const keys = kept.map(([key]) => key);
361
+ if (shape.kind === 'documents') {
362
+ const documents = [];
363
+ return after(inUnits(kept, ([key, row]) => {
364
+ documents.push(each(documentOf(key, row, shape, bodies)));
365
+ return weight(row);
366
+ }), () => ({ rows: framed({ documents }, window), keys, ...window }));
367
+ }
368
+ const groups = [];
369
+ return after(inUnits(kept, ([, row]) => {
370
+ groups.push(each({
371
+ key: shape.groupBy.map((_, index) => row[`key${index}`]),
372
+ count: row.count,
373
+ }));
374
+ return ROW_WEIGHT;
375
+ }), () => ({ rows: framed({ groups }, window), keys, ...window }));
376
+ }
279
377
  export function reconciled(previous, next) {
280
- const before = rowsOf(previous.result);
281
- const held = new Map(previous.keys.map((key, index) => [key, before[index]]));
378
+ const before = previous.result;
379
+ const after = next.result;
380
+ if ('pending' in before || 'pending' in after) {
381
+ if (deepEquals(before, after))
382
+ return null;
383
+ return { keyed: next, message: next };
384
+ }
385
+ const earlier = 'groups' in before ? before.groups : before.documents;
386
+ const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
282
387
  const changed = [];
283
- const rows = rowsOf(next.result).map((row, index) => {
388
+ const rows = ('groups' in after ? after.groups : after.documents).map((row, index) => {
284
389
  const key = next.keys[index];
285
390
  const kept = held.get(key);
286
391
  if (kept !== undefined && deepEquals(kept, row))
@@ -289,22 +394,48 @@ export function reconciled(previous, next) {
289
394
  return row;
290
395
  });
291
396
  const present = new Set(next.keys);
292
- const survivors = previous.keys.filter((key) => present.has(key));
293
397
  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)
398
+ const moved = reordered(previous.keys, next.keys, present);
399
+ const covered = !deepEquals(before.coverage, after.coverage);
400
+ const framing = reframed(before, after);
401
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered && framing === null) {
297
402
  return null;
403
+ }
404
+ const window = { next: after.next, total: after.total };
298
405
  return {
299
406
  keyed: {
300
- result: 'groups' in next.result
301
- ? { groups: rows }
302
- : { documents: rows },
407
+ result: 'groups' in after
408
+ ? framed({ groups: rows, coverage: after.coverage }, window)
409
+ : framed({ documents: rows, coverage: after.coverage }, window),
303
410
  keys: next.keys,
304
411
  },
305
- change: { changed, removed, order: moved ? next.keys : null },
412
+ message: {
413
+ changed,
414
+ removed,
415
+ order: moved ? next.keys : null,
416
+ ...(covered ? { coverage: after.coverage } : {}),
417
+ ...framing,
418
+ },
306
419
  };
307
420
  }
421
+ function reframed(before, after) {
422
+ const next = before.next === after.next ? {} : { next: after.next ?? null };
423
+ const total = before.total === after.total || after.total === undefined ? {} : { total: after.total };
424
+ return Object.keys(next).length === 0 && Object.keys(total).length === 0
425
+ ? null
426
+ : { ...next, ...total };
427
+ }
428
+ function reordered(previous, next, present) {
429
+ let at = 0;
430
+ for (const key of previous) {
431
+ if (!present.has(key))
432
+ continue;
433
+ if (next[at] !== key)
434
+ return true;
435
+ at += 1;
436
+ }
437
+ return at !== next.length;
438
+ }
308
439
  export function createReadSide() {
309
440
  configureIndexDevMode({ enabled: false });
310
441
  let sync;
@@ -324,23 +455,144 @@ export function createReadSide() {
324
455
  });
325
456
  const shared = new Map();
326
457
  function searched(row) {
327
- const body = rows.get(row.body);
458
+ const read = row.text === null ? null : parse(row.text, row.path);
459
+ if (read === null || !read.ok)
460
+ return null;
328
461
  return {
329
462
  room: row.room,
330
463
  path: row.path,
331
464
  type: row.type,
332
465
  fields: row.fields,
333
- preamble: body.preamble,
334
- sections: body.sections,
466
+ preamble: read.document.preamble,
467
+ sections: read.document.sections,
335
468
  };
336
469
  }
337
- function publish(entry) {
338
- const next = reconciled(entry.result, keyedOf(entry.plan, [...entry.built.live.entries()]));
339
- if (next === null)
470
+ function kept(entry, key, row) {
471
+ const held = entry.rows.get(key);
472
+ if (held !== undefined && deepEquals(held, row))
340
473
  return;
341
- entry.result = next.keyed;
342
- for (const listener of entry.listeners)
343
- listener(next.change);
474
+ entry.rows.set(key, row);
475
+ }
476
+ function counted(entry) {
477
+ return entry.built.count === null ? undefined : entry.built.count.size;
478
+ }
479
+ function rebuilt(entry) {
480
+ return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], counted(entry), entry.bodies), ({ rows: found, keys, next, total }) => {
481
+ const present = new Set(keys);
482
+ for (const key of [...entry.rows.keys()])
483
+ if (!present.has(key))
484
+ entry.rows.delete(key);
485
+ for (const key of [...entry.bodies.keys()]) {
486
+ if (!present.has(key))
487
+ entry.bodies.delete(key);
488
+ }
489
+ keys.forEach((key, index) => kept(entry, key, rowAt(found, index)));
490
+ return { keys, next, total };
491
+ });
492
+ }
493
+ function refreshed(entry, dirty) {
494
+ const shape = entry.plan.shape;
495
+ const { kept: inWindow, next, total, } = windowOf(entry.plan, [...entry.built.live.entries()], counted(entry));
496
+ const keys = inWindow.map(([key]) => key);
497
+ const present = new Set(keys);
498
+ for (const key of [...entry.rows.keys()]) {
499
+ if (present.has(key))
500
+ continue;
501
+ entry.rows.delete(key);
502
+ entry.bodies.delete(key);
503
+ }
504
+ const values = inWindow.filter(([key]) => dirty.has(key) || !entry.rows.has(key));
505
+ const done = inUnits(values, ([key, value]) => {
506
+ kept(entry, key, documentOf(key, value, shape, entry.bodies));
507
+ return weight(value);
508
+ });
509
+ return after(done, () => ({ keys, next, total }));
510
+ }
511
+ function current(entry, found, coverage) {
512
+ const rows = found.keys.map((key) => entry.rows.get(key));
513
+ return {
514
+ result: entry.plan.shape.kind === 'groups'
515
+ ? framed({ groups: rows, coverage }, found)
516
+ : framed({ documents: rows, coverage }, found),
517
+ keys: found.keys,
518
+ };
519
+ }
520
+ function delivered(entry, next) {
521
+ const previous = entry.result;
522
+ entry.result = next;
523
+ const message = previous === undefined ? null : changedFrom(previous, next);
524
+ if (message !== null)
525
+ for (const listener of entry.ready)
526
+ listener(message);
527
+ for (const listener of entry.fresh) {
528
+ entry.ready.add(listener);
529
+ listener(next);
530
+ }
531
+ entry.fresh.clear();
532
+ }
533
+ function run(entry) {
534
+ while (entry.listeners.size > 0 &&
535
+ (entry.whole || entry.dirty.size > 0 || entry.stale || entry.fresh.size > 0)) {
536
+ entry.stale = false;
537
+ const cover = entry.cover();
538
+ if ('pending' in cover) {
539
+ entry.whole = true;
540
+ entry.dirty.clear();
541
+ entry.rows.clear();
542
+ entry.bodies.clear();
543
+ delivered(entry, { result: cover, keys: [] });
544
+ break;
545
+ }
546
+ const whole = entry.whole ||
547
+ entry.plan.shape.kind === 'groups' ||
548
+ entry.result === undefined ||
549
+ 'pending' in entry.result.result;
550
+ const dirty = entry.dirty;
551
+ entry.dirty = new Set();
552
+ entry.whole = false;
553
+ const found = whole ? rebuilt(entry) : refreshed(entry, dirty);
554
+ if (found instanceof Promise) {
555
+ void found
556
+ .then((window) => {
557
+ if (entry.listeners.size > 0)
558
+ delivered(entry, current(entry, window, cover.coverage));
559
+ run(entry);
560
+ })
561
+ .catch(() => {
562
+ entry.running = false;
563
+ });
564
+ return;
565
+ }
566
+ delivered(entry, current(entry, found, cover.coverage));
567
+ }
568
+ entry.running = false;
569
+ }
570
+ function wake(entry) {
571
+ if (entry.subscription === null || entry.running)
572
+ return;
573
+ entry.running = true;
574
+ try {
575
+ run(entry);
576
+ }
577
+ catch (error) {
578
+ entry.running = false;
579
+ throw error;
580
+ }
581
+ }
582
+ function recover() {
583
+ for (const entry of shared.values()) {
584
+ if (entry.result === undefined || entry.subscription === null)
585
+ continue;
586
+ const cover = entry.cover();
587
+ const before = entry.result.result;
588
+ const held = 'pending' in cover
589
+ ? deepEquals(before, cover)
590
+ : 'coverage' in before && deepEquals(before.coverage, cover.coverage);
591
+ if (held)
592
+ continue;
593
+ entry.stale = true;
594
+ wake(entry);
595
+ }
344
596
  }
345
597
  return {
346
598
  apply(set) {
@@ -351,70 +603,117 @@ export function createReadSide() {
351
603
  for (const row of change.after)
352
604
  next.set(row.key, row);
353
605
  }
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;
606
+ if (next.size > 0) {
607
+ sync.begin();
608
+ for (const [key, row] of next) {
609
+ if (row === null) {
610
+ if (rows.has(key))
611
+ sync.write({ type: 'delete', key });
612
+ continue;
613
+ }
614
+ sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
362
615
  }
363
- sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
616
+ sync.commit();
364
617
  }
365
- sync.commit();
618
+ recover();
366
619
  },
367
- async query(plan) {
368
- const { live: collection, dispose } = built(rows, plan);
620
+ recover,
621
+ async query(plan, at) {
622
+ const { live: collection, count, dispose } = built(rows, plan);
369
623
  try {
370
624
  await collection.toArrayWhenReady();
371
- return keyedOf(plan, [...collection.entries()]).result;
625
+ if (count !== null)
626
+ await count.toArrayWhenReady();
627
+ const entries = [...collection.entries()];
628
+ const held = at();
629
+ const found = await rowsOfPlan(plan, entries, count?.size, null, structuredClone);
630
+ return { rows: found.rows, at: held };
372
631
  }
373
632
  finally {
374
633
  dispose();
375
634
  }
376
635
  },
377
- watch(plan, identity, deliver) {
636
+ watch(plan, identity, cover, deliverTo) {
378
637
  let entry = shared.get(identity);
379
638
  if (entry === undefined) {
380
639
  const created = {
381
640
  plan,
382
641
  built: built(rows, plan),
642
+ cover,
383
643
  result: undefined,
644
+ rows: new Map(),
645
+ bodies: new Map(),
384
646
  listeners: new Set(),
647
+ fresh: new Set(),
648
+ ready: new Set(),
385
649
  subscription: null,
650
+ counting: null,
651
+ whole: true,
652
+ dirty: new Set(),
653
+ stale: false,
654
+ running: false,
386
655
  };
387
656
  created.built.live.on('status:change', ({ status }) => {
388
- if (status === 'cleaned-up' && created.listeners.size === 0)
657
+ if (status !== 'cleaned-up' || created.listeners.size > 0)
658
+ return;
659
+ if (shared.get(identity) === created)
389
660
  shared.delete(identity);
390
661
  });
391
662
  shared.set(identity, created);
392
663
  entry = created;
393
664
  }
394
665
  const observed = entry;
395
- observed.listeners.add(deliver);
396
- observed.built.live.onFirstReady(() => {
397
- if (!observed.listeners.has(deliver))
666
+ observed.listeners.add(deliverTo);
667
+ const ready = (then) => observed.built.live.onFirstReady(() => observed.built.count === null ? then() : observed.built.count.onFirstReady(then));
668
+ const settled = () => {
669
+ if (observed.built.count === null)
670
+ wake(observed);
671
+ else
672
+ queueMicrotask(() => wake(observed));
673
+ };
674
+ ready(() => {
675
+ if (!observed.listeners.has(deliverTo))
398
676
  return;
677
+ observed.fresh.add(deliverTo);
399
678
  if (observed.subscription === null) {
400
- observed.result = keyedOf(observed.plan, [...observed.built.live.entries()]);
401
- observed.subscription = observed.built.live.subscribeChanges(() => {
402
- publish(observed);
679
+ observed.whole = true;
680
+ observed.subscription = observed.built.live.subscribeChanges((changes) => {
681
+ if (observed.plan.shape.kind === 'groups')
682
+ observed.whole = true;
683
+ else
684
+ for (const { key } of changes)
685
+ observed.dirty.add(key);
686
+ settled();
403
687
  }, { includeInitialState: false });
688
+ observed.counting =
689
+ observed.built.count?.subscribeChanges(() => {
690
+ observed.stale = true;
691
+ settled();
692
+ }, { includeInitialState: false }) ?? null;
404
693
  }
405
- deliver(observed.result);
694
+ wake(observed);
406
695
  });
407
696
  return () => {
408
- if (!observed.listeners.delete(deliver) || observed.listeners.size > 0)
697
+ observed.fresh.delete(deliverTo);
698
+ observed.ready.delete(deliverTo);
699
+ if (!observed.listeners.delete(deliverTo) || observed.listeners.size > 0)
409
700
  return;
410
701
  observed.subscription?.unsubscribe();
411
702
  observed.subscription = null;
703
+ observed.counting?.unsubscribe();
704
+ observed.counting = null;
705
+ observed.result = undefined;
706
+ observed.rows.clear();
707
+ observed.bodies.clear();
708
+ observed.dirty.clear();
709
+ observed.whole = true;
710
+ if (shared.get(identity) === observed)
711
+ shared.delete(identity);
712
+ observed.built.dispose();
412
713
  };
413
714
  },
414
- documents() {
415
- return rows.toArray
416
- .filter((row) => row.kind === 'document' && row.type !== null)
417
- .map(searched);
715
+ room(room) {
716
+ return rows.toArray.flatMap((row) => row.kind !== 'document' || row.type === null || row.room !== room ? [] : [row.path]);
418
717
  },
419
718
  document(room, path) {
420
719
  const row = rows.get(documentKey(room, path));
@@ -423,7 +722,10 @@ export function createReadSide() {
423
722
  close() {
424
723
  for (const entry of shared.values()) {
425
724
  entry.listeners.clear();
725
+ entry.fresh.clear();
726
+ entry.ready.clear();
426
727
  entry.subscription?.unsubscribe();
728
+ entry.counting?.unsubscribe();
427
729
  entry.built.dispose();
428
730
  }
429
731
  shared.clear();
@@ -431,3 +733,35 @@ export function createReadSide() {
431
733
  },
432
734
  };
433
735
  }
736
+ function changedFrom(previous, next) {
737
+ const before = previous.result;
738
+ const after = next.result;
739
+ if ('pending' in before || 'pending' in after)
740
+ return deepEquals(before, after) ? null : next;
741
+ const earlier = 'groups' in before ? before.groups : before.documents;
742
+ const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
743
+ const later = 'groups' in after ? after.groups : after.documents;
744
+ const changed = [];
745
+ next.keys.forEach((key, index) => {
746
+ if (held.get(key) !== later[index])
747
+ changed.push([key, later[index]]);
748
+ });
749
+ const present = new Set(next.keys);
750
+ const removed = previous.keys.filter((key) => !present.has(key));
751
+ const moved = reordered(previous.keys, next.keys, present);
752
+ const covered = !deepEquals(before.coverage, after.coverage);
753
+ const framing = reframed(before, after);
754
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered && framing === null) {
755
+ return null;
756
+ }
757
+ return {
758
+ changed,
759
+ removed,
760
+ order: moved ? next.keys : null,
761
+ ...(covered ? { coverage: after.coverage } : {}),
762
+ ...framing,
763
+ };
764
+ }
765
+ function rowAt(rows, index) {
766
+ return 'groups' in rows ? rows.groups[index] : rows.documents[index];
767
+ }