@agentshouse/mdruntime 0.1.0-cfcf93be6310b → 0.1.0-cff1b70c6ad46

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