@agentshouse/mdruntime 0.1.0-cacb4c3f6734e → 0.1.0-cb2e390ee7b89

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