@agentshouse/mdruntime 0.1.0-c2aaa9c0e3ca4 → 0.1.0-c2c52158b7381

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 (199) hide show
  1. package/AGENTS.md +285 -276
  2. package/README.md +9 -8
  3. package/dist/adapter.d.ts +80 -21
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/attached-model.d.ts +4 -1
  6. package/dist/attached-model.d.ts.map +1 -1
  7. package/dist/attached-model.js +13 -3
  8. package/dist/attempt.d.ts.map +1 -1
  9. package/dist/attempt.js +14 -1
  10. package/dist/bootstrap.d.ts +3 -2
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +83 -26
  13. package/dist/change-set.d.ts +7 -2
  14. package/dist/change-set.d.ts.map +1 -1
  15. package/dist/change-set.js +37 -10
  16. package/dist/change.d.ts +31 -1
  17. package/dist/change.d.ts.map +1 -1
  18. package/dist/change.js +10 -1
  19. package/dist/chat/acts.d.ts +3 -4
  20. package/dist/chat/acts.d.ts.map +1 -1
  21. package/dist/chat/acts.js +10 -10
  22. package/dist/chat/earlier.d.ts +10 -0
  23. package/dist/chat/earlier.d.ts.map +1 -0
  24. package/dist/chat/earlier.js +54 -0
  25. package/dist/chat/house.d.ts +25 -0
  26. package/dist/chat/house.d.ts.map +1 -0
  27. package/dist/chat/house.js +38 -0
  28. package/dist/chat/input.d.ts +3 -1
  29. package/dist/chat/input.d.ts.map +1 -1
  30. package/dist/chat/input.js +11 -18
  31. package/dist/chat/kit.d.ts +2 -1
  32. package/dist/chat/kit.d.ts.map +1 -1
  33. package/dist/chat/kit.js +3 -3
  34. package/dist/chat/list.d.ts +9 -1
  35. package/dist/chat/list.d.ts.map +1 -1
  36. package/dist/chat/list.js +53 -9
  37. package/dist/chat/markdown.d.ts +3 -1
  38. package/dist/chat/markdown.d.ts.map +1 -1
  39. package/dist/chat/markdown.js +85 -26
  40. package/dist/chat/message.d.ts +1 -0
  41. package/dist/chat/message.d.ts.map +1 -1
  42. package/dist/chat/message.js +57 -12
  43. package/dist/chat/messages.d.ts +9 -1
  44. package/dist/chat/messages.d.ts.map +1 -1
  45. package/dist/chat/messages.js +67 -28
  46. package/dist/chat/name.d.ts +1 -0
  47. package/dist/chat/name.d.ts.map +1 -1
  48. package/dist/chat/name.js +1 -1
  49. package/dist/chat/opened.d.ts +1 -0
  50. package/dist/chat/opened.d.ts.map +1 -1
  51. package/dist/chat/opened.js +2 -0
  52. package/dist/chat/overlay.d.ts +1 -0
  53. package/dist/chat/overlay.d.ts.map +1 -1
  54. package/dist/chat/overlay.js +11 -5
  55. package/dist/chat/parts.d.ts +1 -0
  56. package/dist/chat/parts.d.ts.map +1 -1
  57. package/dist/chat/parts.js +3 -2
  58. package/dist/chat/pieces.d.ts +62 -0
  59. package/dist/chat/pieces.d.ts.map +1 -0
  60. package/dist/chat/pieces.js +1110 -0
  61. package/dist/chat/provider.d.ts +7 -2
  62. package/dist/chat/provider.d.ts.map +1 -1
  63. package/dist/chat/provider.js +129 -155
  64. package/dist/chat/question.d.ts +1 -1
  65. package/dist/chat/question.d.ts.map +1 -1
  66. package/dist/chat/question.js +7 -18
  67. package/dist/chat/ready.d.ts +5 -1
  68. package/dist/chat/ready.d.ts.map +1 -1
  69. package/dist/chat/ready.js +9 -7
  70. package/dist/chat/room-file.d.ts +8 -0
  71. package/dist/chat/room-file.d.ts.map +1 -0
  72. package/dist/chat/room-file.js +72 -0
  73. package/dist/chat/steady.d.ts +3 -0
  74. package/dist/chat/steady.d.ts.map +1 -0
  75. package/dist/chat/steady.js +26 -0
  76. package/dist/chat/style.d.ts.map +1 -1
  77. package/dist/chat/style.js +8 -4
  78. package/dist/chat.d.ts +3 -2
  79. package/dist/chat.d.ts.map +1 -1
  80. package/dist/chat.js +2 -2
  81. package/dist/client.d.ts +21 -9
  82. package/dist/client.d.ts.map +1 -1
  83. package/dist/client.js +47 -8
  84. package/dist/collection.d.ts +21 -6
  85. package/dist/collection.d.ts.map +1 -1
  86. package/dist/collection.js +332 -40
  87. package/dist/connection.d.ts +39 -11
  88. package/dist/connection.d.ts.map +1 -1
  89. package/dist/connection.js +2294 -492
  90. package/dist/conversation.d.ts +28 -9
  91. package/dist/conversation.d.ts.map +1 -1
  92. package/dist/conversation.js +500 -165
  93. package/dist/digest.d.ts +7 -0
  94. package/dist/digest.d.ts.map +1 -0
  95. package/dist/digest.js +113 -0
  96. package/dist/durability.d.ts +8 -1
  97. package/dist/durability.d.ts.map +1 -1
  98. package/dist/entry.d.ts +18 -0
  99. package/dist/entry.d.ts.map +1 -0
  100. package/dist/entry.js +157 -0
  101. package/dist/fence.d.ts +6 -0
  102. package/dist/fence.d.ts.map +1 -0
  103. package/dist/fence.js +10 -0
  104. package/dist/file.d.ts +2 -10
  105. package/dist/file.d.ts.map +1 -1
  106. package/dist/file.js +3 -120
  107. package/dist/host.d.ts +15 -3
  108. package/dist/host.d.ts.map +1 -1
  109. package/dist/host.js +103 -13
  110. package/dist/identity.d.ts +2 -0
  111. package/dist/identity.d.ts.map +1 -1
  112. package/dist/identity.js +6 -0
  113. package/dist/index.d.ts +15 -9
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +5 -0
  116. package/dist/memory.d.ts +8 -2
  117. package/dist/memory.d.ts.map +1 -1
  118. package/dist/memory.js +43 -12
  119. package/dist/model.d.ts +4 -2
  120. package/dist/model.d.ts.map +1 -1
  121. package/dist/model.js +10 -1
  122. package/dist/name.d.ts +4 -0
  123. package/dist/name.d.ts.map +1 -0
  124. package/dist/name.js +122 -0
  125. package/dist/observation.d.ts +7 -2
  126. package/dist/observation.d.ts.map +1 -1
  127. package/dist/observation.js +29 -12
  128. package/dist/opened.d.ts +22 -0
  129. package/dist/opened.d.ts.map +1 -0
  130. package/dist/opened.js +107 -0
  131. package/dist/outcome.d.ts +39 -4
  132. package/dist/outcome.d.ts.map +1 -1
  133. package/dist/outcome.js +29 -0
  134. package/dist/overlay.d.ts +6 -4
  135. package/dist/overlay.d.ts.map +1 -1
  136. package/dist/overlay.js +229 -161
  137. package/dist/query.d.ts +57 -4
  138. package/dist/query.d.ts.map +1 -1
  139. package/dist/query.js +37 -7
  140. package/dist/reach.d.ts +11 -4
  141. package/dist/reach.d.ts.map +1 -1
  142. package/dist/reach.js +192 -41
  143. package/dist/react.d.ts +8 -6
  144. package/dist/react.d.ts.map +1 -1
  145. package/dist/react.js +7 -17
  146. package/dist/read-side.d.ts +18 -9
  147. package/dist/read-side.d.ts.map +1 -1
  148. package/dist/read-side.js +894 -155
  149. package/dist/readiness.d.ts +53 -0
  150. package/dist/readiness.d.ts.map +1 -0
  151. package/dist/readiness.js +33 -0
  152. package/dist/reads.d.ts +11 -0
  153. package/dist/reads.d.ts.map +1 -0
  154. package/dist/reads.js +98 -0
  155. package/dist/recovery.d.ts +25 -0
  156. package/dist/recovery.d.ts.map +1 -0
  157. package/dist/recovery.js +198 -0
  158. package/dist/refresh.d.ts +4 -4
  159. package/dist/refresh.d.ts.map +1 -1
  160. package/dist/refresh.js +46 -20
  161. package/dist/replica.d.ts +12 -7
  162. package/dist/replica.d.ts.map +1 -1
  163. package/dist/replica.js +151 -64
  164. package/dist/room-draft.d.ts +16 -2
  165. package/dist/room-draft.d.ts.map +1 -1
  166. package/dist/room-draft.js +32 -25
  167. package/dist/rows.d.ts +6 -13
  168. package/dist/rows.d.ts.map +1 -1
  169. package/dist/rows.js +10 -13
  170. package/dist/save.d.ts +52 -10
  171. package/dist/save.d.ts.map +1 -1
  172. package/dist/save.js +519 -159
  173. package/dist/search.d.ts +49 -6
  174. package/dist/search.d.ts.map +1 -1
  175. package/dist/search.js +242 -46
  176. package/dist/seed.d.ts +16 -0
  177. package/dist/seed.d.ts.map +1 -0
  178. package/dist/seed.js +75 -0
  179. package/dist/shared.d.ts +19 -0
  180. package/dist/shared.d.ts.map +1 -0
  181. package/dist/shared.js +461 -0
  182. package/dist/status.d.ts +4 -0
  183. package/dist/status.d.ts.map +1 -1
  184. package/dist/store.d.ts +61 -17
  185. package/dist/store.d.ts.map +1 -1
  186. package/dist/store.js +581 -310
  187. package/dist/tables.d.ts +242 -0
  188. package/dist/tables.d.ts.map +1 -0
  189. package/dist/tables.js +406 -0
  190. package/dist/turn.d.ts +9 -0
  191. package/dist/turn.d.ts.map +1 -0
  192. package/dist/turn.js +67 -0
  193. package/dist/windows.d.ts +21 -0
  194. package/dist/windows.d.ts.map +1 -0
  195. package/dist/windows.js +84 -0
  196. package/dist/wire.d.ts +168 -32
  197. package/dist/wire.d.ts.map +1 -1
  198. package/dist/wire.js +56 -31
  199. package/package.json +10 -2
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,83 +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,
124
166
  binary: d.binary,
167
+ ...Object.fromEntries(shape.orderBy.map(({ key }, index) => [`order${index}`, keyed(key, d)])),
125
168
  ...(shape.fields === null ? {} : { fields: d.fields }),
126
- ...(shape.sections === 'all' ? { sections: b.sections } : {}),
127
- ...(Array.isArray(shape.sections) ? named('section', shape.sections, b.sections) : {}),
128
- ...(shape.preamble ? { preamble: b.preamble } : {}),
129
- ...(shape.markdown ? { markdown: b.markdown } : {}),
130
- ...(shape.backlinks ? { backlinks: toArray(backlinked(q, rows, d, plan.reach)) } : {}),
169
+ ...(bodied(shape) ? { text: d.text } : {}),
131
170
  ...(shape.change ? { change: d.change } : {}),
171
+ ...(shape.fileId ? { fileId: d.fileId } : {}),
132
172
  }));
133
173
  }
134
- function backlinked(q, rows, document, reach) {
174
+ function references(q, rows, reach, locked) {
135
175
  const from = q.from({ r: rows });
136
176
  return from
137
- .where(({ r }) => every([eq(r.target, document.key), ...reached(reach, r.room, r.source)]))
138
- .orderBy(({ r }) => r.path, { stringSort: 'lexical' })
139
- .orderBy(({ r }) => r.field, { stringSort: 'lexical' })
140
- .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;
226
+ }
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 });
141
232
  }
142
- function groups(q, stage, shape, limit) {
233
+ function groups(q, stage, shape) {
143
234
  const keys = shape.groupBy.map((_, index) => `key${index}`);
144
235
  const from = q.from({ m: stage });
145
236
  let query = from.groupBy(({ m }) => keys.map((key) => m[key]));
146
237
  for (const { by, direction } of shape.orderBy) {
147
238
  query = query.orderBy(({ m }) => (by === 'count' ? count(m.key) : m[keys[by]]), direction);
148
239
  }
149
- if (limit !== null && shape.orderBy.length > 0)
150
- query = query.limit(limit);
151
240
  return query.select(({ m }) => ({
152
241
  ...Object.fromEntries(keys.map((key) => [key, m[key]])),
153
242
  count: count(m.key),
@@ -157,28 +246,56 @@ function built(rows, plan) {
157
246
  const shape = plan.shape;
158
247
  if (shape.kind === 'documents') {
159
248
  const collection = live((q) => documents(q, rows, plan, shape));
160
- 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
+ };
161
267
  }
162
268
  const stage = live((q) => filtered(q, rows, plan).select(({ d }) => ({
163
269
  key: d.key,
164
270
  ...Object.fromEntries(shape.groupBy.map((key, index) => [`key${index}`, keyed(key, d)])),
165
271
  })));
166
- const collection = live((q) => groups(q, stage, shape, plan.limit));
272
+ const collection = live((q) => groups(q, stage, shape));
167
273
  return {
168
274
  live: collection,
275
+ count: null,
276
+ links: null,
169
277
  dispose: () => {
170
278
  void collection.cleanup();
171
279
  void stage.cleanup();
172
280
  },
173
281
  };
174
282
  }
175
- function present(entries) {
176
- return Object.fromEntries(entries.filter(([, value]) => value !== undefined));
177
- }
178
- function part(row, prefix, names, whole) {
283
+ function sectionsOf(sections, names) {
179
284
  if (names === 'all')
180
- return whole;
181
- 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;
182
299
  }
183
300
  function masked(pointers) {
184
301
  const root = { whole: false, members: new Map() };
@@ -229,15 +346,17 @@ function cut(value, mask) {
229
346
  function selected(fields, pointers) {
230
347
  return (cut(fields, masked(pointers)) ?? {});
231
348
  }
232
- function documentOf(row, shape) {
349
+ function documentOf(key, row, shape, bodies, linked) {
233
350
  if (row.type === null) {
234
351
  const binary = row.binary;
235
352
  return {
236
353
  room: row.room,
237
354
  path: row.path,
238
355
  ...(binary === null ? {} : { binaryFile: binary }),
356
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
239
357
  };
240
358
  }
359
+ const body = bodied(shape) ? bodyOf(row, key, bodies) : null;
241
360
  return {
242
361
  room: row.room,
243
362
  path: row.path,
@@ -249,192 +368,812 @@ function documentOf(row, shape) {
249
368
  ? row.fields
250
369
  : selected(row.fields, shape.fields),
251
370
  }),
252
- ...(shape.sections === null
371
+ ...(shape.sections === null || body === null
253
372
  ? {}
254
- : {
255
- sections: part(row, 'section', shape.sections, row.sections),
256
- }),
257
- ...(shape.preamble ? { preamble: row.preamble } : {}),
258
- ...(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 } : {}),
259
376
  ...(shape.backlinks
260
377
  ? {
261
- 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),
262
381
  }
263
382
  : {}),
264
383
  ...(shape.change ? { change: row.change } : {}),
384
+ ...(shape.fileId ? { fileId: row.fileId } : {}),
265
385
  };
266
386
  }
267
- function keyedOf(plan, entries) {
268
- const shape = plan.shape;
269
- if (shape.kind === 'documents') {
270
- return {
271
- result: structuredClone({ documents: entries.map(([, row]) => documentOf(row, shape)) }),
272
- keys: entries.map(([key]) => key),
273
- };
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;
274
406
  }
275
- 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 }) {
276
465
  return {
277
- result: structuredClone({
278
- groups: bounded.map(([, row]) => ({
279
- key: shape.groupBy.map((_, index) => row[`key${index}`]),
280
- count: row.count,
281
- })),
282
- }),
283
- keys: bounded.map(([key]) => key),
466
+ ...rows,
467
+ ...(next === undefined ? {} : { next }),
468
+ ...(total === undefined ? {} : { total }),
284
469
  };
285
470
  }
286
- export function reconciled(previous, next) {
287
- const before = rowsOf(previous.result);
288
- 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();
289
516
  const changed = [];
290
- const rows = rowsOf(next.result).map((row, index) => {
291
- const key = next.keys[index];
517
+ const rows = [];
518
+ yield* strides(next.keys.entries(), ([index, key]) => {
519
+ const row = later[index];
292
520
  const kept = held.get(key);
521
+ present.add(key);
293
522
  if (kept !== undefined && deepEquals(kept, row))
294
- return kept;
295
- changed.push([key, row]);
296
- return row;
523
+ rows.push(kept);
524
+ else {
525
+ changed.push([key, row]);
526
+ rows.push(row);
527
+ }
528
+ return ROW_WEIGHT + textOf(row);
529
+ });
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;
297
556
  });
298
- const present = new Set(next.keys);
299
- const survivors = previous.keys.filter((key) => present.has(key));
300
- const removed = previous.keys.filter((key) => !present.has(key));
301
- const moved = survivors.length !== next.keys.length ||
302
- survivors.some((key, index) => key !== next.keys[index]);
303
- if (changed.length === 0 && removed.length === 0 && !moved)
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) {
304
561
  return null;
562
+ }
305
563
  return {
306
- keyed: {
307
- result: 'groups' in next.result
308
- ? { groups: rows }
309
- : { documents: rows },
310
- keys: next.keys,
311
- },
312
- 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,
313
569
  };
314
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
+ }
315
578
  export function createReadSide() {
316
579
  configureIndexDevMode({ enabled: false });
317
- let sync;
318
- const rows = createCollection({
319
- getKey: (row) => row.key,
320
- gcTime: 0,
321
- startSync: true,
322
- autoIndex: 'eager',
323
- defaultIndexType: BasicIndex,
324
- sync: {
325
- rowUpdateMode: 'full',
326
- sync: (params) => {
327
- sync = params;
328
- params.markReady();
329
- },
330
- },
331
- });
580
+ const rows = new Map();
581
+ const roomPaths = new Map();
582
+ const views = new Set();
332
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
+ }
333
693
  function searched(row) {
334
- 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;
335
697
  return {
336
698
  room: row.room,
337
699
  path: row.path,
338
700
  type: row.type,
339
701
  fields: row.fields,
340
- preamble: body.preamble,
341
- sections: body.sections,
702
+ preamble: read.document.preamble,
703
+ sections: read.document.sections,
342
704
  };
343
705
  }
344
- function publish(entry) {
345
- const next = reconciled(entry.result, keyedOf(entry.plan, [...entry.built.live.entries()]));
346
- if (next === null)
347
- return;
348
- entry.result = next.keyed;
349
- for (const listener of entry.listeners)
350
- listener(next.change);
706
+ function counted(entry) {
707
+ return entry.built.count === null ? undefined : entry.built.count.size;
351
708
  }
352
- return {
353
- apply(set) {
354
- const next = new Map();
355
- for (const change of set.documents) {
356
- for (const row of change.before)
357
- next.set(row.key, null);
358
- for (const row of change.after)
359
- 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;
360
744
  }
361
- if (next.size === 0)
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]);
767
+ }
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
+ });
362
851
  return;
363
- 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();
364
911
  for (const [key, row] of next) {
365
- if (row === null) {
366
- if (rows.has(key))
367
- sync.write({ type: 'delete', key });
368
- continue;
369
- }
370
- 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));
959
+ }
960
+ for (const row of after) {
961
+ unit.set(row.key, row);
962
+ spent += weight(row) + charges(held.get(row.key), row);
371
963
  }
372
- sync.commit();
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);
373
1008
  },
374
- async query(plan) {
375
- 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;
376
1016
  try {
377
- await collection.toArrayWhenReady();
378
- 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 };
379
1037
  }
380
1038
  finally {
381
- dispose();
1039
+ following?.unsubscribe();
1040
+ made?.dispose();
1041
+ dropped(view);
382
1042
  }
383
1043
  },
384
- watch(plan, identity, deliver) {
1044
+ watch(plan, identity, cover, deliverTo) {
385
1045
  let entry = shared.get(identity);
386
1046
  if (entry === undefined) {
1047
+ const view = viewOf(plan);
1048
+ void filled(view);
387
1049
  const created = {
388
1050
  plan,
389
- built: built(rows, plan),
1051
+ view,
1052
+ built: built(view.rows, plan),
1053
+ cover,
390
1054
  result: undefined,
1055
+ rows: new Map(),
1056
+ generation: 0,
1057
+ bodies: new Map(),
391
1058
  listeners: new Set(),
1059
+ fresh: new Set(),
1060
+ ready: new Set(),
392
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,
393
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
+ }
394
1082
  created.built.live.on('status:change', ({ status }) => {
395
- 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)
396
1086
  shared.delete(identity);
397
1087
  });
398
1088
  shared.set(identity, created);
399
1089
  entry = created;
400
1090
  }
401
1091
  const observed = entry;
402
- observed.listeners.add(deliver);
403
- observed.built.live.onFirstReady(() => {
404
- 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))
405
1107
  return;
1108
+ observed.fresh.add(deliverTo);
406
1109
  if (observed.subscription === null) {
407
- observed.result = keyedOf(observed.plan, [...observed.built.live.entries()]);
408
- observed.subscription = observed.built.live.subscribeChanges(() => {
409
- 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();
410
1118
  }, { includeInitialState: false });
1119
+ observed.counting =
1120
+ observed.built.count?.subscribeChanges(() => {
1121
+ observed.stale = true;
1122
+ settled();
1123
+ }, { includeInitialState: false }) ?? null;
411
1124
  }
412
- deliver(observed.result);
1125
+ wake(observed);
413
1126
  });
414
1127
  return () => {
415
- 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)
416
1131
  return;
417
1132
  observed.subscription?.unsubscribe();
418
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);
419
1148
  };
420
1149
  },
421
- documents() {
422
- return rows.toArray
423
- .filter((row) => row.kind === 'document' && row.type !== null)
424
- .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
+ }
425
1156
  },
426
1157
  document(room, path) {
427
1158
  const row = rows.get(documentKey(room, path));
428
- return row === undefined || row.type === null ? null : searched(row);
1159
+ return row?.kind !== 'document' || row.type === null ? null : searched(row);
429
1160
  },
430
1161
  close() {
1162
+ closed = true;
431
1163
  for (const entry of shared.values()) {
432
1164
  entry.listeners.clear();
1165
+ entry.fresh.clear();
1166
+ entry.ready.clear();
433
1167
  entry.subscription?.unsubscribe();
1168
+ entry.counting?.unsubscribe();
1169
+ entry.linking?.unsubscribe();
434
1170
  entry.built.dispose();
435
1171
  }
436
1172
  shared.clear();
437
- void rows.cleanup();
1173
+ for (const view of [...views])
1174
+ dropped(view);
1175
+ rows.clear();
1176
+ roomPaths.clear();
438
1177
  },
439
1178
  };
440
1179
  }