@agentshouse/mdruntime 0.1.0-cfbf80d8d4644 → 0.1.0-cfcbb238a5fa0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/AGENTS.md +270 -203
  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 +83 -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/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 +63 -0
  43. package/dist/chat/markdown.d.ts +20 -0
  44. package/dist/chat/markdown.d.ts.map +1 -0
  45. package/dist/chat/markdown.js +188 -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 +155 -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/process.d.ts +20 -0
  65. package/dist/chat/process.d.ts.map +1 -0
  66. package/dist/chat/process.js +32 -0
  67. package/dist/chat/provider.d.ts +63 -0
  68. package/dist/chat/provider.d.ts.map +1 -0
  69. package/dist/chat/provider.js +629 -0
  70. package/dist/chat/question.d.ts +9 -0
  71. package/dist/chat/question.d.ts.map +1 -0
  72. package/dist/chat/question.js +80 -0
  73. package/dist/chat/questions.d.ts +31 -0
  74. package/dist/chat/questions.d.ts.map +1 -0
  75. package/dist/chat/questions.js +111 -0
  76. package/dist/chat/read.d.ts +5 -0
  77. package/dist/chat/read.d.ts.map +1 -0
  78. package/dist/chat/read.js +13 -0
  79. package/dist/chat/ready.d.ts +34 -0
  80. package/dist/chat/ready.d.ts.map +1 -0
  81. package/dist/chat/ready.js +91 -0
  82. package/dist/chat/style.d.ts +4 -0
  83. package/dist/chat/style.d.ts.map +1 -0
  84. package/dist/chat/style.js +302 -0
  85. package/dist/chat.d.ts +14 -0
  86. package/dist/chat.d.ts.map +1 -0
  87. package/dist/chat.js +8 -0
  88. package/dist/client.d.ts +25 -12
  89. package/dist/client.d.ts.map +1 -1
  90. package/dist/client.js +75 -16
  91. package/dist/collection.d.ts +21 -12
  92. package/dist/collection.d.ts.map +1 -1
  93. package/dist/collection.js +271 -23
  94. package/dist/connection.d.ts +44 -15
  95. package/dist/connection.d.ts.map +1 -1
  96. package/dist/connection.js +1788 -400
  97. package/dist/conversation.d.ts +26 -10
  98. package/dist/conversation.d.ts.map +1 -1
  99. package/dist/conversation.js +414 -148
  100. package/dist/durability.d.ts +7 -1
  101. package/dist/durability.d.ts.map +1 -1
  102. package/dist/entry.d.ts +17 -0
  103. package/dist/entry.d.ts.map +1 -0
  104. package/dist/entry.js +156 -0
  105. package/dist/expectation.d.ts +21 -0
  106. package/dist/expectation.d.ts.map +1 -0
  107. package/dist/expectation.js +70 -0
  108. package/dist/failure.d.ts +13 -0
  109. package/dist/failure.d.ts.map +1 -0
  110. package/dist/failure.js +19 -0
  111. package/dist/fence.d.ts +6 -0
  112. package/dist/fence.d.ts.map +1 -0
  113. package/dist/fence.js +10 -0
  114. package/dist/host.d.ts +18 -2
  115. package/dist/host.d.ts.map +1 -1
  116. package/dist/host.js +97 -19
  117. package/dist/identity.d.ts +5 -0
  118. package/dist/identity.d.ts.map +1 -1
  119. package/dist/identity.js +19 -1
  120. package/dist/index.d.ts +16 -8
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +5 -0
  123. package/dist/memory.d.ts +11 -4
  124. package/dist/memory.d.ts.map +1 -1
  125. package/dist/memory.js +87 -25
  126. package/dist/model.d.ts +14 -4
  127. package/dist/model.d.ts.map +1 -1
  128. package/dist/model.js +12 -1
  129. package/dist/observation.d.ts +5 -2
  130. package/dist/observation.d.ts.map +1 -1
  131. package/dist/observation.js +22 -11
  132. package/dist/open.d.ts +2 -0
  133. package/dist/open.d.ts.map +1 -0
  134. package/dist/open.js +1 -0
  135. package/dist/outcome.d.ts +32 -3
  136. package/dist/outcome.d.ts.map +1 -1
  137. package/dist/outcome.js +42 -1
  138. package/dist/overlay.d.ts +16 -3
  139. package/dist/overlay.d.ts.map +1 -1
  140. package/dist/overlay.js +118 -77
  141. package/dist/query.d.ts +40 -4
  142. package/dist/query.d.ts.map +1 -1
  143. package/dist/query.js +60 -21
  144. package/dist/reach.d.ts +8 -3
  145. package/dist/reach.d.ts.map +1 -1
  146. package/dist/reach.js +60 -24
  147. package/dist/react.d.ts +31 -18
  148. package/dist/react.d.ts.map +1 -1
  149. package/dist/react.js +15 -11
  150. package/dist/read-side.d.ts +13 -6
  151. package/dist/read-side.d.ts.map +1 -1
  152. package/dist/read-side.js +394 -129
  153. package/dist/readiness.d.ts +53 -0
  154. package/dist/readiness.d.ts.map +1 -0
  155. package/dist/readiness.js +33 -0
  156. package/dist/reads.d.ts +11 -0
  157. package/dist/reads.d.ts.map +1 -0
  158. package/dist/reads.js +98 -0
  159. package/dist/recovery.d.ts +25 -0
  160. package/dist/recovery.d.ts.map +1 -0
  161. package/dist/recovery.js +197 -0
  162. package/dist/refresh.d.ts +13 -3
  163. package/dist/refresh.d.ts.map +1 -1
  164. package/dist/refresh.js +49 -38
  165. package/dist/replica.d.ts +9 -6
  166. package/dist/replica.d.ts.map +1 -1
  167. package/dist/replica.js +125 -68
  168. package/dist/room-draft.d.ts +38 -9
  169. package/dist/room-draft.d.ts.map +1 -1
  170. package/dist/room-draft.js +134 -25
  171. package/dist/rows.d.ts +11 -14
  172. package/dist/rows.d.ts.map +1 -1
  173. package/dist/rows.js +89 -49
  174. package/dist/save.d.ts +31 -1
  175. package/dist/save.d.ts.map +1 -1
  176. package/dist/save.js +376 -47
  177. package/dist/search.d.ts +39 -8
  178. package/dist/search.d.ts.map +1 -1
  179. package/dist/search.js +132 -46
  180. package/dist/seed.d.ts +16 -0
  181. package/dist/seed.d.ts.map +1 -0
  182. package/dist/seed.js +61 -0
  183. package/dist/shared.d.ts +11 -0
  184. package/dist/shared.d.ts.map +1 -0
  185. package/dist/shared.js +368 -0
  186. package/dist/status.d.ts +6 -2
  187. package/dist/status.d.ts.map +1 -1
  188. package/dist/status.js +0 -8
  189. package/dist/store.d.ts +75 -18
  190. package/dist/store.d.ts.map +1 -1
  191. package/dist/store.js +539 -200
  192. package/dist/turn.d.ts +7 -0
  193. package/dist/turn.d.ts.map +1 -0
  194. package/dist/turn.js +45 -0
  195. package/dist/wire.d.ts +141 -33
  196. package/dist/wire.d.ts.map +1 -1
  197. package/dist/wire.js +61 -31
  198. package/package.json +26 -14
  199. package/dist/attachment.d.ts +0 -3
  200. package/dist/attachment.d.ts.map +0 -1
  201. package/dist/attachment.js +0 -8
package/dist/read-side.js CHANGED
@@ -1,54 +1,58 @@
1
- import { and, BasicIndex, configureIndexDevMode, count, createCollection, createLiveQueryCollection, deepEquals, eq, gt, gte, inArray, isNull, isUndefined, lt, lte, not, or, toArray, } from '@tanstack/db';
2
- import { rowsOf } from './observation.js';
1
+ import { and, BasicIndex, configureIndexDevMode, count, createCollection, createLiveQueryCollection, deepEquals, eq, inArray, or, toArray, } from '@tanstack/db';
2
+ import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
3
3
  import { documentKey } from './rows.js';
4
+ import { after, inUnits, ROW_WEIGHT } from './turn.js';
4
5
  function every(expressions) {
5
6
  const [first, second, ...rest] = expressions;
6
7
  return second === undefined ? first : and(first, second, ...rest);
7
8
  }
8
- function some(expressions) {
9
- const [first, second, ...rest] = expressions;
10
- return second === undefined ? first : or(first, second, ...rest);
9
+ function same(held, value) {
10
+ return Array.isArray(held) ? held.includes(value) : held === value;
11
11
  }
12
- function equal(field, value) {
13
- return or(eq(field, value), inArray(value, field));
12
+ function ordered(held, [operator, bound]) {
13
+ if (typeof held !== typeof bound || held === null || typeof held === 'object')
14
+ return false;
15
+ const left = held;
16
+ const right = bound;
17
+ if (operator === 'gt')
18
+ return left > right;
19
+ if (operator === 'gte')
20
+ return left >= right;
21
+ if (operator === 'lt')
22
+ return left < right;
23
+ return left <= right;
14
24
  }
15
- function tested(test, field, size, written) {
25
+ function satisfied(test, held) {
16
26
  if (test.op === 'eq')
17
- return test.value === null ? isNull(field) : equal(field, test.value);
18
- if (test.op === 'ne') {
19
- return test.value === null
20
- ? not(isNull(field))
21
- : or(isUndefined(field), isNull(field), not(equal(field, test.value)));
22
- }
23
- if (test.op === 'in' || test.op === 'nin') {
24
- const matches = test.values.length === 0
25
- ? inArray(field, [])
26
- : some(test.values.map((value) => (value === null ? isNull(field) : equal(field, value))));
27
- if (test.op === 'in')
28
- return matches;
29
- return some([
30
- isUndefined(field),
31
- ...(test.values.includes(null) ? [] : [isNull(field)]),
32
- not(matches),
33
- ]);
34
- }
35
- if (test.op === 'range') {
36
- const compare = { gt, gte, lt, lte };
37
- return every(test.bounds.map(([operator, bound]) => compare[operator](field, bound)));
38
- }
39
- if (test.op === 'all') {
40
- return every([not(isUndefined(size)), ...test.values.map((value) => inArray(value, field))]);
41
- }
42
- if (test.op === 'size')
43
- return eq(size, test.size);
44
- return test.exists ? not(isUndefined(written)) : isUndefined(written);
27
+ return same(held, test.value);
28
+ if (test.op === 'ne')
29
+ return !same(held, test.value);
30
+ if (test.op === 'in')
31
+ return test.values.some((value) => same(held, value));
32
+ if (test.op === 'nin')
33
+ return !test.values.some((value) => same(held, value));
34
+ if (test.op === 'range')
35
+ return test.bounds.every((bound) => ordered(held, bound));
36
+ if (test.op === 'all')
37
+ return Array.isArray(held) && test.values.every((value) => same(held, value));
38
+ return Array.isArray(held) && held.length === test.size;
39
+ }
40
+ function tested(test, reached) {
41
+ if (test.op === 'exists')
42
+ return test.exists === (reached !== undefined);
43
+ if (reached === undefined)
44
+ return test.op === 'ne' || test.op === 'nin';
45
+ return reached.some((held) => satisfied(test, held));
45
46
  }
46
47
  function conditioned(condition, row) {
47
48
  if ('every' in condition)
48
- return every(condition.every.map((item) => conditioned(item, row)));
49
+ return condition.every.every((item) => conditioned(item, row));
49
50
  if ('some' in condition)
50
- return some(condition.some.map((item) => conditioned(item, row)));
51
- return tested(condition.test, row.values[condition.field], row.sizes[condition.field], row.fields[condition.field]);
51
+ return condition.some.some((item) => conditioned(item, row));
52
+ return tested(condition.test, row.reached[condition.field]);
53
+ }
54
+ function narrowed(query, where, alias) {
55
+ return where === null ? query : query.fn.where((row) => conditioned(where, row[alias]));
52
56
  }
53
57
  function reached(reach, room, document) {
54
58
  return reach === null
@@ -62,7 +66,6 @@ function scoped(scope, row, rooms, reach) {
62
66
  ...reached(reach, row.room, row.key),
63
67
  ...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
64
68
  ...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
65
- ...(scope.where === null ? [] : [conditioned(scope.where, row)]),
66
69
  ]);
67
70
  }
68
71
  function keyed(key, row) {
@@ -77,16 +80,17 @@ function live(query) {
77
80
  function related(q, rows, scope, field, kept, reach) {
78
81
  const matched = kept === 'source' ? 'target' : 'source';
79
82
  const from = q.from({ r: rows });
80
- return from
81
- .where(({ r }) => every([eq(r.kind, 'reference'), ...(field === null ? [] : [eq(r.field, field)])]))
83
+ const joined = from
84
+ .where(({ r }) => every([eq(r.kind, 'reference'), ...(field === null ? [] : [inArray(field, r.forms)])]))
82
85
  .innerJoin({ m: rows }, ({ r, m }) => eq(r[matched], m.key))
83
- .where(({ m }) => scoped(scope, m, null, reach))
86
+ .where(({ m }) => scoped(scope, m, null, reach));
87
+ return narrowed(joined, scope.where, 'm')
84
88
  .groupBy(({ r }) => r[kept])
85
89
  .select(({ r }) => ({ key: r[kept] }));
86
90
  }
87
91
  function filtered(q, rows, plan) {
88
92
  const from = q.from({ d: rows });
89
- let query = from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach));
93
+ let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach)), plan.root.where, 'd');
90
94
  if (plan.hop !== null) {
91
95
  const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach);
92
96
  query = query.innerJoin({ h: hop }, ({ d, h }) => eq(d.key, h.key));
@@ -97,14 +101,11 @@ function filtered(q, rows, plan) {
97
101
  }
98
102
  return query;
99
103
  }
100
- function named(prefix, names, source) {
101
- return Object.fromEntries(names.map((name, index) => [`${prefix}${index}`, source[name]]));
104
+ function bodied(shape) {
105
+ return shape.sections !== null || shape.preamble || shape.markdown;
102
106
  }
103
107
  function documents(q, rows, plan, shape) {
104
108
  let query = filtered(q, rows, plan);
105
- if (shape.sections !== null || shape.preamble || shape.markdown) {
106
- query = query.leftJoin({ b: rows }, ({ d, b }) => eq(d.body, b.key));
107
- }
108
109
  for (const { key, options } of shape.orderBy) {
109
110
  query = query.orderBy(({ d }) => keyed(key, d), options);
110
111
  }
@@ -113,18 +114,16 @@ function documents(q, rows, plan, shape) {
113
114
  }
114
115
  if (plan.limit !== null)
115
116
  query = query.limit(plan.limit);
116
- return query.select(({ d, b }) => ({
117
+ return query.select(({ d }) => ({
117
118
  room: d.room,
118
119
  path: d.path,
119
120
  type: d.type,
120
- ...(shape.fields === 'all' ? { fields: d.fields } : {}),
121
- ...(Array.isArray(shape.fields) ? named('field', shape.fields, d.fields) : {}),
122
- ...(shape.sections === 'all' ? { sections: b.sections } : {}),
123
- ...(Array.isArray(shape.sections) ? named('section', shape.sections, b.sections) : {}),
124
- ...(shape.preamble ? { preamble: b.preamble } : {}),
125
- ...(shape.markdown ? { markdown: b.markdown } : {}),
121
+ binary: d.binary,
122
+ ...(shape.fields === null ? {} : { fields: d.fields }),
123
+ ...(bodied(shape) ? { text: d.text } : {}),
126
124
  ...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
127
125
  ...(shape.change ? { change: d.change } : {}),
126
+ ...(shape.fileId ? { fileId: d.fileId } : {}),
128
127
  }));
129
128
  }
130
129
  function backlinked(q, rows, document, reach) {
@@ -168,63 +167,142 @@ function built(rows, plan) {
168
167
  },
169
168
  };
170
169
  }
171
- function present(entries) {
172
- return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
173
- }
174
- function part(row, prefix, names, whole) {
170
+ function sectionsOf(sections, names) {
175
171
  if (names === 'all')
176
- return whole;
177
- return present(names.map((name, index) => [name, row[`${prefix}${index}`]]));
172
+ return sections;
173
+ return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
174
+ }
175
+ function bodyOf(row, key, bodies) {
176
+ const text = row.text;
177
+ if (text === null)
178
+ return null;
179
+ const held = bodies?.get(key);
180
+ if (held !== undefined && held.text === text)
181
+ return held.document;
182
+ const read = parse(text, row.path);
183
+ const document = read.ok ? read.document : null;
184
+ bodies?.set(key, { text, document });
185
+ return document;
178
186
  }
179
- function documentOf(row, shape) {
180
- if (row.type === null)
181
- return { room: row.room, path: row.path };
187
+ function masked(pointers) {
188
+ const root = { whole: false, members: new Map() };
189
+ for (const pointer of pointers) {
190
+ let mask = root;
191
+ for (const token of pointerSegments(pointer)) {
192
+ const next = mask.members.get(token) ?? { whole: false, members: new Map() };
193
+ mask.members.set(token, next);
194
+ mask = next;
195
+ }
196
+ mask.whole = true;
197
+ }
198
+ return root;
199
+ }
200
+ function merged(left, right) {
201
+ if (left === undefined)
202
+ return right;
203
+ const members = new Map(left.members);
204
+ for (const [token, mask] of right.members)
205
+ members.set(token, merged(members.get(token), mask));
206
+ return { whole: left.whole || right.whole, members };
207
+ }
208
+ function cut(value, mask) {
209
+ if (mask.whole)
210
+ return value;
211
+ if (Array.isArray(value)) {
212
+ const each = {
213
+ whole: false,
214
+ members: new Map([...mask.members].filter(([token]) => !ARRAY_INDEX.test(token))),
215
+ };
216
+ const kept = [];
217
+ value.forEach((item, index) => {
218
+ const part = cut(item, merged(mask.members.get(String(index)), each));
219
+ if (part !== undefined)
220
+ kept[index] = part;
221
+ });
222
+ return kept.length === 0 ? undefined : kept;
223
+ }
224
+ if (typeof value !== 'object' || value === null)
225
+ return undefined;
226
+ const kept = Object.entries(value).flatMap(([key, held]) => {
227
+ const member = mask.members.get(key);
228
+ const part = member === undefined ? undefined : cut(held, member);
229
+ return part === undefined ? [] : [[key, part]];
230
+ });
231
+ return kept.length === 0 ? undefined : Object.fromEntries(kept);
232
+ }
233
+ function selected(fields, pointers) {
234
+ return (cut(fields, masked(pointers)) ?? {});
235
+ }
236
+ function documentOf(key, row, shape, bodies) {
237
+ if (row.type === null) {
238
+ const binary = row.binary;
239
+ return {
240
+ room: row.room,
241
+ path: row.path,
242
+ ...(binary === null ? {} : { binaryFile: binary }),
243
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
244
+ };
245
+ }
246
+ const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
182
247
  return {
183
248
  room: row.room,
184
249
  path: row.path,
185
250
  type: row.type,
186
251
  ...(shape.fields === null
187
- ? {}
188
- : { fields: part(row, 'field', shape.fields, row.fields) }),
189
- ...(shape.sections === null
190
252
  ? {}
191
253
  : {
192
- sections: part(row, 'section', shape.sections, row.sections),
254
+ fields: shape.fields === 'all'
255
+ ? row.fields
256
+ : selected(row.fields, shape.fields),
193
257
  }),
194
- ...(shape.preamble ? { preamble: row.preamble } : {}),
195
- ...(shape.markdown ? { markdown: row.markdown } : {}),
258
+ ...(shape.sections === null || body === null
259
+ ? {}
260
+ : { sections: sectionsOf(body.sections, shape.sections) }),
261
+ ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
262
+ ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
196
263
  ...(shape.backlinks
197
264
  ? {
198
265
  backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
199
266
  }
200
267
  : {}),
201
268
  ...(shape.change ? { change: row.change } : {}),
269
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
202
270
  };
203
271
  }
204
- function keyedOf(plan, entries) {
272
+ function weight(row) {
273
+ return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
274
+ }
275
+ function rowsOfPlan(plan, entries, bodies, each = (row) => row) {
205
276
  const shape = plan.shape;
206
277
  if (shape.kind === 'documents') {
207
- return {
208
- result: structuredClone({ documents: entries.map(([, row]) => documentOf(row, shape)) }),
209
- keys: entries.map(([key]) => key),
210
- };
278
+ const documents = [];
279
+ return after(inUnits(entries, ([key, row]) => {
280
+ documents.push(each(documentOf(key, row, shape, bodies)));
281
+ return weight(row);
282
+ }), () => ({ rows: { documents }, keys: entries.map(([key]) => key) }));
211
283
  }
212
284
  const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
213
- return {
214
- result: structuredClone({
215
- groups: bounded.map(([, row]) => ({
216
- key: shape.groupBy.map((_, index) => row[`key${index}`]),
217
- count: row.count,
218
- })),
219
- }),
220
- keys: bounded.map(([key]) => key),
221
- };
285
+ const groups = [];
286
+ return after(inUnits(bounded, ([, row]) => {
287
+ groups.push(each({
288
+ key: shape.groupBy.map((_, index) => row[`key${index}`]),
289
+ count: row.count,
290
+ }));
291
+ return ROW_WEIGHT;
292
+ }), () => ({ rows: { groups }, keys: bounded.map(([key]) => key) }));
222
293
  }
223
294
  export function reconciled(previous, next) {
224
- const before = rowsOf(previous.result);
225
- const held = new Map(previous.keys.map((key, index) => [key, before[index]]));
295
+ const before = previous.result;
296
+ const after = next.result;
297
+ if ('pending' in before || 'pending' in after) {
298
+ if (deepEquals(before, after))
299
+ return null;
300
+ return { keyed: next, message: next };
301
+ }
302
+ const earlier = 'groups' in before ? before.groups : before.documents;
303
+ const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
226
304
  const changed = [];
227
- const rows = rowsOf(next.result).map((row, index) => {
305
+ const rows = ('groups' in after ? after.groups : after.documents).map((row, index) => {
228
306
  const key = next.keys[index];
229
307
  const kept = held.get(key);
230
308
  if (kept !== undefined && deepEquals(kept, row))
@@ -233,22 +311,37 @@ export function reconciled(previous, next) {
233
311
  return row;
234
312
  });
235
313
  const present = new Set(next.keys);
236
- const survivors = previous.keys.filter((key) => present.has(key));
237
314
  const removed = previous.keys.filter((key) => !present.has(key));
238
- const moved = survivors.length !== next.keys.length ||
239
- survivors.some((key, index) => key !== next.keys[index]);
240
- if (changed.length === 0 && removed.length === 0 && !moved)
315
+ const moved = reordered(previous.keys, next.keys, present);
316
+ const covered = !deepEquals(before.coverage, after.coverage);
317
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered)
241
318
  return null;
242
319
  return {
243
320
  keyed: {
244
- result: 'groups' in next.result
245
- ? { groups: rows }
246
- : { documents: rows },
321
+ result: 'groups' in after
322
+ ? { groups: rows, coverage: after.coverage }
323
+ : { documents: rows, coverage: after.coverage },
247
324
  keys: next.keys,
248
325
  },
249
- change: { changed, removed, order: moved ? next.keys : null },
326
+ message: {
327
+ changed,
328
+ removed,
329
+ order: moved ? next.keys : null,
330
+ ...(covered ? { coverage: after.coverage } : {}),
331
+ },
250
332
  };
251
333
  }
334
+ function reordered(previous, next, present) {
335
+ let at = 0;
336
+ for (const key of previous) {
337
+ if (!present.has(key))
338
+ continue;
339
+ if (next[at] !== key)
340
+ return true;
341
+ at += 1;
342
+ }
343
+ return at !== next.length;
344
+ }
252
345
  export function createReadSide() {
253
346
  configureIndexDevMode({ enabled: false });
254
347
  let sync;
@@ -268,23 +361,139 @@ export function createReadSide() {
268
361
  });
269
362
  const shared = new Map();
270
363
  function searched(row) {
271
- const body = rows.get(row.body);
364
+ const read = row.text === null ? null : parse(row.text, row.path);
365
+ if (read === null || !read.ok)
366
+ return null;
272
367
  return {
273
368
  room: row.room,
274
369
  path: row.path,
275
370
  type: row.type,
276
371
  fields: row.fields,
277
- preamble: body.preamble,
278
- sections: body.sections,
372
+ preamble: read.document.preamble,
373
+ sections: read.document.sections,
279
374
  };
280
375
  }
281
- function publish(entry) {
282
- const next = reconciled(entry.result, keyedOf(entry.plan, [...entry.built.live.entries()]));
283
- if (next === null)
376
+ function kept(entry, key, row) {
377
+ const held = entry.rows.get(key);
378
+ if (held !== undefined && deepEquals(held, row))
284
379
  return;
285
- entry.result = next.keyed;
286
- for (const listener of entry.listeners)
287
- listener(next.change);
380
+ entry.rows.set(key, row);
381
+ }
382
+ function rebuilt(entry) {
383
+ return after(rowsOfPlan(entry.plan, [...entry.built.live.entries()], entry.bodies), ({ rows: found, keys }) => {
384
+ const present = new Set(keys);
385
+ for (const key of [...entry.rows.keys()])
386
+ if (!present.has(key))
387
+ entry.rows.delete(key);
388
+ for (const key of [...entry.bodies.keys()]) {
389
+ if (!present.has(key))
390
+ entry.bodies.delete(key);
391
+ }
392
+ keys.forEach((key, index) => kept(entry, key, rowAt(found, index)));
393
+ return keys;
394
+ });
395
+ }
396
+ function refreshed(entry, dirty) {
397
+ const shape = entry.plan.shape;
398
+ const keys = [...entry.built.live.keys()];
399
+ const named = new Set([...dirty, ...keys.filter((key) => !entry.rows.has(key))]);
400
+ const values = [...named].map((key) => [key, entry.built.live.get(key)]);
401
+ const done = inUnits(values, ([key, value]) => {
402
+ if (value !== undefined)
403
+ kept(entry, key, documentOf(key, value, shape, entry.bodies));
404
+ else {
405
+ entry.rows.delete(key);
406
+ entry.bodies.delete(key);
407
+ }
408
+ return weight(value);
409
+ });
410
+ return after(done, () => keys);
411
+ }
412
+ function current(entry, keys, coverage) {
413
+ const found = keys.map((key) => entry.rows.get(key));
414
+ return {
415
+ result: entry.plan.shape.kind === 'groups'
416
+ ? { groups: found, coverage }
417
+ : { documents: found, coverage },
418
+ keys,
419
+ };
420
+ }
421
+ function delivered(entry, next) {
422
+ const previous = entry.result;
423
+ entry.result = next;
424
+ const message = previous === undefined ? null : changedFrom(previous, next);
425
+ if (message !== null)
426
+ for (const listener of entry.ready)
427
+ listener(message);
428
+ for (const listener of entry.fresh) {
429
+ entry.ready.add(listener);
430
+ listener(next);
431
+ }
432
+ entry.fresh.clear();
433
+ }
434
+ function run(entry) {
435
+ while (entry.listeners.size > 0 &&
436
+ (entry.whole || entry.dirty.size > 0 || entry.stale || entry.fresh.size > 0)) {
437
+ entry.stale = false;
438
+ const cover = entry.cover();
439
+ if ('pending' in cover) {
440
+ entry.whole = true;
441
+ entry.dirty.clear();
442
+ entry.rows.clear();
443
+ entry.bodies.clear();
444
+ delivered(entry, { result: cover, keys: [] });
445
+ break;
446
+ }
447
+ const whole = entry.whole ||
448
+ entry.plan.shape.kind === 'groups' ||
449
+ entry.result === undefined ||
450
+ 'pending' in entry.result.result;
451
+ const dirty = entry.dirty;
452
+ entry.dirty = new Set();
453
+ entry.whole = false;
454
+ const keys = whole ? rebuilt(entry) : refreshed(entry, dirty);
455
+ if (keys instanceof Promise) {
456
+ void keys
457
+ .then((found) => {
458
+ if (entry.listeners.size > 0)
459
+ delivered(entry, current(entry, found, cover.coverage));
460
+ run(entry);
461
+ })
462
+ .catch(() => {
463
+ entry.running = false;
464
+ });
465
+ return;
466
+ }
467
+ delivered(entry, current(entry, keys, cover.coverage));
468
+ }
469
+ entry.running = false;
470
+ }
471
+ function wake(entry) {
472
+ if (entry.subscription === null || entry.running)
473
+ return;
474
+ entry.running = true;
475
+ try {
476
+ run(entry);
477
+ }
478
+ catch (error) {
479
+ entry.running = false;
480
+ throw error;
481
+ }
482
+ }
483
+ function recover() {
484
+ for (const entry of shared.values()) {
485
+ if (entry.result === undefined || entry.subscription === null)
486
+ continue;
487
+ const cover = entry.cover();
488
+ const before = entry.result.result;
489
+ const held = 'pending' in cover
490
+ ? deepEquals(before, cover)
491
+ : 'coverage' in before && deepEquals(before.coverage, cover.coverage);
492
+ if (held)
493
+ continue;
494
+ entry.stale = true;
495
+ wake(entry);
496
+ }
288
497
  }
289
498
  return {
290
499
  apply(set) {
@@ -295,38 +504,52 @@ export function createReadSide() {
295
504
  for (const row of change.after)
296
505
  next.set(row.key, row);
297
506
  }
298
- if (next.size === 0)
299
- return;
300
- sync.begin();
301
- for (const [key, row] of next) {
302
- if (row === null) {
303
- if (rows.has(key))
304
- sync.write({ type: 'delete', key });
305
- continue;
507
+ if (next.size > 0) {
508
+ sync.begin();
509
+ for (const [key, row] of next) {
510
+ if (row === null) {
511
+ if (rows.has(key))
512
+ sync.write({ type: 'delete', key });
513
+ continue;
514
+ }
515
+ sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
306
516
  }
307
- sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
517
+ sync.commit();
308
518
  }
309
- sync.commit();
519
+ recover();
310
520
  },
311
- async query(plan) {
521
+ recover,
522
+ async query(plan, at) {
312
523
  const { live: collection, dispose } = built(rows, plan);
313
524
  try {
314
525
  await collection.toArrayWhenReady();
315
- return keyedOf(plan, [...collection.entries()]).result;
526
+ const entries = [...collection.entries()];
527
+ const held = at();
528
+ const found = await rowsOfPlan(plan, entries, null, structuredClone);
529
+ return { rows: found.rows, at: held };
316
530
  }
317
531
  finally {
318
532
  dispose();
319
533
  }
320
534
  },
321
- watch(plan, identity, deliver) {
535
+ watch(plan, identity, cover, deliverTo) {
322
536
  let entry = shared.get(identity);
323
537
  if (entry === undefined) {
324
538
  const created = {
325
539
  plan,
326
540
  built: built(rows, plan),
541
+ cover,
327
542
  result: undefined,
543
+ rows: new Map(),
544
+ bodies: new Map(),
328
545
  listeners: new Set(),
546
+ fresh: new Set(),
547
+ ready: new Set(),
329
548
  subscription: null,
549
+ whole: true,
550
+ dirty: new Set(),
551
+ stale: false,
552
+ running: false,
330
553
  };
331
554
  created.built.live.on('status:change', ({ status }) => {
332
555
  if (status === 'cleaned-up' && created.listeners.size === 0)
@@ -336,29 +559,40 @@ export function createReadSide() {
336
559
  entry = created;
337
560
  }
338
561
  const observed = entry;
339
- observed.listeners.add(deliver);
562
+ observed.listeners.add(deliverTo);
340
563
  observed.built.live.onFirstReady(() => {
341
- if (!observed.listeners.has(deliver))
564
+ if (!observed.listeners.has(deliverTo))
342
565
  return;
566
+ observed.fresh.add(deliverTo);
343
567
  if (observed.subscription === null) {
344
- observed.result = keyedOf(observed.plan, [...observed.built.live.entries()]);
345
- observed.subscription = observed.built.live.subscribeChanges(() => {
346
- publish(observed);
568
+ observed.whole = true;
569
+ observed.subscription = observed.built.live.subscribeChanges((changes) => {
570
+ if (observed.plan.shape.kind === 'groups')
571
+ observed.whole = true;
572
+ else
573
+ for (const { key } of changes)
574
+ observed.dirty.add(key);
575
+ wake(observed);
347
576
  }, { includeInitialState: false });
348
577
  }
349
- deliver(observed.result);
578
+ wake(observed);
350
579
  });
351
580
  return () => {
352
- if (!observed.listeners.delete(deliver) || observed.listeners.size > 0)
581
+ observed.fresh.delete(deliverTo);
582
+ observed.ready.delete(deliverTo);
583
+ if (!observed.listeners.delete(deliverTo) || observed.listeners.size > 0)
353
584
  return;
354
585
  observed.subscription?.unsubscribe();
355
586
  observed.subscription = null;
587
+ observed.result = undefined;
588
+ observed.rows.clear();
589
+ observed.bodies.clear();
590
+ observed.dirty.clear();
591
+ observed.whole = true;
356
592
  };
357
593
  },
358
- documents() {
359
- return rows.toArray
360
- .filter((row) => row.kind === 'document' && row.type !== null)
361
- .map(searched);
594
+ room(room) {
595
+ return rows.toArray.flatMap((row) => row.kind !== 'document' || row.type === null || row.room !== room ? [] : [row.path]);
362
596
  },
363
597
  document(room, path) {
364
598
  const row = rows.get(documentKey(room, path));
@@ -367,6 +601,8 @@ export function createReadSide() {
367
601
  close() {
368
602
  for (const entry of shared.values()) {
369
603
  entry.listeners.clear();
604
+ entry.fresh.clear();
605
+ entry.ready.clear();
370
606
  entry.subscription?.unsubscribe();
371
607
  entry.built.dispose();
372
608
  }
@@ -375,3 +611,32 @@ export function createReadSide() {
375
611
  },
376
612
  };
377
613
  }
614
+ function changedFrom(previous, next) {
615
+ const before = previous.result;
616
+ const after = next.result;
617
+ if ('pending' in before || 'pending' in after)
618
+ return deepEquals(before, after) ? null : next;
619
+ const earlier = 'groups' in before ? before.groups : before.documents;
620
+ const held = new Map(previous.keys.map((key, index) => [key, earlier[index]]));
621
+ const later = 'groups' in after ? after.groups : after.documents;
622
+ const changed = [];
623
+ next.keys.forEach((key, index) => {
624
+ if (held.get(key) !== later[index])
625
+ changed.push([key, later[index]]);
626
+ });
627
+ const present = new Set(next.keys);
628
+ const removed = previous.keys.filter((key) => !present.has(key));
629
+ const moved = reordered(previous.keys, next.keys, present);
630
+ const covered = !deepEquals(before.coverage, after.coverage);
631
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered)
632
+ return null;
633
+ return {
634
+ changed,
635
+ removed,
636
+ order: moved ? next.keys : null,
637
+ ...(covered ? { coverage: after.coverage } : {}),
638
+ };
639
+ }
640
+ function rowAt(rows, index) {
641
+ return 'groups' in rows ? rows.groups[index] : rows.documents[index];
642
+ }