@agentshouse/mdruntime 0.1.0-ce531b51ff088 → 0.1.0-ceb97106cc2fd

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 (203) hide show
  1. package/AGENTS.md +273 -175
  2. package/README.md +21 -11
  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 +129 -41
  7. package/dist/adapter.d.ts.map +1 -1
  8. package/dist/adapter.js +1 -9
  9. package/dist/attached-model.d.ts +11 -0
  10. package/dist/attached-model.d.ts.map +1 -0
  11. package/dist/attached-model.js +47 -0
  12. package/dist/attempt.d.ts +16 -0
  13. package/dist/attempt.d.ts.map +1 -0
  14. package/dist/attempt.js +70 -0
  15. package/dist/binary.d.ts +23 -0
  16. package/dist/binary.d.ts.map +1 -0
  17. package/dist/binary.js +8 -0
  18. package/dist/bootstrap.d.ts +20 -6
  19. package/dist/bootstrap.d.ts.map +1 -1
  20. package/dist/bootstrap.js +114 -37
  21. package/dist/change-set.d.ts +2 -0
  22. package/dist/change-set.d.ts.map +1 -1
  23. package/dist/change-set.js +49 -16
  24. package/dist/change.d.ts +15 -0
  25. package/dist/change.d.ts.map +1 -1
  26. package/dist/change.js +26 -1
  27. package/dist/chat/acts.d.ts +33 -0
  28. package/dist/chat/acts.d.ts.map +1 -0
  29. package/dist/chat/acts.js +36 -0
  30. package/dist/chat/context.d.ts +3 -0
  31. package/dist/chat/context.d.ts.map +1 -0
  32. package/dist/chat/context.js +10 -0
  33. package/dist/chat/input.d.ts +9 -0
  34. package/dist/chat/input.d.ts.map +1 -0
  35. package/dist/chat/input.js +99 -0
  36. package/dist/chat/kit.d.ts +61 -0
  37. package/dist/chat/kit.d.ts.map +1 -0
  38. package/dist/chat/kit.js +123 -0
  39. package/dist/chat/lasting.d.ts +6 -0
  40. package/dist/chat/lasting.d.ts.map +1 -0
  41. package/dist/chat/lasting.js +19 -0
  42. package/dist/chat/list.d.ts +27 -0
  43. package/dist/chat/list.d.ts.map +1 -0
  44. package/dist/chat/list.js +63 -0
  45. package/dist/chat/markdown.d.ts +20 -0
  46. package/dist/chat/markdown.d.ts.map +1 -0
  47. package/dist/chat/markdown.js +188 -0
  48. package/dist/chat/message.d.ts +6 -0
  49. package/dist/chat/message.d.ts.map +1 -0
  50. package/dist/chat/message.js +192 -0
  51. package/dist/chat/messages.d.ts +22 -0
  52. package/dist/chat/messages.d.ts.map +1 -0
  53. package/dist/chat/messages.js +155 -0
  54. package/dist/chat/name.d.ts +6 -0
  55. package/dist/chat/name.d.ts.map +1 -0
  56. package/dist/chat/name.js +37 -0
  57. package/dist/chat/opened.d.ts +11 -0
  58. package/dist/chat/opened.d.ts.map +1 -0
  59. package/dist/chat/opened.js +17 -0
  60. package/dist/chat/overlay.d.ts +31 -0
  61. package/dist/chat/overlay.d.ts.map +1 -0
  62. package/dist/chat/overlay.js +97 -0
  63. package/dist/chat/parts.d.ts +57 -0
  64. package/dist/chat/parts.d.ts.map +1 -0
  65. package/dist/chat/parts.js +119 -0
  66. package/dist/chat/process.d.ts +20 -0
  67. package/dist/chat/process.d.ts.map +1 -0
  68. package/dist/chat/process.js +32 -0
  69. package/dist/chat/provider.d.ts +63 -0
  70. package/dist/chat/provider.d.ts.map +1 -0
  71. package/dist/chat/provider.js +629 -0
  72. package/dist/chat/question.d.ts +9 -0
  73. package/dist/chat/question.d.ts.map +1 -0
  74. package/dist/chat/question.js +80 -0
  75. package/dist/chat/questions.d.ts +31 -0
  76. package/dist/chat/questions.d.ts.map +1 -0
  77. package/dist/chat/questions.js +111 -0
  78. package/dist/chat/read.d.ts +5 -0
  79. package/dist/chat/read.d.ts.map +1 -0
  80. package/dist/chat/read.js +13 -0
  81. package/dist/chat/ready.d.ts +34 -0
  82. package/dist/chat/ready.d.ts.map +1 -0
  83. package/dist/chat/ready.js +91 -0
  84. package/dist/chat/style.d.ts +4 -0
  85. package/dist/chat/style.d.ts.map +1 -0
  86. package/dist/chat/style.js +302 -0
  87. package/dist/chat.d.ts +14 -0
  88. package/dist/chat.d.ts.map +1 -0
  89. package/dist/chat.js +8 -0
  90. package/dist/client.d.ts +28 -14
  91. package/dist/client.d.ts.map +1 -1
  92. package/dist/client.js +71 -26
  93. package/dist/collection.d.ts +21 -12
  94. package/dist/collection.d.ts.map +1 -1
  95. package/dist/collection.js +271 -23
  96. package/dist/connection.d.ts +50 -23
  97. package/dist/connection.d.ts.map +1 -1
  98. package/dist/connection.js +2074 -735
  99. package/dist/conversation.d.ts +41 -14
  100. package/dist/conversation.d.ts.map +1 -1
  101. package/dist/conversation.js +546 -214
  102. package/dist/durability.d.ts +7 -1
  103. package/dist/durability.d.ts.map +1 -1
  104. package/dist/entry.d.ts +17 -0
  105. package/dist/entry.d.ts.map +1 -0
  106. package/dist/entry.js +156 -0
  107. package/dist/expectation.d.ts +21 -0
  108. package/dist/expectation.d.ts.map +1 -0
  109. package/dist/expectation.js +70 -0
  110. package/dist/failure.d.ts +13 -0
  111. package/dist/failure.d.ts.map +1 -0
  112. package/dist/failure.js +19 -0
  113. package/dist/fence.d.ts +6 -0
  114. package/dist/fence.d.ts.map +1 -0
  115. package/dist/fence.js +10 -0
  116. package/dist/host.d.ts +24 -2
  117. package/dist/host.d.ts.map +1 -1
  118. package/dist/host.js +126 -45
  119. package/dist/identity.d.ts +5 -0
  120. package/dist/identity.d.ts.map +1 -1
  121. package/dist/identity.js +19 -1
  122. package/dist/index.d.ts +16 -8
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +5 -0
  125. package/dist/memory.d.ts +17 -7
  126. package/dist/memory.d.ts.map +1 -1
  127. package/dist/memory.js +159 -46
  128. package/dist/model.d.ts +17 -14
  129. package/dist/model.d.ts.map +1 -1
  130. package/dist/model.js +16 -4
  131. package/dist/observation.d.ts +6 -1
  132. package/dist/observation.d.ts.map +1 -1
  133. package/dist/observation.js +38 -17
  134. package/dist/open.d.ts +2 -0
  135. package/dist/open.d.ts.map +1 -0
  136. package/dist/open.js +1 -0
  137. package/dist/outcome.d.ts +32 -3
  138. package/dist/outcome.d.ts.map +1 -1
  139. package/dist/outcome.js +42 -1
  140. package/dist/overlay.d.ts +16 -3
  141. package/dist/overlay.d.ts.map +1 -1
  142. package/dist/overlay.js +118 -77
  143. package/dist/paths.d.ts +0 -1
  144. package/dist/paths.d.ts.map +1 -1
  145. package/dist/paths.js +0 -16
  146. package/dist/query.d.ts +46 -6
  147. package/dist/query.d.ts.map +1 -1
  148. package/dist/query.js +85 -37
  149. package/dist/reach.d.ts +45 -0
  150. package/dist/reach.d.ts.map +1 -0
  151. package/dist/reach.js +306 -0
  152. package/dist/react.d.ts +44 -0
  153. package/dist/react.d.ts.map +1 -0
  154. package/dist/react.js +66 -0
  155. package/dist/read-side.d.ts +16 -5
  156. package/dist/read-side.d.ts.map +1 -1
  157. package/dist/read-side.js +408 -137
  158. package/dist/readiness.d.ts +53 -0
  159. package/dist/readiness.d.ts.map +1 -0
  160. package/dist/readiness.js +33 -0
  161. package/dist/recovery.d.ts +25 -0
  162. package/dist/recovery.d.ts.map +1 -0
  163. package/dist/recovery.js +197 -0
  164. package/dist/refresh.d.ts +12 -1
  165. package/dist/refresh.d.ts.map +1 -1
  166. package/dist/refresh.js +51 -36
  167. package/dist/replica.d.ts +10 -7
  168. package/dist/replica.d.ts.map +1 -1
  169. package/dist/replica.js +129 -73
  170. package/dist/room-draft.d.ts +41 -9
  171. package/dist/room-draft.d.ts.map +1 -1
  172. package/dist/room-draft.js +139 -28
  173. package/dist/rows.d.ts +11 -14
  174. package/dist/rows.d.ts.map +1 -1
  175. package/dist/rows.js +87 -50
  176. package/dist/save.d.ts +32 -3
  177. package/dist/save.d.ts.map +1 -1
  178. package/dist/save.js +465 -92
  179. package/dist/search.d.ts +37 -8
  180. package/dist/search.d.ts.map +1 -1
  181. package/dist/search.js +116 -45
  182. package/dist/seed.d.ts +16 -0
  183. package/dist/seed.d.ts.map +1 -0
  184. package/dist/seed.js +61 -0
  185. package/dist/shared.d.ts +11 -0
  186. package/dist/shared.d.ts.map +1 -0
  187. package/dist/shared.js +368 -0
  188. package/dist/status.d.ts +6 -2
  189. package/dist/status.d.ts.map +1 -1
  190. package/dist/status.js +0 -8
  191. package/dist/store.d.ts +76 -19
  192. package/dist/store.d.ts.map +1 -1
  193. package/dist/store.js +539 -200
  194. package/dist/turn.d.ts +7 -0
  195. package/dist/turn.d.ts.map +1 -0
  196. package/dist/turn.js +45 -0
  197. package/dist/wire.d.ts +162 -19
  198. package/dist/wire.d.ts.map +1 -1
  199. package/dist/wire.js +82 -61
  200. package/package.json +31 -7
  201. package/dist/attachment.d.ts +0 -3
  202. package/dist/attachment.d.ts.map +0 -1
  203. package/dist/attachment.js +0 -8
package/dist/read-side.js CHANGED
@@ -1,62 +1,71 @@
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]));
56
+ }
57
+ function reached(reach, room, document) {
58
+ return reach === null
59
+ ? []
60
+ : [or(inArray(room, [...reach.rooms]), inArray(document, [...reach.documents]))];
52
61
  }
53
- function scoped(scope, row, rooms) {
62
+ function scoped(scope, row, rooms, reach) {
54
63
  return every([
55
64
  eq(row.kind, 'document'),
56
65
  ...(rooms === null ? [] : [inArray(row.room, [...rooms])]),
66
+ ...reached(reach, row.room, row.key),
57
67
  ...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
58
68
  ...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
59
- ...(scope.where === null ? [] : [conditioned(scope.where, row)]),
60
69
  ]);
61
70
  }
62
71
  function keyed(key, row) {
@@ -68,37 +77,35 @@ function live(query) {
68
77
  startSync: true,
69
78
  });
70
79
  }
71
- function related(q, rows, scope, field, kept) {
80
+ function related(q, rows, scope, field, kept, reach) {
72
81
  const matched = kept === 'source' ? 'target' : 'source';
73
82
  const from = q.from({ r: rows });
74
- return from
75
- .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)])]))
76
85
  .innerJoin({ m: rows }, ({ r, m }) => eq(r[matched], m.key))
77
- .where(({ m }) => scoped(scope, m, null))
86
+ .where(({ m }) => scoped(scope, m, null, reach));
87
+ return narrowed(joined, scope.where, 'm')
78
88
  .groupBy(({ r }) => r[kept])
79
89
  .select(({ r }) => ({ key: r[kept] }));
80
90
  }
81
91
  function filtered(q, rows, plan) {
82
92
  const from = q.from({ d: rows });
83
- let query = from.where(({ d }) => scoped(plan.root, d, plan.rooms));
93
+ let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach)), plan.root.where, 'd');
84
94
  if (plan.hop !== null) {
85
- const hop = related(q, rows, plan.hop, plan.hop.field, 'source');
95
+ const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach);
86
96
  query = query.innerJoin({ h: hop }, ({ d, h }) => eq(d.key, h.key));
87
97
  }
88
98
  if (plan.backlinks !== null) {
89
- const backlinks = related(q, rows, plan.backlinks, plan.backlinks.field, 'target');
99
+ const backlinks = related(q, rows, plan.backlinks, plan.backlinks.field, 'target', plan.reach);
90
100
  query = query.innerJoin({ k: backlinks }, ({ d, k }) => eq(d.key, k.key));
91
101
  }
92
102
  return query;
93
103
  }
94
- function named(prefix, names, source) {
95
- return Object.fromEntries(names.map((name, index) => [`${prefix}${index}`, source[name]]));
104
+ function bodied(shape) {
105
+ return shape.sections !== null || shape.preamble || shape.markdown;
96
106
  }
97
107
  function documents(q, rows, plan, shape) {
98
108
  let query = filtered(q, rows, plan);
99
- if (shape.sections !== null || shape.preamble || shape.markdown) {
100
- query = query.leftJoin({ b: rows }, ({ d, b }) => eq(d.body, b.key));
101
- }
102
109
  for (const { key, options } of shape.orderBy) {
103
110
  query = query.orderBy(({ d }) => keyed(key, d), options);
104
111
  }
@@ -107,24 +114,22 @@ function documents(q, rows, plan, shape) {
107
114
  }
108
115
  if (plan.limit !== null)
109
116
  query = query.limit(plan.limit);
110
- return query.select(({ d, b }) => ({
117
+ return query.select(({ d }) => ({
111
118
  room: d.room,
112
119
  path: d.path,
113
120
  type: d.type,
114
- ...(shape.fields === 'all' ? { fields: d.fields } : {}),
115
- ...(Array.isArray(shape.fields) ? named('field', shape.fields, d.fields) : {}),
116
- ...(shape.sections === 'all' ? { sections: b.sections } : {}),
117
- ...(Array.isArray(shape.sections) ? named('section', shape.sections, b.sections) : {}),
118
- ...(shape.preamble ? { preamble: b.preamble } : {}),
119
- ...(shape.markdown ? { markdown: b.markdown } : {}),
120
- ...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d)) } : {}),
121
+ binary: d.binary,
122
+ ...(shape.fields === null ? {} : { fields: d.fields }),
123
+ ...(bodied(shape) ? { text: d.text } : {}),
124
+ ...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
121
125
  ...(shape.change ? { change: d.change } : {}),
126
+ ...(shape.fileId ? { fileId: d.fileId } : {}),
122
127
  }));
123
128
  }
124
- function backlinked(q, rows, document) {
129
+ function backlinked(q, rows, document, reach) {
125
130
  const from = q.from({ r: rows });
126
131
  return from
127
- .where(({ r }) => eq(r.target, document.key))
132
+ .where(({ r }) => every([eq(r.target, document.key), ...reached(reach, r.room, r.source)]))
128
133
  .orderBy(({ r }) => r.path, { stringSort: 'lexical' })
129
134
  .orderBy(({ r }) => r.field, { stringSort: 'lexical' })
130
135
  .select(({ r }) => ({ path: r.path, type: r.type, field: r.field }));
@@ -162,63 +167,142 @@ function built(rows, plan) {
162
167
  },
163
168
  };
164
169
  }
165
- function present(entries) {
166
- return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
167
- }
168
- function part(row, prefix, names, whole) {
170
+ function sectionsOf(sections, names) {
169
171
  if (names === 'all')
170
- return whole;
171
- 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;
186
+ }
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 };
172
207
  }
173
- function documentOf(row, shape) {
174
- if (row.type === null)
175
- return { room: row.room, path: row.path };
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;
176
247
  return {
177
248
  room: row.room,
178
249
  path: row.path,
179
250
  type: row.type,
180
251
  ...(shape.fields === null
181
- ? {}
182
- : { fields: part(row, 'field', shape.fields, row.fields) }),
183
- ...(shape.sections === null
184
252
  ? {}
185
253
  : {
186
- sections: part(row, 'section', shape.sections, row.sections),
254
+ fields: shape.fields === 'all'
255
+ ? row.fields
256
+ : selected(row.fields, shape.fields),
187
257
  }),
188
- ...(shape.preamble ? { preamble: row.preamble } : {}),
189
- ...(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 } : {}),
190
263
  ...(shape.backlinks
191
264
  ? {
192
265
  backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
193
266
  }
194
267
  : {}),
195
268
  ...(shape.change ? { change: row.change } : {}),
269
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
196
270
  };
197
271
  }
198
- 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) {
199
276
  const shape = plan.shape;
200
277
  if (shape.kind === 'documents') {
201
- return {
202
- result: structuredClone({ documents: entries.map(([, row]) => documentOf(row, shape)) }),
203
- keys: entries.map(([key]) => key),
204
- };
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) }));
205
283
  }
206
284
  const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
207
- return {
208
- result: structuredClone({
209
- groups: bounded.map(([, row]) => ({
210
- key: shape.groupBy.map((_, index) => row[`key${index}`]),
211
- count: row.count,
212
- })),
213
- }),
214
- keys: bounded.map(([key]) => key),
215
- };
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) }));
216
293
  }
217
- function reconciled(previous, next) {
218
- const before = rowsOf(previous.result);
219
- const held = new Map(previous.keys.map((key, index) => [key, before[index]]));
294
+ export function reconciled(previous, next) {
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]]));
220
304
  const changed = [];
221
- const rows = rowsOf(next.result).map((row, index) => {
305
+ const rows = ('groups' in after ? after.groups : after.documents).map((row, index) => {
222
306
  const key = next.keys[index];
223
307
  const kept = held.get(key);
224
308
  if (kept !== undefined && deepEquals(kept, row))
@@ -227,22 +311,37 @@ function reconciled(previous, next) {
227
311
  return row;
228
312
  });
229
313
  const present = new Set(next.keys);
230
- const survivors = previous.keys.filter((key) => present.has(key));
231
314
  const removed = previous.keys.filter((key) => !present.has(key));
232
- const moved = survivors.length !== next.keys.length ||
233
- survivors.some((key, index) => key !== next.keys[index]);
234
- 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)
235
318
  return null;
236
319
  return {
237
320
  keyed: {
238
- result: 'groups' in next.result
239
- ? { groups: rows }
240
- : { documents: rows },
321
+ result: 'groups' in after
322
+ ? { groups: rows, coverage: after.coverage }
323
+ : { documents: rows, coverage: after.coverage },
241
324
  keys: next.keys,
242
325
  },
243
- 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
+ },
244
332
  };
245
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
+ }
246
345
  export function createReadSide() {
247
346
  configureIndexDevMode({ enabled: false });
248
347
  let sync;
@@ -262,23 +361,139 @@ export function createReadSide() {
262
361
  });
263
362
  const shared = new Map();
264
363
  function searched(row) {
265
- 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;
266
367
  return {
267
368
  room: row.room,
268
369
  path: row.path,
269
370
  type: row.type,
270
371
  fields: row.fields,
271
- preamble: body.preamble,
272
- sections: body.sections,
372
+ preamble: read.document.preamble,
373
+ sections: read.document.sections,
273
374
  };
274
375
  }
275
- function publish(entry) {
276
- const next = reconciled(entry.result, keyedOf(entry.plan, [...entry.built.live.entries()]));
277
- if (next === null)
376
+ function kept(entry, key, row) {
377
+ const held = entry.rows.get(key);
378
+ if (held !== undefined && deepEquals(held, row))
278
379
  return;
279
- entry.result = next.keyed;
280
- for (const listener of entry.listeners)
281
- 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
+ }
282
497
  }
283
498
  return {
284
499
  apply(set) {
@@ -289,38 +504,52 @@ export function createReadSide() {
289
504
  for (const row of change.after)
290
505
  next.set(row.key, row);
291
506
  }
292
- if (next.size === 0)
293
- return;
294
- sync.begin();
295
- for (const [key, row] of next) {
296
- if (row === null) {
297
- if (rows.has(key))
298
- sync.write({ type: 'delete', key });
299
- 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 });
300
516
  }
301
- sync.write({ type: rows.has(key) ? 'update' : 'insert', value: row });
517
+ sync.commit();
302
518
  }
303
- sync.commit();
519
+ recover();
304
520
  },
305
- async query(plan) {
521
+ recover,
522
+ async query(plan, at) {
306
523
  const { live: collection, dispose } = built(rows, plan);
307
524
  try {
308
525
  await collection.toArrayWhenReady();
309
- 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 };
310
530
  }
311
531
  finally {
312
532
  dispose();
313
533
  }
314
534
  },
315
- watch(plan, identity, deliver) {
535
+ watch(plan, identity, cover, deliverTo) {
316
536
  let entry = shared.get(identity);
317
537
  if (entry === undefined) {
318
538
  const created = {
319
539
  plan,
320
540
  built: built(rows, plan),
541
+ cover,
321
542
  result: undefined,
543
+ rows: new Map(),
544
+ bodies: new Map(),
322
545
  listeners: new Set(),
546
+ fresh: new Set(),
547
+ ready: new Set(),
323
548
  subscription: null,
549
+ whole: true,
550
+ dirty: new Set(),
551
+ stale: false,
552
+ running: false,
324
553
  };
325
554
  created.built.live.on('status:change', ({ status }) => {
326
555
  if (status === 'cleaned-up' && created.listeners.size === 0)
@@ -330,29 +559,40 @@ export function createReadSide() {
330
559
  entry = created;
331
560
  }
332
561
  const observed = entry;
333
- observed.listeners.add(deliver);
562
+ observed.listeners.add(deliverTo);
334
563
  observed.built.live.onFirstReady(() => {
335
- if (!observed.listeners.has(deliver))
564
+ if (!observed.listeners.has(deliverTo))
336
565
  return;
566
+ observed.fresh.add(deliverTo);
337
567
  if (observed.subscription === null) {
338
- observed.result = keyedOf(observed.plan, [...observed.built.live.entries()]);
339
- observed.subscription = observed.built.live.subscribeChanges(() => {
340
- 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);
341
576
  }, { includeInitialState: false });
342
577
  }
343
- deliver(observed.result);
578
+ wake(observed);
344
579
  });
345
580
  return () => {
346
- 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)
347
584
  return;
348
585
  observed.subscription?.unsubscribe();
349
586
  observed.subscription = null;
587
+ observed.result = undefined;
588
+ observed.rows.clear();
589
+ observed.bodies.clear();
590
+ observed.dirty.clear();
591
+ observed.whole = true;
350
592
  };
351
593
  },
352
- documents() {
353
- return rows.toArray
354
- .filter((row) => row.kind === 'document' && row.type !== null)
355
- .map(searched);
594
+ room(room) {
595
+ return rows.toArray.flatMap((row) => row.kind !== 'document' || row.type === null || row.room !== room ? [] : [row.path]);
356
596
  },
357
597
  document(room, path) {
358
598
  const row = rows.get(documentKey(room, path));
@@ -361,6 +601,8 @@ export function createReadSide() {
361
601
  close() {
362
602
  for (const entry of shared.values()) {
363
603
  entry.listeners.clear();
604
+ entry.fresh.clear();
605
+ entry.ready.clear();
364
606
  entry.subscription?.unsubscribe();
365
607
  entry.built.dispose();
366
608
  }
@@ -369,3 +611,32 @@ export function createReadSide() {
369
611
  },
370
612
  };
371
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
+ }