@agentshouse/mdruntime 0.1.0-cee9c8aa4cd86 → 0.1.0-cef76ea952695

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