@canonical/pragma-cli 0.36.0 → 0.38.0

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 (312) hide show
  1. package/README.md +3 -2
  2. package/bundled-skills/adoption-a1-styles/SKILL.md +24 -0
  3. package/bundled-skills/adoption-a2-components/SKILL.md +22 -6
  4. package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +1397 -327
  5. package/bundled-skills/anatomy-author/SKILL.md +840 -137
  6. package/bundled-skills/design-auditor/SKILL.md +133 -9
  7. package/bundled-skills/implement-component/SKILL.md +192 -23
  8. package/bundled-skills/specify-component/SKILL.md +124 -12
  9. package/bundled-skills/specify-pattern/SKILL.md +47 -8
  10. package/dist/package.json +23 -20
  11. package/dist/pragma.conf.js +963 -40
  12. package/dist/pragma.conf.js.map +1 -1
  13. package/dist/src/capabilities/capabilities/catalog.js +14 -5
  14. package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
  15. package/dist/src/capabilities/capabilities/hints.js +33 -5
  16. package/dist/src/capabilities/capabilities/hints.js.map +1 -1
  17. package/dist/src/capabilities/config/show.render.js +30 -2
  18. package/dist/src/capabilities/config/show.render.js.map +1 -1
  19. package/dist/src/capabilities/create/create.verb.js +2 -1
  20. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  21. package/dist/src/capabilities/create/createSurface.generated.js +23 -1
  22. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
  23. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
  24. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
  25. package/dist/src/capabilities/doctor/checks/harnessInventory.js +1 -1
  26. package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -1
  27. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
  28. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
  29. package/dist/src/capabilities/doctor/checks/targetHealth.js +104 -14
  30. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  31. package/dist/src/capabilities/graph/connect.render.js +101 -0
  32. package/dist/src/capabilities/graph/connect.render.js.map +1 -0
  33. package/dist/src/capabilities/graph/connect.types.js +21 -0
  34. package/dist/src/capabilities/graph/connect.types.js.map +1 -0
  35. package/dist/src/capabilities/graph/connect.verb.js +147 -0
  36. package/dist/src/capabilities/graph/connect.verb.js.map +1 -0
  37. package/dist/src/capabilities/graph/index.js +5 -3
  38. package/dist/src/capabilities/graph/index.js.map +1 -1
  39. package/dist/src/capabilities/graph/queryText.js +206 -0
  40. package/dist/src/capabilities/graph/queryText.js.map +1 -0
  41. package/dist/src/capabilities/graph/relationIndex.js +302 -0
  42. package/dist/src/capabilities/graph/relationIndex.js.map +1 -0
  43. package/dist/src/capabilities/graph/runConnect.js +456 -0
  44. package/dist/src/capabilities/graph/runConnect.js.map +1 -0
  45. package/dist/src/capabilities/graph/runQuery.js +21 -4
  46. package/dist/src/capabilities/graph/runQuery.js.map +1 -1
  47. package/dist/src/capabilities/setup/buildPlan.js +6 -2
  48. package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
  49. package/dist/src/capabilities/setup/operations/index.js +7 -2
  50. package/dist/src/capabilities/setup/operations/index.js.map +1 -1
  51. package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
  52. package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
  53. package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
  54. package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
  55. package/dist/src/capabilities/setup/operations/setupGenerator.js +72 -14
  56. package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
  57. package/dist/src/capabilities/setup/operations/setupLsp.js +372 -62
  58. package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
  59. package/dist/src/capabilities/setup/operations/setupMcp.js +150 -5
  60. package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
  61. package/dist/src/capabilities/setup/operations/setupSkills.js +4 -2
  62. package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
  63. package/dist/src/capabilities/setup/operations/writability.js +114 -0
  64. package/dist/src/capabilities/setup/operations/writability.js.map +1 -0
  65. package/dist/src/capabilities/setup/plan.js.map +1 -1
  66. package/dist/src/capabilities/setup/setup.verb.js +80 -31
  67. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  68. package/dist/src/capabilities/setup/targets.js +124 -42
  69. package/dist/src/capabilities/setup/targets.js.map +1 -1
  70. package/dist/src/capabilities/shared/index.js +1 -1
  71. package/dist/src/capabilities/shared/index.js.map +1 -1
  72. package/dist/src/constants.js +7 -1
  73. package/dist/src/constants.js.map +1 -1
  74. package/dist/src/kernel/error/renderError.js +3 -2
  75. package/dist/src/kernel/error/renderError.js.map +1 -1
  76. package/dist/src/kernel/error/validOptions.js +41 -0
  77. package/dist/src/kernel/error/validOptions.js.map +1 -0
  78. package/dist/src/kernel/packs/compile.js +90 -6
  79. package/dist/src/kernel/packs/compile.js.map +1 -1
  80. package/dist/src/kernel/packs/cursor.js +135 -0
  81. package/dist/src/kernel/packs/cursor.js.map +1 -0
  82. package/dist/src/kernel/packs/graphql/buildLookupDocument.js +9 -2
  83. package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -1
  84. package/dist/src/kernel/packs/paging.js +108 -0
  85. package/dist/src/kernel/packs/paging.js.map +1 -0
  86. package/dist/src/kernel/packs/renderPack.js +154 -5
  87. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  88. package/dist/src/kernel/packs/resolveEntity.js +118 -19
  89. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  90. package/dist/src/kernel/packs/runBodies.js +156 -18
  91. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  92. package/dist/src/kernel/packs/schema.js +160 -12
  93. package/dist/src/kernel/packs/schema.js.map +1 -1
  94. package/dist/src/kernel/packs/sparql/authorQuery.js +227 -0
  95. package/dist/src/kernel/packs/sparql/authorQuery.js.map +1 -0
  96. package/dist/src/kernel/packs/sparql/buildListQuery.js +251 -0
  97. package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -0
  98. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +125 -22
  99. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  100. package/dist/src/kernel/packs/sparql/filterValues.js +145 -0
  101. package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -0
  102. package/dist/src/kernel/packs/sparql/index.js +24 -14
  103. package/dist/src/kernel/packs/sparql/index.js.map +1 -1
  104. package/dist/src/kernel/packs/sparql/runSelect.js +10 -4
  105. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  106. package/dist/src/kernel/packs/sparql/searchTerm.js +25 -0
  107. package/dist/src/kernel/packs/sparql/searchTerm.js.map +1 -0
  108. package/dist/src/kernel/packs/storyRules.js +151 -0
  109. package/dist/src/kernel/packs/storyRules.js.map +1 -0
  110. package/dist/src/kernel/packs/tierScope.js +310 -0
  111. package/dist/src/kernel/packs/tierScope.js.map +1 -0
  112. package/dist/src/kernel/packs/types.js +80 -0
  113. package/dist/src/kernel/packs/types.js.map +1 -1
  114. package/dist/src/kernel/project/cli/buildProgram.js +10 -1
  115. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  116. package/dist/src/kernel/project/cli/dispatch.js +104 -3
  117. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  118. package/dist/src/kernel/project/cli/rootHelp.js +8 -5
  119. package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
  120. package/dist/src/kernel/project/cli/suggestNames.js +31 -7
  121. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
  122. package/dist/src/kernel/project/mcp/registerVerb.js +5 -1
  123. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  124. package/dist/src/kernel/render/renderers.js +34 -2
  125. package/dist/src/kernel/render/renderers.js.map +1 -1
  126. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
  127. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
  128. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
  129. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
  130. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
  131. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
  132. package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
  133. package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
  134. package/dist/src/kernel/runtime/graphpack/read.js +17 -10
  135. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  136. package/dist/src/kernel/runtime/loadSession.js +3 -1
  137. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  138. package/dist/src/kernel/runtime/probe.js +1 -1
  139. package/dist/src/kernel/runtime/probe.js.map +1 -1
  140. package/dist/src/kernel/runtime/readEntity.js +19 -4
  141. package/dist/src/kernel/runtime/readEntity.js.map +1 -1
  142. package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
  143. package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
  144. package/dist/src/kernel/runtime/refs/resolve.js +38 -5
  145. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
  146. package/dist/src/kernel/spec/emitReference.js +1 -1
  147. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  148. package/package.json +23 -20
  149. package/pragma.conf.ts +983 -40
  150. package/src/capabilities/block.parity.test.ts +207 -27
  151. package/src/capabilities/block.shipped.exec.test.ts +156 -14
  152. package/src/capabilities/block.tierRank.exec.test.ts +46 -9
  153. package/src/capabilities/capabilities/catalog.ts +14 -5
  154. package/src/capabilities/capabilities/hints.ts +40 -5
  155. package/src/capabilities/config/collectConfigShow.test.ts +1 -0
  156. package/src/capabilities/config/set.test.ts +24 -25
  157. package/src/capabilities/config/show.render.test.ts +9 -4
  158. package/src/capabilities/config/show.render.ts +30 -2
  159. package/src/capabilities/create/byteEquality.test.ts +28 -1
  160. package/src/capabilities/create/create.test.ts +1 -0
  161. package/src/capabilities/create/create.verb.ts +2 -1
  162. package/src/capabilities/create/createGrammar.test.ts +4 -1
  163. package/src/capabilities/create/createSurface.generated.ts +23 -1
  164. package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
  165. package/src/capabilities/distribution.parity.test.ts +12 -11
  166. package/src/capabilities/distribution.test.ts +77 -7
  167. package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
  168. package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
  169. package/src/capabilities/doctor/checks/harnessInventory.test.ts +13 -6
  170. package/src/capabilities/doctor/checks/harnessInventory.ts +8 -4
  171. package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
  172. package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
  173. package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
  174. package/src/capabilities/doctor/checks/targetHealth.ts +114 -13
  175. package/src/capabilities/doctor/doctor.test.ts +327 -36
  176. package/src/capabilities/emptyRecovery.test.ts +84 -28
  177. package/src/capabilities/graph/connect.render.ts +141 -0
  178. package/src/capabilities/graph/connect.shipped.exec.test.ts +370 -0
  179. package/src/capabilities/graph/connect.test.ts +361 -0
  180. package/src/capabilities/graph/connect.types.ts +136 -0
  181. package/src/capabilities/graph/connect.verb.ts +165 -0
  182. package/src/capabilities/graph/index.ts +5 -3
  183. package/src/capabilities/graph/query.shipped.exec.test.ts +66 -0
  184. package/src/capabilities/graph/query.test.ts +17 -0
  185. package/src/capabilities/graph/queryText.test.ts +121 -0
  186. package/src/capabilities/graph/queryText.ts +240 -0
  187. package/src/capabilities/graph/relationIndex.ts +372 -0
  188. package/src/capabilities/graph/runConnect.ts +577 -0
  189. package/src/capabilities/graph/runQuery.ts +21 -6
  190. package/src/capabilities/lazy.test.ts +32 -2
  191. package/src/capabilities/listBudget.shipped.exec.test.ts +239 -0
  192. package/src/capabilities/listLookup.shipped.exec.test.ts +387 -0
  193. package/src/capabilities/listQuery.shipped.exec.test.ts +400 -0
  194. package/src/capabilities/resources/resources.test.ts +38 -2
  195. package/src/capabilities/setup/buildPlan.ts +7 -1
  196. package/src/capabilities/setup/operations/index.ts +14 -0
  197. package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
  198. package/src/capabilities/setup/operations/setupConfig.ts +7 -2
  199. package/src/capabilities/setup/operations/setupGenerator.ts +93 -15
  200. package/src/capabilities/setup/operations/setupLsp.ts +544 -85
  201. package/src/capabilities/setup/operations/setupMcp.ts +212 -4
  202. package/src/capabilities/setup/operations/setupSkills.ts +7 -2
  203. package/src/capabilities/setup/operations/writability.test.ts +151 -0
  204. package/src/capabilities/setup/operations/writability.ts +135 -0
  205. package/src/capabilities/setup/plan.ts +11 -0
  206. package/src/capabilities/setup/setup.test.ts +992 -16
  207. package/src/capabilities/setup/setup.verb.ts +84 -36
  208. package/src/capabilities/setup/targets.ts +152 -48
  209. package/src/capabilities/setup/types.ts +41 -5
  210. package/src/capabilities/shared/index.ts +1 -0
  211. package/src/capabilities/sources/sources.test.ts +55 -0
  212. package/src/capabilities/surface.test.ts +59 -11
  213. package/src/capabilities/tier.parity.test.ts +2 -2
  214. package/src/capabilities/token.parity.test.ts +784 -0
  215. package/src/capabilities/tokenGraph.cli.test.ts +279 -0
  216. package/src/capabilities/tokenGraph.shipped.exec.test.ts +653 -0
  217. package/src/capabilities/variable.parity.test.ts +448 -0
  218. package/src/constants.ts +8 -0
  219. package/src/kernel/completion/shellDrive.test.ts +32 -30
  220. package/src/kernel/config/defaults.test.ts +21 -2
  221. package/src/kernel/config/readConfig.test.ts +1 -1
  222. package/src/kernel/error/renderError.ts +3 -2
  223. package/src/kernel/error/validOptions.test.ts +57 -0
  224. package/src/kernel/error/validOptions.ts +45 -0
  225. package/src/kernel/packs/collect.test.ts +2 -0
  226. package/src/kernel/packs/compile.test.ts +188 -15
  227. package/src/kernel/packs/compile.ts +109 -14
  228. package/src/kernel/packs/cursor.ts +135 -0
  229. package/src/kernel/packs/filteredEmpty.test.ts +213 -0
  230. package/src/kernel/packs/graphql/buildLookupDocument.ts +16 -9
  231. package/src/kernel/packs/graphqlSource.test.ts +152 -0
  232. package/src/kernel/packs/listNotice.test.ts +156 -0
  233. package/src/kernel/packs/paging.ts +110 -0
  234. package/src/kernel/packs/renderPack.ts +186 -13
  235. package/src/kernel/packs/resolveEntity.test.ts +62 -3
  236. package/src/kernel/packs/resolveEntity.ts +186 -26
  237. package/src/kernel/packs/runBodies.ts +196 -26
  238. package/src/kernel/packs/schema.ts +180 -13
  239. package/src/kernel/packs/sparql/authorQuery.test.ts +179 -0
  240. package/src/kernel/packs/sparql/authorQuery.ts +257 -0
  241. package/src/kernel/packs/sparql/buildListQuery.test.ts +344 -0
  242. package/src/kernel/packs/sparql/buildListQuery.ts +344 -0
  243. package/src/kernel/packs/sparql/buildLookupQuery.test.ts +138 -0
  244. package/src/kernel/packs/sparql/buildLookupQuery.ts +140 -20
  245. package/src/kernel/packs/sparql/filterValues.test.ts +86 -0
  246. package/src/kernel/packs/sparql/filterValues.ts +182 -0
  247. package/src/kernel/packs/sparql/index.ts +30 -14
  248. package/src/kernel/packs/sparql/runSelect.test.ts +63 -0
  249. package/src/kernel/packs/sparql/runSelect.ts +42 -2
  250. package/src/kernel/packs/sparql/searchTerm.ts +29 -0
  251. package/src/kernel/packs/storyRules.ts +180 -0
  252. package/src/kernel/packs/tierScope.test.ts +487 -0
  253. package/src/kernel/packs/tierScope.ts +409 -0
  254. package/src/kernel/packs/toolDescription.test.ts +13 -10
  255. package/src/kernel/packs/types.ts +311 -9
  256. package/src/kernel/project/cli/buildProgram.ts +17 -1
  257. package/src/kernel/project/cli/dispatch.test.ts +87 -0
  258. package/src/kernel/project/cli/dispatch.ts +114 -4
  259. package/src/kernel/project/cli/rootHelp.ts +8 -5
  260. package/src/kernel/project/cli/suggest.test.ts +35 -0
  261. package/src/kernel/project/cli/suggestNames.ts +34 -7
  262. package/src/kernel/project/mcp/registerVerb.ts +5 -1
  263. package/src/kernel/render/contracts.ts +2 -0
  264. package/src/kernel/render/prefixes.test.ts +2 -0
  265. package/src/kernel/render/renderers.test.ts +43 -0
  266. package/src/kernel/render/renderers.ts +34 -1
  267. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
  268. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
  269. package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
  270. package/src/kernel/runtime/graphpack/embedded.ts +14 -6
  271. package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
  272. package/src/kernel/runtime/graphpack/read.ts +17 -13
  273. package/src/kernel/runtime/loadSession.ts +3 -1
  274. package/src/kernel/runtime/probe.ts +1 -1
  275. package/src/kernel/runtime/readEntity.ts +19 -4
  276. package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
  277. package/src/kernel/runtime/refs/parseRef.ts +25 -1
  278. package/src/kernel/runtime/refs/resolve.ts +39 -5
  279. package/src/kernel/runtime/types.ts +30 -0
  280. package/src/kernel/spec/emitReference.ts +1 -1
  281. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  282. package/src/kernel/spec/types.ts +17 -0
  283. package/src/testing/PARITY_GAPS.ts +4 -4
  284. package/src/testing/behavioral/agentSession.mcp.test.ts +43 -39
  285. package/src/testing/behavioral/completion.test.ts +7 -6
  286. package/src/testing/behavioral/disclosure.test.ts +39 -37
  287. package/src/testing/behavioral/errorMatrix.mcp.test.ts +82 -64
  288. package/src/testing/behavioral/errorMatrix.storeless.test.ts +11 -10
  289. package/src/testing/behavioral/journeys.cli.test.ts +71 -32
  290. package/src/testing/behavioral/journeys.defaultPack.test.ts +6 -4
  291. package/src/testing/behavioral/journeys.firstInstall.test.ts +70 -5
  292. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +4 -3
  293. package/src/testing/behavioral/journeys.packageStories.test.ts +2 -1
  294. package/src/testing/behavioral/mcpSurface.test.ts +28 -25
  295. package/src/testing/behavioral/parity.test.ts +25 -23
  296. package/src/testing/eval/__snapshots__/eval.test.ts.snap +20 -0
  297. package/src/testing/eval/cases/effectful.ts +7 -1
  298. package/src/testing/eval/cases/readNouns.ts +100 -11
  299. package/src/testing/fixtures/blockGraph.ts +89 -5
  300. package/src/testing/fixtures/connectGraph.ts +159 -0
  301. package/src/testing/fixtures/graph/canonical.ts +128 -32
  302. package/src/testing/helpers/fsProbe.ts +88 -0
  303. package/src/testing/helpers/runCli.test.ts +44 -0
  304. package/src/testing/helpers/runCli.ts +27 -3
  305. package/src/testing/perf/budgets.test.ts +3 -1
  306. package/src/testing/perf/budgets.ts +138 -54
  307. package/dist/src/kernel/packs/sparql/applyFilters.js +0 -167
  308. package/dist/src/kernel/packs/sparql/applyFilters.js.map +0 -1
  309. package/dist/src/kernel/packs/sparql/applySearch.js +0 -25
  310. package/dist/src/kernel/packs/sparql/applySearch.js.map +0 -1
  311. package/src/kernel/packs/sparql/applyFilters.ts +0 -203
  312. package/src/kernel/packs/sparql/applySearch.ts +0 -33
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Read a list story's author query as the three parts a page has to keep apart:
3
+ * the PROLOGUE it declares, the VARIABLES it projects, and the BODY a wrapping
4
+ * select can hold.
5
+ *
6
+ * Every list answer is one page now, and a page is a wrapping select over the
7
+ * story's own query ({@link ./buildListQuery.buildListQuery}). Three properties
8
+ * of the author's text decide whether that wrap is legal, and none of them can
9
+ * be asked of the store:
10
+ *
11
+ * - **The prologue cannot be wrapped.** `PREFIX` and `BASE` belong to a QUERY;
12
+ * a group graph pattern admits only patterns, so splicing author text that
13
+ * opens with `PREFIX` inside one is a parse error (`expected OPTIONAL`). The
14
+ * declarations are lifted to the wrapper's own prologue instead, in order, and
15
+ * the body from the `SELECT` keyword on is what gets wrapped.
16
+ * - **The projection has to be reproduced.** `SELECT *` in the wrapper hands
17
+ * back alphabetised binding keys, so `block list`'s rows would go from
18
+ * `{uri,name,type,tier}` to `{name,tier,type,uri}` and every `--format json`
19
+ * answer would change shape for no reason a caller asked for. The store cannot
20
+ * answer this either: `SelectResult.variables` is derived from the rows that
21
+ * came back, not from the query's header — over the shipped pack `block list`
22
+ * reports four of its five, because the fifth is unbound on the first row. So
23
+ * the projection is read from the query TEXT, which is the only place that
24
+ * states it. An author who wrote `SELECT *` is wrapped with `SELECT *`, which
25
+ * preserves their key order because it is the same alphabetisation twice.
26
+ * - **A dataset clause cannot be wrapped either.** `FROM` sits between the
27
+ * select clause and the where clause, and `SubSelect` has no place for it. It
28
+ * is REFUSED rather than lifted, because lifting it would mean cutting a span
29
+ * out of the middle of the author's text — the prologue is a prefix, which is
30
+ * why lifting that one is a split and not an edit.
31
+ *
32
+ * This is a lexer for the two clauses that precede the where clause, not a
33
+ * SPARQL parser. It covers the whole grammar of both — `Prologue ::= (BaseDecl
34
+ * | PrefixDecl)*` and `SelectClause ::= 'SELECT' ('DISTINCT'|'REDUCED')?
35
+ * ((Var | '(' Expression 'AS' Var ')')+ | '*')` — and it skips comments and
36
+ * string literals everywhere it scans, so a `#` inside a prefix IRI, a `# select
37
+ * the blocks` heading, and a `WHERE {` inside a `REPLACE` literal are all read
38
+ * as what they are. What it cannot read it says it cannot read, naming the
39
+ * shape it found: a refusal that misdiagnoses sends the author to the wrong
40
+ * file.
41
+ */
42
+ /** A SPARQL variable as this reader recognises it (`?name`, `$name`). */
43
+ const VARIABLE = /^[?$]([A-Za-z_][A-Za-z0-9_]*)/;
44
+ /** The `AS ?var` binding that names a projection expression's result. */
45
+ const ALIAS = /\bAS\s+[?$]([A-Za-z_][A-Za-z0-9_]*)\s*$/i;
46
+ /** Whitespace and `#` comments, which may sit between any two tokens. */
47
+ const IGNORABLE = /^(?:\s+|#[^\n\r]*)+/;
48
+ /** `PNAME_NS ::= PN_PREFIX? ':'` — the name a `PREFIX` declaration binds. */
49
+ const PREFIX_NAME = /^[A-Za-z][\w.-]*:|^:/;
50
+ /**
51
+ * Split an author query into its prologue, its projection and its body.
52
+ *
53
+ * @param text - The author's SPARQL SELECT text.
54
+ * @returns The three parts, or the reason the text cannot be wrapped — a
55
+ * sentence fragment naming what the reader FOUND, for a caller to quote.
56
+ */
57
+ export function readAuthorQuery(text) {
58
+ const prologue = readPrologue(text);
59
+ const start = skipIgnorable(text, prologue.end);
60
+ if (!/^SELECT\b/i.test(text.slice(start))) {
61
+ return { ok: false, reason: "its text declares no SPARQL SELECT" };
62
+ }
63
+ const clause = readSelectClause(text, start + "SELECT".length);
64
+ if (!clause.ok)
65
+ return clause;
66
+ return {
67
+ ok: true,
68
+ query: {
69
+ prologue: text.slice(prologue.start, prologue.end),
70
+ projection: clause.projection,
71
+ body: text.slice(start),
72
+ },
73
+ };
74
+ }
75
+ /** Where the prologue's declarations begin and end (equal when there are none). */
76
+ function readPrologue(text) {
77
+ const start = skipIgnorable(text, 0);
78
+ let end = start;
79
+ let at = start;
80
+ for (;;) {
81
+ const declaration = readDeclaration(text, at);
82
+ if (declaration === undefined)
83
+ return { start, end };
84
+ end = declaration;
85
+ at = skipIgnorable(text, declaration);
86
+ }
87
+ }
88
+ /**
89
+ * The index just past one `PREFIX`/`BASE` declaration, or `undefined` when the
90
+ * text at {@link at} is not one.
91
+ *
92
+ * The IRI is scanned to its closing `>`, which is exact: `IRIREF` excludes `>`
93
+ * from its character class, so the first one closes it — and it does NOT
94
+ * exclude `#`, which is why a comment-aware scan has to read the IRI as an IRI
95
+ * rather than skip to end of line.
96
+ */
97
+ function readDeclaration(text, at) {
98
+ const keyword = text.slice(at).match(/^(BASE|PREFIX)\b/i);
99
+ if (!keyword?.[1])
100
+ return undefined;
101
+ let index = skipIgnorable(text, at + keyword[0].length);
102
+ if (keyword[1].toUpperCase() === "PREFIX") {
103
+ const name = text.slice(index).match(PREFIX_NAME);
104
+ if (!name)
105
+ return undefined;
106
+ index = skipIgnorable(text, index + name[0].length);
107
+ }
108
+ if (text[index] !== "<")
109
+ return undefined;
110
+ const close = text.indexOf(">", index + 1);
111
+ return close === -1 ? undefined : close + 1;
112
+ }
113
+ /** Read the select clause that starts just past the `SELECT` keyword. */
114
+ function readSelectClause(text, from) {
115
+ const projected = [];
116
+ let star = false;
117
+ let at = from;
118
+ while (at < text.length) {
119
+ at = skipIgnorable(text, at);
120
+ const rest = text.slice(at);
121
+ // The clause ends at the WHERE keyword or at the group graph pattern's
122
+ // brace, whichever the author wrote (`WHERE` is optional in SPARQL).
123
+ if (rest === "" || /^(?:WHERE\b|\{)/i.test(rest))
124
+ break;
125
+ if (/^FROM\b/i.test(rest)) {
126
+ return {
127
+ ok: false,
128
+ reason: "it carries a FROM dataset clause, and a sub-select cannot carry one",
129
+ };
130
+ }
131
+ // DISTINCT/REDUCED sit between the keyword and the projection.
132
+ const modifier = rest.match(/^(?:DISTINCT|REDUCED)\b/i);
133
+ if (modifier && projected.length === 0 && !star) {
134
+ at += modifier[0].length;
135
+ continue;
136
+ }
137
+ if (rest.startsWith("*")) {
138
+ star = true;
139
+ at += 1;
140
+ continue;
141
+ }
142
+ const variable = rest.match(VARIABLE);
143
+ if (variable?.[1]) {
144
+ projected.push(variable[1]);
145
+ at += variable[0].length;
146
+ continue;
147
+ }
148
+ if (rest.startsWith("(")) {
149
+ const end = matchingParen(text, at);
150
+ const alias = end === -1 ? null : text.slice(at + 1, end).match(ALIAS);
151
+ if (!alias?.[1]) {
152
+ return {
153
+ ok: false,
154
+ reason: "its SELECT clause carries a parenthesised expression that names no result variable (`(… AS ?name)`)",
155
+ };
156
+ }
157
+ projected.push(alias[1]);
158
+ at = end + 1;
159
+ continue;
160
+ }
161
+ return {
162
+ ok: false,
163
+ reason: `its SELECT clause carries ${quoted(rest)}, which is neither a variable nor an \`(expression AS ?variable)\``,
164
+ };
165
+ }
166
+ if (star) {
167
+ return projected.length === 0
168
+ ? { ok: true, projection: undefined }
169
+ : {
170
+ ok: false,
171
+ reason: "its SELECT clause mixes `*` with named variables",
172
+ };
173
+ }
174
+ return projected.length > 0
175
+ ? { ok: true, projection: projected }
176
+ : { ok: false, reason: "its SELECT clause projects nothing" };
177
+ }
178
+ /** The first token of {@link rest}, quoted short enough to read in a message. */
179
+ function quoted(rest) {
180
+ const token = rest.match(/^\S{1,24}/)?.[0] ?? rest.slice(0, 24);
181
+ return `"${token}"`;
182
+ }
183
+ /** Whitespace and comments consumed: the next index that is neither. */
184
+ function skipIgnorable(text, at) {
185
+ const skip = text.slice(at).match(IGNORABLE);
186
+ return skip ? at + skip[0].length : at;
187
+ }
188
+ /**
189
+ * The index of the `)` closing the `(` at {@link open}, or -1 when unbalanced.
190
+ *
191
+ * Depth-counted rather than regex-matched: a projection expression nests
192
+ * (`(COALESCE(IF(…), 0) AS ?rank)`), and the first `)` is rarely the right one.
193
+ * Quoted strings are skipped so a `)` inside a literal cannot close the
194
+ * expression.
195
+ */
196
+ function matchingParen(text, open) {
197
+ let depth = 0;
198
+ for (let index = open; index < text.length; index += 1) {
199
+ const char = text[index];
200
+ if (char === '"' || char === "'") {
201
+ index = skipLiteral(text, index);
202
+ continue;
203
+ }
204
+ if (char === "(")
205
+ depth += 1;
206
+ if (char === ")") {
207
+ depth -= 1;
208
+ if (depth === 0)
209
+ return index;
210
+ }
211
+ }
212
+ return -1;
213
+ }
214
+ /** The index of the quote closing the literal opened at {@link open}. */
215
+ function skipLiteral(text, open) {
216
+ const quote = text[open];
217
+ for (let index = open + 1; index < text.length; index += 1) {
218
+ if (text[index] === "\\") {
219
+ index += 1;
220
+ continue;
221
+ }
222
+ if (text[index] === quote)
223
+ return index;
224
+ }
225
+ return text.length;
226
+ }
227
+ //# sourceMappingURL=authorQuery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorQuery.js","sourceRoot":"","sources":["../../../../../src/kernel/packs/sparql/authorQuery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Compile a list story's declared filters, its search term, its tier scope and
3
+ * one page's bounds into the SPARQL the store actually runs.
4
+ *
5
+ * WHY IN THE QUERY. Filters used to be predicates over the rows a query had
6
+ * already returned. That was injection-safe and order-preserving — the two
7
+ * properties this module has to keep — but it put filtering on the wrong side
8
+ * of any cap: a `LIMIT` in a story's own text truncates BEFORE the filter runs,
9
+ * so `--category testing` over a capped population answers from whatever
10
+ * happened to be inside the window. Nothing was being truncated yet, because no
11
+ * story declared a cap and the kernel had none to declare; the moment one
12
+ * exists, every declared filter starts deciding over a truncated population.
13
+ * Pagination is that cap, so the two move together and they move inward.
14
+ *
15
+ * The TIER SCOPE is here for that reason and not by analogy: it narrows the
16
+ * 252-row `block list` under a 300-row page today, so a page that started
17
+ * truncating would decide the default scope by where the window fell. It is the
18
+ * one clause that constrains the ENTITY rather than a projected cell — see
19
+ * {@link ListTierScope}.
20
+ *
21
+ * ONE SHAPE, ALWAYS. The author query becomes a sub-select inside a wrapping
22
+ * SELECT that carries the predicates and the page bounds — whether or not there
23
+ * is anything to filter. It has to be a wrap rather than an appended `HAVING`
24
+ * when there IS something: a `match: "set"` filter reads a cell the author's own
25
+ * `GROUP BY` computed (`standard list`'s `GROUP_CONCAT`), so the predicate can
26
+ * only run after aggregation, and the cap only after the predicate.
27
+ *
28
+ * It is a wrap when there is NOTHING to filter, too, and that is a correction.
29
+ * Appending `LIMIT n` to the author's own text is a syntax error for every
30
+ * story whose text already ends in a solution modifier or a trailing `VALUES`
31
+ * block — `LIMIT` after `LIMIT`, after `OFFSET`, after `VALUES` — and because
32
+ * the page has a DEFAULT, that error fired on every call rather than only on
33
+ * paged ones. The shortcut existed to keep the store running, character for
34
+ * character, the query it ran before. Measured over all nine shipped bodies
35
+ * (median of 15 warm store queries per body, both texts, 2026-09-10) the wrap
36
+ * ran between 2.7 ms FASTER and 0.5 ms slower, deltas straddling zero and the
37
+ * one clear win being `block list`, the 252-row body; every body's rows came
38
+ * back byte-identical through the wrap, key order included. So the shortcut
39
+ * bought nothing measurable and cost a whole class of author query. Keeping it
40
+ * would have meant a guard recognising every trailing-modifier shape the
41
+ * grammar admits — more parsing than the wrap itself needs, to reach the
42
+ * answer the wrap already gives.
43
+ *
44
+ * THE AUTHOR'S TEXT IS SPLIT, NEVER EDITED. Two parts of a query cannot live
45
+ * inside a group graph pattern, and {@link ./authorQuery.readAuthorQuery} is
46
+ * what tells them apart:
47
+ *
48
+ * - The PROLOGUE (`PREFIX`, `BASE`) is lifted to the wrapper's own prologue, in
49
+ * the author's order. That is a split at the `SELECT` keyword, not a rewrite:
50
+ * the body is a suffix of the author's text, byte for byte. A COLLIDING
51
+ * prefix resolves the way it always did — the store prepends the pack's own
52
+ * prefix map ahead of whatever it is handed, the author's declaration is
53
+ * emitted after it, and SPARQL's last declaration wins, so an author who
54
+ * redeclares `ds:` shadows the pack's `ds:` exactly as they did before any
55
+ * wrap existed (verified both shapes against the pinned oxigraph). The
56
+ * wrapper declares nothing and names no prefixed term, so it brings no
57
+ * prefix of its own for an author to collide with.
58
+ * - A DATASET clause (`FROM`, `FROM NAMED`) is refused, at declaration and
59
+ * again here. It sits in the MIDDLE of the author's text rather than at its
60
+ * front, so lifting it would mean cutting a span out — an edit, not a split.
61
+ *
62
+ * The wrapper projects the author's own variables, in the author's own order
63
+ * ({@link ./authorQuery}) — `SELECT *` in the wrapper would hand back
64
+ * alphabetised binding keys and change every JSON answer's shape. An author who
65
+ * wrote `SELECT *` themselves gets `SELECT *`, which is the same alphabetisation
66
+ * twice and so preserves their key order; a FILTERABLE story that writes it is
67
+ * refused, because a projection of `*` names nothing to constrain.
68
+ *
69
+ * USER INPUT IS BOUND, NOT SPLICED. Every value a caller supplies reaches the
70
+ * query as a row of a `VALUES` block — the closest thing the store's text-only
71
+ * interface has to a bound parameter — escaped by
72
+ * {@link ./escape.escapeSparqlString}, and the predicate compares against that
73
+ * variable rather than against interpolated text. The `VALUES` block sits
74
+ * inside `FILTER EXISTS` deliberately: joined into the outer group it would
75
+ * MULTIPLY rows, and a repeated flag over a `set` cell carrying two of the
76
+ * requested members would return that row twice.
77
+ *
78
+ * The comparison itself is the row predicate's, transposed:
79
+ * case-insensitively equal for `exact`, membership of a whitespace-separated
80
+ * set for `set`, substring for search, and a cell that is unbound or empty
81
+ * never matches. One divergence is worth naming: the row predicate NFC-
82
+ * normalised both sides, and SPARQL has no normaliser, so the graph's own
83
+ * spelling is compared as it is stored (the caller's side is still normalised,
84
+ * in {@link ./filterValues}).
85
+ */
86
+ import { PragmaError } from "../../error/index.js";
87
+ import { RESERVED_VARIABLE_PREFIX } from "../types.js";
88
+ import { readAuthorQuery } from "./authorQuery.js";
89
+ import { escapeSparqlString, formatTerm } from "./escape.js";
90
+ /**
91
+ * Build the SELECT one page of a list story runs.
92
+ *
93
+ * @param input - The author query, the supplied filters/search, and the page.
94
+ * @returns SPARQL SELECT text composed only from author terms, escaped values
95
+ * and the page's own integers.
96
+ * @throws PragmaError CONFIG_ERROR when the author query cannot be wrapped in a
97
+ * page, when a filterable story's projection cannot be reproduced, or when
98
+ * its query already uses the generated variable prefix. Each names what it
99
+ * found: the same refusal for three different reasons would send an author to
100
+ * the wrong part of their own file.
101
+ */
102
+ export function buildListQuery(input) {
103
+ const { query, predicates, search, scope, window, label } = input;
104
+ const clauses = [
105
+ ...predicates.map((predicate, index) => filterClause(predicate, `${RESERVED_VARIABLE_PREFIX}Filter${index}`)),
106
+ ...(search
107
+ ? [searchClause(search, `${RESERVED_VARIABLE_PREFIX}Search`)]
108
+ : []),
109
+ ...(scope && scope.tiers.length > 0
110
+ ? [scopeClause(scope, `${RESERVED_VARIABLE_PREFIX}Tier`)]
111
+ : []),
112
+ ];
113
+ if (clauses.length > 0 && query.includes(RESERVED_VARIABLE_PREFIX)) {
114
+ throw PragmaError.configError(`Story query in ${label} uses the reserved variable prefix "?${RESERVED_VARIABLE_PREFIX}", ` +
115
+ "which the generated filter clauses bind a caller's values to. Rename it.");
116
+ }
117
+ const read = readAuthorQuery(query);
118
+ if (!read.ok) {
119
+ throw PragmaError.configError(`Story query in ${label} cannot be paged: ${read.reason}. Every list ` +
120
+ "answer is one page, and a page is a wrapping SELECT over the story's own query.");
121
+ }
122
+ const { prologue, projection, body } = read.query;
123
+ // The SCOPE clause is deliberately not counted here: it constrains the entity
124
+ // through a graph edge, not a projected cell, and a sub-select's `SELECT *`
125
+ // still makes the entity variable visible to the wrapper. A FILTER is the
126
+ // case that cannot be checked — it names a variable, and `*` names none.
127
+ if ((predicates.length > 0 || search !== undefined) &&
128
+ projection === undefined) {
129
+ throw PragmaError.configError(`Story query in ${label} declares filters or a search, so its SELECT must ` +
130
+ "project its variables by name — the page projects the same names in the " +
131
+ "same order, which `SELECT *` cannot promise.");
132
+ }
133
+ return [
134
+ ...(prologue === "" ? [] : [prologue]),
135
+ `SELECT ${projection ? projection.map((variable) => `?${variable}`).join(" ") : "*"}`,
136
+ "WHERE {",
137
+ " {",
138
+ body,
139
+ " }",
140
+ ...clauses.map((clause) => ` ${clause}`),
141
+ "}",
142
+ modifier(window),
143
+ ].join("\n");
144
+ }
145
+ /** The page as SPARQL solution modifiers (`OFFSET 0` omitted as the no-op it is). */
146
+ function modifier(window) {
147
+ return window.offset > 0
148
+ ? `LIMIT ${window.limit} OFFSET ${window.offset}`
149
+ : `LIMIT ${window.limit}`;
150
+ }
151
+ /**
152
+ * One filter as a row predicate that cannot duplicate a row.
153
+ *
154
+ * `FILTER EXISTS` makes the `VALUES` block a per-row existence test: joining it
155
+ * into the group instead would pair each row with every requested value, and a
156
+ * `set` cell carrying two of them would be returned twice.
157
+ */
158
+ function filterClause(predicate, bound) {
159
+ const values = predicate.terms
160
+ .map((term) => `"${escapeSparqlString(term)}"`)
161
+ .join(" ");
162
+ const cell = `?${predicate.variable}`;
163
+ const comparison = predicate.match === "set"
164
+ ? setMembership(cell, bound)
165
+ : `STR(${cell}) != "" && LCASE(STR(${cell})) = LCASE(?${bound})`;
166
+ return `FILTER EXISTS { VALUES ?${bound} { ${values} } FILTER(${comparison}) }`;
167
+ }
168
+ /**
169
+ * Membership of a whitespace-separated set, spelled as a padded substring.
170
+ *
171
+ * The cell is a `GROUP_CONCAT` of every value a row belongs to at once — a
172
+ * category and each of its ancestors. Padding both sides with a space is what
173
+ * keeps it a WHOLE-member comparison, so `testing` does not match
174
+ * `testing-unit`; collapsing runs of whitespace first reproduces the row
175
+ * predicate's own `\s+` split, whatever separator the author's aggregate used.
176
+ *
177
+ * TWO WAYS IT IS WIDER THAN THE SPLIT IT REPLACED, both out of reach of every
178
+ * vocabulary the distribution declares and both worth knowing before a
179
+ * vocabulary is widened:
180
+ *
181
+ * - A value that CONTAINS whitespace matches a contiguous RUN of members: cell
182
+ * `"foo bar baz"`, value `"bar baz"` answers true here where
183
+ * `split(/\s+/).includes(value)` answered false. Unreachable today because no
184
+ * admissible value carries whitespace (all 21 `cs:slug` values are clean), and
185
+ * reachable the moment a filter declares `values: ["a b"]` or a graph
186
+ * vocabulary grows a multi-word term.
187
+ * - An EMPTY value matches an EMPTY cell, because `CONTAINS(" ", " ")` is true.
188
+ * The row predicate answered false on an empty cell, and the `exact` branch
189
+ * above still does via its explicit `STR(?c) != ""` guard, which this branch
190
+ * has no equivalent of. Unreachable today because `readFilterVocabularies`
191
+ * drops empty strings from a vocabulary and `rejectUnknownValue` then refuses
192
+ * `""` as unadmitted — i.e. it is the ADMISSION step that closes this, not the
193
+ * predicate.
194
+ */
195
+ function setMembership(cell, bound) {
196
+ const members = `CONCAT(" ", REPLACE(LCASE(STR(${cell})), "\\\\s+", " "), " ")`;
197
+ return `CONTAINS(${members}, CONCAT(" ", LCASE(?${bound}), " "))`;
198
+ }
199
+ /**
200
+ * The tier scope as a row predicate: the entity is in an in-scope tier, or in
201
+ * no tier at all.
202
+ *
203
+ * `EXISTS` with the tiers in a `VALUES` block, for both reasons the filter
204
+ * clauses use that shape. It cannot MULTIPLY a row (an entity in two in-scope
205
+ * tiers is still one entity — the design system asserts one `ds:tier` per block
206
+ * today, and a second one tomorrow must not double the row), and the tier IRIs
207
+ * ride a bound variable rather than being spliced into a pattern.
208
+ *
209
+ * The IRIs are the store's own, read back from the tier hierarchy by
210
+ * {@link ../tierScope.readTierHierarchy} — a caller supplies a tier NAME, which
211
+ * is resolved to a tier before it ever reaches here, so the only thing
212
+ * interpolated is a term the graph itself published.
213
+ *
214
+ * THE UNTIERED HALF IS NOT A COURTESY. `ds:button.icon` is a subcomponent with
215
+ * no `ds:tier`, and a required tier join is exactly what used to hide it: the
216
+ * hand-written block list inner-joined the tier and surfaced it only under
217
+ * `--all-tiers`, which is the defect the declared query's OPTIONAL join closed
218
+ * and `block.parity.test.ts` holds it to. A membership test alone would reopen
219
+ * it — an entity the scope cannot PLACE would become an entity the default
220
+ * scope cannot SHOW. So the predicate is "in scope, or in no tier", which is
221
+ * the same rule the lookup resolver applies to the same rows
222
+ * ({@link ../resolveEntity.applyScope}).
223
+ *
224
+ * `IN` would read closer to the rule as stated, and this is the same predicate:
225
+ * membership of a set, tested per row. A `VALUES` block is how every other
226
+ * generated clause in this module spells a set, so the scope spells it that way
227
+ * too rather than introducing a second idiom for one clause.
228
+ */
229
+ function scopeClause(scope, bound) {
230
+ const values = scope.tiers.map((iri) => `<${iri}>`).join(" ");
231
+ const entity = `?${scope.entity}`;
232
+ const via = formatTerm(scope.via);
233
+ return (`FILTER(EXISTS { VALUES ?${bound} { ${values} } ${entity} ${via} ?${bound} } ` +
234
+ `|| NOT EXISTS { ${entity} ${via} ?${bound}Any })`);
235
+ }
236
+ /**
237
+ * Search as a substring test over every declared variable.
238
+ *
239
+ * `COALESCE(…, false)` per variable rather than one bare disjunction: an
240
+ * unbound cell makes `CONTAINS` raise, and an error inside `||` is not reliably
241
+ * false. The row predicate treated a missing cell as "does not contain", and so
242
+ * does this.
243
+ */
244
+ function searchClause(search, bound) {
245
+ const tests = search.variables
246
+ .map((variable) => `COALESCE(CONTAINS(LCASE(STR(?${variable})), LCASE(?${bound})), false)`)
247
+ .join(" || ");
248
+ return (`FILTER EXISTS { VALUES ?${bound} { "${escapeSparqlString(search.term)}" } ` +
249
+ `FILTER(${tests}) }`);
250
+ }
251
+ //# sourceMappingURL=buildListQuery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildListQuery.js","sourceRoot":"","sources":["../../../../../src/kernel/packs/sparql/buildListQuery.ts"],"names":[],"mappings":"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"}