@agentshouse/mdruntime 0.1.0-cbb8d8acc9d19 → 0.1.0-cc56db96f86ae

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 (223) hide show
  1. package/AGENTS.md +285 -225
  2. package/README.md +21 -10
  3. package/dist/adapter.d.ts +114 -26
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/attempt.d.ts +16 -0
  6. package/dist/attempt.d.ts.map +1 -0
  7. package/dist/attempt.js +70 -0
  8. package/dist/binary.d.ts +23 -0
  9. package/dist/binary.d.ts.map +1 -0
  10. package/dist/binary.js +8 -0
  11. package/dist/bootstrap.d.ts +18 -5
  12. package/dist/bootstrap.d.ts.map +1 -1
  13. package/dist/bootstrap.js +147 -40
  14. package/dist/change-set.d.ts +7 -2
  15. package/dist/change-set.d.ts.map +1 -1
  16. package/dist/change-set.js +50 -16
  17. package/dist/change.d.ts +45 -0
  18. package/dist/change.d.ts.map +1 -1
  19. package/dist/change.js +34 -0
  20. package/dist/chat/acts.d.ts +33 -0
  21. package/dist/chat/acts.d.ts.map +1 -0
  22. package/dist/chat/acts.js +36 -0
  23. package/dist/chat/context.d.ts +3 -0
  24. package/dist/chat/context.d.ts.map +1 -0
  25. package/dist/chat/context.js +10 -0
  26. package/dist/chat/earlier.d.ts +10 -0
  27. package/dist/chat/earlier.d.ts.map +1 -0
  28. package/dist/chat/earlier.js +54 -0
  29. package/dist/chat/house.d.ts +25 -0
  30. package/dist/chat/house.d.ts.map +1 -0
  31. package/dist/chat/house.js +38 -0
  32. package/dist/chat/input.d.ts +11 -0
  33. package/dist/chat/input.d.ts.map +1 -0
  34. package/dist/chat/input.js +92 -0
  35. package/dist/chat/kit.d.ts +61 -0
  36. package/dist/chat/kit.d.ts.map +1 -0
  37. package/dist/chat/kit.js +123 -0
  38. package/dist/chat/lasting.d.ts +6 -0
  39. package/dist/chat/lasting.d.ts.map +1 -0
  40. package/dist/chat/lasting.js +19 -0
  41. package/dist/chat/list.d.ts +28 -0
  42. package/dist/chat/list.d.ts.map +1 -0
  43. package/dist/chat/list.js +103 -0
  44. package/dist/chat/markdown.d.ts +21 -0
  45. package/dist/chat/markdown.d.ts.map +1 -0
  46. package/dist/chat/markdown.js +246 -0
  47. package/dist/chat/message.d.ts +7 -0
  48. package/dist/chat/message.d.ts.map +1 -0
  49. package/dist/chat/message.js +237 -0
  50. package/dist/chat/messages.d.ts +29 -0
  51. package/dist/chat/messages.d.ts.map +1 -0
  52. package/dist/chat/messages.js +190 -0
  53. package/dist/chat/name.d.ts +7 -0
  54. package/dist/chat/name.d.ts.map +1 -0
  55. package/dist/chat/name.js +37 -0
  56. package/dist/chat/opened.d.ts +12 -0
  57. package/dist/chat/opened.d.ts.map +1 -0
  58. package/dist/chat/opened.js +19 -0
  59. package/dist/chat/overlay.d.ts +32 -0
  60. package/dist/chat/overlay.d.ts.map +1 -0
  61. package/dist/chat/overlay.js +103 -0
  62. package/dist/chat/parts.d.ts +58 -0
  63. package/dist/chat/parts.d.ts.map +1 -0
  64. package/dist/chat/parts.js +120 -0
  65. package/dist/chat/pieces.d.ts +62 -0
  66. package/dist/chat/pieces.d.ts.map +1 -0
  67. package/dist/chat/pieces.js +1110 -0
  68. package/dist/chat/process.d.ts +20 -0
  69. package/dist/chat/process.d.ts.map +1 -0
  70. package/dist/chat/process.js +32 -0
  71. package/dist/chat/provider.d.ts +68 -0
  72. package/dist/chat/provider.d.ts.map +1 -0
  73. package/dist/chat/provider.js +619 -0
  74. package/dist/chat/question.d.ts +9 -0
  75. package/dist/chat/question.d.ts.map +1 -0
  76. package/dist/chat/question.js +69 -0
  77. package/dist/chat/questions.d.ts +31 -0
  78. package/dist/chat/questions.d.ts.map +1 -0
  79. package/dist/chat/questions.js +111 -0
  80. package/dist/chat/read.d.ts +5 -0
  81. package/dist/chat/read.d.ts.map +1 -0
  82. package/dist/chat/read.js +13 -0
  83. package/dist/chat/ready.d.ts +37 -0
  84. package/dist/chat/ready.d.ts.map +1 -0
  85. package/dist/chat/ready.js +93 -0
  86. package/dist/chat/room-file.d.ts +8 -0
  87. package/dist/chat/room-file.d.ts.map +1 -0
  88. package/dist/chat/room-file.js +67 -0
  89. package/dist/chat/steady.d.ts +3 -0
  90. package/dist/chat/steady.d.ts.map +1 -0
  91. package/dist/chat/steady.js +26 -0
  92. package/dist/chat/style.d.ts +4 -0
  93. package/dist/chat/style.d.ts.map +1 -0
  94. package/dist/chat/style.js +306 -0
  95. package/dist/chat.d.ts +15 -0
  96. package/dist/chat.d.ts.map +1 -0
  97. package/dist/chat.js +8 -0
  98. package/dist/client.d.ts +33 -13
  99. package/dist/client.d.ts.map +1 -1
  100. package/dist/client.js +81 -16
  101. package/dist/collection.d.ts +23 -13
  102. package/dist/collection.d.ts.map +1 -1
  103. package/dist/collection.js +367 -38
  104. package/dist/connection.d.ts +54 -16
  105. package/dist/connection.d.ts.map +1 -1
  106. package/dist/connection.js +2582 -486
  107. package/dist/conversation.d.ts +39 -12
  108. package/dist/conversation.d.ts.map +1 -1
  109. package/dist/conversation.js +546 -171
  110. package/dist/digest.d.ts +2 -0
  111. package/dist/digest.d.ts.map +1 -0
  112. package/dist/digest.js +100 -0
  113. package/dist/durability.d.ts +7 -1
  114. package/dist/durability.d.ts.map +1 -1
  115. package/dist/entry.d.ts +17 -0
  116. package/dist/entry.d.ts.map +1 -0
  117. package/dist/entry.js +157 -0
  118. package/dist/expectation.d.ts +21 -0
  119. package/dist/expectation.d.ts.map +1 -0
  120. package/dist/expectation.js +70 -0
  121. package/dist/failure.d.ts +13 -0
  122. package/dist/failure.d.ts.map +1 -0
  123. package/dist/failure.js +19 -0
  124. package/dist/fence.d.ts +6 -0
  125. package/dist/fence.d.ts.map +1 -0
  126. package/dist/fence.js +10 -0
  127. package/dist/file.d.ts +2 -10
  128. package/dist/file.d.ts.map +1 -1
  129. package/dist/file.js +3 -120
  130. package/dist/host.d.ts +15 -3
  131. package/dist/host.d.ts.map +1 -1
  132. package/dist/host.js +107 -21
  133. package/dist/identity.d.ts +5 -0
  134. package/dist/identity.d.ts.map +1 -1
  135. package/dist/identity.js +19 -1
  136. package/dist/index.d.ts +20 -10
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +7 -0
  139. package/dist/memory.d.ts +11 -4
  140. package/dist/memory.d.ts.map +1 -1
  141. package/dist/memory.js +92 -27
  142. package/dist/model.d.ts +7 -4
  143. package/dist/model.d.ts.map +1 -1
  144. package/dist/model.js +10 -1
  145. package/dist/observation.d.ts +7 -2
  146. package/dist/observation.d.ts.map +1 -1
  147. package/dist/observation.js +29 -12
  148. package/dist/open.d.ts +2 -0
  149. package/dist/open.d.ts.map +1 -0
  150. package/dist/open.js +1 -0
  151. package/dist/outcome.d.ts +57 -3
  152. package/dist/outcome.d.ts.map +1 -1
  153. package/dist/outcome.js +67 -1
  154. package/dist/overlay.d.ts +18 -5
  155. package/dist/overlay.d.ts.map +1 -1
  156. package/dist/overlay.js +271 -165
  157. package/dist/query.d.ts +61 -4
  158. package/dist/query.d.ts.map +1 -1
  159. package/dist/query.js +40 -7
  160. package/dist/reach.d.ts +13 -4
  161. package/dist/reach.d.ts.map +1 -1
  162. package/dist/reach.js +201 -47
  163. package/dist/react.d.ts +31 -20
  164. package/dist/react.d.ts.map +1 -1
  165. package/dist/react.js +13 -21
  166. package/dist/read-side.d.ts +18 -9
  167. package/dist/read-side.d.ts.map +1 -1
  168. package/dist/read-side.js +903 -157
  169. package/dist/readiness.d.ts +53 -0
  170. package/dist/readiness.d.ts.map +1 -0
  171. package/dist/readiness.js +33 -0
  172. package/dist/reads.d.ts +11 -0
  173. package/dist/reads.d.ts.map +1 -0
  174. package/dist/reads.js +98 -0
  175. package/dist/recovery.d.ts +25 -0
  176. package/dist/recovery.d.ts.map +1 -0
  177. package/dist/recovery.js +198 -0
  178. package/dist/refresh.d.ts +13 -3
  179. package/dist/refresh.d.ts.map +1 -1
  180. package/dist/refresh.js +73 -43
  181. package/dist/replica.d.ts +16 -8
  182. package/dist/replica.d.ts.map +1 -1
  183. package/dist/replica.js +198 -91
  184. package/dist/room-draft.d.ts +40 -9
  185. package/dist/room-draft.d.ts.map +1 -1
  186. package/dist/room-draft.js +153 -37
  187. package/dist/rows.d.ts +10 -13
  188. package/dist/rows.d.ts.map +1 -1
  189. package/dist/rows.js +19 -14
  190. package/dist/save.d.ts +72 -4
  191. package/dist/save.d.ts.map +1 -1
  192. package/dist/save.js +812 -145
  193. package/dist/search.d.ts +52 -8
  194. package/dist/search.d.ts.map +1 -1
  195. package/dist/search.js +242 -46
  196. package/dist/seed.d.ts +16 -0
  197. package/dist/seed.d.ts.map +1 -0
  198. package/dist/seed.js +75 -0
  199. package/dist/shared.d.ts +19 -0
  200. package/dist/shared.d.ts.map +1 -0
  201. package/dist/shared.js +461 -0
  202. package/dist/status.d.ts +6 -2
  203. package/dist/status.d.ts.map +1 -1
  204. package/dist/status.js +0 -8
  205. package/dist/store.d.ts +90 -23
  206. package/dist/store.d.ts.map +1 -1
  207. package/dist/store.js +675 -295
  208. package/dist/tables.d.ts +181 -0
  209. package/dist/tables.d.ts.map +1 -0
  210. package/dist/tables.js +309 -0
  211. package/dist/turn.d.ts +9 -0
  212. package/dist/turn.d.ts.map +1 -0
  213. package/dist/turn.js +67 -0
  214. package/dist/windows.d.ts +21 -0
  215. package/dist/windows.d.ts.map +1 -0
  216. package/dist/windows.js +84 -0
  217. package/dist/wire.d.ts +165 -33
  218. package/dist/wire.d.ts.map +1 -1
  219. package/dist/wire.js +67 -31
  220. package/package.json +30 -14
  221. package/dist/attachment.d.ts +0 -3
  222. package/dist/attachment.d.ts.map +0 -1
  223. package/dist/attachment.js +0 -8
package/dist/read-side.js CHANGED
@@ -1,7 +1,9 @@
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';
1
+ import { and, coalesce, configureIndexDevMode, count, createCollection, createLiveQueryCollection, deepEquals, eq, inArray, not, or, } from '@tanstack/db';
2
+ import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
3
+ import { rowsOf, } from './observation.js';
4
4
  import { documentKey } from './rows.js';
5
+ import { after, inUnits, ROW_WEIGHT, stepped } from './turn.js';
6
+ import { beyond, cursorOf, FROM_START, MAXIMUM_WINDOW } from './windows.js';
5
7
  function every(expressions) {
6
8
  const [first, second, ...rest] = expressions;
7
9
  return second === undefined ? first : and(first, second, ...rest);
@@ -54,16 +56,21 @@ function conditioned(condition, row) {
54
56
  function narrowed(query, where, alias) {
55
57
  return where === null ? query : query.fn.where((row) => conditioned(where, row[alias]));
56
58
  }
57
- function reached(reach, room, document) {
58
- return reach === null
59
- ? []
60
- : [or(inArray(room, [...reach.rooms]), inArray(document, [...reach.documents]))];
59
+ function reached(reach, locked, room, document) {
60
+ return [
61
+ ...(reach === null
62
+ ? []
63
+ : [or(inArray(room, [...reach.rooms]), inArray(document, [...reach.documents]))]),
64
+ ...(locked.length === 0 ? [] : [not(inArray(room, [...locked]))]),
65
+ ];
61
66
  }
62
- function scoped(scope, row, rooms, reach) {
67
+ function scoped(scope, row, rooms, reach, locked, paths = null, folder = null) {
63
68
  return every([
64
69
  eq(row.kind, 'document'),
65
70
  ...(rooms === null ? [] : [inArray(row.room, [...rooms])]),
66
- ...reached(reach, row.room, row.key),
71
+ ...(paths === null ? [] : [inArray(row.path, [...paths])]),
72
+ ...(folder === null ? [] : [eq(row.folder, folder)]),
73
+ ...reached(reach, locked, row.room, row.key),
67
74
  ...(scope.types === null ? [] : [inArray(row.type, [...scope.types])]),
68
75
  ...(scope.pathPrefix === null ? [] : [inArray(scope.pathPrefix, row.directories)]),
69
76
  ]);
@@ -71,82 +78,165 @@ function scoped(scope, row, rooms, reach) {
71
78
  function keyed(key, row) {
72
79
  return key.of === 'field' ? row.values[key.field] : row[key.of];
73
80
  }
81
+ function readied(live) {
82
+ return new Promise((resolve) => {
83
+ live.onFirstReady(resolve);
84
+ });
85
+ }
86
+ function streamed(value) {
87
+ return coalesce(value);
88
+ }
74
89
  function live(query) {
75
90
  return createLiveQueryCollection({
76
91
  query: query,
77
92
  startSync: true,
93
+ gcTime: 0,
78
94
  });
79
95
  }
80
- function related(q, rows, scope, field, kept, reach) {
96
+ function related(q, rows, scope, field, kept, reach, locked) {
81
97
  const matched = kept === 'source' ? 'target' : 'source';
82
98
  const from = q.from({ r: rows });
83
99
  const joined = from
84
100
  .where(({ r }) => every([eq(r.kind, 'reference'), ...(field === null ? [] : [inArray(field, r.forms)])]))
85
- .innerJoin({ m: rows }, ({ r, m }) => eq(r[matched], m.key))
86
- .where(({ m }) => scoped(scope, m, null, reach));
101
+ .innerJoin({ m: rows }, ({ r, m }) => eq(streamed(r[matched]), streamed(m.key)))
102
+ .where(({ m }) => scoped(scope, m, null, reach, locked));
87
103
  return narrowed(joined, scope.where, 'm')
88
104
  .groupBy(({ r }) => r[kept])
89
105
  .select(({ r }) => ({ key: r[kept] }));
90
106
  }
91
107
  function filtered(q, rows, plan) {
92
108
  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');
109
+ let query = narrowed(from.where(({ d }) => scoped(plan.root, d, plan.rooms, plan.reach, plan.locked, plan.paths, plan.folder)), plan.root.where, 'd');
94
110
  if (plan.hop !== null) {
95
- const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach);
96
- query = query.innerJoin({ h: hop }, ({ d, h }) => eq(d.key, h.key));
111
+ const hop = related(q, rows, plan.hop, plan.hop.field, 'source', plan.reach, plan.locked);
112
+ query = query.innerJoin({ h: hop }, ({ d, h }) => eq(streamed(d.key), streamed(h.key)));
97
113
  }
98
114
  if (plan.backlinks !== null) {
99
- const backlinks = related(q, rows, plan.backlinks, plan.backlinks.field, 'target', plan.reach);
100
- query = query.innerJoin({ k: backlinks }, ({ d, k }) => eq(d.key, k.key));
115
+ const backlinks = related(q, rows, plan.backlinks, plan.backlinks.field, 'target', plan.reach, plan.locked);
116
+ query = query.innerJoin({ k: backlinks }, ({ d, k }) => eq(streamed(d.key), streamed(k.key)));
101
117
  }
102
118
  return query;
103
119
  }
104
- function named(prefix, names, source) {
105
- return Object.fromEntries(names.map((name, index) => [`${prefix}${index}`, source[name]]));
120
+ function bodied(shape) {
121
+ return shape.sections !== null || shape.preamble || shape.markdown;
122
+ }
123
+ function orderingOf(options) {
124
+ const collated = options.stringSort === 'locale';
125
+ return {
126
+ direction: options.direction,
127
+ nulls: options.nulls ?? 'first',
128
+ stringSort: options.stringSort ?? 'locale',
129
+ locale: collated ? options.locale : undefined,
130
+ localeOptions: collated
131
+ ? options.localeOptions
132
+ : undefined,
133
+ };
134
+ }
135
+ function storedValue(key, row) {
136
+ return key.of === 'field' ? row.values[key.field] : row[key.of];
137
+ }
138
+ function documentOrder(shape) {
139
+ return shape.orderBy.map(({ options }) => orderingOf(options));
140
+ }
141
+ function documentCursor(shape, row) {
142
+ return cursorOf([
143
+ ...shape.orderBy.map((_, index) => row[`order${index}`]),
144
+ documentKey(row.room, row.path),
145
+ ]);
106
146
  }
107
147
  function documents(q, rows, plan, shape) {
108
148
  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));
149
+ const cursor = plan.after?.values ?? null;
150
+ if (cursor !== null) {
151
+ const orderings = documentOrder(shape);
152
+ query = query.fn.where(({ d }) => beyond([...shape.orderBy.map(({ key }) => storedValue(key, d)), d.key], cursor, orderings));
111
153
  }
112
154
  for (const { key, options } of shape.orderBy) {
113
- query = query.orderBy(({ d }) => keyed(key, d), options);
155
+ query = query.orderBy(({ d }) => streamed(keyed(key, d)), options);
114
156
  }
115
- if (shape.orderBy.length === 0 && plan.limit !== null) {
116
- query = query.orderBy(({ d }) => d.key, { stringSort: 'lexical' });
157
+ if (shape.orderBy.length > 0 || plan.limit !== null || plan.after !== null) {
158
+ query = query.orderBy(({ d }) => streamed(d.key), { stringSort: 'lexical' });
117
159
  }
118
160
  if (plan.limit !== null)
119
- query = query.limit(plan.limit);
120
- return query.select(({ d, b }) => ({
161
+ query = query.limit(plan.limit + 1);
162
+ return query.select(({ d }) => ({
121
163
  room: d.room,
122
164
  path: d.path,
123
165
  type: d.type,
166
+ binary: d.binary,
167
+ ...Object.fromEntries(shape.orderBy.map(({ key }, index) => [`order${index}`, keyed(key, d)])),
124
168
  ...(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 } : {}),
129
- ...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
169
+ ...(bodied(shape) ? { text: d.text } : {}),
130
170
  ...(shape.change ? { change: d.change } : {}),
171
+ ...(shape.fileId ? { fileId: d.fileId } : {}),
131
172
  }));
132
173
  }
133
- function backlinked(q, rows, document, reach) {
174
+ function references(q, rows, reach, locked) {
134
175
  const from = q.from({ r: rows });
135
176
  return from
136
- .where(({ r }) => every([eq(r.target, document.key), ...reached(reach, r.room, r.source)]))
137
- .orderBy(({ r }) => r.path, { stringSort: 'lexical' })
138
- .orderBy(({ r }) => r.field, { stringSort: 'lexical' })
139
- .select(({ r }) => ({ path: r.path, type: r.type, field: r.field }));
177
+ .where(({ r }) => every([eq(r.kind, 'reference'), ...reached(reach, locked, r.room, r.source)]))
178
+ .select(({ r }) => ({
179
+ key: r.key,
180
+ target: r.target,
181
+ path: r.path,
182
+ type: r.type,
183
+ field: r.field,
184
+ }));
185
+ }
186
+ function lexical(left, right) {
187
+ return left < right ? -1 : left > right ? 1 : 0;
188
+ }
189
+ function linkOrder([leftKey, left], [rightKey, right]) {
190
+ return (lexical(left.path, right.path) || lexical(left.field, right.field) || lexical(leftKey, rightKey));
191
+ }
192
+ function linkAt(held, entry) {
193
+ let low = 0;
194
+ for (let high = held.length; low < high;) {
195
+ const middle = (low + high) >>> 1;
196
+ if (linkOrder(held[middle], entry) < 0)
197
+ low = middle + 1;
198
+ else
199
+ high = middle;
200
+ }
201
+ return low;
202
+ }
203
+ function targetOf(change) {
204
+ return change.value.target;
205
+ }
206
+ function relink(linked, { type, key, value }) {
207
+ const row = value;
208
+ const entry = [String(key), { path: row.path, type: row.type, field: row.field }];
209
+ const held = linked.get(row.target) ?? [];
210
+ const at = linkAt(held, entry);
211
+ const present = held[at]?.[0] === entry[0];
212
+ const shifted = (type === 'delete') === present ? held.length - at : 0;
213
+ if (type === 'delete') {
214
+ if (present)
215
+ held.splice(at, 1);
216
+ }
217
+ else if (present)
218
+ held[at] = entry;
219
+ else
220
+ held.splice(at, 0, entry);
221
+ if (held.length === 0)
222
+ linked.delete(row.target);
223
+ else
224
+ linked.set(row.target, held);
225
+ return shifted;
140
226
  }
141
- function groups(q, stage, shape, limit) {
227
+ function relinked(linked, changes) {
228
+ return strides(changes, (change) => ROW_WEIGHT + relink(linked, change) / HELD_PER_WEIGHT);
229
+ }
230
+ function linking(links, received) {
231
+ return links.subscribeChanges(received, { includeInitialState: true });
232
+ }
233
+ function groups(q, stage, shape) {
142
234
  const keys = shape.groupBy.map((_, index) => `key${index}`);
143
235
  const from = q.from({ m: stage });
144
236
  let query = from.groupBy(({ m }) => keys.map((key) => m[key]));
145
237
  for (const { by, direction } of shape.orderBy) {
146
238
  query = query.orderBy(({ m }) => (by === 'count' ? count(m.key) : m[keys[by]]), direction);
147
239
  }
148
- if (limit !== null && shape.orderBy.length > 0)
149
- query = query.limit(limit);
150
240
  return query.select(({ m }) => ({
151
241
  ...Object.fromEntries(keys.map((key) => [key, m[key]])),
152
242
  count: count(m.key),
@@ -156,28 +246,56 @@ function built(rows, plan) {
156
246
  const shape = plan.shape;
157
247
  if (shape.kind === 'documents') {
158
248
  const collection = live((q) => documents(q, rows, plan, shape));
159
- return { live: collection, dispose: () => void collection.cleanup() };
249
+ const count = plan.total
250
+ ? live((q) => filtered(q, rows, plan).select(({ d }) => ({ key: d.key })))
251
+ : null;
252
+ const links = shape.backlinks
253
+ ? live((q) => references(q, rows, plan.reach, plan.locked))
254
+ : null;
255
+ return {
256
+ live: collection,
257
+ count,
258
+ links,
259
+ dispose: () => {
260
+ void collection.cleanup();
261
+ if (count !== null)
262
+ void count.cleanup();
263
+ if (links !== null)
264
+ void links.cleanup();
265
+ },
266
+ };
160
267
  }
161
268
  const stage = live((q) => filtered(q, rows, plan).select(({ d }) => ({
162
269
  key: d.key,
163
270
  ...Object.fromEntries(shape.groupBy.map((key, index) => [`key${index}`, keyed(key, d)])),
164
271
  })));
165
- const collection = live((q) => groups(q, stage, shape, plan.limit));
272
+ const collection = live((q) => groups(q, stage, shape));
166
273
  return {
167
274
  live: collection,
275
+ count: null,
276
+ links: null,
168
277
  dispose: () => {
169
278
  void collection.cleanup();
170
279
  void stage.cleanup();
171
280
  },
172
281
  };
173
282
  }
174
- function present(entries) {
175
- return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
176
- }
177
- function part(row, prefix, names, whole) {
283
+ function sectionsOf(sections, names) {
178
284
  if (names === 'all')
179
- return whole;
180
- return present(names.map((name, index) => [name, row[`${prefix}${index}`]]));
285
+ return sections;
286
+ return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
287
+ }
288
+ function bodyOf(row, key, bodies) {
289
+ const text = row.text;
290
+ if (text === null)
291
+ return null;
292
+ const held = bodies?.get(key);
293
+ if (held !== undefined && held.text === text)
294
+ return held.document;
295
+ const read = parse(text, row.path);
296
+ const document = read.ok ? read.document : null;
297
+ bodies?.set(key, { text, document });
298
+ return document;
181
299
  }
182
300
  function masked(pointers) {
183
301
  const root = { whole: false, members: new Map() };
@@ -228,9 +346,17 @@ function cut(value, mask) {
228
346
  function selected(fields, pointers) {
229
347
  return (cut(fields, masked(pointers)) ?? {});
230
348
  }
231
- function documentOf(row, shape) {
232
- if (row.type === null)
233
- return { room: row.room, path: row.path };
349
+ function documentOf(key, row, shape, bodies, linked) {
350
+ if (row.type === null) {
351
+ const binary = row.binary;
352
+ return {
353
+ room: row.room,
354
+ path: row.path,
355
+ ...(binary === null ? {} : { binaryFile: binary }),
356
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
357
+ };
358
+ }
359
+ const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
234
360
  return {
235
361
  room: row.room,
236
362
  path: row.path,
@@ -242,192 +368,812 @@ function documentOf(row, shape) {
242
368
  ? row.fields
243
369
  : selected(row.fields, shape.fields),
244
370
  }),
245
- ...(shape.sections === null
371
+ ...(shape.sections === null || body === null
246
372
  ? {}
247
- : {
248
- sections: part(row, 'section', shape.sections, row.sections),
249
- }),
250
- ...(shape.preamble ? { preamble: row.preamble } : {}),
251
- ...(shape.markdown ? { markdown: row.markdown } : {}),
373
+ : { sections: sectionsOf(body.sections, shape.sections) }),
374
+ ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
375
+ ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
252
376
  ...(shape.backlinks
253
377
  ? {
254
- backlinks: row.backlinks.map(({ path, type, field }) => ({ path, type, field })),
378
+ backlinks: (linked.get(documentKey(row.room, row.path)) ?? [])
379
+ .slice(0, MAXIMUM_WINDOW)
380
+ .map(([, link]) => link),
255
381
  }
256
382
  : {}),
257
383
  ...(shape.change ? { change: row.change } : {}),
384
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
258
385
  };
259
386
  }
260
- function keyedOf(plan, entries) {
261
- const shape = plan.shape;
262
- if (shape.kind === 'documents') {
263
- return {
264
- result: structuredClone({ documents: entries.map(([, row]) => documentOf(row, shape)) }),
265
- keys: entries.map(([key]) => key),
266
- };
387
+ function valueAt(row, path) {
388
+ let value = row;
389
+ for (const part of path)
390
+ value = value?.[part];
391
+ return value;
392
+ }
393
+ function weight(row) {
394
+ return ROW_WEIGHT + (typeof row?.text === 'string' ? row.text.length : 0);
395
+ }
396
+ const HELD_PER_WEIGHT = 16;
397
+ const STRIDE = ROW_WEIGHT * 16;
398
+ function* strides(items, each) {
399
+ let spent = 0;
400
+ for (const item of items) {
401
+ spent += each(item);
402
+ if (spent < STRIDE)
403
+ continue;
404
+ yield spent;
405
+ spent = 0;
267
406
  }
268
- const bounded = shape.orderBy.length === 0 && plan.limit !== null ? entries.slice(0, plan.limit) : entries;
407
+ if (spent > 0)
408
+ yield spent;
409
+ }
410
+ function joined(plan) {
411
+ return (plan.hop !== null ||
412
+ plan.backlinks !== null ||
413
+ (plan.shape.kind === 'documents' && plan.shape.backlinks));
414
+ }
415
+ function admission(plan) {
416
+ if (joined(plan))
417
+ return () => true;
418
+ const rooms = plan.rooms === null ? null : new Set(plan.rooms);
419
+ const paths = plan.paths === null ? null : new Set(plan.paths);
420
+ return (row) => row.kind === 'document' &&
421
+ (rooms === null || rooms.has(row.room)) &&
422
+ (paths === null || paths.has(row.path));
423
+ }
424
+ function groupValues(shape, [key, row]) {
425
+ return [...shape.orderBy.map(({ by }) => (by === 'count' ? row.count : row[`key${by}`])), key];
426
+ }
427
+ function groupOrder(shape) {
428
+ return shape.orderBy.map(({ direction }) => ({
429
+ direction,
430
+ nulls: 'first',
431
+ stringSort: 'locale',
432
+ locale: undefined,
433
+ localeOptions: undefined,
434
+ }));
435
+ }
436
+ function* windowOf(plan, keys, valueOf, total, each) {
437
+ const shape = plan.shape;
438
+ const cursor = shape.kind === 'groups' ? (plan.after?.values ?? null) : null;
439
+ const orderings = shape.kind === 'groups' ? groupOrder(shape) : [];
440
+ let all = 0;
441
+ let following = 0;
442
+ let last;
443
+ yield* strides(keys, (key) => {
444
+ all += 1;
445
+ const admitted = shape.kind === 'documents' ||
446
+ cursor === null ||
447
+ beyond(groupValues(shape, [key, valueOf(key)]), cursor, orderings);
448
+ if (admitted)
449
+ following += 1;
450
+ if (!admitted || (plan.limit !== null && following > plan.limit))
451
+ return ROW_WEIGHT;
452
+ last = key;
453
+ return each(key);
454
+ });
455
+ const next = plan.limit === null || following <= plan.limit
456
+ ? undefined
457
+ : last === undefined
458
+ ? (plan.after?.text ?? FROM_START)
459
+ : shape.kind === 'documents'
460
+ ? documentCursor(shape, valueOf(last))
461
+ : cursorOf(groupValues(shape, [last, valueOf(last)]));
462
+ return { next, total: shape.kind === 'documents' ? total : plan.total ? all : undefined };
463
+ }
464
+ function framed(rows, { next, total }) {
269
465
  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),
466
+ ...rows,
467
+ ...(next === undefined ? {} : { next }),
468
+ ...(total === undefined ? {} : { total }),
277
469
  };
278
470
  }
279
- export function reconciled(previous, next) {
280
- const before = rowsOf(previous.result);
281
- const held = new Map(previous.keys.map((key, index) => [key, before[index]]));
471
+ function rowOf(shape, [key, row], bodies, linked) {
472
+ if (shape.kind === 'documents')
473
+ return documentOf(key, row, shape, bodies, linked);
474
+ return { key: shape.groupBy.map((_, index) => row[`key${index}`]), count: row.count };
475
+ }
476
+ function resultOf(grouped, rows, coverage, window) {
477
+ return grouped
478
+ ? framed({ groups: rows, coverage }, window)
479
+ : framed({ documents: rows, coverage }, window);
480
+ }
481
+ function* rowsOfPlan(plan, keys, valueOf, total, linked) {
482
+ const rows = [];
483
+ const window = yield* windowOf(plan, keys, valueOf, total, (key) => {
484
+ const value = valueOf(key);
485
+ rows.push(structuredClone(rowOf(plan.shape, [key, value], null, linked)));
486
+ return weight(value);
487
+ });
488
+ return plan.shape.kind === 'groups'
489
+ ? framed({ groups: rows }, window)
490
+ : framed({ documents: rows }, window);
491
+ }
492
+ function textOf(row) {
493
+ if (!('path' in row))
494
+ return 0;
495
+ let length = (row.markdown?.length ?? 0) + (row.preamble?.length ?? 0);
496
+ for (const section of Object.values(row.sections ?? {}))
497
+ length += section.length;
498
+ return length;
499
+ }
500
+ export function* reconciled(previous, next) {
501
+ const before = previous.result;
502
+ const after = next.result;
503
+ if ('pending' in before || 'pending' in after) {
504
+ if (deepEquals(before, after))
505
+ return null;
506
+ return { keyed: next, message: next };
507
+ }
508
+ const earlier = rowsOf(before);
509
+ const held = new Map();
510
+ yield* strides(previous.keys.entries(), ([index, key]) => {
511
+ held.set(key, earlier[index]);
512
+ return ROW_WEIGHT;
513
+ });
514
+ const later = rowsOf(after);
515
+ const present = new Set();
282
516
  const changed = [];
283
- const rows = rowsOf(next.result).map((row, index) => {
284
- const key = next.keys[index];
517
+ const rows = [];
518
+ yield* strides(next.keys.entries(), ([index, key]) => {
519
+ const row = later[index];
285
520
  const kept = held.get(key);
521
+ present.add(key);
286
522
  if (kept !== undefined && deepEquals(kept, row))
287
- return kept;
288
- changed.push([key, row]);
289
- return row;
523
+ rows.push(kept);
524
+ else {
525
+ changed.push([key, row]);
526
+ rows.push(row);
527
+ }
528
+ return ROW_WEIGHT + textOf(row);
290
529
  });
291
- const present = new Set(next.keys);
292
- const survivors = previous.keys.filter((key) => present.has(key));
293
- 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)
530
+ const keyed = {
531
+ result: resultOf('groups' in after, rows, after.coverage, {
532
+ next: after.next,
533
+ total: after.total,
534
+ }),
535
+ keys: next.keys,
536
+ };
537
+ const message = yield* changeOf(previous, keyed, changed, (key) => present.has(key));
538
+ return message === null ? null : { keyed, message };
539
+ }
540
+ function* changeOf(previous, next, changed, present, dropped = () => { }) {
541
+ const before = previous.result;
542
+ const after = next.result;
543
+ const removed = [];
544
+ let at = 0;
545
+ let moved = false;
546
+ yield* strides(previous.keys.entries(), ([index, key]) => {
547
+ if (present(key, index)) {
548
+ moved ||= next.keys[at] !== key;
549
+ at += 1;
550
+ }
551
+ else {
552
+ removed.push(key);
553
+ dropped(key);
554
+ }
555
+ return ROW_WEIGHT;
556
+ });
557
+ moved ||= at !== next.keys.length;
558
+ const covered = !deepEquals(before.coverage, after.coverage);
559
+ const framing = reframed(before, after);
560
+ if (changed.length === 0 && removed.length === 0 && !moved && !covered && framing === null) {
297
561
  return null;
562
+ }
298
563
  return {
299
- keyed: {
300
- result: 'groups' in next.result
301
- ? { groups: rows }
302
- : { documents: rows },
303
- keys: next.keys,
304
- },
305
- change: { changed, removed, order: moved ? next.keys : null },
564
+ changed,
565
+ removed,
566
+ order: moved ? next.keys : null,
567
+ ...(covered ? { coverage: after.coverage } : {}),
568
+ ...framing,
306
569
  };
307
570
  }
571
+ function reframed(before, after) {
572
+ const next = before.next === after.next ? {} : { next: after.next ?? null };
573
+ const total = before.total === after.total || after.total === undefined ? {} : { total: after.total };
574
+ return Object.keys(next).length === 0 && Object.keys(total).length === 0
575
+ ? null
576
+ : { ...next, ...total };
577
+ }
308
578
  export function createReadSide() {
309
579
  configureIndexDevMode({ enabled: false });
310
- let sync;
311
- const rows = createCollection({
312
- getKey: (row) => row.key,
313
- gcTime: 0,
314
- startSync: true,
315
- autoIndex: 'eager',
316
- defaultIndexType: BasicIndex,
317
- sync: {
318
- rowUpdateMode: 'full',
319
- sync: (params) => {
320
- sync = params;
321
- params.markReady();
322
- },
323
- },
324
- });
580
+ const rows = new Map();
581
+ const roomPaths = new Map();
582
+ const views = new Set();
325
583
  const shared = new Map();
584
+ let writing = false;
585
+ let publishing = null;
586
+ let closed = false;
587
+ function viewOf(plan) {
588
+ let sync;
589
+ const view = {
590
+ plan,
591
+ rows: createCollection({
592
+ getKey: (row) => row.key,
593
+ gcTime: 0,
594
+ startSync: true,
595
+ autoIndex: 'off',
596
+ sync: {
597
+ rowUpdateMode: 'full',
598
+ sync: (params) => {
599
+ sync = params;
600
+ },
601
+ },
602
+ }),
603
+ sync,
604
+ admits: admission(plan),
605
+ stopped: false,
606
+ waiting: null,
607
+ };
608
+ views.add(view);
609
+ return view;
610
+ }
611
+ function freeze(view) {
612
+ view.waiting ??= new Map();
613
+ }
614
+ function thaw(view) {
615
+ const waiting = view.waiting;
616
+ if (waiting === null)
617
+ return;
618
+ let unit = new Map();
619
+ return after(inUnits(waiting, ([key, row]) => {
620
+ waiting.delete(key);
621
+ unit.set(key, row);
622
+ const was = view.rows.get(key);
623
+ return weight(row ?? undefined) + charged(view, was, row ?? undefined);
624
+ }, () => {
625
+ synced(view, unit);
626
+ unit = new Map();
627
+ }), () => {
628
+ if (waiting.size > 0)
629
+ return thaw(view);
630
+ view.waiting = null;
631
+ });
632
+ }
633
+ function dropped(view) {
634
+ view.stopped = true;
635
+ views.delete(view);
636
+ void view.rows.cleanup();
637
+ }
638
+ function* offered(view) {
639
+ const { plan } = view;
640
+ const { rooms, paths } = plan;
641
+ if (joined(plan) || rooms === null || paths === null) {
642
+ for (const key of rows.keys()) {
643
+ if (view.stopped)
644
+ return;
645
+ yield key;
646
+ }
647
+ return;
648
+ }
649
+ for (const room of rooms) {
650
+ for (const path of paths) {
651
+ if (view.stopped)
652
+ return;
653
+ yield documentKey(room, path);
654
+ }
655
+ }
656
+ }
657
+ function written(view, writes) {
658
+ if (view.waiting === null)
659
+ synced(view, writes);
660
+ else
661
+ for (const [key, row] of writes)
662
+ view.waiting.set(key, row);
663
+ }
664
+ function synced(view, writes) {
665
+ if (view.stopped || writes.size === 0)
666
+ return;
667
+ view.sync.begin();
668
+ for (const [key, row] of writes) {
669
+ if (row !== null) {
670
+ view.sync.write({ type: view.rows.has(key) ? 'update' : 'insert', value: row });
671
+ }
672
+ else if (view.rows.has(key))
673
+ view.sync.write({ type: 'delete', key });
674
+ }
675
+ view.sync.commit();
676
+ }
677
+ function filled(view) {
678
+ let unit = new Map();
679
+ return after(inUnits(offered(view), (key) => {
680
+ const row = rows.get(key);
681
+ if (row === undefined || !view.admits(row) || view.rows.has(key))
682
+ return ROW_WEIGHT;
683
+ unit.set(key, row);
684
+ return weight(row) + charged(view, undefined, row);
685
+ }, () => {
686
+ written(view, unit);
687
+ unit = new Map();
688
+ }), () => {
689
+ if (!view.stopped)
690
+ view.sync.markReady();
691
+ });
692
+ }
326
693
  function searched(row) {
327
- const body = rows.get(row.body);
694
+ const read = row.text === null ? null : parse(row.text, row.path);
695
+ if (read === null || !read.ok)
696
+ return null;
328
697
  return {
329
698
  room: row.room,
330
699
  path: row.path,
331
700
  type: row.type,
332
701
  fields: row.fields,
333
- preamble: body.preamble,
334
- sections: body.sections,
702
+ preamble: read.document.preamble,
703
+ sections: read.document.sections,
335
704
  };
336
705
  }
337
- function publish(entry) {
338
- const next = reconciled(entry.result, keyedOf(entry.plan, [...entry.built.live.entries()]));
339
- if (next === null)
340
- return;
341
- entry.result = next.keyed;
342
- for (const listener of entry.listeners)
343
- listener(next.change);
706
+ function counted(entry) {
707
+ return entry.built.count === null ? undefined : entry.built.count.size;
344
708
  }
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);
709
+ function* delivery(entry, whole, dirty, touched, relinking, coverage) {
710
+ const { plan } = entry;
711
+ const live = entry.built.live;
712
+ const valueOf = (key) => live.get(key);
713
+ const previous = entry.result;
714
+ const answered = previous === undefined || 'pending' in previous.result ? null : previous;
715
+ const answeredKeys = answered?.keys ?? [];
716
+ const answeredRows = answered === null ? [] : rowsOf(answered.result);
717
+ const matched = new Uint8Array(answeredKeys.length);
718
+ const generation = (entry.generation += 1);
719
+ const keys = [];
720
+ const changed = [];
721
+ const rows = [];
722
+ let at = 0;
723
+ const touches = (key) => {
724
+ const { room, path } = valueOf(key);
725
+ return touched.has(documentKey(room, path));
726
+ };
727
+ const behind = (key) => !live.has(key) || entry.rows.get(key)?.at === generation;
728
+ yield* relinked(entry.linked, relinking);
729
+ const window = yield* windowOf(plan, live.keys(), valueOf, counted(entry), (key) => {
730
+ let skipped = 0;
731
+ while (skipped < STRIDE &&
732
+ at < answeredKeys.length &&
733
+ answeredKeys[at] !== key &&
734
+ behind(answeredKeys[at])) {
735
+ at += 1;
736
+ skipped += ROW_WEIGHT;
737
+ }
738
+ let holder;
739
+ let prior;
740
+ if (at < answeredKeys.length && answeredKeys[at] === key) {
741
+ prior = answeredRows[at];
742
+ matched[at] = 1;
743
+ at += 1;
744
+ }
745
+ else {
746
+ holder = entry.rows.get(key);
747
+ prior = holder?.row;
748
+ if (holder !== undefined)
749
+ holder.at = generation;
750
+ }
751
+ let row = prior;
752
+ let spent = ROW_WEIGHT;
753
+ if (row === undefined || whole || dirty.has(key) || (touched.size > 0 && touches(key))) {
754
+ const value = valueOf(key);
755
+ const renewed = rowOf(plan.shape, [key, value], entry.bodies, entry.linked);
756
+ if (row === undefined || !deepEquals(row, renewed))
757
+ row = renewed;
758
+ spent = weight(value);
759
+ }
760
+ if (row !== prior) {
761
+ holder ??= entry.rows.get(key);
762
+ if (holder === undefined)
763
+ entry.rows.set(key, { row, at: generation });
764
+ else
765
+ holder.row = row;
766
+ changed.push([key, row]);
353
767
  }
354
- if (next.size === 0)
768
+ keys.push(key);
769
+ rows.push(row);
770
+ return skipped + spent;
771
+ });
772
+ const result = {
773
+ result: resultOf(plan.shape.kind === 'groups', rows, coverage, window),
774
+ keys,
775
+ };
776
+ if (previous === undefined)
777
+ return { result, message: null };
778
+ if (answered === null)
779
+ return { result, message: result };
780
+ const message = yield* changeOf(answered, result, changed, (key, index) => matched[index] === 1 || entry.rows.get(key)?.at === generation, (key) => {
781
+ entry.rows.delete(key);
782
+ entry.bodies.delete(key);
783
+ });
784
+ return { result, message };
785
+ }
786
+ function delivered(entry, { result, message }) {
787
+ entry.result = result;
788
+ if (message !== null)
789
+ for (const listener of entry.ready)
790
+ listener(message);
791
+ for (const listener of entry.fresh) {
792
+ entry.ready.add(listener);
793
+ listener(result);
794
+ }
795
+ entry.fresh.clear();
796
+ }
797
+ function run(entry) {
798
+ while (entry.listeners.size > 0 &&
799
+ (entry.whole ||
800
+ entry.dirty.size > 0 ||
801
+ entry.touched.size > 0 ||
802
+ entry.stale ||
803
+ entry.fresh.size > 0)) {
804
+ entry.stale = false;
805
+ const cover = entry.cover();
806
+ if ('pending' in cover) {
807
+ entry.whole = true;
808
+ entry.dirty.clear();
809
+ entry.touched.clear();
810
+ entry.rows.clear();
811
+ entry.bodies.clear();
812
+ const result = { result: cover, keys: [] };
813
+ const previous = entry.result;
814
+ const same = previous === undefined || deepEquals(previous.result, cover);
815
+ delivered(entry, { result, message: same ? null : result });
816
+ break;
817
+ }
818
+ const whole = entry.whole ||
819
+ entry.plan.shape.kind === 'groups' ||
820
+ entry.result === undefined ||
821
+ 'pending' in entry.result.result;
822
+ const dirty = entry.dirty;
823
+ const touched = entry.touched;
824
+ const relinking = entry.relinking;
825
+ entry.dirty = new Set();
826
+ entry.touched = new Set();
827
+ entry.relinking = [];
828
+ entry.whole = false;
829
+ freeze(entry.view);
830
+ let made;
831
+ try {
832
+ made = stepped(delivery(entry, whole, dirty, touched, relinking, cover.coverage));
833
+ }
834
+ catch (error) {
835
+ void thaw(entry.view);
836
+ throw error;
837
+ }
838
+ if (made instanceof Promise) {
839
+ void made
840
+ .then((delivering) => {
841
+ if (entry.listeners.size > 0)
842
+ delivered(entry, delivering);
843
+ })
844
+ .finally(() => thaw(entry.view))
845
+ .then(() => {
846
+ entry.running = false;
847
+ wake(entry);
848
+ }, () => {
849
+ entry.running = false;
850
+ });
355
851
  return;
356
- sync.begin();
852
+ }
853
+ void thaw(entry.view);
854
+ delivered(entry, made);
855
+ }
856
+ entry.running = false;
857
+ }
858
+ function wake(entry) {
859
+ if (entry.subscription === null || entry.running || writing || publishing !== null)
860
+ return;
861
+ entry.running = true;
862
+ try {
863
+ run(entry);
864
+ }
865
+ catch (error) {
866
+ entry.running = false;
867
+ throw error;
868
+ }
869
+ }
870
+ function recover() {
871
+ for (const entry of shared.values()) {
872
+ if (entry.result === undefined || entry.subscription === null)
873
+ continue;
874
+ const cover = entry.cover();
875
+ const before = entry.result.result;
876
+ const held = 'pending' in cover
877
+ ? deepEquals(before, cover)
878
+ : 'coverage' in before && deepEquals(before.coverage, cover.coverage);
879
+ if (held)
880
+ continue;
881
+ entry.stale = true;
882
+ wake(entry);
883
+ }
884
+ }
885
+ function listed(key, row) {
886
+ const held = rows.get(key) ?? row;
887
+ if (held?.kind !== 'document')
888
+ return;
889
+ const paths = roomPaths.get(held.room) ?? new Set();
890
+ if (row?.kind === 'document' && row.type !== null)
891
+ paths.add(key);
892
+ else
893
+ paths.delete(key);
894
+ if (paths.size === 0)
895
+ roomPaths.delete(held.room);
896
+ else
897
+ roomPaths.set(held.room, paths);
898
+ }
899
+ function committed(next) {
900
+ if (closed || next.size === 0)
901
+ return;
902
+ for (const [key, row] of next) {
903
+ listed(key, row);
904
+ if (row === null)
905
+ rows.delete(key);
906
+ else
907
+ rows.set(key, row);
908
+ }
909
+ for (const view of views) {
910
+ const writes = new Map();
357
911
  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 });
912
+ if (row !== null && view.admits(row))
913
+ writes.set(key, row);
914
+ else if (view.rows.has(key) || view.waiting?.has(key))
915
+ writes.set(key, null);
916
+ }
917
+ written(view, writes);
918
+ }
919
+ }
920
+ function moved({ plan }, before, after) {
921
+ const shape = plan.shape;
922
+ if (shape.kind === 'documents' && plan.limit === null && shape.orderBy.length > 0) {
923
+ return true;
924
+ }
925
+ const keys = shape.kind === 'documents' ? shape.orderBy.map(({ key }) => key) : shape.groupBy;
926
+ for (const key of keys) {
927
+ const path = key.of === 'field' ? ['values', key.field] : [key.of];
928
+ if (!deepEquals(valueAt(before, path), valueAt(after, path)))
929
+ return true;
930
+ }
931
+ return false;
932
+ }
933
+ function charged(view, was, row) {
934
+ const kept = was !== undefined && view.admits(was);
935
+ const admitted = row !== undefined && view.admits(row);
936
+ if (!kept && !admitted)
937
+ return 0;
938
+ const shifted = view.rows.size / HELD_PER_WEIGHT;
939
+ return shifted + (kept && admitted && !moved(view, was, row) ? 0 : shifted);
940
+ }
941
+ function charges(was, row) {
942
+ let spent = 0;
943
+ for (const view of views)
944
+ spent += charged(view, was, row);
945
+ return spent;
946
+ }
947
+ function published(set) {
948
+ let unit = new Map();
949
+ return inUnits(set.documents, (change) => {
950
+ if (closed)
951
+ return 0;
952
+ const { before, after } = change;
953
+ const kept = new Set(after.map((row) => row.key));
954
+ const held = new Map(before.map((row) => [row.key, row]));
955
+ let spent = 0;
956
+ for (const row of before) {
957
+ unit.set(row.key, null);
958
+ spent += ROW_WEIGHT + (kept.has(row.key) ? 0 : charges(row, undefined));
364
959
  }
365
- sync.commit();
960
+ for (const row of after) {
961
+ unit.set(row.key, row);
962
+ spent += weight(row) + charges(held.get(row.key), row);
963
+ }
964
+ return spent;
965
+ }, () => {
966
+ committed(unit);
967
+ unit = new Map();
968
+ });
969
+ }
970
+ function released() {
971
+ recover();
972
+ for (const entry of shared.values())
973
+ wake(entry);
974
+ }
975
+ function publishedLater(applied) {
976
+ const fence = applied.then(() => { }, () => { });
977
+ publishing = fence;
978
+ void fence.then(() => {
979
+ if (publishing !== fence)
980
+ return;
981
+ publishing = null;
982
+ released();
983
+ });
984
+ return applied;
985
+ }
986
+ async function settled() {
987
+ while (publishing !== null)
988
+ await publishing;
989
+ }
990
+ return {
991
+ apply(set, next = () => { }) {
992
+ const pending = publishing;
993
+ if (pending !== null || set instanceof Promise) {
994
+ return publishedLater(Promise.all([set, pending]).then(([held]) => after(published(held), () => next(held))));
995
+ }
996
+ writing = true;
997
+ let applied;
998
+ try {
999
+ applied = published(set);
1000
+ }
1001
+ finally {
1002
+ writing = false;
1003
+ }
1004
+ if (applied instanceof Promise)
1005
+ return publishedLater(applied.then(() => next(set)));
1006
+ released();
1007
+ next(set);
366
1008
  },
367
- async query(plan) {
368
- const { live: collection, dispose } = built(rows, plan);
1009
+ recover,
1010
+ settled,
1011
+ async query(plan, at) {
1012
+ const view = viewOf(plan);
1013
+ const linked = new Map();
1014
+ let made = null;
1015
+ let following = null;
369
1016
  try {
370
- await collection.toArrayWhenReady();
371
- return keyedOf(plan, [...collection.entries()]).result;
1017
+ const filling = filled(view);
1018
+ made = built(view.rows, plan);
1019
+ const { live: collection, count, links } = made;
1020
+ if (links !== null) {
1021
+ following = linking(links, (changes) => {
1022
+ for (const change of changes)
1023
+ relink(linked, change);
1024
+ });
1025
+ }
1026
+ await filling;
1027
+ for (const one of [collection, count, links])
1028
+ if (one !== null)
1029
+ await readied(one);
1030
+ await settled();
1031
+ freeze(view);
1032
+ following?.unsubscribe();
1033
+ following = null;
1034
+ const held = at();
1035
+ const rows = await stepped(rowsOfPlan(plan, collection.keys(), (key) => collection.get(key), count?.size, linked));
1036
+ return { rows, at: held };
372
1037
  }
373
1038
  finally {
374
- dispose();
1039
+ following?.unsubscribe();
1040
+ made?.dispose();
1041
+ dropped(view);
375
1042
  }
376
1043
  },
377
- watch(plan, identity, deliver) {
1044
+ watch(plan, identity, cover, deliverTo) {
378
1045
  let entry = shared.get(identity);
379
1046
  if (entry === undefined) {
1047
+ const view = viewOf(plan);
1048
+ void filled(view);
380
1049
  const created = {
381
1050
  plan,
382
- built: built(rows, plan),
1051
+ view,
1052
+ built: built(view.rows, plan),
1053
+ cover,
383
1054
  result: undefined,
1055
+ rows: new Map(),
1056
+ generation: 0,
1057
+ bodies: new Map(),
384
1058
  listeners: new Set(),
1059
+ fresh: new Set(),
1060
+ ready: new Set(),
385
1061
  subscription: null,
1062
+ counting: null,
1063
+ linking: null,
1064
+ linked: new Map(),
1065
+ relinking: [],
1066
+ touched: new Set(),
1067
+ whole: true,
1068
+ dirty: new Set(),
1069
+ stale: false,
1070
+ running: false,
386
1071
  };
1072
+ if (created.built.links !== null) {
1073
+ created.linking = linking(created.built.links, (changes) => {
1074
+ for (const change of changes) {
1075
+ created.relinking.push(change);
1076
+ created.touched.add(targetOf(change));
1077
+ }
1078
+ if (created.subscription !== null)
1079
+ queueMicrotask(() => wake(created));
1080
+ });
1081
+ }
387
1082
  created.built.live.on('status:change', ({ status }) => {
388
- if (status === 'cleaned-up' && created.listeners.size === 0)
1083
+ if (status !== 'cleaned-up' || created.listeners.size > 0)
1084
+ return;
1085
+ if (shared.get(identity) === created)
389
1086
  shared.delete(identity);
390
1087
  });
391
1088
  shared.set(identity, created);
392
1089
  entry = created;
393
1090
  }
394
1091
  const observed = entry;
395
- observed.listeners.add(deliver);
396
- observed.built.live.onFirstReady(() => {
397
- if (!observed.listeners.has(deliver))
1092
+ observed.listeners.add(deliverTo);
1093
+ const ready = (then) => observed.built.live.onFirstReady(() => {
1094
+ const linked = () => observed.built.links === null ? then() : observed.built.links.onFirstReady(then);
1095
+ return observed.built.count === null
1096
+ ? linked()
1097
+ : observed.built.count.onFirstReady(linked);
1098
+ });
1099
+ const settled = () => {
1100
+ if (observed.built.count === null)
1101
+ wake(observed);
1102
+ else
1103
+ queueMicrotask(() => wake(observed));
1104
+ };
1105
+ ready(() => {
1106
+ if (!observed.listeners.has(deliverTo))
398
1107
  return;
1108
+ observed.fresh.add(deliverTo);
399
1109
  if (observed.subscription === null) {
400
- observed.result = keyedOf(observed.plan, [...observed.built.live.entries()]);
401
- observed.subscription = observed.built.live.subscribeChanges(() => {
402
- publish(observed);
1110
+ observed.whole = true;
1111
+ observed.subscription = observed.built.live.subscribeChanges((changes) => {
1112
+ if (observed.plan.shape.kind === 'groups')
1113
+ observed.whole = true;
1114
+ else
1115
+ for (const { key } of changes)
1116
+ observed.dirty.add(key);
1117
+ settled();
403
1118
  }, { includeInitialState: false });
1119
+ observed.counting =
1120
+ observed.built.count?.subscribeChanges(() => {
1121
+ observed.stale = true;
1122
+ settled();
1123
+ }, { includeInitialState: false }) ?? null;
404
1124
  }
405
- deliver(observed.result);
1125
+ wake(observed);
406
1126
  });
407
1127
  return () => {
408
- if (!observed.listeners.delete(deliver) || observed.listeners.size > 0)
1128
+ observed.fresh.delete(deliverTo);
1129
+ observed.ready.delete(deliverTo);
1130
+ if (!observed.listeners.delete(deliverTo) || observed.listeners.size > 0)
409
1131
  return;
410
1132
  observed.subscription?.unsubscribe();
411
1133
  observed.subscription = null;
1134
+ observed.counting?.unsubscribe();
1135
+ observed.counting = null;
1136
+ observed.linking?.unsubscribe();
1137
+ observed.linking = null;
1138
+ observed.result = undefined;
1139
+ observed.rows.clear();
1140
+ observed.bodies.clear();
1141
+ observed.dirty.clear();
1142
+ observed.touched.clear();
1143
+ observed.whole = true;
1144
+ if (shared.get(identity) === observed)
1145
+ shared.delete(identity);
1146
+ observed.built.dispose();
1147
+ dropped(observed.view);
412
1148
  };
413
1149
  },
414
- documents() {
415
- return rows.toArray
416
- .filter((row) => row.kind === 'document' && row.type !== null)
417
- .map(searched);
1150
+ *room(room) {
1151
+ for (const key of roomPaths.get(room) ?? []) {
1152
+ const row = rows.get(key);
1153
+ if (row?.kind === 'document')
1154
+ yield row.path;
1155
+ }
418
1156
  },
419
1157
  document(room, path) {
420
1158
  const row = rows.get(documentKey(room, path));
421
- return row === undefined || row.type === null ? null : searched(row);
1159
+ return row?.kind !== 'document' || row.type === null ? null : searched(row);
422
1160
  },
423
1161
  close() {
1162
+ closed = true;
424
1163
  for (const entry of shared.values()) {
425
1164
  entry.listeners.clear();
1165
+ entry.fresh.clear();
1166
+ entry.ready.clear();
426
1167
  entry.subscription?.unsubscribe();
1168
+ entry.counting?.unsubscribe();
1169
+ entry.linking?.unsubscribe();
427
1170
  entry.built.dispose();
428
1171
  }
429
1172
  shared.clear();
430
- void rows.cleanup();
1173
+ for (const view of [...views])
1174
+ dropped(view);
1175
+ rows.clear();
1176
+ roomPaths.clear();
431
1177
  },
432
1178
  };
433
1179
  }