@agentshouse/mdruntime 0.1.0-ca4dc698bd0c4 → 0.1.0-ca58768ab31fe

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 (231) hide show
  1. package/README.md +19 -16
  2. package/dist/adapter.d.ts +146 -79
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +5 -1
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +38 -4
  7. package/dist/binary.d.ts +0 -5
  8. package/dist/binary.d.ts.map +1 -1
  9. package/dist/binary.js +1 -1
  10. package/dist/bootstrap.d.ts +27 -11
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +181 -99
  13. package/dist/chain.d.ts +12 -0
  14. package/dist/chain.d.ts.map +1 -0
  15. package/dist/chain.js +64 -0
  16. package/dist/change-set.d.ts +9 -16
  17. package/dist/change-set.d.ts.map +1 -1
  18. package/dist/change-set.js +11 -88
  19. package/dist/change.d.ts +31 -1
  20. package/dist/change.d.ts.map +1 -1
  21. package/dist/change.js +10 -1
  22. package/dist/client.d.ts +24 -11
  23. package/dist/client.d.ts.map +1 -1
  24. package/dist/client.js +98 -11
  25. package/dist/collection.d.ts +11 -21
  26. package/dist/collection.d.ts.map +1 -1
  27. package/dist/collection.js +39 -291
  28. package/dist/compile.d.ts +11 -0
  29. package/dist/compile.d.ts.map +1 -0
  30. package/dist/compile.js +586 -0
  31. package/dist/connection.d.ts +33 -17
  32. package/dist/connection.d.ts.map +1 -1
  33. package/dist/connection.js +3082 -1404
  34. package/dist/context.d.ts +2 -0
  35. package/dist/context.d.ts.map +1 -0
  36. package/dist/{chat/context.js → context.js} +1 -4
  37. package/dist/conversation.d.ts +31 -15
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +524 -223
  40. package/dist/digest.d.ts +7 -0
  41. package/dist/digest.d.ts.map +1 -0
  42. package/dist/digest.js +113 -0
  43. package/dist/durability.d.ts +7 -4
  44. package/dist/durability.d.ts.map +1 -1
  45. package/dist/durability.js +5 -0
  46. package/dist/entry.d.ts +5 -2
  47. package/dist/entry.d.ts.map +1 -1
  48. package/dist/entry.js +30 -27
  49. package/dist/file.d.ts +2 -19
  50. package/dist/file.d.ts.map +1 -1
  51. package/dist/file.js +7 -122
  52. package/dist/host.d.ts +6 -8
  53. package/dist/host.d.ts.map +1 -1
  54. package/dist/host.js +98 -24
  55. package/dist/identity.d.ts +0 -2
  56. package/dist/identity.d.ts.map +1 -1
  57. package/dist/identity.js +0 -6
  58. package/dist/index.d.ts +20 -13
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +8 -1
  61. package/dist/job.d.ts +14 -0
  62. package/dist/job.d.ts.map +1 -0
  63. package/dist/job.js +28 -0
  64. package/dist/live.d.ts +23 -0
  65. package/dist/live.d.ts.map +1 -0
  66. package/dist/live.js +127 -0
  67. package/dist/media.d.ts +8 -0
  68. package/dist/media.d.ts.map +1 -0
  69. package/dist/media.js +218 -0
  70. package/dist/memory.d.ts +17 -7
  71. package/dist/memory.d.ts.map +1 -1
  72. package/dist/memory.js +209 -103
  73. package/dist/messages.d.ts +28 -0
  74. package/dist/messages.d.ts.map +1 -0
  75. package/dist/messages.js +89 -0
  76. package/dist/model.d.ts +10 -5
  77. package/dist/model.d.ts.map +1 -1
  78. package/dist/model.js +8 -14
  79. package/dist/name.d.ts +4 -0
  80. package/dist/name.d.ts.map +1 -0
  81. package/dist/name.js +122 -0
  82. package/dist/observation.d.ts +17 -2
  83. package/dist/observation.d.ts.map +1 -1
  84. package/dist/observation.js +100 -5
  85. package/dist/opened.d.ts +22 -0
  86. package/dist/opened.d.ts.map +1 -0
  87. package/dist/opened.js +107 -0
  88. package/dist/outcome.d.ts +45 -7
  89. package/dist/outcome.d.ts.map +1 -1
  90. package/dist/outcome.js +39 -0
  91. package/dist/overlay.d.ts +3 -5
  92. package/dist/overlay.d.ts.map +1 -1
  93. package/dist/overlay.js +95 -156
  94. package/dist/paths.d.ts +1 -0
  95. package/dist/paths.d.ts.map +1 -1
  96. package/dist/paths.js +4 -0
  97. package/dist/query.d.ts +17 -185
  98. package/dist/query.d.ts.map +1 -1
  99. package/dist/query.js +3 -464
  100. package/dist/reach.d.ts +16 -8
  101. package/dist/reach.d.ts.map +1 -1
  102. package/dist/reach.js +221 -90
  103. package/dist/react.d.ts +6 -4
  104. package/dist/react.d.ts.map +1 -1
  105. package/dist/react.js +7 -17
  106. package/dist/readiness.d.ts +14 -17
  107. package/dist/readiness.d.ts.map +1 -1
  108. package/dist/readiness.js +2 -8
  109. package/dist/recovery.d.ts +3 -2
  110. package/dist/recovery.d.ts.map +1 -1
  111. package/dist/recovery.js +34 -7
  112. package/dist/refresh.d.ts +17 -14
  113. package/dist/refresh.d.ts.map +1 -1
  114. package/dist/refresh.js +136 -52
  115. package/dist/replica.d.ts +56 -33
  116. package/dist/replica.d.ts.map +1 -1
  117. package/dist/replica.js +298 -182
  118. package/dist/room-draft.d.ts +37 -6
  119. package/dist/room-draft.d.ts.map +1 -1
  120. package/dist/room-draft.js +161 -74
  121. package/dist/rows.d.ts +22 -29
  122. package/dist/rows.d.ts.map +1 -1
  123. package/dist/rows.js +58 -112
  124. package/dist/save.d.ts +53 -14
  125. package/dist/save.d.ts.map +1 -1
  126. package/dist/save.js +529 -185
  127. package/dist/search.d.ts +15 -77
  128. package/dist/search.d.ts.map +1 -1
  129. package/dist/search.js +10 -184
  130. package/dist/seed.d.ts +16 -0
  131. package/dist/seed.d.ts.map +1 -0
  132. package/dist/seed.js +75 -0
  133. package/dist/shared.d.ts +7 -3
  134. package/dist/shared.d.ts.map +1 -1
  135. package/dist/shared.js +119 -66
  136. package/dist/status.d.ts +4 -3
  137. package/dist/status.d.ts.map +1 -1
  138. package/dist/status.js +3 -1
  139. package/dist/store.d.ts +90 -98
  140. package/dist/store.d.ts.map +1 -1
  141. package/dist/store.js +303 -661
  142. package/dist/stored.d.ts +129 -0
  143. package/dist/stored.d.ts.map +1 -0
  144. package/dist/stored.js +495 -0
  145. package/dist/tables.d.ts +328 -0
  146. package/dist/tables.d.ts.map +1 -0
  147. package/dist/tables.js +587 -0
  148. package/dist/transfer.d.ts +0 -5
  149. package/dist/transfer.d.ts.map +1 -1
  150. package/dist/turn.d.ts +5 -3
  151. package/dist/turn.d.ts.map +1 -1
  152. package/dist/turn.js +51 -21
  153. package/dist/validation.d.ts +29 -0
  154. package/dist/validation.d.ts.map +1 -0
  155. package/dist/validation.js +104 -0
  156. package/dist/windows.d.ts +11 -0
  157. package/dist/windows.d.ts.map +1 -0
  158. package/dist/windows.js +16 -0
  159. package/dist/wire.d.ts +172 -33
  160. package/dist/wire.d.ts.map +1 -1
  161. package/dist/wire.js +57 -32
  162. package/package.json +18 -19
  163. package/AGENTS.md +0 -300
  164. package/dist/acquisition.d.ts +0 -14
  165. package/dist/acquisition.d.ts.map +0 -1
  166. package/dist/acquisition.js +0 -24
  167. package/dist/chat/acts.d.ts +0 -33
  168. package/dist/chat/acts.d.ts.map +0 -1
  169. package/dist/chat/acts.js +0 -36
  170. package/dist/chat/context.d.ts +0 -3
  171. package/dist/chat/context.d.ts.map +0 -1
  172. package/dist/chat/input.d.ts +0 -9
  173. package/dist/chat/input.d.ts.map +0 -1
  174. package/dist/chat/input.js +0 -99
  175. package/dist/chat/kit.d.ts +0 -60
  176. package/dist/chat/kit.d.ts.map +0 -1
  177. package/dist/chat/kit.js +0 -123
  178. package/dist/chat/lasting.d.ts +0 -6
  179. package/dist/chat/lasting.d.ts.map +0 -1
  180. package/dist/chat/lasting.js +0 -19
  181. package/dist/chat/list.d.ts +0 -26
  182. package/dist/chat/list.d.ts.map +0 -1
  183. package/dist/chat/list.js +0 -62
  184. package/dist/chat/markdown.d.ts +0 -20
  185. package/dist/chat/markdown.d.ts.map +0 -1
  186. package/dist/chat/markdown.js +0 -188
  187. package/dist/chat/message.d.ts +0 -6
  188. package/dist/chat/message.d.ts.map +0 -1
  189. package/dist/chat/message.js +0 -192
  190. package/dist/chat/messages.d.ts +0 -21
  191. package/dist/chat/messages.d.ts.map +0 -1
  192. package/dist/chat/messages.js +0 -151
  193. package/dist/chat/name.d.ts +0 -6
  194. package/dist/chat/name.d.ts.map +0 -1
  195. package/dist/chat/name.js +0 -37
  196. package/dist/chat/opened.d.ts +0 -11
  197. package/dist/chat/opened.d.ts.map +0 -1
  198. package/dist/chat/opened.js +0 -17
  199. package/dist/chat/overlay.d.ts +0 -31
  200. package/dist/chat/overlay.d.ts.map +0 -1
  201. package/dist/chat/overlay.js +0 -97
  202. package/dist/chat/parts.d.ts +0 -57
  203. package/dist/chat/parts.d.ts.map +0 -1
  204. package/dist/chat/parts.js +0 -119
  205. package/dist/chat/process.d.ts +0 -20
  206. package/dist/chat/process.d.ts.map +0 -1
  207. package/dist/chat/process.js +0 -32
  208. package/dist/chat/provider.d.ts +0 -63
  209. package/dist/chat/provider.d.ts.map +0 -1
  210. package/dist/chat/provider.js +0 -620
  211. package/dist/chat/question.d.ts +0 -9
  212. package/dist/chat/question.d.ts.map +0 -1
  213. package/dist/chat/question.js +0 -80
  214. package/dist/chat/questions.d.ts +0 -31
  215. package/dist/chat/questions.d.ts.map +0 -1
  216. package/dist/chat/questions.js +0 -111
  217. package/dist/chat/read.d.ts +0 -5
  218. package/dist/chat/read.d.ts.map +0 -1
  219. package/dist/chat/read.js +0 -13
  220. package/dist/chat/ready.d.ts +0 -33
  221. package/dist/chat/ready.d.ts.map +0 -1
  222. package/dist/chat/ready.js +0 -91
  223. package/dist/chat/style.d.ts +0 -4
  224. package/dist/chat/style.d.ts.map +0 -1
  225. package/dist/chat/style.js +0 -302
  226. package/dist/chat.d.ts +0 -14
  227. package/dist/chat.d.ts.map +0 -1
  228. package/dist/chat.js +0 -8
  229. package/dist/read-side.d.ts +0 -25
  230. package/dist/read-side.d.ts.map +0 -1
  231. package/dist/read-side.js +0 -639
@@ -0,0 +1,586 @@
1
+ import { ARRAY_INDEX, bodied, byCodePoint, compared, cursorOf, heldKind, parse, pointerSegments, sql, } from '@agentshouse/mdmodel';
2
+ import { binaryRecord } from './binary.js';
3
+ import { documentKey, pathOfKey, roomOfKey } from './rows.js';
4
+ import { FROM_START, MAXIMUM_WINDOW } from './windows.js';
5
+ const { all, any, exists, fragment, grouped, joined, listed } = sql;
6
+ const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
7
+ function located(alias) {
8
+ return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
9
+ }
10
+ function within(alias, path) {
11
+ return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
12
+ path,
13
+ `${path}/`,
14
+ `${path}0`,
15
+ ]);
16
+ }
17
+ function ancestors(area) {
18
+ const depth = area.path.split('/').length;
19
+ return area.open.flatMap((open) => {
20
+ const segments = open.split('/');
21
+ return segments.slice(depth - 1, -1).map((_, at) => segments.slice(0, depth + at).join('/'));
22
+ });
23
+ }
24
+ function fenced(alias, key, area, folders) {
25
+ const opened = any([
26
+ ...area.open.map((open) => within(alias, open)),
27
+ ...(folders ? ancestors(area).map((path) => fragment(`${alias}.path = ?`, [path])) : []),
28
+ ]);
29
+ return all([
30
+ fragment(`${alias}.key = ?`, [key]),
31
+ within(alias, area.path),
32
+ fragment(`NOT ${grouped(opened).sql}`, opened.bind),
33
+ ]);
34
+ }
35
+ function reached(bound, alias) {
36
+ const reach = bound.reach;
37
+ if (reach === null)
38
+ return fragment('1');
39
+ const rooms = reach.rooms.flatMap((locator) => {
40
+ const key = bound.keys.get(locator);
41
+ return key === undefined ? [] : [key];
42
+ });
43
+ const documents = reach.documents.flatMap((held) => {
44
+ const key = bound.keys.get(roomOfKey(held));
45
+ return key === undefined ? [] : [[key, pathOfKey(held)]];
46
+ });
47
+ const outside = any(reach.areas.flatMap((area) => {
48
+ const key = bound.keys.get(area.room);
49
+ return key === undefined ? [] : [fenced(alias, key, area, false)];
50
+ }));
51
+ return all([
52
+ any([
53
+ ...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
54
+ ...(documents.length === 0
55
+ ? []
56
+ : [
57
+ fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
58
+ ]),
59
+ ]),
60
+ ...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
61
+ ]);
62
+ }
63
+ function related(bound, plan) {
64
+ const parts = [];
65
+ if (plan.hop !== null) {
66
+ parts.push(exists(joined([
67
+ fragment(`SELECT 1 FROM replica_links l JOIN replica_entries m ON m.key = d.key AND m.path = l.target AND ${VISIBLE('m')} WHERE l.doc = d.id AND l.kind = 'field' AND`),
68
+ grouped(sql.placed('l', plan.hop.field, 'link')),
69
+ fragment('AND'),
70
+ grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
71
+ ], ' ')));
72
+ }
73
+ if (plan.backlinks !== null) {
74
+ const field = plan.backlinks.field;
75
+ parts.push(exists(joined([
76
+ fragment(`SELECT 1 FROM replica_links l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE l.key = d.key AND l.target = d.path AND l.kind = 'field' AND`),
77
+ ...(field === null ? [] : [grouped(sql.placed('l', field, 'link')), fragment('AND')]),
78
+ grouped(all([sql.scoped('s', plan.backlinks), reached(bound, 's')])),
79
+ ], ' ')));
80
+ }
81
+ return parts;
82
+ }
83
+ function filtered(bound, plan) {
84
+ const keys = bound.rooms.map(({ key }) => key);
85
+ return all([
86
+ fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
87
+ reached(bound, 'd'),
88
+ sql.tests('d', plan),
89
+ ...related(bound, plan),
90
+ ]);
91
+ }
92
+ function orderingOf(options) {
93
+ const stringSort = options.stringSort ?? 'locale';
94
+ const collated = stringSort === 'locale';
95
+ return {
96
+ direction: options.direction,
97
+ nulls: options.nulls ?? 'first',
98
+ stringSort,
99
+ locale: collated ? options.locale : undefined,
100
+ localeOptions: collated
101
+ ? options.localeOptions
102
+ : undefined,
103
+ };
104
+ }
105
+ const GROUP_ORDER = {
106
+ direction: 'asc',
107
+ nulls: 'first',
108
+ stringSort: 'locale',
109
+ locale: undefined,
110
+ localeOptions: undefined,
111
+ };
112
+ const GROUP_TIES = {
113
+ direction: 'asc',
114
+ nulls: 'last',
115
+ stringSort: 'lexical',
116
+ locale: undefined,
117
+ localeOptions: undefined,
118
+ };
119
+ function keyTerm(key, ordering, index) {
120
+ if (key.of === 'field') {
121
+ const alias = `o${index}`;
122
+ return {
123
+ term: {
124
+ ordering,
125
+ kind: fragment(`${alias}.kind`),
126
+ value: fragment(`${alias}.value`),
127
+ instant: fragment(`${alias}.instant`),
128
+ },
129
+ join: joined([
130
+ fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
131
+ grouped(sql.placed(alias, key.field, 'exact')),
132
+ fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
133
+ ], ''),
134
+ };
135
+ }
136
+ const expression = key.of === 'room'
137
+ ? located('d')
138
+ : key.of === 'path'
139
+ ? fragment('d.path')
140
+ : key.of === 'type'
141
+ ? fragment('d.type')
142
+ : fragment('d.folder');
143
+ return {
144
+ term: {
145
+ ordering,
146
+ kind: fragment(`(CASE WHEN ${expression.sql} IS NULL THEN 'null' ELSE 'string' END)`, expression.bind),
147
+ value: expression,
148
+ },
149
+ join: null,
150
+ };
151
+ }
152
+ function decoded(kind, value) {
153
+ if (kind === 'string')
154
+ return value;
155
+ if (kind === 'number')
156
+ return typeof value === 'bigint' ? Number(value) : value;
157
+ if (kind === 'boolean')
158
+ return Number(value) === 1;
159
+ if (kind === 'array')
160
+ return JSON.parse(value);
161
+ return null;
162
+ }
163
+ function sortedValue(row, index) {
164
+ const kind = row[`k${index}`];
165
+ return sql.sortValue(kind, decoded(kind, row[`v${index}`]), row[`i${index}`]);
166
+ }
167
+ function decodedKey(key, row, index) {
168
+ if (key.of === 'field') {
169
+ const kind = row[`k${index}`];
170
+ return kind === null || kind === undefined ? undefined : sortedValue(row, index);
171
+ }
172
+ return row[`v${index}`] ?? null;
173
+ }
174
+ function instantOf(term) {
175
+ return term.instant ?? fragment('NULL');
176
+ }
177
+ const registered = new WeakMap();
178
+ function hashed(text) {
179
+ let hash = 0x811c9dc5;
180
+ for (let index = 0; index < text.length; index += 1) {
181
+ hash ^= text.charCodeAt(index);
182
+ hash = Math.imul(hash, 0x01000193);
183
+ }
184
+ return (hash >>> 0).toString(36);
185
+ }
186
+ function collating(connection) {
187
+ let held = registered.get(connection);
188
+ if (held === undefined) {
189
+ held = new Set();
190
+ registered.set(connection, held);
191
+ }
192
+ const names = held;
193
+ return (ordering, lists, total) => {
194
+ if (!lists && ordering.stringSort !== 'locale')
195
+ return null;
196
+ const inner = lists
197
+ ? {
198
+ ...ordering,
199
+ direction: 'asc',
200
+ nulls: ordering.direction === 'desc'
201
+ ? ordering.nulls === 'first'
202
+ ? 'last'
203
+ : 'first'
204
+ : ordering.nulls,
205
+ }
206
+ : ordering;
207
+ const spec = JSON.stringify([
208
+ lists,
209
+ total,
210
+ inner.nulls,
211
+ inner.stringSort,
212
+ inner.locale ?? null,
213
+ inner.localeOptions ?? null,
214
+ ]);
215
+ const name = `mdr_${lists ? 'l' : 's'}${hashed(spec)}`;
216
+ if (!names.has(name)) {
217
+ const collator = new Intl.Collator(inner.locale, inner.localeOptions);
218
+ connection.collate(name, lists
219
+ ? (left, right) => Math.sign(compared(JSON.parse(left), JSON.parse(right), inner)) || (total ? Math.sign(byCodePoint(left, right)) : 0)
220
+ : (left, right) => Math.sign(collator.compare(left, right)));
221
+ names.add(name);
222
+ }
223
+ return name;
224
+ };
225
+ }
226
+ function masked(pointers) {
227
+ const root = { whole: false, members: new Map() };
228
+ for (const pointer of pointers) {
229
+ let mask = root;
230
+ for (const token of pointerSegments(pointer)) {
231
+ const next = mask.members.get(token) ?? { whole: false, members: new Map() };
232
+ mask.members.set(token, next);
233
+ mask = next;
234
+ }
235
+ mask.whole = true;
236
+ }
237
+ return root;
238
+ }
239
+ function merged(left, right) {
240
+ if (left === undefined)
241
+ return right;
242
+ const members = new Map(left.members);
243
+ for (const [token, mask] of right.members)
244
+ members.set(token, merged(members.get(token), mask));
245
+ return { whole: left.whole || right.whole, members };
246
+ }
247
+ function cut(value, mask) {
248
+ if (mask.whole)
249
+ return value;
250
+ if (Array.isArray(value)) {
251
+ const each = {
252
+ whole: false,
253
+ members: new Map([...mask.members].filter(([token]) => !ARRAY_INDEX.test(token))),
254
+ };
255
+ const kept = [];
256
+ value.forEach((item, index) => {
257
+ const part = cut(item, merged(mask.members.get(String(index)), each));
258
+ if (part !== undefined)
259
+ kept[index] = part;
260
+ });
261
+ return kept.length === 0 ? undefined : kept;
262
+ }
263
+ if (typeof value !== 'object' || value === null)
264
+ return undefined;
265
+ const kept = Object.entries(value).flatMap(([key, held]) => {
266
+ const member = mask.members.get(key);
267
+ const part = member === undefined ? undefined : cut(held, member);
268
+ return part === undefined ? [] : [[key, part]];
269
+ });
270
+ return kept.length === 0 ? undefined : Object.fromEntries(kept);
271
+ }
272
+ function sectionsOf(sections, names) {
273
+ if (names === 'all')
274
+ return sections;
275
+ return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
276
+ }
277
+ function documentOf(shape, room, row, linked) {
278
+ const path = row.path;
279
+ const type = row.type;
280
+ const fileId = row.file_id ?? null;
281
+ const revision = row.revision ?? null;
282
+ if (type === null) {
283
+ const binary = row.binary === null ? null : JSON.parse(row.binary);
284
+ const content = row.content ?? null;
285
+ return {
286
+ room,
287
+ path,
288
+ ...(binary === null
289
+ ? {}
290
+ : {
291
+ binaryFile: binaryRecord(path, binary, revision ?? undefined),
292
+ }),
293
+ ...(shape.markdown && content !== null ? { markdown: content } : {}),
294
+ ...(shape.fileId ? { fileId } : {}),
295
+ ...(shape.revision ? { revision } : {}),
296
+ };
297
+ }
298
+ const read = bodied(shape) ? parse(row.content, path) : null;
299
+ const body = read?.ok === true ? read.document : null;
300
+ const fields = JSON.parse(row.fields ?? '{}');
301
+ return {
302
+ room,
303
+ path,
304
+ type,
305
+ ...(shape.fields === null
306
+ ? {}
307
+ : {
308
+ fields: shape.fields === 'all'
309
+ ? fields
310
+ : (cut(fields, masked(shape.fields)) ?? {}),
311
+ }),
312
+ ...(shape.sections === null || body === null
313
+ ? {}
314
+ : { sections: sectionsOf(body.sections, shape.sections) }),
315
+ ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
316
+ ...(shape.markdown ? { markdown: row.content } : {}),
317
+ ...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
318
+ ...(shape.change
319
+ ? {
320
+ change: row.change === null ? null : JSON.parse(row.change),
321
+ }
322
+ : {}),
323
+ ...(shape.fileId ? { fileId } : {}),
324
+ ...(shape.revision ? { revision } : {}),
325
+ };
326
+ }
327
+ function backlinksOf(connection, bound, rows) {
328
+ const linked = new Map();
329
+ const byKey = new Map();
330
+ for (const row of rows) {
331
+ const key = Number(row.key);
332
+ const held = byKey.get(key) ?? [];
333
+ held.push(row.path);
334
+ byKey.set(key, held);
335
+ }
336
+ for (const [key, targets] of byKey) {
337
+ const reach = reached(bound, 's');
338
+ const room = bound.rooms.find((one) => one.key === key).locator;
339
+ const found = connection.rows(`SELECT target, path, type, field FROM (SELECT l.target, s.path, s.type, l.field, ROW_NUMBER() OVER (PARTITION BY l.target ORDER BY s.path, l.field) AS rank FROM (SELECT DISTINCT doc, target, field FROM replica_links WHERE key = ? AND kind = 'field' AND target IN (SELECT value FROM json_each(?))) l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE ${reach.sql}) WHERE rank <= ? ORDER BY target, path, field`, [key, JSON.stringify(targets), ...reach.bind, MAXIMUM_WINDOW]);
340
+ for (const row of found) {
341
+ const at = documentKey(room, row.target);
342
+ const held = linked.get(at) ?? [];
343
+ held.push({ path: row.path, type: row.type, field: row.field });
344
+ linked.set(at, held);
345
+ }
346
+ }
347
+ return linked;
348
+ }
349
+ function documents(connection, bound, plan, shape, budget) {
350
+ const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index));
351
+ const filter = filtered(bound, plan);
352
+ const cursor = plan.after?.values ?? null;
353
+ const room = located('d');
354
+ const tie = cursor === null
355
+ ? null
356
+ : (() => {
357
+ const key = cursor[shape.orderBy.length];
358
+ return any([
359
+ fragment(`${room.sql} > ?`, [...room.bind, roomOfKey(key)]),
360
+ fragment(`${room.sql} = ? AND d.path > ?`, [
361
+ ...room.bind,
362
+ roomOfKey(key),
363
+ pathOfKey(key),
364
+ ]),
365
+ ]);
366
+ })();
367
+ const after = cursor === null || tie === null
368
+ ? null
369
+ : sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), tie);
370
+ const selected = joined([
371
+ fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id'),
372
+ ...terms.map(({ term }, index) => joined([
373
+ fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
374
+ fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
375
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
376
+ ], '')),
377
+ fragment(' FROM replica_entries d'),
378
+ ...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
379
+ fragment(' WHERE '),
380
+ after === null ? filter : all([filter, after]),
381
+ fragment(' ORDER BY '),
382
+ joined([...terms.map(({ term }) => sql.sorted(bound.collate, term)), room, fragment('d.path')], ', '),
383
+ ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
384
+ ], '');
385
+ const rows = connection.rows(selected.sql, selected.bind);
386
+ const windowed = plan.limit === null ? rows : rows.slice(0, plan.limit);
387
+ const kept = bodied(shape) ? withContent(connection, windowed, budget) : windowed;
388
+ const cut = kept.length;
389
+ const roomOf = new Map(bound.rooms.map(({ key, locator }) => [key, locator]));
390
+ const linked = shape.backlinks ? backlinksOf(connection, bound, kept) : new Map();
391
+ const found = kept.map((row) => documentOf(shape, roomOf.get(Number(row.key)), row, linked));
392
+ const last = kept.at(-1);
393
+ const cursorAt = (row) => cursorOf([
394
+ ...shape.orderBy.map(({ key }, index) => decodedKey(key, row, index)),
395
+ documentKey(roomOf.get(Number(row.key)), row.path),
396
+ ]);
397
+ const more = cut < windowed.length && last !== undefined ? cursorAt(last) : undefined;
398
+ const next = more !== undefined || plan.limit === null || rows.length <= plan.limit
399
+ ? undefined
400
+ : last === undefined
401
+ ? (plan.after?.text ?? FROM_START)
402
+ : cursorAt(last);
403
+ const total = plan.total
404
+ ? (() => {
405
+ const counted = joined([fragment('SELECT count(*) AS held FROM replica_entries d WHERE '), filter], '');
406
+ return Number(connection.rows(counted.sql, counted.bind)[0].held);
407
+ })()
408
+ : undefined;
409
+ return {
410
+ documents: found,
411
+ ...(next === undefined ? {} : { next }),
412
+ ...(more === undefined ? {} : { more }),
413
+ ...(total === undefined ? {} : { total }),
414
+ };
415
+ }
416
+ function withContent(connection, rows, budget) {
417
+ if (rows.length === 0)
418
+ return rows;
419
+ const ids = rows.map((row) => row.id);
420
+ const sizes = new Map(connection
421
+ .rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${listed(ids).sql}`, ids)
422
+ .map((row) => [row.id, Number(row.size ?? 0)]));
423
+ let cut = rows.length;
424
+ let spent = 0;
425
+ for (let at = 0; budget !== null && at < rows.length; at += 1) {
426
+ const size = sizes.get(rows[at].id) ?? 0;
427
+ if (at > 0 && spent + size > budget) {
428
+ cut = at;
429
+ break;
430
+ }
431
+ spent += size;
432
+ }
433
+ const kept = rows.slice(0, cut);
434
+ const read = kept.map((row) => row.id);
435
+ const held = new Map(connection
436
+ .rows(`SELECT id, content FROM replica_entries WHERE id IN ${listed(read).sql}`, read)
437
+ .map((row) => [row.id, row.content]));
438
+ return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
439
+ }
440
+ function storedFolders(bound, plan) {
441
+ if (plan.paths !== null || plan.fileIds !== null)
442
+ return null;
443
+ if (plan.hop !== null || plan.backlinks !== null)
444
+ return null;
445
+ const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
446
+ if (whole.length === 0)
447
+ return null;
448
+ const keys = whole.map(({ key }) => key);
449
+ const prefix = plan.root.pathPrefix;
450
+ const outside = (bound.reach?.areas ?? []).flatMap((area) => {
451
+ const key = bound.keys.get(area.room);
452
+ if (key === undefined)
453
+ return [];
454
+ const fence = fenced('f', key, area, true);
455
+ return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
456
+ });
457
+ return all([
458
+ fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
459
+ ...outside,
460
+ ...(prefix === null || prefix === ''
461
+ ? []
462
+ : [
463
+ fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
464
+ prefix,
465
+ `${prefix}/`,
466
+ `${prefix}0`,
467
+ ]),
468
+ ]),
469
+ ...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
470
+ ]);
471
+ }
472
+ function groups(connection, bound, plan, shape) {
473
+ const keyed = shape.groupBy.map((key, index) => {
474
+ const { term, join } = keyTerm(key, GROUP_ORDER, index);
475
+ const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
476
+ return { term: { ...term, kind }, join };
477
+ });
478
+ const filter = filtered(bound, plan);
479
+ const inner = joined([
480
+ fragment('SELECT '),
481
+ joined(keyed.map(({ term }, index) => joined([
482
+ fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
483
+ fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
484
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
485
+ ], '')), ', '),
486
+ fragment(', 1 AS n FROM replica_entries d'),
487
+ ...keyed.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
488
+ fragment(' WHERE '),
489
+ filter,
490
+ ], '');
491
+ const folders = shape.groupBy.length === 1 && shape.groupBy[0].of === 'folder'
492
+ ? storedFolders(bound, plan)
493
+ : null;
494
+ const source = folders === null
495
+ ? inner
496
+ : joined([
497
+ inner,
498
+ fragment(" UNION ALL SELECT 'string' AS k0, f.path AS v0, NULL AS i0, 0 AS n FROM replica_folders f WHERE "),
499
+ folders,
500
+ ], '');
501
+ const columns = keyed
502
+ .map((_, index) => `k${index}, min(v${index}) AS v${index}, i${index}`)
503
+ .join(', ');
504
+ const held = keyed
505
+ .map((_, index) => `k${index}, i${index}, CASE WHEN i${index} IS NULL THEN v${index} END`)
506
+ .join(', ');
507
+ const counted = {
508
+ ordering: { ...GROUP_ORDER },
509
+ kind: fragment("'number'"),
510
+ value: fragment('n'),
511
+ };
512
+ const outer = (index, ordering) => ({
513
+ ordering,
514
+ kind: fragment(`k${index}`),
515
+ value: fragment(`v${index}`),
516
+ });
517
+ const ordered = shape.orderBy.map(({ by, direction }) => by === 'count'
518
+ ? { ...counted, ordering: { ...GROUP_ORDER, direction } }
519
+ : { ...outer(by, { ...GROUP_ORDER, direction }), instant: fragment(`i${by}`) });
520
+ const ties = keyed.flatMap((_, index) => [
521
+ { ...outer(index, GROUP_TIES), total: true },
522
+ { ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
523
+ ]);
524
+ const cursor = plan.after?.values ?? null;
525
+ const tieValues = cursor === null
526
+ ? null
527
+ : cursor[shape.orderBy.length].flatMap((value) => [
528
+ value,
529
+ heldKind(value ?? null),
530
+ ]);
531
+ const after = cursor === null || tieValues === null
532
+ ? null
533
+ : sql.following(ordered.map((term, index) => sql.beyond(bound.collate, term, cursor[index])), sql.following(ties
534
+ .slice(0, -1)
535
+ .map((term, index) => sql.beyond(bound.collate, term, tieValues[index])), sql.beyond(bound.collate, ties.at(-1), tieValues.at(-1)).beyond));
536
+ const grouping = joined([fragment(`SELECT ${columns}, sum(n) AS n FROM (`), source, fragment(`) GROUP BY ${held}`)], '');
537
+ const windowed = joined([
538
+ fragment('SELECT * FROM ('),
539
+ grouping,
540
+ fragment(')'),
541
+ ...(after === null ? [] : [fragment(' WHERE '), after]),
542
+ fragment(' ORDER BY '),
543
+ joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
544
+ ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
545
+ ], '');
546
+ const rows = connection.rows(windowed.sql, windowed.bind);
547
+ const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
548
+ const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
549
+ const found = kept.map((row) => ({ key: keyOf(row), count: Number(row.n) }));
550
+ const last = kept.at(-1);
551
+ const next = plan.limit === null || rows.length <= plan.limit
552
+ ? undefined
553
+ : last === undefined
554
+ ? (plan.after?.text ?? FROM_START)
555
+ : cursorOf([
556
+ ...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : sortedValue(last, by)),
557
+ keyOf(last),
558
+ ]);
559
+ const total = plan.total
560
+ ? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
561
+ : undefined;
562
+ return {
563
+ groups: found,
564
+ ...(next === undefined ? {} : { next }),
565
+ ...(total === undefined ? {} : { total }),
566
+ };
567
+ }
568
+ function boundOf(connection, plan, rooms) {
569
+ return {
570
+ rooms: [...rooms].sort((left, right) => left.locator < right.locator ? -1 : left.locator > right.locator ? 1 : 0),
571
+ keys: new Map(rooms.map(({ locator, key }) => [locator, key])),
572
+ reach: plan.reach,
573
+ collate: collating(connection),
574
+ };
575
+ }
576
+ export function answerQuery(connection, plan, rooms, budget = null) {
577
+ const bound = boundOf(connection, plan, rooms);
578
+ if (rooms.length === 0) {
579
+ return plan.shape.kind === 'groups'
580
+ ? { groups: [], ...(plan.total ? { total: 0 } : {}) }
581
+ : { documents: [], ...(plan.total ? { total: 0 } : {}) };
582
+ }
583
+ return plan.shape.kind === 'groups'
584
+ ? groups(connection, bound, plan, plan.shape)
585
+ : documents(connection, bound, plan, plan.shape, budget);
586
+ }