@agentshouse/mdruntime 0.1.0-ce41fec66d91b → 0.1.0-ce46f015571d6

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