@agentshouse/mdruntime 0.1.0-ca6edc1abd618 → 0.1.0-ca8707258fdde

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 (220) 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 +102 -70
  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 +29 -47
  10. package/dist/bootstrap.d.ts +16 -14
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +272 -129
  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 +20 -10
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +55 -6
  23. package/dist/collection.d.ts +7 -4
  24. package/dist/collection.d.ts.map +1 -1
  25. package/dist/collection.js +23 -17
  26. package/dist/compile.d.ts +6 -6
  27. package/dist/compile.d.ts.map +1 -1
  28. package/dist/compile.js +278 -282
  29. package/dist/connection.d.ts +22 -44
  30. package/dist/connection.d.ts.map +1 -1
  31. package/dist/connection.js +1600 -3577
  32. package/dist/context.d.ts +2 -0
  33. package/dist/context.d.ts.map +1 -0
  34. package/dist/context.js +7 -0
  35. package/dist/conversation-row.d.ts +33 -0
  36. package/dist/conversation-row.d.ts.map +1 -0
  37. package/dist/conversation-row.js +47 -0
  38. package/dist/conversation.d.ts +27 -18
  39. package/dist/conversation.d.ts.map +1 -1
  40. package/dist/conversation.js +484 -258
  41. package/dist/durability.d.ts +6 -1
  42. package/dist/durability.d.ts.map +1 -1
  43. package/dist/entry.d.ts +3 -2
  44. package/dist/entry.d.ts.map +1 -1
  45. package/dist/entry.js +18 -8
  46. package/dist/figures.d.ts +48 -0
  47. package/dist/figures.d.ts.map +1 -0
  48. package/dist/figures.js +197 -0
  49. package/dist/file.d.ts +2 -11
  50. package/dist/file.d.ts.map +1 -1
  51. package/dist/file.js +6 -4
  52. package/dist/host.d.ts +3 -7
  53. package/dist/host.d.ts.map +1 -1
  54. package/dist/host.js +29 -32
  55. package/dist/identity.d.ts +0 -1
  56. package/dist/identity.d.ts.map +1 -1
  57. package/dist/identity.js +0 -3
  58. package/dist/index.d.ts +20 -10
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +6 -1
  61. package/dist/install.d.ts +86 -0
  62. package/dist/install.d.ts.map +1 -0
  63. package/dist/install.js +537 -0
  64. package/dist/job.d.ts +33 -0
  65. package/dist/job.d.ts.map +1 -0
  66. package/dist/job.js +54 -0
  67. package/dist/live.d.ts +5 -4
  68. package/dist/live.d.ts.map +1 -1
  69. package/dist/live.js +62 -20
  70. package/dist/log.d.ts +1 -0
  71. package/dist/log.d.ts.map +1 -1
  72. package/dist/media.d.ts +8 -0
  73. package/dist/media.d.ts.map +1 -0
  74. package/dist/media.js +218 -0
  75. package/dist/memory.d.ts +12 -5
  76. package/dist/memory.d.ts.map +1 -1
  77. package/dist/memory.js +216 -85
  78. package/dist/messages.d.ts +27 -3
  79. package/dist/messages.d.ts.map +1 -1
  80. package/dist/messages.js +97 -19
  81. package/dist/model.d.ts +8 -10
  82. package/dist/model.d.ts.map +1 -1
  83. package/dist/model.js +5 -17
  84. package/dist/observation.d.ts +3 -2
  85. package/dist/observation.d.ts.map +1 -1
  86. package/dist/observation.js +8 -3
  87. package/dist/outcome.d.ts +14 -5
  88. package/dist/outcome.d.ts.map +1 -1
  89. package/dist/outcome.js +19 -2
  90. package/dist/overlay.d.ts +1 -0
  91. package/dist/overlay.d.ts.map +1 -1
  92. package/dist/overlay.js +11 -0
  93. package/dist/parts.d.ts +59 -0
  94. package/dist/parts.d.ts.map +1 -0
  95. package/dist/parts.js +118 -0
  96. package/dist/paths.d.ts +1 -0
  97. package/dist/paths.d.ts.map +1 -1
  98. package/dist/paths.js +4 -0
  99. package/dist/query.d.ts +18 -207
  100. package/dist/query.d.ts.map +1 -1
  101. package/dist/query.js +3 -505
  102. package/dist/questions.d.ts +32 -0
  103. package/dist/questions.d.ts.map +1 -0
  104. package/dist/questions.js +118 -0
  105. package/dist/reach.d.ts +18 -11
  106. package/dist/reach.d.ts.map +1 -1
  107. package/dist/reach.js +206 -126
  108. package/dist/react.d.ts +5 -2
  109. package/dist/react.d.ts.map +1 -1
  110. package/dist/read.d.ts +7 -0
  111. package/dist/read.d.ts.map +1 -0
  112. package/dist/read.js +17 -0
  113. package/dist/readiness.d.ts +3 -17
  114. package/dist/readiness.d.ts.map +1 -1
  115. package/dist/readiness.js +4 -10
  116. package/dist/reads.d.ts +34 -10
  117. package/dist/reads.d.ts.map +1 -1
  118. package/dist/reads.js +114 -86
  119. package/dist/recovery.d.ts +1 -0
  120. package/dist/recovery.d.ts.map +1 -1
  121. package/dist/recovery.js +35 -1
  122. package/dist/refresh.d.ts +7 -5
  123. package/dist/refresh.d.ts.map +1 -1
  124. package/dist/refresh.js +33 -5
  125. package/dist/replica.d.ts +10 -11
  126. package/dist/replica.d.ts.map +1 -1
  127. package/dist/replica.js +52 -20
  128. package/dist/room-draft.d.ts +21 -5
  129. package/dist/room-draft.d.ts.map +1 -1
  130. package/dist/room-draft.js +98 -51
  131. package/dist/room-drafts.d.ts +29 -0
  132. package/dist/room-drafts.d.ts.map +1 -0
  133. package/dist/room-drafts.js +216 -0
  134. package/dist/rows.d.ts +18 -16
  135. package/dist/rows.d.ts.map +1 -1
  136. package/dist/rows.js +45 -153
  137. package/dist/save.d.ts +2 -0
  138. package/dist/save.d.ts.map +1 -1
  139. package/dist/save.js +22 -13
  140. package/dist/saves.d.ts +96 -0
  141. package/dist/saves.d.ts.map +1 -0
  142. package/dist/saves.js +999 -0
  143. package/dist/search.d.ts +12 -63
  144. package/dist/search.d.ts.map +1 -1
  145. package/dist/search.js +9 -52
  146. package/dist/seed.d.ts +16 -2
  147. package/dist/seed.d.ts.map +1 -1
  148. package/dist/seed.js +66 -0
  149. package/dist/shared.d.ts +0 -1
  150. package/dist/shared.d.ts.map +1 -1
  151. package/dist/shared.js +0 -10
  152. package/dist/socket/admission.d.ts +7 -0
  153. package/dist/socket/admission.d.ts.map +1 -0
  154. package/dist/socket/admission.js +39 -0
  155. package/dist/socket/answers.d.ts +6 -0
  156. package/dist/socket/answers.d.ts.map +1 -0
  157. package/dist/socket/answers.js +20 -0
  158. package/dist/socket/reads.d.ts +10 -0
  159. package/dist/socket/reads.d.ts.map +1 -0
  160. package/dist/socket/reads.js +31 -0
  161. package/dist/socket.d.ts +43 -0
  162. package/dist/socket.d.ts.map +1 -0
  163. package/dist/socket.js +96 -0
  164. package/dist/source.d.ts +61 -0
  165. package/dist/source.d.ts.map +1 -0
  166. package/dist/source.js +81 -0
  167. package/dist/status.d.ts +4 -5
  168. package/dist/status.d.ts.map +1 -1
  169. package/dist/status.js +3 -7
  170. package/dist/store/fences.d.ts +16 -0
  171. package/dist/store/fences.d.ts.map +1 -0
  172. package/dist/store/fences.js +26 -0
  173. package/dist/store/installs.d.ts +30 -0
  174. package/dist/store/installs.d.ts.map +1 -0
  175. package/dist/store/installs.js +34 -0
  176. package/dist/store/records.d.ts +212 -0
  177. package/dist/store/records.d.ts.map +1 -0
  178. package/dist/store/records.js +408 -0
  179. package/dist/store/room-held.d.ts +8 -0
  180. package/dist/store/room-held.d.ts.map +1 -0
  181. package/dist/store/room-held.js +17 -0
  182. package/dist/store/watchers.d.ts +15 -0
  183. package/dist/store/watchers.d.ts.map +1 -0
  184. package/dist/store/watchers.js +36 -0
  185. package/dist/store/writes.d.ts +24 -0
  186. package/dist/store/writes.d.ts.map +1 -0
  187. package/dist/store/writes.js +125 -0
  188. package/dist/store.d.ts +76 -158
  189. package/dist/store.d.ts.map +1 -1
  190. package/dist/store.js +117 -403
  191. package/dist/stored.d.ts +66 -55
  192. package/dist/stored.d.ts.map +1 -1
  193. package/dist/stored.js +343 -273
  194. package/dist/tables.d.ts +118 -65
  195. package/dist/tables.d.ts.map +1 -1
  196. package/dist/tables.js +199 -99
  197. package/dist/tallies.d.ts +18 -0
  198. package/dist/tallies.d.ts.map +1 -0
  199. package/dist/tallies.js +129 -0
  200. package/dist/transfer.d.ts +0 -5
  201. package/dist/transfer.d.ts.map +1 -1
  202. package/dist/validation.d.ts +3 -4
  203. package/dist/validation.d.ts.map +1 -1
  204. package/dist/validation.js +42 -100
  205. package/dist/windows.d.ts +1 -12
  206. package/dist/windows.d.ts.map +1 -1
  207. package/dist/windows.js +2 -75
  208. package/dist/wire.d.ts +23 -8
  209. package/dist/wire.d.ts.map +1 -1
  210. package/dist/wire.js +8 -2
  211. package/package.json +2 -2
  212. package/dist/fence.d.ts +0 -6
  213. package/dist/fence.d.ts.map +0 -1
  214. package/dist/fence.js +0 -10
  215. package/dist/fulltext.d.ts +0 -27
  216. package/dist/fulltext.d.ts.map +0 -1
  217. package/dist/fulltext.js +0 -368
  218. package/dist/words.d.ts +0 -19
  219. package/dist/words.d.ts.map +0 -1
  220. package/dist/words.js +0 -150
package/dist/compile.js CHANGED
@@ -1,107 +1,36 @@
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 { documentKey, kindOf, pathOfKey, roomOfKey, settingsTextPath } from './rows.js';
4
- import { byCodePoint, compared, cursorOf, FROM_START, MAXIMUM_WINDOW, } from './windows.js';
5
- const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
6
- function fragment(sql, bind = []) {
7
- return { sql, bind };
8
- }
9
- function joined(parts, separator) {
10
- return {
11
- sql: parts.map((part) => part.sql).join(separator),
12
- bind: parts.flatMap((part) => part.bind),
13
- };
14
- }
15
- function all(parts) {
16
- return parts.length === 0 ? fragment('1') : joined(parts.map(grouped), ' AND ');
17
- }
18
- function any(parts) {
19
- return parts.length === 0 ? fragment('0') : joined(parts.map(grouped), ' OR ');
20
- }
21
- function grouped(part) {
22
- return { sql: `(${part.sql})`, bind: part.bind };
23
- }
24
- function list(values) {
25
- return fragment(`(${values.map(() => '?').join(', ')})`, values);
26
- }
27
- function sqlValue(value) {
28
- if (value === null)
29
- return { kind: 'null', value: null };
30
- if (typeof value === 'boolean')
31
- return { kind: 'boolean', value: value ? 1 : 0 };
32
- if (typeof value === 'number')
33
- return { kind: 'number', value };
34
- return { kind: 'string', value };
35
- }
36
- function same(alias, value) {
37
- const held = sqlValue(value);
38
- return held.kind === 'null'
39
- ? fragment(`${alias}.kind = 'null'`)
40
- : fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
41
- }
42
- const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
43
- function heldValues(doc, field, alias, where) {
44
- return fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND ${alias}.form = ? AND (${where.sql})`, [field, ...where.bind]);
45
- }
46
- function exists(part) {
47
- return { sql: `EXISTS (${part.sql})`, bind: part.bind };
48
- }
49
- function matching(held, doc, field, values) {
50
- return any(values.map((value) => any([
51
- same(held, value),
52
- all([
53
- fragment(`${held}.kind = 'array'`),
54
- exists(heldValues(doc, field, 'i', all([fragment(`i.held = ${held}.held AND i.item >= 0`), same('i', value)]))),
55
- ]),
56
- ])));
57
- }
58
- function tested(doc, field, test) {
59
- const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
60
- if (test.op === 'exists')
61
- return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
62
- if (test.op === 'eq' || test.op === 'in') {
63
- const values = test.op === 'eq' ? [test.value] : test.values;
64
- return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
65
- }
66
- if (test.op === 'ne' || test.op === 'nin') {
67
- const values = test.op === 'ne' ? [test.value] : test.values;
68
- const unmatched = matching('h', doc, field, values);
69
- return any([
70
- fragment(`NOT ${reached.sql}`, reached.bind),
71
- exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
72
- ]);
73
- }
74
- if (test.op === 'range') {
75
- return exists(heldValues(doc, field, 'v', all([
76
- fragment('v.item = -1'),
77
- ...test.bounds.map(([operator, bound]) => {
78
- const held = sqlValue(bound);
79
- return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
80
- held.kind,
81
- held.value,
82
- ]);
83
- }),
84
- ])));
85
- }
86
- if (test.op === 'all') {
87
- return exists(heldValues(doc, field, 'h', all([
88
- fragment("h.item = -1 AND h.kind = 'array'"),
89
- ...test.values.map((value) => exists(heldValues(doc, field, 'i', all([fragment('i.held = h.held AND i.item >= 0'), same('i', value)])))),
90
- ])));
91
- }
92
- return exists(heldValues(doc, field, 'v', fragment("v.item = -1 AND v.kind = 'array' AND v.size = ?", [test.size])));
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`;
7
+ function located(alias) {
8
+ return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
9
+ }
10
+ export function within(alias, path) {
11
+ return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
12
+ path,
13
+ `${path}/`,
14
+ `${path}0`,
15
+ ]);
93
16
  }
94
- function conditioned(doc, condition) {
95
- if ('every' in condition)
96
- return all(condition.every.map((one) => conditioned(doc, one)));
97
- if ('some' in condition)
98
- return any(condition.some.map((one) => conditioned(doc, one)));
99
- return tested(doc, condition.field, condition.test);
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
+ });
100
23
  }
101
- function located(bound, alias) {
102
- if (bound.rooms.length === 0)
103
- return fragment("''");
104
- return fragment(`CASE ${alias}.key ${bound.rooms.map(() => 'WHEN ? THEN ?').join(' ')} END`, bound.rooms.flatMap(({ key, locator }) => [key, locator]));
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
+ ]);
105
34
  }
106
35
  function reached(bound, alias) {
107
36
  const reach = bound.reach;
@@ -115,44 +44,38 @@ function reached(bound, alias) {
115
44
  const key = bound.keys.get(roomOfKey(held));
116
45
  return key === undefined ? [] : [[key, pathOfKey(held)]];
117
46
  });
118
- return any([
119
- ...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${list(rooms).sql}`, rooms)]),
120
- ...(documents.length === 0
121
- ? []
122
- : [
123
- fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
124
- ]),
125
- ]);
126
- }
127
- function scoped(alias, scope) {
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
+ }));
128
51
  return all([
129
- ...(scope.types === null
130
- ? []
131
- : [fragment(`${alias}.type IN ${list(scope.types).sql}`, scope.types)]),
132
- ...(scope.pathPrefix === null || scope.pathPrefix === ''
133
- ? []
134
- : [
135
- fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
136
- `${scope.pathPrefix}/`,
137
- `${scope.pathPrefix}0`,
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)]),
138
58
  ]),
139
- ]),
140
- ...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
59
+ ]),
60
+ ...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
141
61
  ]);
142
62
  }
143
63
  function related(bound, plan) {
144
64
  const parts = [];
145
65
  if (plan.hop !== null) {
146
66
  parts.push(exists(joined([
147
- 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 l.form = ? AND`, [plan.hop.field]),
148
- grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
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')])),
149
71
  ], ' ')));
150
72
  }
151
73
  if (plan.backlinks !== null) {
152
74
  const field = plan.backlinks.field;
153
75
  parts.push(exists(joined([
154
- 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'${field === null ? '' : ' AND l.form = ?'} AND`, field === null ? [] : [field]),
155
- grouped(all([scoped('s', plan.backlinks), reached(bound, 's')])),
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')])),
156
79
  ], ' ')));
157
80
  }
158
81
  return parts;
@@ -160,11 +83,9 @@ function related(bound, plan) {
160
83
  function filtered(bound, plan) {
161
84
  const keys = bound.rooms.map(({ key }) => key);
162
85
  return all([
163
- fragment(`d.key IN ${list(keys).sql} AND ${VISIBLE('d')}`, keys),
164
- ...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
165
- ...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
86
+ fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
166
87
  reached(bound, 'd'),
167
- scoped('d', plan.root),
88
+ sql.tests('d', plan),
168
89
  ...related(bound, plan),
169
90
  ]);
170
91
  }
@@ -195,104 +116,65 @@ const GROUP_TIES = {
195
116
  locale: undefined,
196
117
  localeOptions: undefined,
197
118
  };
198
- function classOf(value, ordering) {
199
- if (value === null || value === undefined) {
200
- return { placement: ordering.nulls === 'first' ? 0 : 2, klass: 0, scalar: null };
201
- }
202
- if (typeof value === 'boolean')
203
- return { placement: 1, klass: 1, scalar: value ? 1 : 0 };
204
- if (typeof value === 'number')
205
- return { placement: 1, klass: 1, scalar: value };
206
- if (typeof value === 'string')
207
- return { placement: 1, klass: 2, scalar: value };
208
- return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
209
- }
210
- function placement(term) {
211
- const nulls = term.ordering.nulls === 'first' ? 0 : 2;
212
- 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]);
213
- }
214
- function klass(term) {
215
- 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);
216
- }
217
- function classed(term, which) {
218
- const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
219
- return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
220
- ...term.kind.bind,
221
- ...term.value.bind,
222
- ]);
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]);
223
129
  }
224
- function collated(bound, term, which) {
225
- if (which === 1)
226
- return '';
227
- const name = bound.collate(term.ordering, which === 3, term.total === true);
228
- return name === null ? '' : ` COLLATE ${name}`;
229
- }
230
- function sorted(bound, term) {
231
- const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
232
- return joined([
233
- placement(term),
234
- fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
235
- ...[1, 2, 3].map((which) => {
236
- const value = classed(term, which);
237
- return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
238
- }),
239
- ], ', ');
240
- }
241
- function beyond(bound, term, value) {
242
- const held = classOf(value, term.ordering);
243
- const place = placement(term);
244
- if (held.placement !== 1) {
130
+ function keyTerm(key, ordering, index, prefix) {
131
+ if (key.of === 'field') {
132
+ const alias = `o${index}`;
245
133
  return {
246
- beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
247
- equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
134
+ term: {
135
+ ordering,
136
+ kind: fragment(`${alias}.kind`),
137
+ value: fragment(`${alias}.value`),
138
+ instant: fragment(`${alias}.instant`),
139
+ },
140
+ join: joined([
141
+ fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
142
+ grouped(sql.placed(alias, key.field, 'exact')),
143
+ fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
144
+ ], ''),
248
145
  };
249
146
  }
250
- const after = term.ordering.direction === 'desc' ? '<' : '>';
251
- const kind = klass(term);
252
- const scalar = classed(term, held.klass);
253
- const collation = collated(bound, term, held.klass);
254
- return {
255
- beyond: any([
256
- fragment(`${place.sql} > 1`, place.bind),
257
- all([
258
- fragment(`${place.sql} = 1`, place.bind),
259
- any([
260
- fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
261
- all([
262
- fragment(`${kind.sql} = ${held.klass}`, kind.bind),
263
- fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
264
- ]),
265
- ]),
266
- ]),
267
- ]),
268
- equal: all([
269
- fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
270
- fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
271
- ]),
272
- };
273
- }
274
- function following(parts, tie) {
275
- let condition = tie;
276
- for (const part of [...parts].reverse()) {
277
- condition = any([part.beyond, all([part.equal, condition])]);
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
+ };
278
156
  }
279
- return condition;
280
- }
281
- function keyTerm(bound, key, ordering, index) {
282
- if (key.of === 'field') {
283
- const alias = `o${index}`;
157
+ if (key.of === 'created' || key.of === 'updated') {
158
+ const { value, instant } = TIMES[key.of];
284
159
  return {
285
- term: { ordering, kind: fragment(`${alias}.kind`), value: fragment(`${alias}.value`) },
286
- join: fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND ${alias}.form = ? AND ${alias}.single = 1 AND ${alias}.item = -1`, [key.field]),
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,
287
167
  };
288
168
  }
289
169
  const expression = key.of === 'room'
290
- ? located(bound, 'd')
170
+ ? located('d')
291
171
  : key.of === 'path'
292
172
  ? fragment('d.path')
293
173
  : key.of === 'type'
294
174
  ? fragment('d.type')
295
- : fragment('d.folder');
175
+ : key.of === 'subfolder'
176
+ ? subfolderOf('d.folder', prefix)
177
+ : fragment('d.folder');
296
178
  return {
297
179
  term: {
298
180
  ordering,
@@ -313,12 +195,20 @@ function decoded(kind, value) {
313
195
  return JSON.parse(value);
314
196
  return null;
315
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
+ }
316
204
  function decodedKey(key, row, index) {
317
- if (key.of === 'field') {
318
- const kind = row[`k${index}`];
319
- return kind === null || kind === undefined ? undefined : decoded(kind, row[`v${index}`]);
320
- }
321
- 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');
322
212
  }
323
213
  const registered = new WeakMap();
324
214
  function hashed(text) {
@@ -369,9 +259,6 @@ function collating(connection) {
369
259
  return name;
370
260
  };
371
261
  }
372
- function bodied(shape) {
373
- return shape.sections !== null || shape.preamble || shape.markdown;
374
- }
375
262
  function masked(pointers) {
376
263
  const root = { whole: false, members: new Map() };
377
264
  for (const pointer of pointers) {
@@ -439,11 +326,11 @@ function documentOf(shape, room, row, linked) {
439
326
  : {
440
327
  binaryFile: binaryRecord(path, binary, revision ?? undefined),
441
328
  }),
442
- ...(shape.markdown && content !== null && settingsTextPath(path)
443
- ? { markdown: content }
444
- : {}),
329
+ ...(shape.markdown && content !== null ? { markdown: content } : {}),
330
+ ...(shape.change ? { change: changeOf(row) } : {}),
445
331
  ...(shape.fileId ? { fileId } : {}),
446
332
  ...(shape.revision ? { revision } : {}),
333
+ ...heldFacts(shape, row),
447
334
  };
448
335
  }
449
336
  const read = bodied(shape) ? parse(row.content, path) : null;
@@ -464,15 +351,21 @@ function documentOf(shape, room, row, linked) {
464
351
  ? {}
465
352
  : { sections: sectionsOf(body.sections, shape.sections) }),
466
353
  ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
467
- ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
354
+ ...(shape.markdown ? { markdown: row.content } : {}),
468
355
  ...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
469
- ...(shape.change
470
- ? {
471
- change: row.change === null ? null : JSON.parse(row.change),
472
- }
473
- : {}),
356
+ ...(shape.change ? { change: changeOf(row) } : {}),
474
357
  ...(shape.fileId ? { fileId } : {}),
475
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 } : {}),
476
369
  };
477
370
  }
478
371
  function backlinksOf(connection, bound, rows) {
@@ -498,10 +391,10 @@ function backlinksOf(connection, bound, rows) {
498
391
  return linked;
499
392
  }
500
393
  function documents(connection, bound, plan, shape, budget) {
501
- const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(bound, key, orderingOf(options), index));
394
+ const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index, plan.root.pathPrefix));
502
395
  const filter = filtered(bound, plan);
503
396
  const cursor = plan.after?.values ?? null;
504
- const room = located(bound, 'd');
397
+ const room = located('d');
505
398
  const tie = cursor === null
506
399
  ? null
507
400
  : (() => {
@@ -517,19 +410,20 @@ function documents(connection, bound, plan, shape, budget) {
517
410
  })();
518
411
  const after = cursor === null || tie === null
519
412
  ? null
520
- : 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);
521
414
  const selected = joined([
522
- fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id'),
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'),
523
416
  ...terms.map(({ term }, index) => joined([
524
417
  fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
525
418
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
419
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
526
420
  ], '')),
527
421
  fragment(' FROM replica_entries d'),
528
422
  ...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
529
423
  fragment(' WHERE '),
530
424
  after === null ? filter : all([filter, after]),
531
425
  fragment(' ORDER BY '),
532
- 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')], ', '),
533
427
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
534
428
  ], '');
535
429
  const rows = connection.rows(selected.sql, selected.bind);
@@ -568,7 +462,7 @@ function withContent(connection, rows, budget) {
568
462
  return rows;
569
463
  const ids = rows.map((row) => row.id);
570
464
  const sizes = new Map(connection
571
- .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)
572
466
  .map((row) => [row.id, Number(row.size ?? 0)]));
573
467
  let cut = rows.length;
574
468
  let spent = 0;
@@ -583,74 +477,169 @@ function withContent(connection, rows, budget) {
583
477
  const kept = rows.slice(0, cut);
584
478
  const read = kept.map((row) => row.id);
585
479
  const held = new Map(connection
586
- .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)
587
481
  .map((row) => [row.id, row.content]));
588
482
  return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
589
483
  }
590
- function storedFolders(bound, plan) {
591
- 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)
592
490
  return null;
593
491
  const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
594
492
  if (whole.length === 0)
595
493
  return null;
596
494
  const keys = whole.map(({ key }) => key);
597
- const prefix = plan.root.pathPrefix;
495
+ const prefix = plan.root.pathPrefix ?? '';
496
+ const outside = (bound.reach?.areas ?? []).flatMap((area) => {
497
+ const key = bound.keys.get(area.room);
498
+ if (key === undefined)
499
+ return [];
500
+ const fence = fenced('f', key, area, true);
501
+ return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
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
+ ];
598
512
  return all([
599
- fragment(`f.key IN ${list(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
600
- ...(prefix === null || prefix === ''
601
- ? []
602
- : [
603
- fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
604
- prefix,
605
- `${prefix}/`,
606
- `${prefix}0`,
607
- ]),
608
- ]),
513
+ fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
514
+ ...outside,
515
+ ...under,
516
+ ...(plan.outside ?? []).map((folder) => fragment(`NOT ${within('f', folder).sql}`, within('f', folder).bind)),
609
517
  ...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
610
518
  ]);
611
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
+ }
612
535
  function groups(connection, bound, plan, shape) {
536
+ const prefix = plan.root.pathPrefix;
613
537
  const keyed = shape.groupBy.map((key, index) => {
614
- const { term, join } = keyTerm(bound, key, GROUP_ORDER, index);
538
+ const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
615
539
  const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
616
540
  return { term: { ...term, kind }, join };
617
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);
618
548
  const filter = filtered(bound, plan);
619
549
  const inner = joined([
620
550
  fragment('SELECT '),
621
551
  joined(keyed.map(({ term }, index) => joined([
622
552
  fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
623
553
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
554
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
624
555
  ], '')), ', '),
625
- fragment(', 1 AS n FROM replica_entries d'),
626
- ...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]),
627
566
  fragment(' WHERE '),
628
567
  filter,
629
568
  ], '');
630
- const folders = shape.groupBy.length === 1 && shape.groupBy[0].of === 'folder'
631
- ? storedFolders(bound, plan)
632
- : 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
+ ];
633
578
  const source = folders === null
634
579
  ? inner
635
580
  : joined([
636
581
  inner,
637
- 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),
638
583
  folders,
639
584
  ], '');
640
- const columns = keyed.map((_, index) => `k${index}, v${index}`).join(', ');
641
- const counted = {
642
- ordering: { ...GROUP_ORDER },
643
- kind: fragment("'number'"),
644
- value: fragment('n'),
645
- };
646
- const outer = (index, ordering) => ({
647
- 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,
648
608
  kind: fragment(`k${index}`),
649
609
  value: fragment(`v${index}`),
650
610
  });
651
- const ordered = shape.orderBy.map(({ by, direction }) => by === 'count'
652
- ? { ...counted, ordering: { ...GROUP_ORDER, direction } }
653
- : 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 }));
654
643
  const ties = keyed.flatMap((_, index) => [
655
644
  { ...outer(index, GROUP_TIES), total: true },
656
645
  { ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
@@ -660,34 +649,44 @@ function groups(connection, bound, plan, shape) {
660
649
  ? null
661
650
  : cursor[shape.orderBy.length].flatMap((value) => [
662
651
  value,
663
- kindOf(value ?? null),
652
+ heldKind(value ?? null),
664
653
  ]);
665
654
  const after = cursor === null || tieValues === null
666
655
  ? null
667
- : 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));
668
- 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}`)], '');
669
660
  const windowed = joined([
670
661
  fragment('SELECT * FROM ('),
671
662
  grouping,
672
663
  fragment(')'),
673
664
  ...(after === null ? [] : [fragment(' WHERE '), after]),
674
665
  fragment(' ORDER BY '),
675
- joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
666
+ joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
676
667
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
677
668
  ], '');
678
669
  const rows = connection.rows(windowed.sql, windowed.bind);
679
670
  const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
680
671
  const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
681
- 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
+ }));
682
684
  const last = kept.at(-1);
683
685
  const next = plan.limit === null || rows.length <= plan.limit
684
686
  ? undefined
685
687
  : last === undefined
686
688
  ? (plan.after?.text ?? FROM_START)
687
- : cursorOf([
688
- ...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
689
- keyOf(last),
690
- ]);
689
+ : cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
691
690
  const total = plan.total
692
691
  ? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
693
692
  : undefined;
@@ -705,9 +704,6 @@ function boundOf(connection, plan, rooms) {
705
704
  collate: collating(connection),
706
705
  };
707
706
  }
708
- export function selected(connection, plan, rooms) {
709
- return filtered(boundOf(connection, plan, rooms), plan);
710
- }
711
707
  export function answerQuery(connection, plan, rooms, budget = null) {
712
708
  const bound = boundOf(connection, plan, rooms);
713
709
  if (rooms.length === 0) {