@agentshouse/mdruntime 0.1.0-c5fe3b2cddda7 → 0.1.0-c6197367d5fad

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