@agentshouse/mdruntime 0.1.0-cacb4c3f6734e → 0.1.0-caee7332d2235

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