@agentshouse/mdruntime 0.1.0-c2e8a6f99866d → 0.1.0-c2ef9303a058d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/README.md +11 -9
  2. package/dist/adapter.d.ts +95 -67
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +5 -11
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +36 -47
  7. package/dist/binary.d.ts +0 -5
  8. package/dist/binary.d.ts.map +1 -1
  9. package/dist/binary.js +1 -1
  10. package/dist/bootstrap.d.ts +17 -9
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +143 -94
  13. package/dist/chain.d.ts +2 -1
  14. package/dist/chain.d.ts.map +1 -1
  15. package/dist/change-set.d.ts +1 -7
  16. package/dist/change-set.d.ts.map +1 -1
  17. package/dist/change-set.js +1 -4
  18. package/dist/client.d.ts +15 -12
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +56 -8
  21. package/dist/collection.d.ts +7 -4
  22. package/dist/collection.d.ts.map +1 -1
  23. package/dist/collection.js +24 -19
  24. package/dist/compile.d.ts.map +1 -1
  25. package/dist/compile.js +117 -243
  26. package/dist/connection.d.ts +14 -12
  27. package/dist/connection.d.ts.map +1 -1
  28. package/dist/connection.js +1466 -1431
  29. package/dist/context.d.ts +2 -0
  30. package/dist/context.d.ts.map +1 -0
  31. package/dist/{chat/context.js → context.js} +1 -4
  32. package/dist/conversation.d.ts +18 -15
  33. package/dist/conversation.d.ts.map +1 -1
  34. package/dist/conversation.js +273 -185
  35. package/dist/durability.d.ts +6 -4
  36. package/dist/durability.d.ts.map +1 -1
  37. package/dist/durability.js +5 -0
  38. package/dist/entry.d.ts +3 -2
  39. package/dist/entry.d.ts.map +1 -1
  40. package/dist/entry.js +17 -8
  41. package/dist/file.d.ts +2 -11
  42. package/dist/file.d.ts.map +1 -1
  43. package/dist/file.js +6 -4
  44. package/dist/host.d.ts +3 -7
  45. package/dist/host.d.ts.map +1 -1
  46. package/dist/host.js +55 -38
  47. package/dist/identity.d.ts +0 -2
  48. package/dist/identity.d.ts.map +1 -1
  49. package/dist/identity.js +0 -6
  50. package/dist/index.d.ts +15 -12
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +6 -3
  53. package/dist/job.d.ts +14 -0
  54. package/dist/job.d.ts.map +1 -0
  55. package/dist/job.js +28 -0
  56. package/dist/live.d.ts +2 -1
  57. package/dist/live.d.ts.map +1 -1
  58. package/dist/live.js +9 -5
  59. package/dist/media.d.ts +8 -0
  60. package/dist/media.d.ts.map +1 -0
  61. package/dist/media.js +218 -0
  62. package/dist/memory.d.ts +12 -6
  63. package/dist/memory.d.ts.map +1 -1
  64. package/dist/memory.js +209 -101
  65. package/dist/messages.d.ts +28 -0
  66. package/dist/messages.d.ts.map +1 -0
  67. package/dist/messages.js +89 -0
  68. package/dist/model.d.ts +8 -8
  69. package/dist/model.d.ts.map +1 -1
  70. package/dist/model.js +7 -13
  71. package/dist/observation.d.ts +3 -2
  72. package/dist/observation.d.ts.map +1 -1
  73. package/dist/observation.js +8 -3
  74. package/dist/outcome.d.ts +13 -6
  75. package/dist/outcome.d.ts.map +1 -1
  76. package/dist/outcome.js +16 -5
  77. package/dist/overlay.js +1 -1
  78. package/dist/paths.d.ts +1 -0
  79. package/dist/paths.d.ts.map +1 -1
  80. package/dist/paths.js +4 -0
  81. package/dist/query.d.ts +10 -206
  82. package/dist/query.d.ts.map +1 -1
  83. package/dist/query.js +3 -494
  84. package/dist/reach.d.ts +14 -9
  85. package/dist/reach.d.ts.map +1 -1
  86. package/dist/reach.js +84 -72
  87. package/dist/react.d.ts +3 -1
  88. package/dist/react.d.ts.map +1 -1
  89. package/dist/readiness.d.ts +14 -17
  90. package/dist/readiness.d.ts.map +1 -1
  91. package/dist/readiness.js +2 -8
  92. package/dist/recovery.d.ts +1 -0
  93. package/dist/recovery.d.ts.map +1 -1
  94. package/dist/recovery.js +26 -0
  95. package/dist/refresh.d.ts +10 -4
  96. package/dist/refresh.d.ts.map +1 -1
  97. package/dist/refresh.js +88 -30
  98. package/dist/replica.d.ts +11 -8
  99. package/dist/replica.d.ts.map +1 -1
  100. package/dist/replica.js +30 -15
  101. package/dist/room-draft.d.ts +22 -5
  102. package/dist/room-draft.d.ts.map +1 -1
  103. package/dist/room-draft.js +131 -51
  104. package/dist/rows.d.ts +18 -16
  105. package/dist/rows.d.ts.map +1 -1
  106. package/dist/rows.js +45 -153
  107. package/dist/save.d.ts +2 -0
  108. package/dist/save.d.ts.map +1 -1
  109. package/dist/save.js +13 -7
  110. package/dist/search.d.ts +12 -95
  111. package/dist/search.d.ts.map +1 -1
  112. package/dist/search.js +10 -349
  113. package/dist/shared.d.ts +0 -1
  114. package/dist/shared.d.ts.map +1 -1
  115. package/dist/shared.js +0 -10
  116. package/dist/status.d.ts +4 -3
  117. package/dist/status.d.ts.map +1 -1
  118. package/dist/status.js +3 -1
  119. package/dist/store.d.ts +25 -62
  120. package/dist/store.d.ts.map +1 -1
  121. package/dist/store.js +43 -282
  122. package/dist/stored.d.ts +12 -26
  123. package/dist/stored.d.ts.map +1 -1
  124. package/dist/stored.js +189 -226
  125. package/dist/tables.d.ts +55 -48
  126. package/dist/tables.d.ts.map +1 -1
  127. package/dist/tables.js +107 -57
  128. package/dist/transfer.d.ts +0 -5
  129. package/dist/transfer.d.ts.map +1 -1
  130. package/dist/validation.d.ts +3 -4
  131. package/dist/validation.d.ts.map +1 -1
  132. package/dist/validation.js +39 -97
  133. package/dist/windows.d.ts +1 -12
  134. package/dist/windows.d.ts.map +1 -1
  135. package/dist/windows.js +2 -75
  136. package/dist/wire.d.ts +14 -9
  137. package/dist/wire.d.ts.map +1 -1
  138. package/dist/wire.js +5 -3
  139. package/package.json +14 -18
  140. package/AGENTS.md +0 -312
  141. package/dist/chat/acts.d.ts +0 -33
  142. package/dist/chat/acts.d.ts.map +0 -1
  143. package/dist/chat/acts.js +0 -36
  144. package/dist/chat/context.d.ts +0 -3
  145. package/dist/chat/context.d.ts.map +0 -1
  146. package/dist/chat/earlier.d.ts +0 -10
  147. package/dist/chat/earlier.d.ts.map +0 -1
  148. package/dist/chat/earlier.js +0 -54
  149. package/dist/chat/house.d.ts +0 -25
  150. package/dist/chat/house.d.ts.map +0 -1
  151. package/dist/chat/house.js +0 -38
  152. package/dist/chat/input.d.ts +0 -11
  153. package/dist/chat/input.d.ts.map +0 -1
  154. package/dist/chat/input.js +0 -92
  155. package/dist/chat/kit.d.ts +0 -61
  156. package/dist/chat/kit.d.ts.map +0 -1
  157. package/dist/chat/kit.js +0 -123
  158. package/dist/chat/lasting.d.ts +0 -6
  159. package/dist/chat/lasting.d.ts.map +0 -1
  160. package/dist/chat/lasting.js +0 -19
  161. package/dist/chat/list.d.ts +0 -28
  162. package/dist/chat/list.d.ts.map +0 -1
  163. package/dist/chat/list.js +0 -103
  164. package/dist/chat/markdown.d.ts +0 -21
  165. package/dist/chat/markdown.d.ts.map +0 -1
  166. package/dist/chat/markdown.js +0 -246
  167. package/dist/chat/message.d.ts +0 -7
  168. package/dist/chat/message.d.ts.map +0 -1
  169. package/dist/chat/message.js +0 -244
  170. package/dist/chat/messages.d.ts +0 -30
  171. package/dist/chat/messages.d.ts.map +0 -1
  172. package/dist/chat/messages.js +0 -198
  173. package/dist/chat/name.d.ts +0 -7
  174. package/dist/chat/name.d.ts.map +0 -1
  175. package/dist/chat/name.js +0 -37
  176. package/dist/chat/opened.d.ts +0 -12
  177. package/dist/chat/opened.d.ts.map +0 -1
  178. package/dist/chat/opened.js +0 -19
  179. package/dist/chat/overlay.d.ts +0 -32
  180. package/dist/chat/overlay.d.ts.map +0 -1
  181. package/dist/chat/overlay.js +0 -103
  182. package/dist/chat/parts.d.ts +0 -58
  183. package/dist/chat/parts.d.ts.map +0 -1
  184. package/dist/chat/parts.js +0 -120
  185. package/dist/chat/pieces.d.ts +0 -62
  186. package/dist/chat/pieces.d.ts.map +0 -1
  187. package/dist/chat/pieces.js +0 -1110
  188. package/dist/chat/process.d.ts +0 -20
  189. package/dist/chat/process.d.ts.map +0 -1
  190. package/dist/chat/process.js +0 -32
  191. package/dist/chat/provider.d.ts +0 -70
  192. package/dist/chat/provider.d.ts.map +0 -1
  193. package/dist/chat/provider.js +0 -635
  194. package/dist/chat/question.d.ts +0 -9
  195. package/dist/chat/question.d.ts.map +0 -1
  196. package/dist/chat/question.js +0 -69
  197. package/dist/chat/questions.d.ts +0 -31
  198. package/dist/chat/questions.d.ts.map +0 -1
  199. package/dist/chat/questions.js +0 -111
  200. package/dist/chat/read.d.ts +0 -5
  201. package/dist/chat/read.d.ts.map +0 -1
  202. package/dist/chat/read.js +0 -13
  203. package/dist/chat/ready.d.ts +0 -37
  204. package/dist/chat/ready.d.ts.map +0 -1
  205. package/dist/chat/ready.js +0 -93
  206. package/dist/chat/room-file.d.ts +0 -8
  207. package/dist/chat/room-file.d.ts.map +0 -1
  208. package/dist/chat/room-file.js +0 -72
  209. package/dist/chat/steady.d.ts +0 -3
  210. package/dist/chat/steady.d.ts.map +0 -1
  211. package/dist/chat/steady.js +0 -26
  212. package/dist/chat/style.d.ts +0 -4
  213. package/dist/chat/style.d.ts.map +0 -1
  214. package/dist/chat/style.js +0 -306
  215. package/dist/chat.d.ts +0 -15
  216. package/dist/chat.d.ts.map +0 -1
  217. package/dist/chat.js +0 -8
  218. package/dist/reads.d.ts +0 -11
  219. package/dist/reads.d.ts.map +0 -1
  220. package/dist/reads.js +0 -98
@@ -1 +1 @@
1
- {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAsB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,eAAO,MAAM,QAAQ,EAAE,eAAwE,CAAC;AAEhG,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,WAAW,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE;QAAE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,GAAG,IAAI,CAAC;CACnE,CAAC;AAEF,wBAAgB,QAAQ,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAKxE;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAI5E;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACtC,WAAW,EAAE,WAAW,GACvB,UAAU,CAcZ;AAED,wBAAiB,WAAW,CAC1B,UAAU,EAAE,UAAU,GACrB,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAQhE;AAED,wBAAiB,UAAU,CACzB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EACnB,KAAK,GAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAW,GACpC,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,IAAI,CAAC,CAazC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAEvD;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAK3D"}
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAErF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAIpD,eAAO,MAAM,QAAQ,EAAE,eAAwE,CAAC;AAEhG,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,WAAW,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE;QAAE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,GAAG,IAAI,CAAC;IAClE,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAc3F,wBAAgB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,QAAQ,CAS7D;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAKxE;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAI5E;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACtC,WAAW,EAAE,WAAW,GACvB,UAAU,CAcZ;AAED,wBAAiB,WAAW,CAC1B,UAAU,EAAE,UAAU,GACrB,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAQhE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAEvD;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAE3D"}
@@ -1,8 +1,29 @@
1
1
  import { parse, VERSION } from '@agentshouse/mdmodel';
2
2
  import { beneath, normalize } from './paths.js';
3
- import { keysIn, sortedKeys } from './shared.js';
4
- import { ROW_WEIGHT } from './turn.js';
3
+ import { hashed } from './observation.js';
4
+ import { sortedKeys } from './shared.js';
5
5
  export const MD_MODEL = { identity: '@agentshouse/mdmodel', version: VERSION };
6
+ const typesPrinted = new WeakMap();
7
+ function typesPrint(model) {
8
+ if (model.status === 'unavailable')
9
+ return '';
10
+ let print = typesPrinted.get(model.types);
11
+ if (print === undefined) {
12
+ print = hashed(JSON.stringify(model.types)).toString(36);
13
+ typesPrinted.set(model.types, print);
14
+ }
15
+ return print;
16
+ }
17
+ export function composedOf(composition) {
18
+ const { model, selection } = composition;
19
+ return {
20
+ composition,
21
+ fingerprint: JSON.stringify([
22
+ typesPrint(model),
23
+ selection === null ? null : [selection.roots, typesPrint(selection.model)],
24
+ ]),
25
+ };
26
+ }
6
27
  export function selected(composition, path) {
7
28
  return (composition.selection !== null &&
8
29
  composition.selection.roots.some((root) => beneath(root, path)));
@@ -39,25 +60,9 @@ export function* documentsOf(collection) {
39
60
  }
40
61
  })();
41
62
  }
42
- export function* unreadable(text, paths = null) {
43
- const named = paths ?? (yield* keysIn(text));
44
- let least = null;
45
- for (const path of named) {
46
- const content = Object.hasOwn(text, path) ? text[path] : undefined;
47
- if (content === undefined)
48
- continue;
49
- const read = parse(content, path);
50
- if (!read.ok && (least === null || path < least.path)) {
51
- least = { path, refused: { kind: 'refused', refusal: read.refusal } };
52
- }
53
- yield ROW_WEIGHT + content.length;
54
- }
55
- return least?.refused ?? null;
56
- }
57
63
  export function validRoot(root) {
58
64
  return typeof root === 'string' && normalize(root) === root;
59
65
  }
60
66
  export function available(composition) {
61
- return (composition.model.status !== 'unavailable' &&
62
- (composition.selection === null || composition.selection.model.status !== 'unavailable'));
67
+ return composition.model.status !== 'unavailable';
63
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAOV,IAAI,EAGJ,SAAS,EAIV,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AAU1D,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AA8pB3E,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAmSjE,wBAAgB,WAAW,CACzB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,MAAM,GAAE,MAAM,GAAG,IAAW,GAC3B,WAAW,CAiBb"}
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,IAAI,EAAuC,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvF,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AAG1D,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AA+a3E,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAkUjE,wBAAgB,WAAW,CACzB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,MAAM,GAAE,MAAM,GAAG,IAAW,GAC3B,WAAW,CAUb"}
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';
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;
5
6
  const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
6
- function fragment(sql, bind = []) {
7
- return { sql, bind };
7
+ function located(alias) {
8
+ return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
8
9
  }
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])));
10
+ 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,99 +116,25 @@ 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
- ]);
223
- }
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) {
245
- return {
246
- beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
247
- equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
248
- };
249
- }
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])]);
278
- }
279
- return condition;
280
- }
281
- function keyTerm(bound, key, ordering, index) {
119
+ function keyTerm(key, ordering, index) {
282
120
  if (key.of === 'field') {
283
121
  const alias = `o${index}`;
284
122
  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]),
123
+ term: {
124
+ ordering,
125
+ kind: fragment(`${alias}.kind`),
126
+ value: fragment(`${alias}.value`),
127
+ instant: fragment(`${alias}.instant`),
128
+ },
129
+ join: joined([
130
+ fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
131
+ grouped(sql.placed(alias, key.field, 'exact')),
132
+ fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
133
+ ], ''),
287
134
  };
288
135
  }
289
136
  const expression = key.of === 'room'
290
- ? located(bound, 'd')
137
+ ? located('d')
291
138
  : key.of === 'path'
292
139
  ? fragment('d.path')
293
140
  : key.of === 'type'
@@ -313,13 +160,20 @@ function decoded(kind, value) {
313
160
  return JSON.parse(value);
314
161
  return null;
315
162
  }
163
+ function sortedValue(row, index) {
164
+ const kind = row[`k${index}`];
165
+ return sql.sortValue(kind, decoded(kind, row[`v${index}`]), row[`i${index}`]);
166
+ }
316
167
  function decodedKey(key, row, index) {
317
168
  if (key.of === 'field') {
318
169
  const kind = row[`k${index}`];
319
- return kind === null || kind === undefined ? undefined : decoded(kind, row[`v${index}`]);
170
+ return kind === null || kind === undefined ? undefined : sortedValue(row, index);
320
171
  }
321
172
  return row[`v${index}`] ?? null;
322
173
  }
174
+ function instantOf(term) {
175
+ return term.instant ?? fragment('NULL');
176
+ }
323
177
  const registered = new WeakMap();
324
178
  function hashed(text) {
325
179
  let hash = 0x811c9dc5;
@@ -369,9 +223,6 @@ function collating(connection) {
369
223
  return name;
370
224
  };
371
225
  }
372
- function bodied(shape) {
373
- return shape.sections !== null || shape.preamble || shape.markdown;
374
- }
375
226
  function masked(pointers) {
376
227
  const root = { whole: false, members: new Map() };
377
228
  for (const pointer of pointers) {
@@ -427,6 +278,7 @@ function documentOf(shape, room, row, linked) {
427
278
  const path = row.path;
428
279
  const type = row.type;
429
280
  const fileId = row.file_id ?? null;
281
+ const revision = row.revision ?? null;
430
282
  if (type === null) {
431
283
  const binary = row.binary === null ? null : JSON.parse(row.binary);
432
284
  const content = row.content ?? null;
@@ -436,12 +288,11 @@ function documentOf(shape, room, row, linked) {
436
288
  ...(binary === null
437
289
  ? {}
438
290
  : {
439
- binaryFile: binaryRecord(path, binary, row.revision ?? undefined),
291
+ binaryFile: binaryRecord(path, binary, revision ?? undefined),
440
292
  }),
441
- ...(shape.markdown && content !== null && settingsTextPath(path)
442
- ? { markdown: content }
443
- : {}),
293
+ ...(shape.markdown && content !== null ? { markdown: content } : {}),
444
294
  ...(shape.fileId ? { fileId } : {}),
295
+ ...(shape.revision ? { revision } : {}),
445
296
  };
446
297
  }
447
298
  const read = bodied(shape) ? parse(row.content, path) : null;
@@ -462,7 +313,7 @@ function documentOf(shape, room, row, linked) {
462
313
  ? {}
463
314
  : { sections: sectionsOf(body.sections, shape.sections) }),
464
315
  ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
465
- ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
316
+ ...(shape.markdown ? { markdown: row.content } : {}),
466
317
  ...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
467
318
  ...(shape.change
468
319
  ? {
@@ -470,6 +321,7 @@ function documentOf(shape, room, row, linked) {
470
321
  }
471
322
  : {}),
472
323
  ...(shape.fileId ? { fileId } : {}),
324
+ ...(shape.revision ? { revision } : {}),
473
325
  };
474
326
  }
475
327
  function backlinksOf(connection, bound, rows) {
@@ -495,10 +347,10 @@ function backlinksOf(connection, bound, rows) {
495
347
  return linked;
496
348
  }
497
349
  function documents(connection, bound, plan, shape, budget) {
498
- const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(bound, key, orderingOf(options), index));
350
+ const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index));
499
351
  const filter = filtered(bound, plan);
500
352
  const cursor = plan.after?.values ?? null;
501
- const room = located(bound, 'd');
353
+ const room = located('d');
502
354
  const tie = cursor === null
503
355
  ? null
504
356
  : (() => {
@@ -514,19 +366,20 @@ function documents(connection, bound, plan, shape, budget) {
514
366
  })();
515
367
  const after = cursor === null || tie === null
516
368
  ? null
517
- : following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
369
+ : sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), tie);
518
370
  const selected = joined([
519
371
  fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id'),
520
372
  ...terms.map(({ term }, index) => joined([
521
373
  fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
522
374
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
375
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
523
376
  ], '')),
524
377
  fragment(' FROM replica_entries d'),
525
378
  ...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
526
379
  fragment(' WHERE '),
527
380
  after === null ? filter : all([filter, after]),
528
381
  fragment(' ORDER BY '),
529
- joined([...terms.map(({ term }) => sorted(bound, term)), room, fragment('d.path')], ', '),
382
+ joined([...terms.map(({ term }) => sql.sorted(bound.collate, term)), room, fragment('d.path')], ', '),
530
383
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
531
384
  ], '');
532
385
  const rows = connection.rows(selected.sql, selected.bind);
@@ -565,7 +418,7 @@ function withContent(connection, rows, budget) {
565
418
  return rows;
566
419
  const ids = rows.map((row) => row.id);
567
420
  const sizes = new Map(connection
568
- .rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${list(ids).sql}`, ids)
421
+ .rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${listed(ids).sql}`, ids)
569
422
  .map((row) => [row.id, Number(row.size ?? 0)]));
570
423
  let cut = rows.length;
571
424
  let spent = 0;
@@ -580,20 +433,30 @@ function withContent(connection, rows, budget) {
580
433
  const kept = rows.slice(0, cut);
581
434
  const read = kept.map((row) => row.id);
582
435
  const held = new Map(connection
583
- .rows(`SELECT id, content FROM replica_entries WHERE id IN ${list(read).sql}`, read)
436
+ .rows(`SELECT id, content FROM replica_entries WHERE id IN ${listed(read).sql}`, read)
584
437
  .map((row) => [row.id, row.content]));
585
438
  return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
586
439
  }
587
440
  function storedFolders(bound, plan) {
588
- if (plan.paths !== null || plan.hop !== null || plan.backlinks !== null)
441
+ if (plan.paths !== null || plan.fileIds !== null)
442
+ return null;
443
+ if (plan.hop !== null || plan.backlinks !== null)
589
444
  return null;
590
445
  const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
591
446
  if (whole.length === 0)
592
447
  return null;
593
448
  const keys = whole.map(({ key }) => key);
594
449
  const prefix = plan.root.pathPrefix;
450
+ const outside = (bound.reach?.areas ?? []).flatMap((area) => {
451
+ const key = bound.keys.get(area.room);
452
+ if (key === undefined)
453
+ return [];
454
+ const fence = fenced('f', key, area, true);
455
+ return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
456
+ });
595
457
  return all([
596
- fragment(`f.key IN ${list(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
458
+ fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
459
+ ...outside,
597
460
  ...(prefix === null || prefix === ''
598
461
  ? []
599
462
  : [
@@ -608,7 +471,7 @@ function storedFolders(bound, plan) {
608
471
  }
609
472
  function groups(connection, bound, plan, shape) {
610
473
  const keyed = shape.groupBy.map((key, index) => {
611
- const { term, join } = keyTerm(bound, key, GROUP_ORDER, index);
474
+ const { term, join } = keyTerm(key, GROUP_ORDER, index);
612
475
  const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
613
476
  return { term: { ...term, kind }, join };
614
477
  });
@@ -618,6 +481,7 @@ function groups(connection, bound, plan, shape) {
618
481
  joined(keyed.map(({ term }, index) => joined([
619
482
  fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
620
483
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
484
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
621
485
  ], '')), ', '),
622
486
  fragment(', 1 AS n FROM replica_entries d'),
623
487
  ...keyed.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
@@ -631,10 +495,15 @@ function groups(connection, bound, plan, shape) {
631
495
  ? inner
632
496
  : joined([
633
497
  inner,
634
- fragment(" UNION ALL SELECT 'string' AS k0, f.path AS v0, 0 AS n FROM replica_folders f WHERE "),
498
+ fragment(" UNION ALL SELECT 'string' AS k0, f.path AS v0, NULL AS i0, 0 AS n FROM replica_folders f WHERE "),
635
499
  folders,
636
500
  ], '');
637
- const columns = keyed.map((_, index) => `k${index}, v${index}`).join(', ');
501
+ const columns = keyed
502
+ .map((_, index) => `k${index}, min(v${index}) AS v${index}, i${index}`)
503
+ .join(', ');
504
+ const held = keyed
505
+ .map((_, index) => `k${index}, i${index}, CASE WHEN i${index} IS NULL THEN v${index} END`)
506
+ .join(', ');
638
507
  const counted = {
639
508
  ordering: { ...GROUP_ORDER },
640
509
  kind: fragment("'number'"),
@@ -647,7 +516,7 @@ function groups(connection, bound, plan, shape) {
647
516
  });
648
517
  const ordered = shape.orderBy.map(({ by, direction }) => by === 'count'
649
518
  ? { ...counted, ordering: { ...GROUP_ORDER, direction } }
650
- : outer(by, { ...GROUP_ORDER, direction }));
519
+ : { ...outer(by, { ...GROUP_ORDER, direction }), instant: fragment(`i${by}`) });
651
520
  const ties = keyed.flatMap((_, index) => [
652
521
  { ...outer(index, GROUP_TIES), total: true },
653
522
  { ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
@@ -657,19 +526,21 @@ function groups(connection, bound, plan, shape) {
657
526
  ? null
658
527
  : cursor[shape.orderBy.length].flatMap((value) => [
659
528
  value,
660
- kindOf(value ?? null),
529
+ heldKind(value ?? null),
661
530
  ]);
662
531
  const after = cursor === null || tieValues === null
663
532
  ? 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}`)], '');
533
+ : sql.following(ordered.map((term, index) => sql.beyond(bound.collate, term, cursor[index])), sql.following(ties
534
+ .slice(0, -1)
535
+ .map((term, index) => sql.beyond(bound.collate, term, tieValues[index])), sql.beyond(bound.collate, ties.at(-1), tieValues.at(-1)).beyond));
536
+ const grouping = joined([fragment(`SELECT ${columns}, sum(n) AS n FROM (`), source, fragment(`) GROUP BY ${held}`)], '');
666
537
  const windowed = joined([
667
538
  fragment('SELECT * FROM ('),
668
539
  grouping,
669
540
  fragment(')'),
670
541
  ...(after === null ? [] : [fragment(' WHERE '), after]),
671
542
  fragment(' ORDER BY '),
672
- joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
543
+ joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
673
544
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
674
545
  ], '');
675
546
  const rows = connection.rows(windowed.sql, windowed.bind);
@@ -682,7 +553,7 @@ function groups(connection, bound, plan, shape) {
682
553
  : last === undefined
683
554
  ? (plan.after?.text ?? FROM_START)
684
555
  : cursorOf([
685
- ...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
556
+ ...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : sortedValue(last, by)),
686
557
  keyOf(last),
687
558
  ]);
688
559
  const total = plan.total
@@ -694,13 +565,16 @@ function groups(connection, bound, plan, shape) {
694
565
  ...(total === undefined ? {} : { total }),
695
566
  };
696
567
  }
697
- export function answerQuery(connection, plan, rooms, budget = null) {
698
- const bound = {
568
+ function boundOf(connection, plan, rooms) {
569
+ return {
699
570
  rooms: [...rooms].sort((left, right) => left.locator < right.locator ? -1 : left.locator > right.locator ? 1 : 0),
700
571
  keys: new Map(rooms.map(({ locator, key }) => [locator, key])),
701
572
  reach: plan.reach,
702
573
  collate: collating(connection),
703
574
  };
575
+ }
576
+ export function answerQuery(connection, plan, rooms, budget = null) {
577
+ const bound = boundOf(connection, plan, rooms);
704
578
  if (rooms.length === 0) {
705
579
  return plan.shape.kind === 'groups'
706
580
  ? { groups: [], ...(plan.total ? { total: 0 } : {}) }