@digital-science-dsl/dimensions-analytics-mcp 0.5.3

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 (722) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +152 -0
  3. package/bin/dimensions-analytics-mcp-http.js +2 -0
  4. package/bin/dimensions-analytics-mcp.js +2 -0
  5. package/dist/client/auth/config-loaders.d.ts +22 -0
  6. package/dist/client/auth/config-loaders.d.ts.map +1 -0
  7. package/dist/client/auth/config-loaders.js +68 -0
  8. package/dist/client/auth/config-loaders.js.map +1 -0
  9. package/dist/client/auth/decorators/index.d.ts +7 -0
  10. package/dist/client/auth/decorators/index.d.ts.map +1 -0
  11. package/dist/client/auth/decorators/index.js +6 -0
  12. package/dist/client/auth/decorators/index.js.map +1 -0
  13. package/dist/client/auth/decorators/with-caching.d.ts +81 -0
  14. package/dist/client/auth/decorators/with-caching.d.ts.map +1 -0
  15. package/dist/client/auth/decorators/with-caching.js +168 -0
  16. package/dist/client/auth/decorators/with-caching.js.map +1 -0
  17. package/dist/client/auth/factory.d.ts +13 -0
  18. package/dist/client/auth/factory.d.ts.map +1 -0
  19. package/dist/client/auth/factory.js +29 -0
  20. package/dist/client/auth/factory.js.map +1 -0
  21. package/dist/client/auth/index.d.ts +7 -0
  22. package/dist/client/auth/index.d.ts.map +1 -0
  23. package/dist/client/auth/index.js +6 -0
  24. package/dist/client/auth/index.js.map +1 -0
  25. package/dist/client/auth/jwt-utils.d.ts +17 -0
  26. package/dist/client/auth/jwt-utils.d.ts.map +1 -0
  27. package/dist/client/auth/jwt-utils.js +33 -0
  28. package/dist/client/auth/jwt-utils.js.map +1 -0
  29. package/dist/client/auth/node.d.ts +16 -0
  30. package/dist/client/auth/node.d.ts.map +1 -0
  31. package/dist/client/auth/node.js +15 -0
  32. package/dist/client/auth/node.js.map +1 -0
  33. package/dist/client/auth/providers/index.d.ts +2 -0
  34. package/dist/client/auth/providers/index.d.ts.map +1 -0
  35. package/dist/client/auth/providers/index.js +2 -0
  36. package/dist/client/auth/providers/index.js.map +1 -0
  37. package/dist/client/auth/providers/jwt-auth-provider.d.ts +70 -0
  38. package/dist/client/auth/providers/jwt-auth-provider.d.ts.map +1 -0
  39. package/dist/client/auth/providers/jwt-auth-provider.js +134 -0
  40. package/dist/client/auth/providers/jwt-auth-provider.js.map +1 -0
  41. package/dist/client/auth/schemas.d.ts +19 -0
  42. package/dist/client/auth/schemas.d.ts.map +1 -0
  43. package/dist/client/auth/schemas.js +23 -0
  44. package/dist/client/auth/schemas.js.map +1 -0
  45. package/dist/client/auth/types.d.ts +25 -0
  46. package/dist/client/auth/types.d.ts.map +1 -0
  47. package/dist/client/auth/types.js +9 -0
  48. package/dist/client/auth/types.js.map +1 -0
  49. package/dist/client/command.d.ts +44 -0
  50. package/dist/client/command.d.ts.map +1 -0
  51. package/dist/client/command.js +23 -0
  52. package/dist/client/command.js.map +1 -0
  53. package/dist/client/config/index.d.ts +11 -0
  54. package/dist/client/config/index.d.ts.map +1 -0
  55. package/dist/client/config/index.js +8 -0
  56. package/dist/client/config/index.js.map +1 -0
  57. package/dist/client/config/loader.d.ts +27 -0
  58. package/dist/client/config/loader.d.ts.map +1 -0
  59. package/dist/client/config/loader.js +128 -0
  60. package/dist/client/config/loader.js.map +1 -0
  61. package/dist/client/config/node.d.ts +18 -0
  62. package/dist/client/config/node.d.ts.map +1 -0
  63. package/dist/client/config/node.js +17 -0
  64. package/dist/client/config/node.js.map +1 -0
  65. package/dist/client/config/schemas.d.ts +23 -0
  66. package/dist/client/config/schemas.d.ts.map +1 -0
  67. package/dist/client/config/schemas.js +24 -0
  68. package/dist/client/config/schemas.js.map +1 -0
  69. package/dist/client/config/types.d.ts +37 -0
  70. package/dist/client/config/types.d.ts.map +1 -0
  71. package/dist/client/config/types.js +6 -0
  72. package/dist/client/config/types.js.map +1 -0
  73. package/dist/client/config/unified-loader.d.ts +41 -0
  74. package/dist/client/config/unified-loader.d.ts.map +1 -0
  75. package/dist/client/config/unified-loader.js +86 -0
  76. package/dist/client/config/unified-loader.js.map +1 -0
  77. package/dist/client/config.d.ts +43 -0
  78. package/dist/client/config.d.ts.map +1 -0
  79. package/dist/client/config.js +56 -0
  80. package/dist/client/config.js.map +1 -0
  81. package/dist/client/deployment-config.d.ts +36 -0
  82. package/dist/client/deployment-config.d.ts.map +1 -0
  83. package/dist/client/deployment-config.js +79 -0
  84. package/dist/client/deployment-config.js.map +1 -0
  85. package/dist/client/errors.d.ts +180 -0
  86. package/dist/client/errors.d.ts.map +1 -0
  87. package/dist/client/errors.js +253 -0
  88. package/dist/client/errors.js.map +1 -0
  89. package/dist/client/http-client.d.ts +215 -0
  90. package/dist/client/http-client.d.ts.map +1 -0
  91. package/dist/client/http-client.js +419 -0
  92. package/dist/client/http-client.js.map +1 -0
  93. package/dist/client/index.d.ts +28 -0
  94. package/dist/client/index.d.ts.map +1 -0
  95. package/dist/client/index.js +18 -0
  96. package/dist/client/index.js.map +1 -0
  97. package/dist/client/internal-dsl-client.d.ts +30 -0
  98. package/dist/client/internal-dsl-client.d.ts.map +1 -0
  99. package/dist/client/internal-dsl-client.js +101 -0
  100. package/dist/client/internal-dsl-client.js.map +1 -0
  101. package/dist/client/rate-limiter.d.ts +79 -0
  102. package/dist/client/rate-limiter.d.ts.map +1 -0
  103. package/dist/client/rate-limiter.js +118 -0
  104. package/dist/client/rate-limiter.js.map +1 -0
  105. package/dist/client/resolve-api-key-user.d.ts +24 -0
  106. package/dist/client/resolve-api-key-user.d.ts.map +1 -0
  107. package/dist/client/resolve-api-key-user.js +80 -0
  108. package/dist/client/resolve-api-key-user.js.map +1 -0
  109. package/dist/client/types.d.ts +44 -0
  110. package/dist/client/types.d.ts.map +1 -0
  111. package/dist/client/types.js +10 -0
  112. package/dist/client/types.js.map +1 -0
  113. package/dist/dsl/client.d.ts +330 -0
  114. package/dist/dsl/client.d.ts.map +1 -0
  115. package/dist/dsl/client.js +433 -0
  116. package/dist/dsl/client.js.map +1 -0
  117. package/dist/dsl/commands/apply-filters.d.ts +15 -0
  118. package/dist/dsl/commands/apply-filters.d.ts.map +1 -0
  119. package/dist/dsl/commands/apply-filters.js +29 -0
  120. package/dist/dsl/commands/apply-filters.js.map +1 -0
  121. package/dist/dsl/commands/facet/index.d.ts +6 -0
  122. package/dist/dsl/commands/facet/index.d.ts.map +1 -0
  123. package/dist/dsl/commands/facet/index.js +6 -0
  124. package/dist/dsl/commands/facet/index.js.map +1 -0
  125. package/dist/dsl/commands/facet/schemas.d.ts +71 -0
  126. package/dist/dsl/commands/facet/schemas.d.ts.map +1 -0
  127. package/dist/dsl/commands/facet/schemas.js +84 -0
  128. package/dist/dsl/commands/facet/schemas.js.map +1 -0
  129. package/dist/dsl/commands/functions/ClassifyCommand.d.ts +96 -0
  130. package/dist/dsl/commands/functions/ClassifyCommand.d.ts.map +1 -0
  131. package/dist/dsl/commands/functions/ClassifyCommand.js +95 -0
  132. package/dist/dsl/commands/functions/ClassifyCommand.js.map +1 -0
  133. package/dist/dsl/commands/functions/ExtractAffiliationsCommand.d.ts +86 -0
  134. package/dist/dsl/commands/functions/ExtractAffiliationsCommand.d.ts.map +1 -0
  135. package/dist/dsl/commands/functions/ExtractAffiliationsCommand.js +131 -0
  136. package/dist/dsl/commands/functions/ExtractAffiliationsCommand.js.map +1 -0
  137. package/dist/dsl/commands/functions/ExtractConceptsCommand.d.ts +73 -0
  138. package/dist/dsl/commands/functions/ExtractConceptsCommand.d.ts.map +1 -0
  139. package/dist/dsl/commands/functions/ExtractConceptsCommand.js +110 -0
  140. package/dist/dsl/commands/functions/ExtractConceptsCommand.js.map +1 -0
  141. package/dist/dsl/commands/functions/ExtractGrantsCommand.d.ts +66 -0
  142. package/dist/dsl/commands/functions/ExtractGrantsCommand.d.ts.map +1 -0
  143. package/dist/dsl/commands/functions/ExtractGrantsCommand.js +87 -0
  144. package/dist/dsl/commands/functions/ExtractGrantsCommand.js.map +1 -0
  145. package/dist/dsl/commands/functions/index.d.ts +10 -0
  146. package/dist/dsl/commands/functions/index.d.ts.map +1 -0
  147. package/dist/dsl/commands/functions/index.js +10 -0
  148. package/dist/dsl/commands/functions/index.js.map +1 -0
  149. package/dist/dsl/commands/index.d.ts +8 -0
  150. package/dist/dsl/commands/index.d.ts.map +1 -0
  151. package/dist/dsl/commands/index.js +7 -0
  152. package/dist/dsl/commands/index.js.map +1 -0
  153. package/dist/dsl/commands/validate-input.d.ts +18 -0
  154. package/dist/dsl/commands/validate-input.d.ts.map +1 -0
  155. package/dist/dsl/commands/validate-input.js +27 -0
  156. package/dist/dsl/commands/validate-input.js.map +1 -0
  157. package/dist/dsl/create-client.d.ts +32 -0
  158. package/dist/dsl/create-client.d.ts.map +1 -0
  159. package/dist/dsl/create-client.js +58 -0
  160. package/dist/dsl/create-client.js.map +1 -0
  161. package/dist/dsl/fluent-query-builder.d.ts +439 -0
  162. package/dist/dsl/fluent-query-builder.d.ts.map +1 -0
  163. package/dist/dsl/fluent-query-builder.js +534 -0
  164. package/dist/dsl/fluent-query-builder.js.map +1 -0
  165. package/dist/dsl/index.d.ts +32 -0
  166. package/dist/dsl/index.d.ts.map +1 -0
  167. package/dist/dsl/index.js +31 -0
  168. package/dist/dsl/index.js.map +1 -0
  169. package/dist/dsl/pagination.d.ts +82 -0
  170. package/dist/dsl/pagination.d.ts.map +1 -0
  171. package/dist/dsl/pagination.js +135 -0
  172. package/dist/dsl/pagination.js.map +1 -0
  173. package/dist/dsl/query-builder.d.ts +456 -0
  174. package/dist/dsl/query-builder.d.ts.map +1 -0
  175. package/dist/dsl/query-builder.js +994 -0
  176. package/dist/dsl/query-builder.js.map +1 -0
  177. package/dist/dsl/response-parser.d.ts +111 -0
  178. package/dist/dsl/response-parser.d.ts.map +1 -0
  179. package/dist/dsl/response-parser.js +210 -0
  180. package/dist/dsl/response-parser.js.map +1 -0
  181. package/dist/dsl/schema/cache.d.ts +52 -0
  182. package/dist/dsl/schema/cache.d.ts.map +1 -0
  183. package/dist/dsl/schema/cache.js +97 -0
  184. package/dist/dsl/schema/cache.js.map +1 -0
  185. package/dist/dsl/schema/extract.d.ts +18 -0
  186. package/dist/dsl/schema/extract.d.ts.map +1 -0
  187. package/dist/dsl/schema/extract.js +41 -0
  188. package/dist/dsl/schema/extract.js.map +1 -0
  189. package/dist/dsl/schema/index.d.ts +13 -0
  190. package/dist/dsl/schema/index.d.ts.map +1 -0
  191. package/dist/dsl/schema/index.js +12 -0
  192. package/dist/dsl/schema/index.js.map +1 -0
  193. package/dist/dsl/schema/load.d.ts +41 -0
  194. package/dist/dsl/schema/load.d.ts.map +1 -0
  195. package/dist/dsl/schema/load.js +119 -0
  196. package/dist/dsl/schema/load.js.map +1 -0
  197. package/dist/dsl/schema/store.d.ts +137 -0
  198. package/dist/dsl/schema/store.d.ts.map +1 -0
  199. package/dist/dsl/schema/store.js +204 -0
  200. package/dist/dsl/schema/store.js.map +1 -0
  201. package/dist/dsl/schema/structured-entities.d.ts +21 -0
  202. package/dist/dsl/schema/structured-entities.d.ts.map +1 -0
  203. package/dist/dsl/schema/structured-entities.js +39 -0
  204. package/dist/dsl/schema/structured-entities.js.map +1 -0
  205. package/dist/dsl/schema/summary.d.ts +51 -0
  206. package/dist/dsl/schema/summary.d.ts.map +1 -0
  207. package/dist/dsl/schema/summary.js +61 -0
  208. package/dist/dsl/schema/summary.js.map +1 -0
  209. package/dist/dsl/schema/types.d.ts +36 -0
  210. package/dist/dsl/schema/types.d.ts.map +1 -0
  211. package/dist/dsl/schema/types.js +6 -0
  212. package/dist/dsl/schema/types.js.map +1 -0
  213. package/dist/dsl/schema/validation.d.ts +21 -0
  214. package/dist/dsl/schema/validation.d.ts.map +1 -0
  215. package/dist/dsl/schema/validation.js +44 -0
  216. package/dist/dsl/schema/validation.js.map +1 -0
  217. package/dist/dsl/types/buckets.d.ts +18 -0
  218. package/dist/dsl/types/buckets.d.ts.map +1 -0
  219. package/dist/dsl/types/buckets.js +6 -0
  220. package/dist/dsl/types/buckets.js.map +1 -0
  221. package/dist/dsl/types/common.d.ts +91 -0
  222. package/dist/dsl/types/common.d.ts.map +1 -0
  223. package/dist/dsl/types/common.js +6 -0
  224. package/dist/dsl/types/common.js.map +1 -0
  225. package/dist/dsl/types/entities.d.ts +35 -0
  226. package/dist/dsl/types/entities.d.ts.map +1 -0
  227. package/dist/dsl/types/entities.js +11 -0
  228. package/dist/dsl/types/entities.js.map +1 -0
  229. package/dist/dsl/types/index.d.ts +12 -0
  230. package/dist/dsl/types/index.d.ts.map +1 -0
  231. package/dist/dsl/types/index.js +7 -0
  232. package/dist/dsl/types/index.js.map +1 -0
  233. package/dist/dsl/types/return-clauses.d.ts +107 -0
  234. package/dist/dsl/types/return-clauses.d.ts.map +1 -0
  235. package/dist/dsl/types/return-clauses.js +22 -0
  236. package/dist/dsl/types/return-clauses.js.map +1 -0
  237. package/dist/dsl/types/special-functions.d.ts +175 -0
  238. package/dist/dsl/types/special-functions.d.ts.map +1 -0
  239. package/dist/dsl/types/special-functions.js +25 -0
  240. package/dist/dsl/types/special-functions.js.map +1 -0
  241. package/dist/dsl/types/vocabulary.d.ts +21 -0
  242. package/dist/dsl/types/vocabulary.d.ts.map +1 -0
  243. package/dist/dsl/types/vocabulary.js +44 -0
  244. package/dist/dsl/types/vocabulary.js.map +1 -0
  245. package/dist/dsl/usage-policy.d.ts +87 -0
  246. package/dist/dsl/usage-policy.d.ts.map +1 -0
  247. package/dist/dsl/usage-policy.js +133 -0
  248. package/dist/dsl/usage-policy.js.map +1 -0
  249. package/dist/dsl/utils/escape.d.ts +24 -0
  250. package/dist/dsl/utils/escape.d.ts.map +1 -0
  251. package/dist/dsl/utils/escape.js +124 -0
  252. package/dist/dsl/utils/escape.js.map +1 -0
  253. package/dist/dsl/utils/sanitize.d.ts +13 -0
  254. package/dist/dsl/utils/sanitize.d.ts.map +1 -0
  255. package/dist/dsl/utils/sanitize.js +19 -0
  256. package/dist/dsl/utils/sanitize.js.map +1 -0
  257. package/dist/dsl/utils/string-utils.d.ts +35 -0
  258. package/dist/dsl/utils/string-utils.d.ts.map +1 -0
  259. package/dist/dsl/utils/string-utils.js +145 -0
  260. package/dist/dsl/utils/string-utils.js.map +1 -0
  261. package/dist/dsl/utils/validate-field-name.d.ts +11 -0
  262. package/dist/dsl/utils/validate-field-name.d.ts.map +1 -0
  263. package/dist/dsl/utils/validate-field-name.js +18 -0
  264. package/dist/dsl/utils/validate-field-name.js.map +1 -0
  265. package/dist/examples/dsl-examples.d.ts +11 -0
  266. package/dist/examples/dsl-examples.d.ts.map +1 -0
  267. package/dist/examples/dsl-examples.js +49 -0
  268. package/dist/examples/dsl-examples.js.map +1 -0
  269. package/dist/examples/usage-scenarios.d.ts +22 -0
  270. package/dist/examples/usage-scenarios.d.ts.map +1 -0
  271. package/dist/examples/usage-scenarios.js +173 -0
  272. package/dist/examples/usage-scenarios.js.map +1 -0
  273. package/dist/funder-org-names.d.ts +12 -0
  274. package/dist/funder-org-names.d.ts.map +1 -0
  275. package/dist/funder-org-names.js +30 -0
  276. package/dist/funder-org-names.js.map +1 -0
  277. package/dist/http-main.d.ts +7 -0
  278. package/dist/http-main.d.ts.map +1 -0
  279. package/dist/http-main.js +36 -0
  280. package/dist/http-main.js.map +1 -0
  281. package/dist/http.d.ts +29 -0
  282. package/dist/http.d.ts.map +1 -0
  283. package/dist/http.js +173 -0
  284. package/dist/http.js.map +1 -0
  285. package/dist/index.d.ts +9 -0
  286. package/dist/index.d.ts.map +1 -0
  287. package/dist/index.js +7 -0
  288. package/dist/index.js.map +1 -0
  289. package/dist/kwq/build-query.d.ts +26 -0
  290. package/dist/kwq/build-query.d.ts.map +1 -0
  291. package/dist/kwq/build-query.js +37 -0
  292. package/dist/kwq/build-query.js.map +1 -0
  293. package/dist/kwq/concept-groups.d.ts +49 -0
  294. package/dist/kwq/concept-groups.d.ts.map +1 -0
  295. package/dist/kwq/concept-groups.js +55 -0
  296. package/dist/kwq/concept-groups.js.map +1 -0
  297. package/dist/kwq/dsl-assembler.d.ts +28 -0
  298. package/dist/kwq/dsl-assembler.d.ts.map +1 -0
  299. package/dist/kwq/dsl-assembler.js +124 -0
  300. package/dist/kwq/dsl-assembler.js.map +1 -0
  301. package/dist/kwq/ensemble.d.ts +38 -0
  302. package/dist/kwq/ensemble.d.ts.map +1 -0
  303. package/dist/kwq/ensemble.js +107 -0
  304. package/dist/kwq/ensemble.js.map +1 -0
  305. package/dist/kwq/index.d.ts +16 -0
  306. package/dist/kwq/index.d.ts.map +1 -0
  307. package/dist/kwq/index.js +14 -0
  308. package/dist/kwq/index.js.map +1 -0
  309. package/dist/kwq/types.d.ts +120 -0
  310. package/dist/kwq/types.d.ts.map +1 -0
  311. package/dist/kwq/types.js +11 -0
  312. package/dist/kwq/types.js.map +1 -0
  313. package/dist/lambda.d.ts +10 -0
  314. package/dist/lambda.d.ts.map +1 -0
  315. package/dist/lambda.js +4 -0
  316. package/dist/lambda.js.map +1 -0
  317. package/dist/main.d.ts +8 -0
  318. package/dist/main.d.ts.map +1 -0
  319. package/dist/main.js +33 -0
  320. package/dist/main.js.map +1 -0
  321. package/dist/mcp/batch-fetch.d.ts +89 -0
  322. package/dist/mcp/batch-fetch.d.ts.map +1 -0
  323. package/dist/mcp/batch-fetch.js +178 -0
  324. package/dist/mcp/batch-fetch.js.map +1 -0
  325. package/dist/mcp/examples/dsl-examples.d.ts +21 -0
  326. package/dist/mcp/examples/dsl-examples.d.ts.map +1 -0
  327. package/dist/mcp/examples/dsl-examples.js +61 -0
  328. package/dist/mcp/examples/dsl-examples.js.map +1 -0
  329. package/dist/mcp/examples/usage-scenarios.d.ts +20 -0
  330. package/dist/mcp/examples/usage-scenarios.d.ts.map +1 -0
  331. package/dist/mcp/examples/usage-scenarios.js +169 -0
  332. package/dist/mcp/examples/usage-scenarios.js.map +1 -0
  333. package/dist/mcp/export-format.d.ts +43 -0
  334. package/dist/mcp/export-format.d.ts.map +1 -0
  335. package/dist/mcp/export-format.js +92 -0
  336. package/dist/mcp/export-format.js.map +1 -0
  337. package/dist/mcp/funder-org-names.d.ts +12 -0
  338. package/dist/mcp/funder-org-names.d.ts.map +1 -0
  339. package/dist/mcp/funder-org-names.js +30 -0
  340. package/dist/mcp/funder-org-names.js.map +1 -0
  341. package/dist/mcp/http-server.d.ts +20 -0
  342. package/dist/mcp/http-server.d.ts.map +1 -0
  343. package/dist/mcp/http-server.js +118 -0
  344. package/dist/mcp/http-server.js.map +1 -0
  345. package/dist/mcp/middleware/field-aliases.d.ts +97 -0
  346. package/dist/mcp/middleware/field-aliases.d.ts.map +1 -0
  347. package/dist/mcp/middleware/field-aliases.js +208 -0
  348. package/dist/mcp/middleware/field-aliases.js.map +1 -0
  349. package/dist/mcp/resources/schema.d.ts +13 -0
  350. package/dist/mcp/resources/schema.d.ts.map +1 -0
  351. package/dist/mcp/resources/schema.js +262 -0
  352. package/dist/mcp/resources/schema.js.map +1 -0
  353. package/dist/mcp/schema/context.d.ts +10 -0
  354. package/dist/mcp/schema/context.d.ts.map +1 -0
  355. package/dist/mcp/schema/context.js +6 -0
  356. package/dist/mcp/schema/context.js.map +1 -0
  357. package/dist/mcp/schema/index.d.ts +8 -0
  358. package/dist/mcp/schema/index.d.ts.map +1 -0
  359. package/dist/mcp/schema/index.js +6 -0
  360. package/dist/mcp/schema/index.js.map +1 -0
  361. package/dist/mcp/server.d.ts +56 -0
  362. package/dist/mcp/server.d.ts.map +1 -0
  363. package/dist/mcp/server.js +168 -0
  364. package/dist/mcp/server.js.map +1 -0
  365. package/dist/mcp/shared-schema.d.ts +13 -0
  366. package/dist/mcp/shared-schema.d.ts.map +1 -0
  367. package/dist/mcp/shared-schema.js +28 -0
  368. package/dist/mcp/shared-schema.js.map +1 -0
  369. package/dist/mcp/tools/analytics-filters.d.ts +18 -0
  370. package/dist/mcp/tools/analytics-filters.d.ts.map +1 -0
  371. package/dist/mcp/tools/analytics-filters.js +35 -0
  372. package/dist/mcp/tools/analytics-filters.js.map +1 -0
  373. package/dist/mcp/tools/analytics.d.ts +16 -0
  374. package/dist/mcp/tools/analytics.d.ts.map +1 -0
  375. package/dist/mcp/tools/analytics.js +381 -0
  376. package/dist/mcp/tools/analytics.js.map +1 -0
  377. package/dist/mcp/tools/concept-profile.d.ts +14 -0
  378. package/dist/mcp/tools/concept-profile.d.ts.map +1 -0
  379. package/dist/mcp/tools/concept-profile.js +130 -0
  380. package/dist/mcp/tools/concept-profile.js.map +1 -0
  381. package/dist/mcp/tools/dataset-profile.d.ts +15 -0
  382. package/dist/mcp/tools/dataset-profile.d.ts.map +1 -0
  383. package/dist/mcp/tools/dataset-profile.js +194 -0
  384. package/dist/mcp/tools/dataset-profile.js.map +1 -0
  385. package/dist/mcp/tools/fetch-search-pages.d.ts +15 -0
  386. package/dist/mcp/tools/fetch-search-pages.d.ts.map +1 -0
  387. package/dist/mcp/tools/fetch-search-pages.js +237 -0
  388. package/dist/mcp/tools/fetch-search-pages.js.map +1 -0
  389. package/dist/mcp/tools/functions.d.ts +14 -0
  390. package/dist/mcp/tools/functions.d.ts.map +1 -0
  391. package/dist/mcp/tools/functions.js +87 -0
  392. package/dist/mcp/tools/functions.js.map +1 -0
  393. package/dist/mcp/tools/impact-chain.d.ts +15 -0
  394. package/dist/mcp/tools/impact-chain.d.ts.map +1 -0
  395. package/dist/mcp/tools/impact-chain.js +96 -0
  396. package/dist/mcp/tools/impact-chain.js.map +1 -0
  397. package/dist/mcp/tools/kwq.d.ts +19 -0
  398. package/dist/mcp/tools/kwq.d.ts.map +1 -0
  399. package/dist/mcp/tools/kwq.js +156 -0
  400. package/dist/mcp/tools/kwq.js.map +1 -0
  401. package/dist/mcp/tools/lookup.d.ts +14 -0
  402. package/dist/mcp/tools/lookup.d.ts.map +1 -0
  403. package/dist/mcp/tools/lookup.js +146 -0
  404. package/dist/mcp/tools/lookup.js.map +1 -0
  405. package/dist/mcp/tools/policy-profile.d.ts +15 -0
  406. package/dist/mcp/tools/policy-profile.d.ts.map +1 -0
  407. package/dist/mcp/tools/policy-profile.js +174 -0
  408. package/dist/mcp/tools/policy-profile.js.map +1 -0
  409. package/dist/mcp/tools/profile.d.ts +14 -0
  410. package/dist/mcp/tools/profile.d.ts.map +1 -0
  411. package/dist/mcp/tools/profile.js +349 -0
  412. package/dist/mcp/tools/profile.js.map +1 -0
  413. package/dist/mcp/tools/query.d.ts +16 -0
  414. package/dist/mcp/tools/query.d.ts.map +1 -0
  415. package/dist/mcp/tools/query.js +75 -0
  416. package/dist/mcp/tools/query.js.map +1 -0
  417. package/dist/mcp/tools/schema.d.ts +23 -0
  418. package/dist/mcp/tools/schema.d.ts.map +1 -0
  419. package/dist/mcp/tools/schema.js +124 -0
  420. package/dist/mcp/tools/schema.js.map +1 -0
  421. package/dist/mcp/tools/search-entity-metadata.d.ts +18 -0
  422. package/dist/mcp/tools/search-entity-metadata.d.ts.map +1 -0
  423. package/dist/mcp/tools/search-entity-metadata.js +221 -0
  424. package/dist/mcp/tools/search-entity-metadata.js.map +1 -0
  425. package/dist/mcp/tools/search-input.d.ts +58 -0
  426. package/dist/mcp/tools/search-input.d.ts.map +1 -0
  427. package/dist/mcp/tools/search-input.js +76 -0
  428. package/dist/mcp/tools/search-input.js.map +1 -0
  429. package/dist/mcp/tools/search.d.ts +24 -0
  430. package/dist/mcp/tools/search.d.ts.map +1 -0
  431. package/dist/mcp/tools/search.js +115 -0
  432. package/dist/mcp/tools/search.js.map +1 -0
  433. package/dist/mcp/utils.d.ts +82 -0
  434. package/dist/mcp/utils.d.ts.map +1 -0
  435. package/dist/mcp/utils.js +126 -0
  436. package/dist/mcp/utils.js.map +1 -0
  437. package/dist/middleware/field-aliases.d.ts +97 -0
  438. package/dist/middleware/field-aliases.d.ts.map +1 -0
  439. package/dist/middleware/field-aliases.js +208 -0
  440. package/dist/middleware/field-aliases.js.map +1 -0
  441. package/dist/resources/schema.d.ts +13 -0
  442. package/dist/resources/schema.d.ts.map +1 -0
  443. package/dist/resources/schema.js +204 -0
  444. package/dist/resources/schema.js.map +1 -0
  445. package/dist/schema/context.d.ts +10 -0
  446. package/dist/schema/context.d.ts.map +1 -0
  447. package/dist/schema/context.js +6 -0
  448. package/dist/schema/context.js.map +1 -0
  449. package/dist/schema/index.d.ts +9 -0
  450. package/dist/schema/index.d.ts.map +1 -0
  451. package/dist/schema/index.js +6 -0
  452. package/dist/schema/index.js.map +1 -0
  453. package/dist/schema/load.d.ts +43 -0
  454. package/dist/schema/load.d.ts.map +1 -0
  455. package/dist/schema/load.js +139 -0
  456. package/dist/schema/load.js.map +1 -0
  457. package/dist/schema/store.d.ts +112 -0
  458. package/dist/schema/store.d.ts.map +1 -0
  459. package/dist/schema/store.js +173 -0
  460. package/dist/schema/store.js.map +1 -0
  461. package/dist/schema/structured-entities.d.ts +21 -0
  462. package/dist/schema/structured-entities.d.ts.map +1 -0
  463. package/dist/schema/structured-entities.js +39 -0
  464. package/dist/schema/structured-entities.js.map +1 -0
  465. package/dist/schema/types.d.ts +36 -0
  466. package/dist/schema/types.d.ts.map +1 -0
  467. package/dist/schema/types.js +6 -0
  468. package/dist/schema/types.js.map +1 -0
  469. package/dist/server.d.ts +57 -0
  470. package/dist/server.d.ts.map +1 -0
  471. package/dist/server.js +144 -0
  472. package/dist/server.js.map +1 -0
  473. package/dist/telemetry.d.ts +172 -0
  474. package/dist/telemetry.d.ts.map +1 -0
  475. package/dist/telemetry.js +233 -0
  476. package/dist/telemetry.js.map +1 -0
  477. package/dist/tools/analytics-filters.d.ts +18 -0
  478. package/dist/tools/analytics-filters.d.ts.map +1 -0
  479. package/dist/tools/analytics-filters.js +35 -0
  480. package/dist/tools/analytics-filters.js.map +1 -0
  481. package/dist/tools/analytics.d.ts +16 -0
  482. package/dist/tools/analytics.d.ts.map +1 -0
  483. package/dist/tools/analytics.js +381 -0
  484. package/dist/tools/analytics.js.map +1 -0
  485. package/dist/tools/concept-profile.d.ts +14 -0
  486. package/dist/tools/concept-profile.d.ts.map +1 -0
  487. package/dist/tools/concept-profile.js +130 -0
  488. package/dist/tools/concept-profile.js.map +1 -0
  489. package/dist/tools/dataset-profile.d.ts +15 -0
  490. package/dist/tools/dataset-profile.d.ts.map +1 -0
  491. package/dist/tools/dataset-profile.js +194 -0
  492. package/dist/tools/dataset-profile.js.map +1 -0
  493. package/dist/tools/functions.d.ts +14 -0
  494. package/dist/tools/functions.d.ts.map +1 -0
  495. package/dist/tools/functions.js +294 -0
  496. package/dist/tools/functions.js.map +1 -0
  497. package/dist/tools/impact-chain.d.ts +15 -0
  498. package/dist/tools/impact-chain.d.ts.map +1 -0
  499. package/dist/tools/impact-chain.js +96 -0
  500. package/dist/tools/impact-chain.js.map +1 -0
  501. package/dist/tools/kwq.d.ts +19 -0
  502. package/dist/tools/kwq.d.ts.map +1 -0
  503. package/dist/tools/kwq.js +156 -0
  504. package/dist/tools/kwq.js.map +1 -0
  505. package/dist/tools/lookup.d.ts +14 -0
  506. package/dist/tools/lookup.d.ts.map +1 -0
  507. package/dist/tools/lookup.js +213 -0
  508. package/dist/tools/lookup.js.map +1 -0
  509. package/dist/tools/policy-profile.d.ts +15 -0
  510. package/dist/tools/policy-profile.d.ts.map +1 -0
  511. package/dist/tools/policy-profile.js +174 -0
  512. package/dist/tools/policy-profile.js.map +1 -0
  513. package/dist/tools/profile.d.ts +14 -0
  514. package/dist/tools/profile.d.ts.map +1 -0
  515. package/dist/tools/profile.js +349 -0
  516. package/dist/tools/profile.js.map +1 -0
  517. package/dist/tools/query.d.ts +16 -0
  518. package/dist/tools/query.d.ts.map +1 -0
  519. package/dist/tools/query.js +53 -0
  520. package/dist/tools/query.js.map +1 -0
  521. package/dist/tools/schema.d.ts +23 -0
  522. package/dist/tools/schema.d.ts.map +1 -0
  523. package/dist/tools/schema.js +124 -0
  524. package/dist/tools/schema.js.map +1 -0
  525. package/dist/tools/search-entity-metadata.d.ts +18 -0
  526. package/dist/tools/search-entity-metadata.d.ts.map +1 -0
  527. package/dist/tools/search-entity-metadata.js +221 -0
  528. package/dist/tools/search-entity-metadata.js.map +1 -0
  529. package/dist/tools/search.d.ts +23 -0
  530. package/dist/tools/search.d.ts.map +1 -0
  531. package/dist/tools/search.js +110 -0
  532. package/dist/tools/search.js.map +1 -0
  533. package/dist/tools/solr-fields.d.ts +39 -0
  534. package/dist/tools/solr-fields.d.ts.map +1 -0
  535. package/dist/tools/solr-fields.js +40 -0
  536. package/dist/tools/solr-fields.js.map +1 -0
  537. package/dist/utils.d.ts +70 -0
  538. package/dist/utils.d.ts.map +1 -0
  539. package/dist/utils.js +104 -0
  540. package/dist/utils.js.map +1 -0
  541. package/package.json +41 -0
  542. package/src/client/auth/config-loaders.ts +81 -0
  543. package/src/client/auth/decorators/index.ts +7 -0
  544. package/src/client/auth/decorators/with-caching.ts +237 -0
  545. package/src/client/auth/factory.ts +34 -0
  546. package/src/client/auth/index.ts +6 -0
  547. package/src/client/auth/jwt-utils.ts +36 -0
  548. package/src/client/auth/node.ts +16 -0
  549. package/src/client/auth/providers/index.ts +1 -0
  550. package/src/client/auth/providers/jwt-auth-provider.ts +153 -0
  551. package/src/client/auth/schemas.ts +25 -0
  552. package/src/client/auth/types.ts +30 -0
  553. package/src/client/command.ts +54 -0
  554. package/src/client/config/index.ts +15 -0
  555. package/src/client/config/loader.ts +155 -0
  556. package/src/client/config/node.ts +22 -0
  557. package/src/client/config/schemas.ts +28 -0
  558. package/src/client/config/types.ts +39 -0
  559. package/src/client/config/unified-loader.ts +118 -0
  560. package/src/client/config.ts +69 -0
  561. package/src/client/deployment-config.ts +98 -0
  562. package/src/client/errors.ts +295 -0
  563. package/src/client/http-client.ts +577 -0
  564. package/src/client/index.ts +68 -0
  565. package/src/client/internal-dsl-client.ts +128 -0
  566. package/src/client/rate-limiter.ts +139 -0
  567. package/src/client/resolve-api-key-user.ts +99 -0
  568. package/src/client/types.ts +44 -0
  569. package/src/dsl/client.ts +538 -0
  570. package/src/dsl/commands/apply-filters.ts +35 -0
  571. package/src/dsl/commands/facet/index.ts +12 -0
  572. package/src/dsl/commands/facet/schemas.ts +101 -0
  573. package/src/dsl/commands/functions/ClassifyCommand.ts +129 -0
  574. package/src/dsl/commands/functions/ExtractAffiliationsCommand.ts +163 -0
  575. package/src/dsl/commands/functions/ExtractConceptsCommand.ts +141 -0
  576. package/src/dsl/commands/functions/ExtractGrantsCommand.ts +112 -0
  577. package/src/dsl/commands/functions/index.ts +31 -0
  578. package/src/dsl/commands/index.ts +29 -0
  579. package/src/dsl/commands/validate-input.ts +29 -0
  580. package/src/dsl/create-client.ts +80 -0
  581. package/src/dsl/fluent-query-builder.ts +684 -0
  582. package/src/dsl/index.ts +206 -0
  583. package/src/dsl/pagination.ts +202 -0
  584. package/src/dsl/query-builder.ts +1120 -0
  585. package/src/dsl/response-parser.ts +299 -0
  586. package/src/dsl/schema/cache.ts +124 -0
  587. package/src/dsl/schema/extract.ts +44 -0
  588. package/src/dsl/schema/index.ts +50 -0
  589. package/src/dsl/schema/load.ts +160 -0
  590. package/src/dsl/schema/store.ts +253 -0
  591. package/src/dsl/schema/structured-entities.ts +44 -0
  592. package/src/dsl/schema/summary.ts +94 -0
  593. package/src/dsl/schema/types.ts +40 -0
  594. package/src/dsl/schema/validation.ts +56 -0
  595. package/src/dsl/types/buckets.ts +22 -0
  596. package/src/dsl/types/common.ts +99 -0
  597. package/src/dsl/types/entities.ts +43 -0
  598. package/src/dsl/types/index.ts +63 -0
  599. package/src/dsl/types/return-clauses.ts +147 -0
  600. package/src/dsl/types/special-functions.ts +220 -0
  601. package/src/dsl/types/vocabulary.ts +82 -0
  602. package/src/dsl/usage-policy.ts +219 -0
  603. package/src/dsl/utils/escape.ts +123 -0
  604. package/src/dsl/utils/sanitize.ts +21 -0
  605. package/src/dsl/utils/string-utils.ts +147 -0
  606. package/src/dsl/utils/validate-field-name.ts +19 -0
  607. package/src/http-main.ts +41 -0
  608. package/src/index.ts +14 -0
  609. package/src/main.ts +40 -0
  610. package/src/mcp/batch-fetch.ts +255 -0
  611. package/src/mcp/examples/dsl-examples.ts +66 -0
  612. package/src/mcp/examples/usage-scenarios.ts +185 -0
  613. package/src/mcp/export-format.ts +113 -0
  614. package/src/mcp/funder-org-names.ts +30 -0
  615. package/src/mcp/http-server.ts +148 -0
  616. package/src/mcp/middleware/field-aliases.ts +274 -0
  617. package/src/mcp/resources/schema.ts +364 -0
  618. package/src/mcp/schema/context.ts +11 -0
  619. package/src/mcp/schema/index.ts +38 -0
  620. package/src/mcp/server.ts +247 -0
  621. package/src/mcp/shared-schema.ts +34 -0
  622. package/src/mcp/tools/analytics-filters.ts +46 -0
  623. package/src/mcp/tools/analytics.ts +453 -0
  624. package/src/mcp/tools/fetch-search-pages.ts +329 -0
  625. package/src/mcp/tools/functions.ts +105 -0
  626. package/src/mcp/tools/lookup.ts +182 -0
  627. package/src/mcp/tools/query.ts +91 -0
  628. package/src/mcp/tools/schema.ts +147 -0
  629. package/src/mcp/tools/search-entity-metadata.ts +253 -0
  630. package/src/mcp/tools/search-input.ts +87 -0
  631. package/src/mcp/tools/search.ts +168 -0
  632. package/src/mcp/utils.ts +199 -0
  633. package/test/client/auth/config-loaders.test.ts +92 -0
  634. package/test/client/auth/decorators/with-caching.test.ts +482 -0
  635. package/test/client/auth/factory.test.ts +68 -0
  636. package/test/client/auth/providers/jwt-auth-provider.test.ts +398 -0
  637. package/test/client/auth/types.test.ts +18 -0
  638. package/test/client/command.test.ts +163 -0
  639. package/test/client/config/loader.test.ts +377 -0
  640. package/test/client/config/unified-loader.test.ts +136 -0
  641. package/test/client/deployment-config.test.ts +69 -0
  642. package/test/client/errors/index.test.ts +408 -0
  643. package/test/client/helpers/mock-fetch.ts +42 -0
  644. package/test/client/http-client.test.ts +1148 -0
  645. package/test/client/integration/auth-providers.integration.test.ts +186 -0
  646. package/test/client/internal-dsl-client.test.ts +76 -0
  647. package/test/client/rate-limiter.test.ts +204 -0
  648. package/test/client/resolve-api-key-user.test.ts +87 -0
  649. package/test/dsl/client.special-functions.test.ts +387 -0
  650. package/test/dsl/client.test.ts +457 -0
  651. package/test/dsl/commands/apply-filters.test.ts +147 -0
  652. package/test/dsl/commands/functions/ClassifyCommand.test.ts +170 -0
  653. package/test/dsl/commands/functions/ExtractAffiliationsCommand.test.ts +306 -0
  654. package/test/dsl/commands/functions/ExtractConceptsCommand.test.ts +136 -0
  655. package/test/dsl/commands/functions/ExtractGrantsCommand.test.ts +202 -0
  656. package/test/dsl/commands/search/filters.test.ts +42 -0
  657. package/test/dsl/fluent-query-builder.test.ts +865 -0
  658. package/test/dsl/integration/client.integration.test.ts +294 -0
  659. package/test/dsl/integration/facet-api.integration.test.ts +276 -0
  660. package/test/dsl/integration/field-existence.integration.test.ts +148 -0
  661. package/test/dsl/integration/fluent-api-docs-examples.integration.test.ts +988 -0
  662. package/test/dsl/integration/fluent-api.integration.test.ts +615 -0
  663. package/test/dsl/integration/search-helpers.ts +27 -0
  664. package/test/dsl/integration/special-functions.integration.test.ts +224 -0
  665. package/test/dsl/integration/test-config.ts +52 -0
  666. package/test/dsl/pagination.test.ts +86 -0
  667. package/test/dsl/query-builder.test.ts +1775 -0
  668. package/test/dsl/response-parser.test.ts +520 -0
  669. package/test/dsl/schema/cache.test.ts +53 -0
  670. package/test/dsl/schema/load.test.ts +165 -0
  671. package/test/dsl/schema/summary.test.ts +61 -0
  672. package/test/dsl/schema/validation.test.ts +46 -0
  673. package/test/dsl/time-series.test.ts +135 -0
  674. package/test/dsl/unnest.test.ts +151 -0
  675. package/test/dsl/usage-policy.test.ts +100 -0
  676. package/test/dsl/utils/escape.property.test.ts +109 -0
  677. package/test/dsl/utils/escape.test.ts +140 -0
  678. package/test/dsl/utils/validate-field-name.property.test.ts +99 -0
  679. package/test/dsl/utils/validate-field-name.test.ts +55 -0
  680. package/test/examples/dsl-examples.test.ts +29 -0
  681. package/test/examples/usage-scenarios.test.ts +90 -0
  682. package/test/fixtures/describe-schema.json +4000 -0
  683. package/test/funder-org-names.test.ts +20 -0
  684. package/test/helpers/schema-fixture.ts +18 -0
  685. package/test/helpers/tool-test-harness.ts +114 -0
  686. package/test/integration/assertions.ts +263 -0
  687. package/test/integration/env.ts +108 -0
  688. package/test/integration/harness.ts +307 -0
  689. package/test/integration/hosted-client.ts +56 -0
  690. package/test/integration/hosted-mocks.ts +81 -0
  691. package/test/integration/hosted-run.ts +86 -0
  692. package/test/integration/hosted.e2e.test.ts +101 -0
  693. package/test/integration/reporter.ts +100 -0
  694. package/test/integration/run.ts +74 -0
  695. package/test/integration/suites/analytics.integration.ts +72 -0
  696. package/test/integration/suites/functions.integration.ts +37 -0
  697. package/test/integration/suites/hosted-smoke.integration.ts +38 -0
  698. package/test/integration/suites/index.ts +22 -0
  699. package/test/integration/suites/lookup.integration.ts +57 -0
  700. package/test/integration/suites/query.integration.ts +49 -0
  701. package/test/integration/suites/resources.integration.ts +68 -0
  702. package/test/integration/suites/search.integration.ts +80 -0
  703. package/test/integration/types.ts +92 -0
  704. package/test/integration/verify-usage-scenarios.ts +162 -0
  705. package/test/mcp/export-format.test.ts +67 -0
  706. package/test/mcp/http-server.test.ts +80 -0
  707. package/test/middleware/field-aliases.test.ts +287 -0
  708. package/test/resources/schema.test.ts +28 -0
  709. package/test/schema/describe-contract.test.ts +24 -0
  710. package/test/schema/store.test.ts +45 -0
  711. package/test/server.test.ts +170 -0
  712. package/test/tools/analytics-filters.test.ts +35 -0
  713. package/test/tools/analytics.test.ts +419 -0
  714. package/test/tools/fetch-search-pages.test.ts +200 -0
  715. package/test/tools/functions.test.ts +153 -0
  716. package/test/tools/lookup.test.ts +296 -0
  717. package/test/tools/query.test.ts +100 -0
  718. package/test/tools/schema.test.ts +37 -0
  719. package/test/tools/search.test.ts +415 -0
  720. package/test/utils.test.ts +45 -0
  721. package/tsconfig.json +9 -0
  722. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,1120 @@
1
+ /**
2
+ * Query builder for constructing Dimensions DSL queries programmatically.
3
+ * @module query-builder
4
+ */
5
+
6
+ import { ValidationError } from "../client/index.js";
7
+ import type { SchemaStore } from "./schema/store.js";
8
+ import { assertValidEntity, assertValidSearchIndex } from "./schema/validation.js";
9
+ import type {
10
+ AggregatedFacetReturnClause,
11
+ Currency,
12
+ FacetClause,
13
+ FacetReturnClause,
14
+ GroupedReturnClause,
15
+ TimeSeriesReturnClause,
16
+ } from "./types/return-clauses.js";
17
+ import type { EntityType, SearchIndex, SortOrder, WhereOperator } from "./types/vocabulary.js";
18
+ import { VALID_OPERATORS } from "./types/vocabulary.js";
19
+ import { escapeDslString } from "./utils/escape.js";
20
+ import { MAX_SEARCH_TEXT_LENGTH, sanitizeInput } from "./utils/sanitize.js";
21
+ import { validateFieldName } from "./utils/validate-field-name.js";
22
+
23
+ // Re-export types for backwards compatibility
24
+ export type { EntityType, SearchIndex, SortOrder, WhereOperator };
25
+
26
+ /**
27
+ * Boolean connector between conditions.
28
+ */
29
+ type BooleanConnector = "and" | "or" | "not";
30
+
31
+ /**
32
+ * Represents a node in the boolean expression tree.
33
+ */
34
+ type ExpressionNode =
35
+ | { readonly type: "condition"; readonly condition: string }
36
+ | { readonly type: "connector"; readonly connector: BooleanConnector }
37
+ | { readonly type: "group_start" }
38
+ | { readonly type: "group_end" };
39
+
40
+ /**
41
+ * Fluent builder for constructing Dimensions DSL queries.
42
+ * Provides a type-safe way to build search queries with conditions, sorting, and pagination.
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const query = new QueryBuilder()
47
+ * .search("publications")
48
+ * .for("machine learning")
49
+ * .where("year", ">=", 2020)
50
+ * .fields(["id", "title", "doi"])
51
+ * .sort("times_cited", "desc")
52
+ * .limit(100)
53
+ * .build();
54
+ * ```
55
+ */
56
+ export class QueryBuilder {
57
+ private readonly schemaStore: SchemaStore | undefined;
58
+ private entity: string | null = null;
59
+ private searchIndex: string | null = null;
60
+ private searchTerms: string | null = null;
61
+ private similarText: string | null = null;
62
+ private complexPhrase: string | null = null;
63
+ private complexMaxDist: number | null = null;
64
+ private minShouldMatchPhrase: string | null = null;
65
+ private minShouldMatchMin: number | null = null;
66
+ private expressionNodes: ExpressionNode[] = [];
67
+ private groupDepth: number = 0;
68
+ private pendingConnector: BooleanConnector | null = null;
69
+ private returnFields: string[] = [];
70
+ private unnestFields: string[] = [];
71
+ private sortField: string | null = null;
72
+ private sortOrder: SortOrder = "asc";
73
+ private skipValue: number | null = null;
74
+ private limitValue: number | null = null;
75
+ private facetClauses: FacetClause[] = [];
76
+ private timeSeriesClauses: TimeSeriesReturnClause[] = [];
77
+ private groupedClauses: GroupedReturnClause[] = [];
78
+
79
+ /**
80
+ * @param schemaStore - Optional describe schema for entity/index validation
81
+ */
82
+ constructor(schemaStore?: SchemaStore) {
83
+ this.schemaStore = schemaStore;
84
+ }
85
+
86
+ /**
87
+ * Adds a condition node to the expression tree.
88
+ * Handles automatic AND connector insertion.
89
+ * @param condition - The condition string to add
90
+ */
91
+ private addConditionNode(condition: string): void {
92
+ // Add pending connector if exists
93
+ if (this.pendingConnector) {
94
+ this.expressionNodes.push({
95
+ type: "connector",
96
+ connector: this.pendingConnector,
97
+ });
98
+ this.pendingConnector = null;
99
+ } else if (this.expressionNodes.length > 0) {
100
+ // Default to AND if no connector specified
101
+ const lastNode = this.expressionNodes[this.expressionNodes.length - 1];
102
+ if (lastNode?.type === "condition" || lastNode?.type === "group_end") {
103
+ this.expressionNodes.push({ type: "connector", connector: "and" });
104
+ }
105
+ }
106
+ this.expressionNodes.push({ type: "condition", condition });
107
+ }
108
+
109
+ /**
110
+ * Determines if a space should be added before an expression node.
111
+ * Handles spacing rules for parentheses in boolean expressions.
112
+ * @param node - The current expression node
113
+ * @param prevNode - The previous expression node (or null if first)
114
+ * @returns True if a space should be added before the node
115
+ */
116
+ private shouldAddSpaceBefore(node: ExpressionNode, prevNode: ExpressionNode | null): boolean {
117
+ if (prevNode === null) return false;
118
+ if (prevNode.type === "group_start") return false;
119
+ if (node.type === "group_end") return false;
120
+ return true;
121
+ }
122
+
123
+ /**
124
+ * Sets the entity type to search.
125
+ * @param entity - The entity type to search (publications, grants, etc.)
126
+ * @returns This builder for chaining
127
+ * @throws {ValidationError} If entity type is invalid
128
+ */
129
+ search(entity: EntityType | string): this {
130
+ assertValidEntity(this.schemaStore, entity);
131
+ this.entity = entity;
132
+ return this;
133
+ }
134
+
135
+ /**
136
+ * Sets the search index for full-text search.
137
+ * Requires search terms via {@link for} — building without search terms will throw.
138
+ * @param index - The search index to use
139
+ * @returns This builder for chaining
140
+ * @throws {ValidationError} If index is invalid
141
+ * @throws {ValidationError} If used without search terms at build time
142
+ */
143
+ in(index: SearchIndex | string): this {
144
+ assertValidSearchIndex(this.schemaStore, this.entity, index);
145
+ this.searchIndex = index;
146
+ return this;
147
+ }
148
+
149
+ /**
150
+ * Sets the search terms for full-text search.
151
+ *
152
+ * **Note:** `"*"` is **not** a wildcard in the Dimensions DSL — it is treated as a literal
153
+ * string. To search all records without a text filter, simply omit the `for()` call and
154
+ * use `where()` conditions instead.
155
+ *
156
+ * @param terms - Search terms to look for
157
+ * @returns This builder for chaining
158
+ */
159
+ for(terms: string): this {
160
+ const sanitized = sanitizeInput(terms);
161
+ if (sanitized.length > MAX_SEARCH_TEXT_LENGTH) {
162
+ throw new ValidationError(
163
+ `Search text exceeds maximum length of ${MAX_SEARCH_TEXT_LENGTH} characters`,
164
+ );
165
+ }
166
+ this.searchTerms = sanitized;
167
+ return this;
168
+ }
169
+
170
+ /**
171
+ * Sets the search to find semantically similar documents based on text.
172
+ * Uses the similar_documents() DSL function.
173
+ * @param text - The text (abstract, description) to find similar documents for
174
+ * @returns This builder for chaining
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * const query = new QueryBuilder()
179
+ * .search("publications")
180
+ * .forSimilar("After spinal cord injury, macrophages infiltrate...")
181
+ * .where("year", ">", 2015)
182
+ * .limit(10)
183
+ * .build();
184
+ * // Generates: search publications for similar_documents("...") where year > 2015 ...
185
+ * ```
186
+ */
187
+ forSimilar(text: string): this {
188
+ const sanitized = sanitizeInput(text);
189
+ if (sanitized.length > MAX_SEARCH_TEXT_LENGTH) {
190
+ throw new ValidationError(
191
+ `Search text exceeds maximum length of ${MAX_SEARCH_TEXT_LENGTH} characters`,
192
+ );
193
+ }
194
+ this.similarText = sanitized;
195
+ return this;
196
+ }
197
+
198
+ /**
199
+ * Sets the search to use proximity matching via the complex() DSL function.
200
+ * Finds documents where search terms appear within a maximum distance of each other.
201
+ * @param phrase - The search phrase
202
+ * @param maxDist - Maximum distance between terms (must be >= 1)
203
+ * @returns This builder for chaining
204
+ * @throws {ValidationError} If maxDist is less than 1
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * const query = new QueryBuilder()
209
+ * .search("publications")
210
+ * .forComplex("quantum networking", 3)
211
+ * .build();
212
+ * // Generates: search publications for complex("quantum networking", 3)
213
+ * ```
214
+ */
215
+ forComplex(phrase: string, maxDist: number): this {
216
+ if (maxDist < 1) {
217
+ throw new ValidationError("maxDist must be >= 1");
218
+ }
219
+ const sanitized = sanitizeInput(phrase);
220
+ if (sanitized.length > MAX_SEARCH_TEXT_LENGTH) {
221
+ throw new ValidationError(
222
+ `Search text exceeds maximum length of ${MAX_SEARCH_TEXT_LENGTH} characters`,
223
+ );
224
+ }
225
+ this.complexPhrase = sanitized;
226
+ this.complexMaxDist = maxDist;
227
+ return this;
228
+ }
229
+
230
+ /**
231
+ * Sets the search to use minimum term matching via the min_should_match() DSL function.
232
+ * Finds documents matching at least `min` of the terms in the phrase.
233
+ * @param phrase - The search phrase
234
+ * @param min - Minimum number of terms that must match (must be >= 1)
235
+ * @returns This builder for chaining
236
+ * @throws {ValidationError} If min is less than 1
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * const query = new QueryBuilder()
241
+ * .search("publications")
242
+ * .forMinShouldMatch("quantum OR optical networking", 2)
243
+ * .build();
244
+ * // Generates: search publications for min_should_match("quantum OR optical networking", 2)
245
+ * ```
246
+ */
247
+ forMinShouldMatch(phrase: string, min: number): this {
248
+ if (min < 1) {
249
+ throw new ValidationError("min must be >= 1");
250
+ }
251
+ const sanitized = sanitizeInput(phrase);
252
+ if (sanitized.length > MAX_SEARCH_TEXT_LENGTH) {
253
+ throw new ValidationError(
254
+ `Search text exceeds maximum length of ${MAX_SEARCH_TEXT_LENGTH} characters`,
255
+ );
256
+ }
257
+ this.minShouldMatchPhrase = sanitized;
258
+ this.minShouldMatchMin = min;
259
+ return this;
260
+ }
261
+
262
+ /**
263
+ * Adds a where clause condition.
264
+ * @param field - Field name to filter on
265
+ * @param operator - Comparison operator
266
+ * @param value - Value to compare against
267
+ * @returns This builder for chaining
268
+ * @throws {ValidationError} If operator is invalid
269
+ */
270
+ where(field: string, operator: WhereOperator, value: string | number | boolean): this {
271
+ validateFieldName(field);
272
+ if (!VALID_OPERATORS.includes(operator)) {
273
+ throw new ValidationError(`Invalid operator: ${operator}`);
274
+ }
275
+ const formattedValue = typeof value === "string" ? `"${escapeDslString(value)}"` : value;
276
+ this.addConditionNode(`${field} ${operator} ${formattedValue}`);
277
+ return this;
278
+ }
279
+
280
+ /**
281
+ * Adds a condition checking if a field is empty.
282
+ * @param field - Field name to check
283
+ * @returns This builder for chaining
284
+ */
285
+ whereEmpty(field: string): this {
286
+ validateFieldName(field);
287
+ this.addConditionNode(`${field} is empty`);
288
+ return this;
289
+ }
290
+
291
+ /**
292
+ * Adds a condition checking if a field is not empty.
293
+ * @param field - Field name to check
294
+ * @returns This builder for chaining
295
+ */
296
+ whereNotEmpty(field: string): this {
297
+ validateFieldName(field);
298
+ this.addConditionNode(`${field} is not empty`);
299
+ return this;
300
+ }
301
+
302
+ /**
303
+ * Adds a list filter condition (field in ["a", "b", "c"]).
304
+ * @param field - Field name to filter on
305
+ * @param values - Array of values to match
306
+ * @returns This builder for chaining
307
+ * @throws {ValidationError} If values array is empty
308
+ * @throws {ValidationError} If values array exceeds 400 items (DSL limit)
309
+ */
310
+ whereIn(field: string, values: readonly (string | number)[]): this {
311
+ validateFieldName(field);
312
+ if (values.length === 0) {
313
+ throw new ValidationError("List filter must have at least one value");
314
+ }
315
+ if (values.length > 400) {
316
+ throw new ValidationError("List filter cannot exceed 400 items");
317
+ }
318
+ const formattedValues = values
319
+ .map((v) => (typeof v === "string" ? `"${escapeDslString(v)}"` : v))
320
+ .join(", ");
321
+ this.addConditionNode(`${field} in [${formattedValues}]`);
322
+ return this;
323
+ }
324
+
325
+ /**
326
+ * Adds a range filter condition (field in [start:end]).
327
+ * @param field - Field name to filter on
328
+ * @param start - Start of range (inclusive)
329
+ * @param end - End of range (inclusive)
330
+ * @returns This builder for chaining
331
+ * @throws {ValidationError} If numeric start is greater than numeric end
332
+ */
333
+ whereRange(field: string, start: string | number, end: string | number): this {
334
+ validateFieldName(field);
335
+ if (typeof start === "number" && typeof end === "number" && start > end) {
336
+ throw new ValidationError("Range start must not be greater than end");
337
+ }
338
+ const formattedStart = typeof start === "string" ? `"${escapeDslString(start)}"` : start;
339
+ const formattedEnd = typeof end === "string" ? `"${escapeDslString(end)}"` : end;
340
+ this.addConditionNode(`${field} in [${formattedStart}:${formattedEnd}]`);
341
+ return this;
342
+ }
343
+
344
+ /**
345
+ * Sets the next connector to OR.
346
+ * @returns This builder for chaining
347
+ */
348
+ or(): this {
349
+ this.pendingConnector = "or";
350
+ return this;
351
+ }
352
+
353
+ /**
354
+ * Sets the next connector to NOT.
355
+ * @returns This builder for chaining
356
+ */
357
+ not(): this {
358
+ this.pendingConnector = "not";
359
+ return this;
360
+ }
361
+
362
+ /**
363
+ * Sets the next connector to AND explicitly.
364
+ * Note: AND is the default connector between conditions, but this method
365
+ * provides API symmetry with or() and not().
366
+ * @returns This builder for chaining
367
+ */
368
+ and(): this {
369
+ this.pendingConnector = "and";
370
+ return this;
371
+ }
372
+
373
+ /**
374
+ * Opens a parenthesized group.
375
+ * @returns This builder for chaining
376
+ */
377
+ openGroup(): this {
378
+ // Add pending connector if exists
379
+ if (this.pendingConnector) {
380
+ this.expressionNodes.push({
381
+ type: "connector",
382
+ connector: this.pendingConnector,
383
+ });
384
+ this.pendingConnector = null;
385
+ } else if (this.expressionNodes.length > 0) {
386
+ const lastNode = this.expressionNodes[this.expressionNodes.length - 1];
387
+ if (lastNode?.type === "condition" || lastNode?.type === "group_end") {
388
+ this.expressionNodes.push({ type: "connector", connector: "and" });
389
+ }
390
+ }
391
+ this.expressionNodes.push({ type: "group_start" });
392
+ this.groupDepth++;
393
+ return this;
394
+ }
395
+
396
+ /**
397
+ * Closes a parenthesized group.
398
+ * @returns This builder for chaining
399
+ * @throws {ValidationError} If no group is open
400
+ */
401
+ closeGroup(): this {
402
+ if (this.groupDepth === 0) {
403
+ throw new ValidationError("No group to close");
404
+ }
405
+ this.expressionNodes.push({ type: "group_end" });
406
+ this.groupDepth--;
407
+ return this;
408
+ }
409
+
410
+ /**
411
+ * Adds a count filter condition (count(field) op value).
412
+ * @param field - Multi-value field name to count
413
+ * @param operator - Comparison operator
414
+ * @param value - Count to compare against
415
+ * @returns This builder for chaining
416
+ */
417
+ whereCount(field: string, operator: "=" | "!=" | ">" | "<" | ">=" | "<=", value: number): this {
418
+ validateFieldName(field);
419
+ this.addConditionNode(`count(${field}) ${operator} ${value}`);
420
+ return this;
421
+ }
422
+
423
+ /**
424
+ * Sets the fields to return.
425
+ * @param fieldList - Array of field names to return
426
+ * @returns This builder for chaining
427
+ */
428
+ fields(fieldList: string[]): this {
429
+ for (const field of fieldList) {
430
+ validateFieldName(field);
431
+ }
432
+ this.returnFields = [...fieldList];
433
+ return this;
434
+ }
435
+
436
+ /**
437
+ * Sets the fields to return including unnest operations.
438
+ * Unnest flattens nested arrays into separate rows (Cartesian product).
439
+ * DSL: return publications[id+title+unnest(researchers)+unnest(category_for)]
440
+ *
441
+ * @param fieldList - Array of regular field names to return
442
+ * @param unnestFieldList - Array of field names to unnest
443
+ * @returns This builder for chaining
444
+ * @throws {ValidationError} If any unnest field name is empty or whitespace-only
445
+ *
446
+ * @example
447
+ * ```typescript
448
+ * const query = new QueryBuilder()
449
+ * .search("publications")
450
+ * .for("machine learning")
451
+ * .fieldsWithUnnest(["id", "title"], ["researchers", "category_for"])
452
+ * .build();
453
+ * // Returns flattened rows - one row per researcher × category combination
454
+ * ```
455
+ */
456
+ fieldsWithUnnest(fieldList: readonly string[], unnestFieldList: readonly string[]): this {
457
+ for (const field of fieldList) {
458
+ validateFieldName(field);
459
+ }
460
+ for (const field of unnestFieldList) {
461
+ validateFieldName(field);
462
+ }
463
+ this.returnFields = [...fieldList];
464
+ this.unnestFields = [...unnestFieldList];
465
+ return this;
466
+ }
467
+
468
+ /**
469
+ * Adds an unnest field to the return clause.
470
+ * Can be chained with fields() to add unnest operations.
471
+ * DSL: return publications[id+title+unnest(researchers)]
472
+ *
473
+ * @param field - Field name to unnest
474
+ * @returns This builder for chaining
475
+ * @throws {ValidationError} If field name is empty or whitespace-only
476
+ *
477
+ * @example
478
+ * ```typescript
479
+ * const query = new QueryBuilder()
480
+ * .search("publications")
481
+ * .for("test")
482
+ * .fields(["id", "title"])
483
+ * .addUnnest("researchers")
484
+ * .addUnnest("category_for")
485
+ * .build();
486
+ * ```
487
+ */
488
+ addUnnest(field: string): this {
489
+ validateFieldName(field);
490
+ this.unnestFields.push(field);
491
+ return this;
492
+ }
493
+
494
+ /**
495
+ * Sets the sort field and order.
496
+ * @param field - Field name to sort by
497
+ * @param order - Sort direction (asc or desc)
498
+ * @returns This builder for chaining
499
+ */
500
+ sort(field: string, order: SortOrder = "asc"): this {
501
+ validateFieldName(field);
502
+ this.sortField = field;
503
+ this.sortOrder = order;
504
+ return this;
505
+ }
506
+
507
+ /**
508
+ * Sets the number of results to skip (for pagination).
509
+ * @param count - Number of results to skip
510
+ * @returns This builder for chaining
511
+ * @throws {ValidationError} If count is negative
512
+ */
513
+ skip(count: number): this {
514
+ if (count < 0) {
515
+ throw new ValidationError("Skip must be non-negative");
516
+ }
517
+ this.skipValue = count;
518
+ return this;
519
+ }
520
+
521
+ /**
522
+ * Sets the maximum number of results to return.
523
+ * @param count - Maximum number of results
524
+ * @returns This builder for chaining
525
+ * @throws {ValidationError} If count is negative
526
+ */
527
+ limit(count: number): this {
528
+ if (count < 0) {
529
+ throw new ValidationError("Limit must be non-negative");
530
+ }
531
+ this.limitValue = count;
532
+ return this;
533
+ }
534
+
535
+ /**
536
+ * Adds a facet return clause.
537
+ * DSL: return <field> [limit <n>]
538
+ * @param field - Facet field name
539
+ * @param options - Optional limit for facet results
540
+ * @returns This builder for chaining
541
+ * @throws {ValidationError} If field is empty or whitespace-only
542
+ * @throws {ValidationError} If limit is negative
543
+ */
544
+ returnFacet(field: string, options?: { limit?: number }): this {
545
+ validateFieldName(field);
546
+ if (options?.limit !== undefined && options.limit < 0) {
547
+ throw new ValidationError("Facet limit must be non-negative", {
548
+ limit: options.limit,
549
+ });
550
+ }
551
+ const clause: FacetReturnClause = {
552
+ type: "facet",
553
+ field,
554
+ limit: options?.limit,
555
+ };
556
+ this.facetClauses.push(clause);
557
+ return this;
558
+ }
559
+
560
+ /**
561
+ * Adds an aggregated facet return clause.
562
+ * DSL: return <field> aggregate <indicators> [sort by <indicator> <order>] [limit <n>]
563
+ * @param field - Facet field name
564
+ * @param indicators - Array of indicator names to aggregate
565
+ * @param options - Optional sort and limit options
566
+ * @returns This builder for chaining
567
+ * @throws {ValidationError} If field is empty or whitespace-only
568
+ * @throws {ValidationError} If indicators array is empty
569
+ * @throws {ValidationError} If any indicator is empty or whitespace-only
570
+ * @throws {ValidationError} If limit is negative
571
+ * @throws {ValidationError} If sortBy is not one of indicators or "count"
572
+ */
573
+ returnAggregate(
574
+ field: string,
575
+ indicators: readonly string[],
576
+ options?: {
577
+ sortBy?: string;
578
+ sortOrder?: SortOrder;
579
+ limit?: number;
580
+ },
581
+ ): this {
582
+ validateFieldName(field);
583
+ if (indicators.length === 0) {
584
+ throw new ValidationError("Aggregate must have at least one indicator", {
585
+ field,
586
+ });
587
+ }
588
+ for (const indicator of indicators) {
589
+ if (!indicator || indicator.trim().length === 0) {
590
+ throw new ValidationError("Indicator names must be non-empty strings", {
591
+ field,
592
+ indicator,
593
+ });
594
+ }
595
+ }
596
+ if (options?.limit !== undefined && options.limit < 0) {
597
+ throw new ValidationError("Facet limit must be non-negative", {
598
+ field,
599
+ limit: options.limit,
600
+ });
601
+ }
602
+ if (options?.sortBy) {
603
+ const validSortTargets = [...indicators, "count"];
604
+ if (!validSortTargets.includes(options.sortBy)) {
605
+ throw new ValidationError(
606
+ `sortBy must be one of [${validSortTargets.join(", ")}], got "${options.sortBy}"`,
607
+ { field, sortBy: options.sortBy, validSortTargets },
608
+ );
609
+ }
610
+ }
611
+ const clause: AggregatedFacetReturnClause = {
612
+ type: "aggregated_facet",
613
+ field,
614
+ indicators,
615
+ sortBy: options?.sortBy,
616
+ sortOrder: options?.sortOrder ?? "desc",
617
+ limit: options?.limit,
618
+ };
619
+ this.facetClauses.push(clause);
620
+ return this;
621
+ }
622
+
623
+ /**
624
+ * Adds a citations_per_year time-series return clause.
625
+ * Returns citation counts per year for the specified range.
626
+ * DSL: return citations_per_year(2010, 2023)
627
+ *
628
+ * @param startYear - Start year for the time series (inclusive)
629
+ * @param endYear - End year for the time series (inclusive)
630
+ * @returns This builder for chaining
631
+ * @throws {ValidationError} If startYear is greater than endYear
632
+ *
633
+ * @example
634
+ * ```typescript
635
+ * const query = new QueryBuilder()
636
+ * .search("publications")
637
+ * .for("machine learning")
638
+ * .returnCitationsPerYear(2010, 2023)
639
+ * .build();
640
+ * // "search publications for ... return citations_per_year(2010, 2023)"
641
+ * ```
642
+ */
643
+ returnCitationsPerYear(startYear: number, endYear: number): this {
644
+ if (startYear > endYear) {
645
+ throw new ValidationError("startYear must not be greater than endYear", {
646
+ startYear,
647
+ endYear,
648
+ });
649
+ }
650
+ this.timeSeriesClauses.push({
651
+ type: "time_series",
652
+ function: "citations_per_year",
653
+ startYear,
654
+ endYear,
655
+ });
656
+ return this;
657
+ }
658
+
659
+ /**
660
+ * Adds a funding_per_year time-series return clause.
661
+ * Returns funding amounts per year for the specified range.
662
+ * DSL: return funding_per_year(2015, 2023, "USD")
663
+ *
664
+ * @param startYear - Start year for the time series (inclusive)
665
+ * @param endYear - End year for the time series (inclusive)
666
+ * @param currency - Currency for the funding amounts (default: "USD")
667
+ * @returns This builder for chaining
668
+ * @throws {ValidationError} If startYear is greater than endYear
669
+ *
670
+ * @example
671
+ * ```typescript
672
+ * const query = new QueryBuilder()
673
+ * .search("grants")
674
+ * .for("cancer research")
675
+ * .returnFundingPerYear(2015, 2023, "EUR")
676
+ * .build();
677
+ * // "search grants for ... return funding_per_year(2015, 2023, "EUR")"
678
+ * ```
679
+ */
680
+ returnFundingPerYear(startYear: number, endYear: number, currency: Currency = "USD"): this {
681
+ if (startYear > endYear) {
682
+ throw new ValidationError("startYear must not be greater than endYear", {
683
+ startYear,
684
+ endYear,
685
+ });
686
+ }
687
+ this.timeSeriesClauses.push({
688
+ type: "time_series",
689
+ function: "funding_per_year",
690
+ startYear,
691
+ endYear,
692
+ currency,
693
+ });
694
+ return this;
695
+ }
696
+
697
+ /**
698
+ * Adds a grouped entity return clause.
699
+ * DSL: return in "docs" publications[id + title]
700
+ *
701
+ * @param groupName - Name for the result group
702
+ * @param options - Entity return options (fields, limit, skip, sort)
703
+ * @returns This builder for chaining
704
+ * @throws {ValidationError} If groupName is empty or whitespace-only
705
+ *
706
+ * @example
707
+ * ```typescript
708
+ * const query = new QueryBuilder()
709
+ * .search("publications")
710
+ * .for("test")
711
+ * .returnGrouped("docs", { fields: ["id", "title"], limit: 10 })
712
+ * .build();
713
+ * // "... return in "docs" publications[id+title] limit 10"
714
+ * ```
715
+ */
716
+ returnGrouped(
717
+ groupName: string,
718
+ options?: {
719
+ fields?: readonly string[];
720
+ limit?: number;
721
+ skip?: number;
722
+ sortField?: string;
723
+ sortOrder?: SortOrder;
724
+ },
725
+ ): this {
726
+ if (!groupName || groupName.trim().length === 0) {
727
+ throw new ValidationError("Group name must be non-empty", { groupName });
728
+ }
729
+ if (options?.fields) {
730
+ for (const field of options.fields) {
731
+ validateFieldName(field);
732
+ }
733
+ }
734
+ if (options?.sortField) {
735
+ validateFieldName(options.sortField);
736
+ }
737
+ this.groupedClauses.push({
738
+ type: "grouped",
739
+ groupName,
740
+ entityOrFacet: {
741
+ type: "entity",
742
+ fields: options?.fields,
743
+ limit: options?.limit,
744
+ skip: options?.skip,
745
+ sortField: options?.sortField,
746
+ sortOrder: options?.sortOrder,
747
+ },
748
+ });
749
+ return this;
750
+ }
751
+
752
+ /**
753
+ * Adds a grouped facet return clause (simple facet).
754
+ * DSL: return in "facets" year limit 10
755
+ *
756
+ * @param groupName - Name for the result group
757
+ * @param field - Facet field name
758
+ * @param options - Optional limit for facet results
759
+ * @returns This builder for chaining
760
+ * @throws {ValidationError} If groupName or field is empty
761
+ *
762
+ * @example
763
+ * ```typescript
764
+ * const query = new QueryBuilder()
765
+ * .search("publications")
766
+ * .for("test")
767
+ * .returnGroupedFacet("years", "year", { limit: 20 })
768
+ * .build();
769
+ * // "... return in "years" year limit 20"
770
+ * ```
771
+ */
772
+ returnGroupedFacet(groupName: string, field: string, options?: { limit?: number }): this {
773
+ if (!groupName || groupName.trim().length === 0) {
774
+ throw new ValidationError("Group name must be non-empty", { groupName });
775
+ }
776
+ validateFieldName(field);
777
+ this.groupedClauses.push({
778
+ type: "grouped",
779
+ groupName,
780
+ entityOrFacet: {
781
+ type: "facet",
782
+ field,
783
+ limit: options?.limit,
784
+ },
785
+ });
786
+ return this;
787
+ }
788
+
789
+ /**
790
+ * Adds a grouped aggregated facet return clause.
791
+ * DSL: return in "metrics" funders aggregate rcr_avg, funding_usd sort by rcr_avg desc
792
+ *
793
+ * @param groupName - Name for the result group
794
+ * @param field - Facet field name
795
+ * @param indicators - Array of indicator names to aggregate
796
+ * @param options - Optional sort and limit options
797
+ * @returns This builder for chaining
798
+ * @throws {ValidationError} If groupName, field, or indicators are invalid
799
+ *
800
+ * @example
801
+ * ```typescript
802
+ * const query = new QueryBuilder()
803
+ * .search("publications")
804
+ * .for("test")
805
+ * .returnGroupedAggregate("metrics", "funders", ["rcr_avg"], { sortBy: "rcr_avg" })
806
+ * .build();
807
+ * // "... return in "metrics" funders aggregate rcr_avg sort by rcr_avg desc"
808
+ * ```
809
+ */
810
+ returnGroupedAggregate(
811
+ groupName: string,
812
+ field: string,
813
+ indicators: readonly string[],
814
+ options?: {
815
+ sortBy?: string;
816
+ sortOrder?: SortOrder;
817
+ limit?: number;
818
+ },
819
+ ): this {
820
+ if (!groupName || groupName.trim().length === 0) {
821
+ throw new ValidationError("Group name must be non-empty", { groupName });
822
+ }
823
+ validateFieldName(field);
824
+ if (indicators.length === 0) {
825
+ throw new ValidationError("Aggregate must have at least one indicator", {
826
+ field,
827
+ });
828
+ }
829
+ this.groupedClauses.push({
830
+ type: "grouped",
831
+ groupName,
832
+ entityOrFacet: {
833
+ type: "aggregated_facet",
834
+ field,
835
+ indicators,
836
+ sortBy: options?.sortBy,
837
+ sortOrder: options?.sortOrder ?? "desc",
838
+ limit: options?.limit,
839
+ },
840
+ });
841
+ return this;
842
+ }
843
+
844
+ /**
845
+ * Builds the DSL query string.
846
+ * @returns The constructed DSL query
847
+ * @throws {ValidationError} If entity type is not set
848
+ */
849
+ build(): string {
850
+ if (!this.entity) {
851
+ throw new ValidationError("Entity type must be specified");
852
+ }
853
+ const parts: string[] = [];
854
+ const searchTerms = this.searchTerms;
855
+
856
+ // search <entity> [in <index>] for "<terms>" OR function expression search
857
+ if (this.similarText) {
858
+ const escapedText = escapeDslString(this.similarText);
859
+ parts.push(`search ${this.entity} for similar_documents("${escapedText}")`);
860
+ } else if (this.complexPhrase !== null) {
861
+ const escapedPhrase = escapeDslString(this.complexPhrase);
862
+ const indexPart =
863
+ this.searchIndex && this.searchIndex !== "full_data" ? ` in ${this.searchIndex}` : "";
864
+ parts.push(
865
+ `search ${this.entity}${indexPart} for complex("${escapedPhrase}", ${this.complexMaxDist})`,
866
+ );
867
+ } else if (this.minShouldMatchPhrase !== null) {
868
+ const escapedPhrase = escapeDslString(this.minShouldMatchPhrase);
869
+ const indexPart =
870
+ this.searchIndex && this.searchIndex !== "full_data" ? ` in ${this.searchIndex}` : "";
871
+ parts.push(
872
+ `search ${this.entity}${indexPart} for min_should_match("${escapedPhrase}", ${this.minShouldMatchMin})`,
873
+ );
874
+ } else if (searchTerms && this.searchIndex && this.searchIndex !== "full_data") {
875
+ parts.push(
876
+ `search ${this.entity} in ${this.searchIndex} for "${escapeDslString(searchTerms)}"`,
877
+ );
878
+ } else if (searchTerms) {
879
+ parts.push(`search ${this.entity} for "${escapeDslString(searchTerms)}"`);
880
+ } else {
881
+ if (this.searchIndex && this.searchIndex !== "full_data") {
882
+ throw new ValidationError("Search index requires search terms: call for() before in()");
883
+ }
884
+ parts.push(`search ${this.entity}`);
885
+ }
886
+
887
+ // Check for unbalanced parentheses
888
+ if (this.groupDepth !== 0) {
889
+ throw new ValidationError("Unbalanced parentheses: unclosed group");
890
+ }
891
+
892
+ // where <conditions> - build from expression tree
893
+ if (this.expressionNodes.length > 0) {
894
+ let whereStr = "";
895
+ for (let i = 0; i < this.expressionNodes.length; i++) {
896
+ const node = this.expressionNodes[i];
897
+ const prevNode = i > 0 ? this.expressionNodes[i - 1] : null;
898
+
899
+ if (this.shouldAddSpaceBefore(node, prevNode)) {
900
+ whereStr += " ";
901
+ }
902
+
903
+ switch (node.type) {
904
+ case "condition":
905
+ whereStr += node.condition;
906
+ break;
907
+ case "connector":
908
+ whereStr += node.connector;
909
+ break;
910
+ case "group_start":
911
+ whereStr += "(";
912
+ break;
913
+ case "group_end":
914
+ whereStr += ")";
915
+ break;
916
+ }
917
+ }
918
+ parts.push(`where ${whereStr}`);
919
+ }
920
+
921
+ // return <entity>[<fields>] or return <entity>
922
+ // Note: return clause is required before limit/skip/sort modifiers
923
+ // Note: fields must be separated with '+' not ','
924
+ // Note: unnest() wraps fields that should be flattened
925
+ const hasFields = this.returnFields.length > 0 || this.unnestFields.length > 0;
926
+ const hasOtherReturnClauses =
927
+ this.facetClauses.length > 0 ||
928
+ this.timeSeriesClauses.length > 0 ||
929
+ this.groupedClauses.length > 0;
930
+
931
+ // Validate limit(0) usage - the Dimensions API rejects "return <entity> limit 0"
932
+ if (this.limitValue === 0) {
933
+ if (!hasOtherReturnClauses) {
934
+ throw new ValidationError(
935
+ "limit(0) requires at least one facet, time-series, or grouped return clause. " +
936
+ "The Dimensions API does not support 'return <entity> limit 0'. " +
937
+ "Use returnFacet(), returnCitationsPerYear(), or similar to get facet-only results.",
938
+ { limit: 0 },
939
+ );
940
+ }
941
+ if (hasFields) {
942
+ throw new ValidationError(
943
+ "limit(0) cannot be used with fields(). " +
944
+ "The Dimensions API does not support 'return <entity>[fields] limit 0'. " +
945
+ "Either remove fields() to get facet-only results, or use limit > 0.",
946
+ { limit: 0, fields: this.returnFields },
947
+ );
948
+ }
949
+ }
950
+
951
+ // When limit=0 with facets/time-series/grouped returns and no fields,
952
+ // skip the entire entity return section (return clause + modifiers).
953
+ // User intent: "I don't want entity results, only facet results."
954
+ const skipEntityReturn = this.limitValue === 0 && hasOtherReturnClauses && !hasFields;
955
+
956
+ if (hasFields) {
957
+ const allFieldParts: string[] = [];
958
+ // Add regular fields
959
+ for (const field of this.returnFields) {
960
+ allFieldParts.push(field);
961
+ }
962
+ // Add unnested fields
963
+ for (const field of this.unnestFields) {
964
+ allFieldParts.push(`unnest(${field})`);
965
+ }
966
+ parts.push(`return ${this.entity}[${allFieldParts.join("+")}]`);
967
+ } else if (
968
+ !skipEntityReturn &&
969
+ (this.sortField !== null || this.skipValue !== null || this.limitValue !== null)
970
+ ) {
971
+ // API requires return clause when using modifiers
972
+ parts.push(`return ${this.entity}`);
973
+ }
974
+
975
+ // sort by <field> <order>
976
+ // Skip when suppressing entity return (sort only applies to entity results)
977
+ if (this.sortField && !skipEntityReturn) {
978
+ parts.push(`sort by ${this.sortField} ${this.sortOrder}`);
979
+ }
980
+
981
+ // limit <n> (must come before skip)
982
+ // Skip when suppressing entity return
983
+ if (this.limitValue !== null && !skipEntityReturn) {
984
+ parts.push(`limit ${this.limitValue}`);
985
+ }
986
+
987
+ // skip <n> (must come after limit)
988
+ // Skip when suppressing entity return
989
+ if (this.skipValue !== null && !skipEntityReturn) {
990
+ parts.push(`skip ${this.skipValue}`);
991
+ }
992
+
993
+ // Facet return clauses
994
+ for (const clause of this.facetClauses) {
995
+ let facetDsl = `return ${clause.field}`;
996
+
997
+ if (clause.type === "aggregated_facet" && clause.indicators.length > 0) {
998
+ facetDsl += ` aggregate ${clause.indicators.join(", ")}`;
999
+ if (clause.sortBy) {
1000
+ facetDsl += ` sort by ${clause.sortBy} ${clause.sortOrder}`;
1001
+ }
1002
+ }
1003
+
1004
+ if (clause.limit !== undefined) {
1005
+ facetDsl += ` limit ${clause.limit}`;
1006
+ }
1007
+
1008
+ parts.push(facetDsl);
1009
+ }
1010
+
1011
+ // Time-series return clauses
1012
+ for (const clause of this.timeSeriesClauses) {
1013
+ if (clause.function === "citations_per_year") {
1014
+ parts.push(`return citations_per_year(${clause.startYear}, ${clause.endYear})`);
1015
+ } else if (clause.function === "funding_per_year") {
1016
+ parts.push(
1017
+ `return funding_per_year(${clause.startYear}, ${clause.endYear}, "${clause.currency}")`,
1018
+ );
1019
+ }
1020
+ }
1021
+
1022
+ // Grouped return clauses
1023
+ for (const clause of this.groupedClauses) {
1024
+ const inner = clause.entityOrFacet;
1025
+ let groupedDsl = `return in "${escapeDslString(clause.groupName)}"`;
1026
+
1027
+ if (inner.type === "entity") {
1028
+ if (inner.fields && inner.fields.length > 0) {
1029
+ groupedDsl += ` ${this.entity}[${inner.fields.join("+")}]`;
1030
+ } else {
1031
+ groupedDsl += ` ${this.entity}`;
1032
+ }
1033
+ if (inner.sortField) {
1034
+ groupedDsl += ` sort by ${inner.sortField} ${inner.sortOrder ?? "asc"}`;
1035
+ }
1036
+ if (inner.limit !== undefined) {
1037
+ groupedDsl += ` limit ${inner.limit}`;
1038
+ }
1039
+ if (inner.skip !== undefined) {
1040
+ groupedDsl += ` skip ${inner.skip}`;
1041
+ }
1042
+ } else if (inner.type === "facet") {
1043
+ groupedDsl += ` ${inner.field}`;
1044
+ if (inner.limit !== undefined) {
1045
+ groupedDsl += ` limit ${inner.limit}`;
1046
+ }
1047
+ } else if (inner.type === "aggregated_facet") {
1048
+ groupedDsl += ` ${inner.field} aggregate ${inner.indicators.join(", ")}`;
1049
+ if (inner.sortBy) {
1050
+ groupedDsl += ` sort by ${inner.sortBy} ${inner.sortOrder ?? "desc"}`;
1051
+ }
1052
+ if (inner.limit !== undefined) {
1053
+ groupedDsl += ` limit ${inner.limit}`;
1054
+ }
1055
+ }
1056
+
1057
+ parts.push(groupedDsl);
1058
+ }
1059
+
1060
+ return parts.join(" ");
1061
+ }
1062
+
1063
+ /**
1064
+ * Creates a deep copy of this builder.
1065
+ * Useful when branching queries (e.g. adding different facets from a shared base).
1066
+ * @returns A new QueryBuilder with the same state
1067
+ */
1068
+ clone(): QueryBuilder {
1069
+ const copy = new QueryBuilder(this.schemaStore);
1070
+ copy.entity = this.entity;
1071
+ copy.searchIndex = this.searchIndex;
1072
+ copy.searchTerms = this.searchTerms;
1073
+ copy.similarText = this.similarText;
1074
+ copy.complexPhrase = this.complexPhrase;
1075
+ copy.complexMaxDist = this.complexMaxDist;
1076
+ copy.minShouldMatchPhrase = this.minShouldMatchPhrase;
1077
+ copy.minShouldMatchMin = this.minShouldMatchMin;
1078
+ copy.expressionNodes = [...this.expressionNodes];
1079
+ copy.groupDepth = this.groupDepth;
1080
+ copy.pendingConnector = this.pendingConnector;
1081
+ copy.returnFields = [...this.returnFields];
1082
+ copy.unnestFields = [...this.unnestFields];
1083
+ copy.sortField = this.sortField;
1084
+ copy.sortOrder = this.sortOrder;
1085
+ copy.skipValue = this.skipValue;
1086
+ copy.limitValue = this.limitValue;
1087
+ copy.facetClauses = [...this.facetClauses];
1088
+ copy.timeSeriesClauses = [...this.timeSeriesClauses];
1089
+ copy.groupedClauses = [...this.groupedClauses];
1090
+ return copy;
1091
+ }
1092
+
1093
+ /**
1094
+ * Resets the builder to its initial state.
1095
+ * @returns This builder for chaining
1096
+ */
1097
+ reset(): this {
1098
+ this.entity = null;
1099
+ this.searchIndex = null;
1100
+ this.searchTerms = null;
1101
+ this.similarText = null;
1102
+ this.complexPhrase = null;
1103
+ this.complexMaxDist = null;
1104
+ this.minShouldMatchPhrase = null;
1105
+ this.minShouldMatchMin = null;
1106
+ this.expressionNodes = [];
1107
+ this.groupDepth = 0;
1108
+ this.pendingConnector = null;
1109
+ this.returnFields = [];
1110
+ this.unnestFields = [];
1111
+ this.sortField = null;
1112
+ this.sortOrder = "asc";
1113
+ this.skipValue = null;
1114
+ this.limitValue = null;
1115
+ this.facetClauses = [];
1116
+ this.timeSeriesClauses = [];
1117
+ this.groupedClauses = [];
1118
+ return this;
1119
+ }
1120
+ }