@agentshouse/mdruntime 0.1.0-c588e78cf6975 → 0.1.0-c59a5a6bd2f31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/README.md +2 -2
  2. package/dist/acceptance.d.ts +26 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +384 -0
  5. package/dist/adapter.d.ts +38 -25
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/attached-model.d.ts +3 -10
  8. package/dist/attached-model.d.ts.map +1 -1
  9. package/dist/attached-model.js +21 -56
  10. package/dist/bootstrap.d.ts +8 -4
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +209 -78
  13. package/dist/chain.d.ts +2 -1
  14. package/dist/chain.d.ts.map +1 -1
  15. package/dist/change.d.ts +1 -0
  16. package/dist/change.d.ts.map +1 -1
  17. package/dist/chat-types.d.ts +3 -0
  18. package/dist/chat-types.d.ts.map +1 -0
  19. package/dist/chat-types.js +2 -0
  20. package/dist/client.d.ts +14 -4
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +3 -0
  23. package/dist/collection.d.ts +2 -1
  24. package/dist/collection.d.ts.map +1 -1
  25. package/dist/compile.d.ts +7 -7
  26. package/dist/compile.d.ts.map +1 -1
  27. package/dist/compile.js +229 -297
  28. package/dist/connection.d.ts +19 -43
  29. package/dist/connection.d.ts.map +1 -1
  30. package/dist/connection.js +1325 -3586
  31. package/dist/context.d.ts +2 -0
  32. package/dist/context.d.ts.map +1 -0
  33. package/dist/context.js +7 -0
  34. package/dist/conversation-row.d.ts +33 -0
  35. package/dist/conversation-row.d.ts.map +1 -0
  36. package/dist/conversation-row.js +47 -0
  37. package/dist/conversation.d.ts +18 -9
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +375 -224
  40. package/dist/durability.d.ts +6 -0
  41. package/dist/durability.d.ts.map +1 -1
  42. package/dist/entry.d.ts +2 -2
  43. package/dist/entry.d.ts.map +1 -1
  44. package/dist/entry.js +17 -8
  45. package/dist/figures.d.ts +48 -0
  46. package/dist/figures.d.ts.map +1 -0
  47. package/dist/figures.js +197 -0
  48. package/dist/host.d.ts +4 -5
  49. package/dist/host.d.ts.map +1 -1
  50. package/dist/host.js +22 -21
  51. package/dist/identity.d.ts +0 -1
  52. package/dist/identity.d.ts.map +1 -1
  53. package/dist/identity.js +0 -3
  54. package/dist/index.d.ts +18 -8
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +5 -1
  57. package/dist/install.d.ts +86 -0
  58. package/dist/install.d.ts.map +1 -0
  59. package/dist/install.js +537 -0
  60. package/dist/job.d.ts +33 -0
  61. package/dist/job.d.ts.map +1 -0
  62. package/dist/job.js +54 -0
  63. package/dist/live.d.ts +5 -4
  64. package/dist/live.d.ts.map +1 -1
  65. package/dist/live.js +62 -20
  66. package/dist/log.d.ts +1 -0
  67. package/dist/log.d.ts.map +1 -1
  68. package/dist/memory.d.ts +4 -1
  69. package/dist/memory.d.ts.map +1 -1
  70. package/dist/memory.js +43 -4
  71. package/dist/messages.d.ts +27 -3
  72. package/dist/messages.d.ts.map +1 -1
  73. package/dist/messages.js +97 -19
  74. package/dist/model.d.ts +2 -9
  75. package/dist/model.d.ts.map +1 -1
  76. package/dist/model.js +0 -9
  77. package/dist/observation.d.ts +2 -2
  78. package/dist/observation.d.ts.map +1 -1
  79. package/dist/observation.js +7 -2
  80. package/dist/outcome.d.ts +1 -0
  81. package/dist/outcome.d.ts.map +1 -1
  82. package/dist/outcome.js +3 -0
  83. package/dist/overlay.d.ts +1 -0
  84. package/dist/overlay.d.ts.map +1 -1
  85. package/dist/overlay.js +11 -0
  86. package/dist/parts.d.ts +59 -0
  87. package/dist/parts.d.ts.map +1 -0
  88. package/dist/parts.js +118 -0
  89. package/dist/query.d.ts +13 -210
  90. package/dist/query.d.ts.map +1 -1
  91. package/dist/query.js +3 -508
  92. package/dist/questions.d.ts +32 -0
  93. package/dist/questions.d.ts.map +1 -0
  94. package/dist/questions.js +118 -0
  95. package/dist/reach.d.ts +11 -11
  96. package/dist/reach.d.ts.map +1 -1
  97. package/dist/reach.js +166 -113
  98. package/dist/react.d.ts +4 -2
  99. package/dist/react.d.ts.map +1 -1
  100. package/dist/read.d.ts +7 -0
  101. package/dist/read.d.ts.map +1 -0
  102. package/dist/read.js +17 -0
  103. package/dist/readiness.d.ts +0 -2
  104. package/dist/readiness.d.ts.map +1 -1
  105. package/dist/reads.d.ts +35 -0
  106. package/dist/reads.d.ts.map +1 -0
  107. package/dist/reads.js +128 -0
  108. package/dist/recovery.d.ts.map +1 -1
  109. package/dist/recovery.js +9 -1
  110. package/dist/refresh.d.ts +1 -1
  111. package/dist/refresh.d.ts.map +1 -1
  112. package/dist/refresh.js +8 -20
  113. package/dist/replica.d.ts +8 -8
  114. package/dist/replica.d.ts.map +1 -1
  115. package/dist/replica.js +50 -18
  116. package/dist/room-draft.d.ts +3 -2
  117. package/dist/room-draft.d.ts.map +1 -1
  118. package/dist/room-draft.js +4 -4
  119. package/dist/room-drafts.d.ts +29 -0
  120. package/dist/room-drafts.d.ts.map +1 -0
  121. package/dist/room-drafts.js +216 -0
  122. package/dist/rows.d.ts +14 -22
  123. package/dist/rows.d.ts.map +1 -1
  124. package/dist/rows.js +48 -189
  125. package/dist/save.d.ts.map +1 -1
  126. package/dist/save.js +11 -8
  127. package/dist/saves.d.ts +96 -0
  128. package/dist/saves.d.ts.map +1 -0
  129. package/dist/saves.js +999 -0
  130. package/dist/search.d.ts +12 -63
  131. package/dist/search.d.ts.map +1 -1
  132. package/dist/search.js +9 -52
  133. package/dist/seed.d.ts +16 -2
  134. package/dist/seed.d.ts.map +1 -1
  135. package/dist/seed.js +66 -0
  136. package/dist/socket/admission.d.ts +7 -0
  137. package/dist/socket/admission.d.ts.map +1 -0
  138. package/dist/socket/admission.js +39 -0
  139. package/dist/socket/answers.d.ts +6 -0
  140. package/dist/socket/answers.d.ts.map +1 -0
  141. package/dist/socket/answers.js +20 -0
  142. package/dist/socket/reads.d.ts +10 -0
  143. package/dist/socket/reads.d.ts.map +1 -0
  144. package/dist/socket/reads.js +31 -0
  145. package/dist/socket.d.ts +43 -0
  146. package/dist/socket.d.ts.map +1 -0
  147. package/dist/socket.js +96 -0
  148. package/dist/source.d.ts +61 -0
  149. package/dist/source.d.ts.map +1 -0
  150. package/dist/source.js +81 -0
  151. package/dist/status.d.ts +0 -2
  152. package/dist/status.d.ts.map +1 -1
  153. package/dist/status.js +0 -6
  154. package/dist/store/fences.d.ts +16 -0
  155. package/dist/store/fences.d.ts.map +1 -0
  156. package/dist/store/fences.js +26 -0
  157. package/dist/store/installs.d.ts +30 -0
  158. package/dist/store/installs.d.ts.map +1 -0
  159. package/dist/store/installs.js +34 -0
  160. package/dist/store/records.d.ts +212 -0
  161. package/dist/store/records.d.ts.map +1 -0
  162. package/dist/store/records.js +408 -0
  163. package/dist/store/room-held.d.ts +8 -0
  164. package/dist/store/room-held.d.ts.map +1 -0
  165. package/dist/store/room-held.js +17 -0
  166. package/dist/store/watchers.d.ts +15 -0
  167. package/dist/store/watchers.d.ts.map +1 -0
  168. package/dist/store/watchers.js +36 -0
  169. package/dist/store/writes.d.ts +24 -0
  170. package/dist/store/writes.d.ts.map +1 -0
  171. package/dist/store/writes.js +125 -0
  172. package/dist/store.d.ts +78 -156
  173. package/dist/store.d.ts.map +1 -1
  174. package/dist/store.js +117 -401
  175. package/dist/stored.d.ts +58 -65
  176. package/dist/stored.d.ts.map +1 -1
  177. package/dist/stored.js +264 -243
  178. package/dist/tables.d.ts +107 -51
  179. package/dist/tables.d.ts.map +1 -1
  180. package/dist/tables.js +193 -59
  181. package/dist/tallies.d.ts +18 -0
  182. package/dist/tallies.d.ts.map +1 -0
  183. package/dist/tallies.js +129 -0
  184. package/dist/validation.d.ts.map +1 -1
  185. package/dist/validation.js +7 -4
  186. package/dist/windows.d.ts +1 -12
  187. package/dist/windows.d.ts.map +1 -1
  188. package/dist/windows.js +2 -75
  189. package/dist/wire.d.ts +14 -0
  190. package/dist/wire.d.ts.map +1 -1
  191. package/dist/wire.js +5 -0
  192. package/package.json +2 -2
  193. package/dist/fence.d.ts +0 -6
  194. package/dist/fence.d.ts.map +0 -1
  195. package/dist/fence.js +0 -10
  196. package/dist/fulltext.d.ts +0 -27
  197. package/dist/fulltext.d.ts.map +0 -1
  198. package/dist/fulltext.js +0 -370
  199. package/dist/words.d.ts +0 -19
  200. package/dist/words.d.ts.map +0 -1
  201. package/dist/words.js +0 -156
package/dist/compile.js CHANGED
@@ -1,126 +1,13 @@
1
- import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
1
+ import { ARRAY_INDEX, bodied, byCodePoint, compared, cursorOf, heldKind, parse, pointerSegments, sql, } from '@agentshouse/mdmodel';
2
2
  import { binaryRecord } from './binary.js';
3
- import { bodied, } from './query.js';
4
- import { documentKey, kindOf, pathOfKey, placedOf, roomOfKey } from './rows.js';
5
- import { byCodePoint, compared, cursorOf, FROM_START, MAXIMUM_WINDOW, } from './windows.js';
6
- const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
7
- function fragment(sql, bind = []) {
8
- return { sql, bind };
9
- }
10
- function joined(parts, separator) {
11
- return {
12
- sql: parts.map((part) => part.sql).join(separator),
13
- bind: parts.flatMap((part) => part.bind),
14
- };
15
- }
16
- function balanced(parts, separator) {
17
- if (parts.length === 1)
18
- return grouped(parts[0]);
19
- const half = Math.ceil(parts.length / 2);
20
- return grouped(joined([balanced(parts.slice(0, half), separator), balanced(parts.slice(half), separator)], separator));
21
- }
22
- function all(parts) {
23
- return parts.length === 0 ? fragment('1') : balanced(parts, ' AND ');
24
- }
25
- function any(parts) {
26
- return parts.length === 0 ? fragment('0') : balanced(parts, ' OR ');
27
- }
28
- function grouped(part) {
29
- return { sql: `(${part.sql})`, bind: part.bind };
30
- }
31
- function list(values) {
32
- return fragment(`(${values.map(() => '?').join(', ')})`, values);
33
- }
34
- function sqlValue(value) {
35
- if (value === null)
36
- return { kind: 'null', value: null };
37
- if (typeof value === 'boolean')
38
- return { kind: 'boolean', value: value ? 1 : 0 };
39
- if (typeof value === 'number')
40
- return { kind: 'number', value };
41
- return { kind: 'string', value };
42
- }
43
- function same(alias, value) {
44
- const held = sqlValue(value);
45
- return held.kind === 'null'
46
- ? fragment(`${alias}.kind = 'null'`)
47
- : fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
48
- }
49
- const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
50
- function pointed(alias, field, reach) {
51
- const { keys, places, glob } = placedOf(field, reach);
52
- return fragment(`${alias}.keys = ? AND ${alias}.places ${glob ? 'GLOB' : '='} ?`, [keys, places]);
53
- }
54
- function heldValues(doc, field, alias, where) {
55
- return joined([
56
- fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND `),
57
- grouped(pointed(alias, field, 'value')),
58
- fragment(' AND '),
59
- grouped(where),
60
- ], '');
61
- }
62
- function exists(part) {
63
- return { sql: `EXISTS (${part.sql})`, bind: part.bind };
64
- }
65
- function itemOf(held, where) {
66
- return exists(joined([
67
- fragment(`SELECT 1 FROM replica_values i WHERE i.doc = ${held}.doc AND i.keys = ${held}.keys AND i.places = ${held}.places AND i.item >= 0 AND `),
68
- grouped(where),
69
- ], ''));
70
- }
71
- function matching(held, values) {
72
- return any(values.map((value) => any([
73
- same(held, value),
74
- all([fragment(`${held}.kind = 'array'`), itemOf(held, same('i', value))]),
75
- ])));
76
- }
77
- function tested(doc, field, test) {
78
- const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
79
- if (test.op === 'exists')
80
- return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
81
- if (test.op === 'eq' || test.op === 'in') {
82
- const values = test.op === 'eq' ? [test.value] : test.values;
83
- return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
84
- }
85
- if (test.op === 'ne' || test.op === 'nin') {
86
- const values = test.op === 'ne' ? [test.value] : test.values;
87
- const unmatched = matching('h', values);
88
- return any([
89
- fragment(`NOT ${reached.sql}`, reached.bind),
90
- exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
91
- ]);
92
- }
93
- if (test.op === 'range') {
94
- return exists(heldValues(doc, field, 'v', all([
95
- fragment('v.item = -1'),
96
- ...test.bounds.map(([operator, bound]) => {
97
- const held = sqlValue(bound);
98
- return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
99
- held.kind,
100
- held.value,
101
- ]);
102
- }),
103
- ])));
104
- }
105
- if (test.op === 'all') {
106
- return exists(heldValues(doc, field, 'h', all([
107
- fragment("h.item = -1 AND h.kind = 'array'"),
108
- ...test.values.map((value) => itemOf('h', same('i', value))),
109
- ])));
110
- }
111
- return exists(heldValues(doc, field, 'v', fragment("v.item = -1 AND v.kind = 'array' AND v.size = ?", [test.size])));
112
- }
113
- function conditioned(doc, condition) {
114
- if ('every' in condition)
115
- return all(condition.every.map((one) => conditioned(doc, one)));
116
- if ('some' in condition)
117
- return any(condition.some.map((one) => conditioned(doc, one)));
118
- return tested(doc, condition.field, condition.test);
119
- }
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
+ export const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
120
7
  function located(alias) {
121
8
  return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
122
9
  }
123
- function within(alias, path) {
10
+ export function within(alias, path) {
124
11
  return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
125
12
  path,
126
13
  `${path}/`,
@@ -163,7 +50,7 @@ function reached(bound, alias) {
163
50
  }));
164
51
  return all([
165
52
  any([
166
- ...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${list(rooms).sql}`, rooms)]),
53
+ ...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
167
54
  ...(documents.length === 0
168
55
  ? []
169
56
  : [
@@ -173,40 +60,22 @@ function reached(bound, alias) {
173
60
  ...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
174
61
  ]);
175
62
  }
176
- function unindexed(column) {
177
- return `+${column}`;
178
- }
179
- function scoped(alias, scope, byPaths = false) {
180
- const type = byPaths ? unindexed(`${alias}.type`) : `${alias}.type`;
181
- return all([
182
- ...(scope.types === null ? [] : [fragment(`${type} IN ${list(scope.types).sql}`, scope.types)]),
183
- ...(scope.pathPrefix === null || scope.pathPrefix === ''
184
- ? []
185
- : [
186
- fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
187
- `${scope.pathPrefix}/`,
188
- `${scope.pathPrefix}0`,
189
- ]),
190
- ]),
191
- ...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
192
- ]);
193
- }
194
63
  function related(bound, plan) {
195
64
  const parts = [];
196
65
  if (plan.hop !== null) {
197
66
  parts.push(exists(joined([
198
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`),
199
- grouped(pointed('l', plan.hop.field, 'link')),
68
+ grouped(sql.placed('l', plan.hop.field, 'link')),
200
69
  fragment('AND'),
201
- grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
70
+ grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
202
71
  ], ' ')));
203
72
  }
204
73
  if (plan.backlinks !== null) {
205
74
  const field = plan.backlinks.field;
206
75
  parts.push(exists(joined([
207
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`),
208
- ...(field === null ? [] : [grouped(pointed('l', field, 'link')), fragment('AND')]),
209
- grouped(all([scoped('s', plan.backlinks), reached(bound, 's')])),
77
+ ...(field === null ? [] : [grouped(sql.placed('l', field, 'link')), fragment('AND')]),
78
+ grouped(all([sql.scoped('s', plan.backlinks), reached(bound, 's')])),
210
79
  ], ' ')));
211
80
  }
212
81
  return parts;
@@ -214,11 +83,9 @@ function related(bound, plan) {
214
83
  function filtered(bound, plan) {
215
84
  const keys = bound.rooms.map(({ key }) => key);
216
85
  return all([
217
- fragment(`d.key IN ${list(keys).sql} AND ${VISIBLE('d')}`, keys),
218
- ...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
219
- ...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
86
+ fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
220
87
  reached(bound, 'd'),
221
- scoped('d', plan.root, plan.paths !== null),
88
+ sql.tests('d', plan),
222
89
  ...related(bound, plan),
223
90
  ]);
224
91
  }
@@ -249,108 +116,65 @@ const GROUP_TIES = {
249
116
  locale: undefined,
250
117
  localeOptions: undefined,
251
118
  };
252
- function classOf(value, ordering) {
253
- if (value === null || value === undefined) {
254
- return { placement: ordering.nulls === 'first' ? 0 : 2, klass: 0, scalar: null };
255
- }
256
- if (typeof value === 'boolean')
257
- return { placement: 1, klass: 1, scalar: value ? 1 : 0 };
258
- if (typeof value === 'number')
259
- return { placement: 1, klass: 1, scalar: value };
260
- if (typeof value === 'string')
261
- return { placement: 1, klass: 2, scalar: value };
262
- return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
263
- }
264
- function placement(term) {
265
- const nulls = term.ordering.nulls === 'first' ? 0 : 2;
266
- return fragment(`CASE WHEN ${term.kind.sql} IS NULL OR ${term.kind.sql} = 'null' THEN ${nulls} ELSE 1 END`, [...term.kind.bind, ...term.kind.bind]);
267
- }
268
- function klass(term) {
269
- return fragment(`CASE ${term.kind.sql} WHEN 'boolean' THEN 1 WHEN 'number' THEN 1 WHEN 'string' THEN 2 WHEN 'array' THEN 3 ELSE 4 END`, term.kind.bind);
270
- }
271
- function classed(term, which) {
272
- const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
273
- return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
274
- ...term.kind.bind,
275
- ...term.value.bind,
276
- ]);
277
- }
278
- function collated(bound, term, which) {
279
- if (which === 1)
280
- return '';
281
- const name = bound.collate(term.ordering, which === 3, term.total === true);
282
- return name === null ? '' : ` COLLATE ${name}`;
283
- }
284
- function sorted(bound, term) {
285
- const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
286
- return joined([
287
- placement(term),
288
- fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
289
- ...[1, 2, 3].map((which) => {
290
- const value = classed(term, which);
291
- return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
292
- }),
293
- ], ', ');
294
- }
295
- function beyond(bound, term, value) {
296
- const held = classOf(value, term.ordering);
297
- const place = placement(term);
298
- if (held.placement !== 1) {
299
- return {
300
- beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
301
- equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
302
- };
303
- }
304
- const after = term.ordering.direction === 'desc' ? '<' : '>';
305
- const kind = klass(term);
306
- const scalar = classed(term, held.klass);
307
- const collation = collated(bound, term, held.klass);
308
- return {
309
- beyond: any([
310
- fragment(`${place.sql} > 1`, place.bind),
311
- all([
312
- fragment(`${place.sql} = 1`, place.bind),
313
- any([
314
- fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
315
- all([
316
- fragment(`${kind.sql} = ${held.klass}`, kind.bind),
317
- fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
318
- ]),
319
- ]),
320
- ]),
321
- ]),
322
- equal: all([
323
- fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
324
- fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
325
- ]),
326
- };
327
- }
328
- function following(parts, tie) {
329
- let condition = tie;
330
- for (const part of [...parts].reverse()) {
331
- condition = any([part.beyond, all([part.equal, condition])]);
332
- }
333
- return condition;
119
+ const GREATEST = { ...GROUP_ORDER, direction: 'desc', nulls: 'last' };
120
+ const TIMES = {
121
+ created: { value: 'd.created', instant: 'd.created_instant' },
122
+ updated: { value: "(d.change ->> '$.time')", instant: 'd.changed_instant' },
123
+ };
124
+ function subfolderOf(column, prefix) {
125
+ const root = prefix ?? '';
126
+ const base = root === '' ? '' : `${root}/`;
127
+ const rest = `substr(${column}, length(?) + 1)`;
128
+ return fragment(`(CASE WHEN ${column} = ? THEN NULL WHEN instr(${rest}, '/') = 0 THEN ${column} ELSE ? || substr(${rest}, 1, instr(${rest}, '/') - 1) END)`, [root, base, base, base, base]);
334
129
  }
335
- function keyTerm(key, ordering, index) {
130
+ function keyTerm(key, ordering, index, prefix) {
336
131
  if (key.of === 'field') {
337
132
  const alias = `o${index}`;
338
133
  return {
339
- term: { ordering, kind: fragment(`${alias}.kind`), value: fragment(`${alias}.value`) },
134
+ term: {
135
+ ordering,
136
+ kind: fragment(`${alias}.kind`),
137
+ value: fragment(`${alias}.value`),
138
+ instant: fragment(`${alias}.instant`),
139
+ },
340
140
  join: joined([
341
141
  fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
342
- grouped(pointed(alias, key.field, 'exact')),
142
+ grouped(sql.placed(alias, key.field, 'exact')),
343
143
  fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
344
144
  ], ''),
345
145
  };
346
146
  }
147
+ if (key.of === 'size') {
148
+ return {
149
+ term: {
150
+ ordering,
151
+ kind: fragment("(CASE WHEN d.bytes IS NULL THEN 'null' ELSE 'number' END)"),
152
+ value: fragment('d.bytes'),
153
+ },
154
+ join: null,
155
+ };
156
+ }
157
+ if (key.of === 'created' || key.of === 'updated') {
158
+ const { value, instant } = TIMES[key.of];
159
+ return {
160
+ term: {
161
+ ordering,
162
+ kind: fragment(`(CASE WHEN ${value} IS NULL THEN 'null' ELSE 'string' END)`),
163
+ value: fragment(value),
164
+ instant: fragment(instant),
165
+ },
166
+ join: null,
167
+ };
168
+ }
347
169
  const expression = key.of === 'room'
348
170
  ? located('d')
349
171
  : key.of === 'path'
350
172
  ? fragment('d.path')
351
173
  : key.of === 'type'
352
174
  ? fragment('d.type')
353
- : fragment('d.folder');
175
+ : key.of === 'subfolder'
176
+ ? subfolderOf('d.folder', prefix)
177
+ : fragment('d.folder');
354
178
  return {
355
179
  term: {
356
180
  ordering,
@@ -371,12 +195,20 @@ function decoded(kind, value) {
371
195
  return JSON.parse(value);
372
196
  return null;
373
197
  }
198
+ function sortedOf(kind, value, instant) {
199
+ return sql.sortValue(kind, decoded(kind, value), instant);
200
+ }
201
+ function sortedValue(row, index) {
202
+ return sortedOf(row[`k${index}`], row[`v${index}`], row[`i${index}`]);
203
+ }
374
204
  function decodedKey(key, row, index) {
375
- if (key.of === 'field') {
376
- const kind = row[`k${index}`];
377
- return kind === null || kind === undefined ? undefined : decoded(kind, row[`v${index}`]);
378
- }
379
- return row[`v${index}`] ?? null;
205
+ const kind = row[`k${index}`];
206
+ if (key.of === 'field' && (kind === null || kind === undefined))
207
+ return undefined;
208
+ return sortedValue(row, index);
209
+ }
210
+ function instantOf(term) {
211
+ return term.instant ?? fragment('NULL');
380
212
  }
381
213
  const registered = new WeakMap();
382
214
  function hashed(text) {
@@ -483,10 +315,9 @@ function documentOf(shape, room, row, linked) {
483
315
  const type = row.type;
484
316
  const fileId = row.file_id ?? null;
485
317
  const revision = row.revision ?? null;
486
- const content = row.content ?? null;
487
- const unheld = Number(row.unheld) === 1 ? { textHeld: false } : {};
488
318
  if (type === null) {
489
319
  const binary = row.binary === null ? null : JSON.parse(row.binary);
320
+ const content = row.content ?? null;
490
321
  return {
491
322
  room,
492
323
  path,
@@ -496,12 +327,13 @@ function documentOf(shape, room, row, linked) {
496
327
  binaryFile: binaryRecord(path, binary, revision ?? undefined),
497
328
  }),
498
329
  ...(shape.markdown && content !== null ? { markdown: content } : {}),
499
- ...unheld,
330
+ ...(shape.change ? { change: changeOf(row) } : {}),
500
331
  ...(shape.fileId ? { fileId } : {}),
501
332
  ...(shape.revision ? { revision } : {}),
333
+ ...heldFacts(shape, row),
502
334
  };
503
335
  }
504
- const read = bodied(shape) && content !== null ? parse(content, path) : null;
336
+ const read = bodied(shape) ? parse(row.content, path) : null;
505
337
  const body = read?.ok === true ? read.document : null;
506
338
  const fields = JSON.parse(row.fields ?? '{}');
507
339
  return {
@@ -519,16 +351,21 @@ function documentOf(shape, room, row, linked) {
519
351
  ? {}
520
352
  : { sections: sectionsOf(body.sections, shape.sections) }),
521
353
  ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
522
- ...(shape.markdown && content !== null ? { markdown: content } : {}),
523
- ...unheld,
354
+ ...(shape.markdown ? { markdown: row.content } : {}),
524
355
  ...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
525
- ...(shape.change
526
- ? {
527
- change: row.change === null ? null : JSON.parse(row.change),
528
- }
529
- : {}),
356
+ ...(shape.change ? { change: changeOf(row) } : {}),
530
357
  ...(shape.fileId ? { fileId } : {}),
531
358
  ...(shape.revision ? { revision } : {}),
359
+ ...heldFacts(shape, row),
360
+ };
361
+ }
362
+ function changeOf(row) {
363
+ return row.change === null ? null : JSON.parse(row.change);
364
+ }
365
+ function heldFacts(shape, row) {
366
+ return {
367
+ ...(shape.size ? { size: row.bytes === null ? null : Number(row.bytes) } : {}),
368
+ ...(shape.created ? { created: row.created ?? null } : {}),
532
369
  };
533
370
  }
534
371
  function backlinksOf(connection, bound, rows) {
@@ -554,7 +391,7 @@ function backlinksOf(connection, bound, rows) {
554
391
  return linked;
555
392
  }
556
393
  function documents(connection, bound, plan, shape, budget) {
557
- const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index));
394
+ const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index, plan.root.pathPrefix));
558
395
  const filter = filtered(bound, plan);
559
396
  const cursor = plan.after?.values ?? null;
560
397
  const room = located('d');
@@ -573,19 +410,20 @@ function documents(connection, bound, plan, shape, budget) {
573
410
  })();
574
411
  const after = cursor === null || tie === null
575
412
  ? null
576
- : following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
413
+ : sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), tie);
577
414
  const selected = joined([
578
- fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id, d.content IS NULL AND d.outline IS NOT NULL AS unheld'),
415
+ fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id, d.bytes, d.created'),
579
416
  ...terms.map(({ term }, index) => joined([
580
417
  fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
581
418
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
419
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
582
420
  ], '')),
583
421
  fragment(' FROM replica_entries d'),
584
422
  ...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
585
423
  fragment(' WHERE '),
586
424
  after === null ? filter : all([filter, after]),
587
425
  fragment(' ORDER BY '),
588
- joined([...terms.map(({ term }) => sorted(bound, term)), room, fragment('d.path')], ', '),
426
+ joined([...terms.map(({ term }) => sql.sorted(bound.collate, term)), room, fragment('d.path')], ', '),
589
427
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
590
428
  ], '');
591
429
  const rows = connection.rows(selected.sql, selected.bind);
@@ -624,7 +462,7 @@ function withContent(connection, rows, budget) {
624
462
  return rows;
625
463
  const ids = rows.map((row) => row.id);
626
464
  const sizes = new Map(connection
627
- .rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${list(ids).sql}`, ids)
465
+ .rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${listed(ids).sql}`, ids)
628
466
  .map((row) => [row.id, Number(row.size ?? 0)]));
629
467
  let cut = rows.length;
630
468
  let spent = 0;
@@ -639,18 +477,22 @@ function withContent(connection, rows, budget) {
639
477
  const kept = rows.slice(0, cut);
640
478
  const read = kept.map((row) => row.id);
641
479
  const held = new Map(connection
642
- .rows(`SELECT id, content FROM replica_entries WHERE id IN ${list(read).sql}`, read)
480
+ .rows(`SELECT id, content FROM replica_entries WHERE id IN ${listed(read).sql}`, read)
643
481
  .map((row) => [row.id, row.content]));
644
482
  return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
645
483
  }
646
- function storedFolders(bound, plan) {
647
- if (plan.paths !== null || plan.hop !== null || plan.backlinks !== null)
484
+ function storedFolders(bound, plan, subfolder) {
485
+ if (plan.paths !== null || plan.fileIds !== null)
486
+ return null;
487
+ if (plan.hop !== null || plan.backlinks !== null)
488
+ return null;
489
+ if (subfolder && plan.folder !== null)
648
490
  return null;
649
491
  const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
650
492
  if (whole.length === 0)
651
493
  return null;
652
494
  const keys = whole.map(({ key }) => key);
653
- const prefix = plan.root.pathPrefix;
495
+ const prefix = plan.root.pathPrefix ?? '';
654
496
  const outside = (bound.reach?.areas ?? []).flatMap((area) => {
655
497
  const key = bound.keys.get(area.room);
656
498
  if (key === undefined)
@@ -658,63 +500,146 @@ function storedFolders(bound, plan) {
658
500
  const fence = fenced('f', key, area, true);
659
501
  return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
660
502
  });
503
+ const under = prefix === ''
504
+ ? subfolder
505
+ ? [fragment("f.path <> ''")]
506
+ : []
507
+ : [
508
+ subfolder
509
+ ? fragment('(f.path >= ? AND f.path < ?)', [`${prefix}/`, `${prefix}0`])
510
+ : within('f', prefix),
511
+ ];
661
512
  return all([
662
- fragment(`f.key IN ${list(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
513
+ fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
663
514
  ...outside,
664
- ...(prefix === null || prefix === ''
665
- ? []
666
- : [
667
- fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
668
- prefix,
669
- `${prefix}/`,
670
- `${prefix}0`,
671
- ]),
672
- ]),
515
+ ...under,
516
+ ...(plan.outside ?? []).map((folder) => fragment(`NOT ${within('f', folder).sql}`, within('f', folder).bind)),
673
517
  ...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
674
518
  ]);
675
519
  }
520
+ const SIZES = [
521
+ 'CASE WHEN d.binary IS NULL THEN 1 ELSE 0 END AS dn',
522
+ 'CASE WHEN d.binary IS NULL THEN d.bytes ELSE 0 END AS ds',
523
+ 'CASE WHEN d.binary IS NULL THEN 0 ELSE 1 END AS fn',
524
+ 'CASE WHEN d.binary IS NULL THEN 0 ELSE d.bytes END AS fs',
525
+ ];
526
+ const LATEST = "d.changed_instant || ' ' || (d.change ->> '$.time') AS u";
527
+ function greatestTerm(ordering) {
528
+ return { ordering, kind: fragment('gk'), value: fragment('gv'), instant: fragment('gi') };
529
+ }
530
+ function latestOf(held) {
531
+ if (typeof held !== 'string')
532
+ return null;
533
+ return held.slice(held.indexOf(' ') + 1);
534
+ }
676
535
  function groups(connection, bound, plan, shape) {
536
+ const prefix = plan.root.pathPrefix;
677
537
  const keyed = shape.groupBy.map((key, index) => {
678
- const { term, join } = keyTerm(key, GROUP_ORDER, index);
538
+ const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
679
539
  const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
680
540
  return { term: { ...term, kind }, join };
681
541
  });
542
+ const ordering = shape.orderBy.map(({ by }) => by);
543
+ const sized = shape.size || ordering.includes('size');
544
+ const dated = shape.updated || ordering.includes('updated');
545
+ const greatest = shape.greatest === null
546
+ ? null
547
+ : keyTerm({ of: 'field', field: shape.greatest }, GREATEST, keyed.length, prefix);
682
548
  const filter = filtered(bound, plan);
683
549
  const inner = joined([
684
550
  fragment('SELECT '),
685
551
  joined(keyed.map(({ term }, index) => joined([
686
552
  fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
687
553
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
554
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
688
555
  ], '')), ', '),
689
- fragment(', 1 AS n FROM replica_entries d'),
690
- ...keyed.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
556
+ fragment(['', '1 AS n', ...(sized ? SIZES : []), ...(dated ? [LATEST] : [])].join(', ')),
557
+ ...(greatest === null
558
+ ? []
559
+ : [
560
+ fragment(`, ${greatest.term.kind.sql} AS gk`, greatest.term.kind.bind),
561
+ fragment(`, ${greatest.term.value.sql} AS gv`, greatest.term.value.bind),
562
+ fragment(`, ${instantOf(greatest.term).sql} AS gi`, instantOf(greatest.term).bind),
563
+ ]),
564
+ fragment(' FROM replica_entries d'),
565
+ ...[...keyed, ...(greatest === null ? [] : [greatest])].flatMap(({ join }) => join === null ? [] : [fragment(' '), join]),
691
566
  fragment(' WHERE '),
692
567
  filter,
693
568
  ], '');
694
- const folders = shape.groupBy.length === 1 && shape.groupBy[0].of === 'folder'
695
- ? storedFolders(bound, plan)
696
- : null;
569
+ const only = shape.groupBy.length === 1 ? shape.groupBy[0].of : null;
570
+ const subfolder = only === 'subfolder';
571
+ const folders = only === 'folder' || subfolder ? storedFolders(bound, plan, subfolder) : null;
572
+ const folderKey = subfolder ? subfolderOf('f.path', prefix) : fragment('f.path');
573
+ const zeros = [
574
+ ...(sized ? ['0', '0', '0', '0'] : []),
575
+ ...(dated ? ['NULL'] : []),
576
+ ...(greatest === null ? [] : ['NULL', 'NULL', 'NULL']),
577
+ ];
697
578
  const source = folders === null
698
579
  ? inner
699
580
  : joined([
700
581
  inner,
701
- fragment(" UNION ALL SELECT 'string' AS k0, f.path AS v0, 0 AS n FROM replica_folders f WHERE "),
582
+ fragment(` UNION ALL SELECT 'string' AS k0, ${folderKey.sql} AS v0, NULL AS i0, 0 AS n${zeros.map((one) => `, ${one}`).join('')} FROM replica_folders f WHERE `, folderKey.bind),
702
583
  folders,
703
584
  ], '');
704
- const columns = keyed.map((_, index) => `k${index}, v${index}`).join(', ');
705
- const counted = {
706
- ordering: { ...GROUP_ORDER },
707
- kind: fragment("'number'"),
708
- value: fragment('n'),
709
- };
710
- const outer = (index, ordering) => ({
711
- ordering,
585
+ const held = keyed
586
+ .map((_, index) => `k${index}, i${index}, CASE WHEN i${index} IS NULL THEN v${index} END`)
587
+ .join(', ');
588
+ const ranked = greatest === null
589
+ ? source
590
+ : joined([
591
+ fragment(`SELECT *, ROW_NUMBER() OVER (PARTITION BY ${held} ORDER BY `),
592
+ sql.sorted(bound.collate, greatestTerm(GREATEST)),
593
+ fragment(', gv) AS gr FROM ('),
594
+ source,
595
+ fragment(')'),
596
+ ], '');
597
+ const columns = [
598
+ ...keyed.map((_, index) => `k${index}, min(v${index}) AS v${index}, i${index}`),
599
+ 'sum(n) AS n',
600
+ ...(sized ? ['sum(dn) AS dn', 'sum(ds) AS ds', 'sum(fn) AS fn', 'sum(fs) AS fs'] : []),
601
+ ...(dated ? ['max(u) AS u'] : []),
602
+ ...(greatest === null
603
+ ? []
604
+ : ['gk', 'gv', 'gi'].map((column) => `min(CASE WHEN gr = 1 THEN ${column} END) AS ${column}`)),
605
+ ].join(', ');
606
+ const outer = (index, order) => ({
607
+ ordering: order,
712
608
  kind: fragment(`k${index}`),
713
609
  value: fragment(`v${index}`),
714
610
  });
715
- const ordered = shape.orderBy.map(({ by, direction }) => by === 'count'
716
- ? { ...counted, ordering: { ...GROUP_ORDER, direction } }
717
- : outer(by, { ...GROUP_ORDER, direction }));
611
+ const termOf = (by, order) => {
612
+ if (by === 'count')
613
+ return { ordering: order, kind: fragment("'number'"), value: fragment('n') };
614
+ if (by === 'size') {
615
+ return { ordering: order, kind: fragment("'number'"), value: fragment('(ds + fs)') };
616
+ }
617
+ if (by === 'updated') {
618
+ return {
619
+ ordering: order,
620
+ kind: fragment("(CASE WHEN u IS NULL THEN 'null' ELSE 'string' END)"),
621
+ value: fragment("substr(u, instr(u, ' ') + 1)"),
622
+ instant: fragment("substr(u, 1, instr(u, ' ') - 1)"),
623
+ };
624
+ }
625
+ if (by === 'greatest')
626
+ return greatestTerm(order);
627
+ return { ...outer(by, order), instant: fragment(`i${by}`) };
628
+ };
629
+ const valueOf = (row, by) => {
630
+ if (by === 'count')
631
+ return Number(row.n);
632
+ if (by === 'size')
633
+ return Number(row.ds) + Number(row.fs);
634
+ if (by === 'updated') {
635
+ const text = latestOf(row.u);
636
+ return text === null ? null : sql.sortValue('string', text, row.u.split(' ')[0]);
637
+ }
638
+ if (by === 'greatest')
639
+ return sortedOf(row.gk, row.gv, row.gi);
640
+ return sortedValue(row, by);
641
+ };
642
+ const ordered = shape.orderBy.map(({ by, direction, nulls }) => termOf(by, { ...GROUP_ORDER, direction, nulls: nulls ?? GROUP_ORDER.nulls }));
718
643
  const ties = keyed.flatMap((_, index) => [
719
644
  { ...outer(index, GROUP_TIES), total: true },
720
645
  { ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
@@ -724,34 +649,44 @@ function groups(connection, bound, plan, shape) {
724
649
  ? null
725
650
  : cursor[shape.orderBy.length].flatMap((value) => [
726
651
  value,
727
- kindOf(value ?? null),
652
+ heldKind(value ?? null),
728
653
  ]);
729
654
  const after = cursor === null || tieValues === null
730
655
  ? null
731
- : following(ordered.map((term, index) => beyond(bound, term, cursor[index])), following(ties.slice(0, -1).map((term, index) => beyond(bound, term, tieValues[index])), beyond(bound, ties.at(-1), tieValues.at(-1)).beyond));
732
- const grouping = joined([fragment(`SELECT ${columns}, sum(n) AS n FROM (`), source, fragment(`) GROUP BY ${columns}`)], '');
656
+ : sql.following(ordered.map((term, index) => sql.beyond(bound.collate, term, cursor[index])), sql.following(ties
657
+ .slice(0, -1)
658
+ .map((term, index) => sql.beyond(bound.collate, term, tieValues[index])), sql.beyond(bound.collate, ties.at(-1), tieValues.at(-1)).beyond));
659
+ const grouping = joined([fragment(`SELECT ${columns} FROM (`), ranked, fragment(`) GROUP BY ${held}`)], '');
733
660
  const windowed = joined([
734
661
  fragment('SELECT * FROM ('),
735
662
  grouping,
736
663
  fragment(')'),
737
664
  ...(after === null ? [] : [fragment(' WHERE '), after]),
738
665
  fragment(' ORDER BY '),
739
- joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
666
+ joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
740
667
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
741
668
  ], '');
742
669
  const rows = connection.rows(windowed.sql, windowed.bind);
743
670
  const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
744
671
  const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
745
- const found = kept.map((row) => ({ key: keyOf(row), count: Number(row.n) }));
672
+ const found = kept.map((row) => ({
673
+ key: keyOf(row),
674
+ count: Number(row.n),
675
+ ...(shape.size
676
+ ? {
677
+ documents: { count: Number(row.dn), size: Number(row.ds) },
678
+ files: { count: Number(row.fn), size: Number(row.fs) },
679
+ }
680
+ : {}),
681
+ ...(shape.updated ? { updated: latestOf(row.u) } : {}),
682
+ ...(shape.greatest === null ? {} : { greatest: decoded(row.gk, row.gv) }),
683
+ }));
746
684
  const last = kept.at(-1);
747
685
  const next = plan.limit === null || rows.length <= plan.limit
748
686
  ? undefined
749
687
  : last === undefined
750
688
  ? (plan.after?.text ?? FROM_START)
751
- : cursorOf([
752
- ...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
753
- keyOf(last),
754
- ]);
689
+ : cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
755
690
  const total = plan.total
756
691
  ? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
757
692
  : undefined;
@@ -769,9 +704,6 @@ function boundOf(connection, plan, rooms) {
769
704
  collate: collating(connection),
770
705
  };
771
706
  }
772
- export function selected(connection, plan, rooms) {
773
- return filtered(boundOf(connection, plan, rooms), plan);
774
- }
775
707
  export function answerQuery(connection, plan, rooms, budget = null) {
776
708
  const bound = boundOf(connection, plan, rooms);
777
709
  if (rooms.length === 0) {