@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,865 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import type { QueryExecutor } from "../../src/client/index.js";
3
+ import { FluentQueryBuilder } from "../../src/dsl/fluent-query-builder.js";
4
+ import type { Grant, Patent, Publication, Researcher } from "../../src/dsl/types/entities.js";
5
+
6
+ describe("FluentQueryBuilder", () => {
7
+ let mockExecutor: QueryExecutor;
8
+ let mockRawQuery: ReturnType<typeof vi.fn>;
9
+
10
+ beforeEach(() => {
11
+ mockRawQuery = vi.fn();
12
+ mockExecutor = {
13
+ rawQuery: mockRawQuery,
14
+ };
15
+ });
16
+
17
+ describe("publications", () => {
18
+ let builder: FluentQueryBuilder<Publication, "publications">;
19
+
20
+ beforeEach(() => {
21
+ builder = new FluentQueryBuilder<Publication, "publications">(mockExecutor, "publications");
22
+ });
23
+
24
+ it("builds and executes a simple search query", async () => {
25
+ mockRawQuery.mockResolvedValue({
26
+ publications: [{ id: "pub1", title: "Test Publication" }],
27
+ _stats: { total_count: 1 },
28
+ });
29
+
30
+ const result = await builder.for("machine learning").execute();
31
+
32
+ expect(mockRawQuery).toHaveBeenCalledWith('search publications for "machine learning"');
33
+ expect(result.data).toEqual([{ id: "pub1", title: "Test Publication" }]);
34
+ expect(result.totalCount).toBe(1);
35
+ });
36
+
37
+ it("builds query with where clause", async () => {
38
+ mockRawQuery.mockResolvedValue({
39
+ publications: [],
40
+ _stats: { total_count: 0 },
41
+ });
42
+
43
+ await builder.for("AI").where("year", ">=", 2020).where("type", "=", "article").execute();
44
+
45
+ expect(mockRawQuery).toHaveBeenCalledWith(
46
+ 'search publications for "AI" where year >= 2020 and type = "article"',
47
+ );
48
+ });
49
+
50
+ it("builds query with fields", async () => {
51
+ mockRawQuery.mockResolvedValue({
52
+ publications: [],
53
+ _stats: { total_count: 0 },
54
+ });
55
+
56
+ await builder.for("test").fields(["id", "title", "doi"]).execute();
57
+
58
+ expect(mockRawQuery).toHaveBeenCalledWith(
59
+ 'search publications for "test" return publications[id+title+doi]',
60
+ );
61
+ });
62
+
63
+ it("builds query with sort, limit, and skip", async () => {
64
+ mockRawQuery.mockResolvedValue({
65
+ publications: [],
66
+ _stats: { total_count: 0 },
67
+ });
68
+
69
+ await builder.for("test").sort("times_cited", "desc").limit(50).skip(100).execute();
70
+
71
+ expect(mockRawQuery).toHaveBeenCalledWith(
72
+ 'search publications for "test" return publications sort by times_cited desc limit 50 skip 100',
73
+ );
74
+ });
75
+
76
+ it("builds query with search index", async () => {
77
+ mockRawQuery.mockResolvedValue({
78
+ publications: [],
79
+ _stats: { total_count: 0 },
80
+ });
81
+
82
+ await builder.in("title_abstract_only").for("CRISPR").execute();
83
+
84
+ expect(mockRawQuery).toHaveBeenCalledWith(
85
+ 'search publications in title_abstract_only for "CRISPR"',
86
+ );
87
+ });
88
+
89
+ it("builds query with boolean logic", async () => {
90
+ mockRawQuery.mockResolvedValue({
91
+ publications: [],
92
+ _stats: { total_count: 0 },
93
+ });
94
+
95
+ await builder
96
+ .for("research")
97
+ .where("type", "=", "article")
98
+ .or()
99
+ .where("type", "=", "review")
100
+ .execute();
101
+
102
+ expect(mockRawQuery).toHaveBeenCalledWith(
103
+ 'search publications for "research" where type = "article" or type = "review"',
104
+ );
105
+ });
106
+
107
+ it("builds query with explicit and() connector", async () => {
108
+ mockRawQuery.mockResolvedValue({
109
+ publications: [],
110
+ _stats: { total_count: 0 },
111
+ });
112
+
113
+ await builder
114
+ .for("test")
115
+ .where("year", "=", 2020)
116
+ .and()
117
+ .where("type", "=", "article")
118
+ .execute();
119
+
120
+ expect(mockRawQuery).toHaveBeenCalledWith(
121
+ 'search publications for "test" where year = 2020 and type = "article"',
122
+ );
123
+ });
124
+
125
+ it("builds query with not() connector", async () => {
126
+ mockRawQuery.mockResolvedValue({
127
+ publications: [],
128
+ _stats: { total_count: 0 },
129
+ });
130
+
131
+ await builder
132
+ .for("test")
133
+ .where("type", "=", "article")
134
+ .not()
135
+ .where("year", "<", 2000)
136
+ .execute();
137
+
138
+ expect(mockRawQuery).toHaveBeenCalledWith(
139
+ 'search publications for "test" where type = "article" not year < 2000',
140
+ );
141
+ });
142
+
143
+ it("builds query with parenthesized expressions", async () => {
144
+ mockRawQuery.mockResolvedValue({
145
+ publications: [],
146
+ _stats: { total_count: 0 },
147
+ });
148
+
149
+ await builder
150
+ .for("AI")
151
+ .openGroup()
152
+ .where("type", "=", "article")
153
+ .or()
154
+ .where("type", "=", "review")
155
+ .closeGroup()
156
+ .where("year", ">=", 2020)
157
+ .execute();
158
+
159
+ expect(mockRawQuery).toHaveBeenCalledWith(
160
+ 'search publications for "AI" where (type = "article" or type = "review") and year >= 2020',
161
+ );
162
+ });
163
+
164
+ it("builds query with whereIn", async () => {
165
+ mockRawQuery.mockResolvedValue({
166
+ publications: [],
167
+ _stats: { total_count: 0 },
168
+ });
169
+
170
+ await builder.for("test").whereIn("type", ["article", "review"]).execute();
171
+
172
+ expect(mockRawQuery).toHaveBeenCalledWith(
173
+ 'search publications for "test" where type in ["article", "review"]',
174
+ );
175
+ });
176
+
177
+ it("builds query with whereRange", async () => {
178
+ mockRawQuery.mockResolvedValue({
179
+ publications: [],
180
+ _stats: { total_count: 0 },
181
+ });
182
+
183
+ await builder.for("test").whereRange("year", 2018, 2023).execute();
184
+
185
+ expect(mockRawQuery).toHaveBeenCalledWith(
186
+ 'search publications for "test" where year in [2018:2023]',
187
+ );
188
+ });
189
+
190
+ it("builds query with whereEmpty and whereNotEmpty", async () => {
191
+ mockRawQuery.mockResolvedValue({
192
+ publications: [],
193
+ _stats: { total_count: 0 },
194
+ });
195
+
196
+ await builder.for("test").whereNotEmpty("doi").whereEmpty("pmid").execute();
197
+
198
+ expect(mockRawQuery).toHaveBeenCalledWith(
199
+ 'search publications for "test" where doi is not empty and pmid is empty',
200
+ );
201
+ });
202
+
203
+ it("builds query with forSimilar for similar-document lookup", async () => {
204
+ mockRawQuery.mockResolvedValue({
205
+ publications: [{ id: "pub1", title: "Similar Publication" }],
206
+ _stats: { total_count: 5 },
207
+ });
208
+
209
+ const result = await builder
210
+ .forSimilar("Machine learning for drug discovery and precision medicine")
211
+ .where("year", ">", 2015)
212
+ .limit(10)
213
+ .execute();
214
+
215
+ expect(mockRawQuery).toHaveBeenCalledWith(
216
+ 'search publications for similar_documents("Machine learning for drug discovery and precision medicine") where year > 2015 return publications limit 10',
217
+ );
218
+ expect(result.data).toEqual([{ id: "pub1", title: "Similar Publication" }]);
219
+ expect(result.totalCount).toBe(5);
220
+ });
221
+
222
+ it("builds query with forSimilar escaping quotes and newlines", async () => {
223
+ mockRawQuery.mockResolvedValue({
224
+ publications: [],
225
+ _stats: { total_count: 0 },
226
+ });
227
+
228
+ await builder.forSimilar('Text with "quotes"\nand newlines').execute();
229
+
230
+ expect(mockRawQuery).toHaveBeenCalledWith(
231
+ 'search publications for similar_documents("Text with \\"quotes\\"\\nand newlines")',
232
+ );
233
+ });
234
+
235
+ it("builds query with whereCount", async () => {
236
+ mockRawQuery.mockResolvedValue({
237
+ publications: [],
238
+ _stats: { total_count: 0 },
239
+ });
240
+
241
+ await builder.for("collaboration").whereCount("researchers", ">", 5).execute();
242
+
243
+ expect(mockRawQuery).toHaveBeenCalledWith(
244
+ 'search publications for "collaboration" where count(researchers) > 5',
245
+ );
246
+ });
247
+
248
+ it("returns stats from response", async () => {
249
+ mockRawQuery.mockResolvedValue({
250
+ publications: [{ id: "pub1" }],
251
+ _stats: { total_count: 1000 },
252
+ });
253
+
254
+ const result = await builder.for("test").limit(10).execute();
255
+
256
+ expect(result.totalCount).toBe(1000);
257
+ expect(result.data.length).toBe(1);
258
+ });
259
+
260
+ it("handles missing stats gracefully", async () => {
261
+ mockRawQuery.mockResolvedValue({
262
+ publications: [{ id: "pub1" }],
263
+ });
264
+
265
+ const result = await builder.for("test").execute();
266
+
267
+ expect(result.totalCount).toBe(1);
268
+ expect(result.data.length).toBe(1);
269
+ });
270
+
271
+ it("executes successfully without for()", async () => {
272
+ mockRawQuery.mockResolvedValue({
273
+ publications: [{ id: "pub1" }],
274
+ _stats: { total_count: 100 },
275
+ });
276
+
277
+ const result = await builder.execute();
278
+
279
+ expect(mockRawQuery).toHaveBeenCalledWith("search publications");
280
+ expect(result.totalCount).toBe(100);
281
+ expect(result.data.length).toBe(1);
282
+ });
283
+
284
+ it("propagates errors from rawQuery", async () => {
285
+ mockRawQuery.mockRejectedValue(new Error("Network error"));
286
+
287
+ await expect(builder.for("test").execute()).rejects.toThrow("Network error");
288
+ });
289
+
290
+ it("throws on unbalanced parentheses", async () => {
291
+ await expect(
292
+ builder.for("test").openGroup().where("year", "=", 2020).execute(),
293
+ ).rejects.toThrow("Unbalanced parentheses");
294
+ });
295
+ });
296
+
297
+ describe("grants", () => {
298
+ let builder: FluentQueryBuilder<Grant, "grants">;
299
+
300
+ beforeEach(() => {
301
+ builder = new FluentQueryBuilder<Grant, "grants">(mockExecutor, "grants");
302
+ });
303
+
304
+ it("builds and executes a grants query", async () => {
305
+ mockRawQuery.mockResolvedValue({
306
+ grants: [{ id: "grant1", title: "Test Grant" }],
307
+ _stats: { total_count: 1 },
308
+ });
309
+
310
+ const result = await builder
311
+ .for("cancer research")
312
+ .where("funding_usd", ">", 1000000)
313
+ .execute();
314
+
315
+ expect(mockRawQuery).toHaveBeenCalledWith(
316
+ 'search grants for "cancer research" where funding_usd > 1000000',
317
+ );
318
+ expect(result.data).toEqual([{ id: "grant1", title: "Test Grant" }]);
319
+ });
320
+ });
321
+
322
+ describe("researchers", () => {
323
+ let builder: FluentQueryBuilder<Researcher, "researchers">;
324
+
325
+ beforeEach(() => {
326
+ builder = new FluentQueryBuilder<Researcher, "researchers">(mockExecutor, "researchers");
327
+ });
328
+
329
+ it("builds and executes a researchers query", async () => {
330
+ mockRawQuery.mockResolvedValue({
331
+ researchers: [{ id: "res1", first_name: "John", last_name: "Smith" }],
332
+ _stats: { total_count: 1 },
333
+ });
334
+
335
+ const result = await builder.for("smith").execute();
336
+
337
+ expect(mockRawQuery).toHaveBeenCalledWith('search researchers for "smith"');
338
+ expect(result.data).toEqual([{ id: "res1", first_name: "John", last_name: "Smith" }]);
339
+ });
340
+ });
341
+
342
+ describe("patents", () => {
343
+ let builder: FluentQueryBuilder<Patent, "patents">;
344
+
345
+ beforeEach(() => {
346
+ builder = new FluentQueryBuilder<Patent, "patents">(mockExecutor, "patents");
347
+ });
348
+
349
+ it("builds and executes a patents query", async () => {
350
+ mockRawQuery.mockResolvedValue({
351
+ patents: [{ id: "pat1", title: "Test Patent" }],
352
+ _stats: { total_count: 1 },
353
+ });
354
+
355
+ const result = await builder.for("battery technology").where("year", ">=", 2020).execute();
356
+
357
+ expect(mockRawQuery).toHaveBeenCalledWith(
358
+ 'search patents for "battery technology" where year >= 2020',
359
+ );
360
+ expect(result.data).toEqual([{ id: "pat1", title: "Test Patent" }]);
361
+ });
362
+ });
363
+
364
+ describe("getDsl", () => {
365
+ it("returns the DSL query without executing", () => {
366
+ const builder = new FluentQueryBuilder<Publication, "publications">(
367
+ mockExecutor,
368
+ "publications",
369
+ );
370
+
371
+ const dsl = builder.for("machine learning").where("year", ">=", 2020).getDsl();
372
+
373
+ expect(dsl).toBe('search publications for "machine learning" where year >= 2020');
374
+ expect(mockRawQuery).not.toHaveBeenCalled();
375
+ });
376
+ });
377
+
378
+ describe("complex queries", () => {
379
+ it("builds a fully-featured query", async () => {
380
+ const builder = new FluentQueryBuilder<Publication, "publications">(
381
+ mockExecutor,
382
+ "publications",
383
+ );
384
+ mockRawQuery.mockResolvedValue({
385
+ publications: [],
386
+ _stats: { total_count: 0 },
387
+ });
388
+
389
+ await builder
390
+ .in("title_abstract_only")
391
+ .for("deep learning")
392
+ .openGroup()
393
+ .where("type", "=", "article")
394
+ .or()
395
+ .where("type", "=", "review")
396
+ .closeGroup()
397
+ .where("year", ">=", 2020)
398
+ .whereNotEmpty("doi")
399
+ .whereCount("researchers", "<=", 10)
400
+ .fields(["id", "title", "doi", "year", "authors"])
401
+ .sort("times_cited", "desc")
402
+ .limit(100)
403
+ .skip(0)
404
+ .execute();
405
+
406
+ expect(mockRawQuery).toHaveBeenCalledWith(
407
+ 'search publications in title_abstract_only for "deep learning" ' +
408
+ 'where (type = "article" or type = "review") and year >= 2020 and doi is not empty and count(researchers) <= 10 ' +
409
+ "return publications[id+title+doi+year+authors] sort by times_cited desc limit 100 skip 0",
410
+ );
411
+ });
412
+ });
413
+
414
+ describe("facet methods", () => {
415
+ let builder: FluentQueryBuilder<Publication, "publications">;
416
+
417
+ beforeEach(() => {
418
+ builder = new FluentQueryBuilder<Publication, "publications">(mockExecutor, "publications");
419
+ });
420
+
421
+ it("builds query with simple facet", async () => {
422
+ mockRawQuery.mockResolvedValue({
423
+ publications: [],
424
+ year: [
425
+ { id: 2023, count: 100 },
426
+ { id: 2022, count: 80 },
427
+ ],
428
+ _stats: { total_count: 0 },
429
+ });
430
+
431
+ const result = await builder.for("CRISPR").withFacet("year").executeWithFacets();
432
+
433
+ expect(mockRawQuery).toHaveBeenCalledWith('search publications for "CRISPR" return year');
434
+ expect(result.facets.year).toBeDefined();
435
+ expect(result.facets.year.buckets).toHaveLength(2);
436
+ expect(result.facets.year.buckets[0].id).toBe(2023);
437
+ expect(result.facets.year.buckets[0].count).toBe(100);
438
+ });
439
+
440
+ it("builds query with facet limit", async () => {
441
+ mockRawQuery.mockResolvedValue({
442
+ funders: [{ id: "f1", name: "NIH", count: 50 }],
443
+ _stats: { total_count: 0 },
444
+ });
445
+
446
+ await builder.for("CRISPR").withFacet("funders", { limit: 10 }).executeWithFacets();
447
+
448
+ expect(mockRawQuery).toHaveBeenCalledWith(
449
+ 'search publications for "CRISPR" return funders limit 10',
450
+ );
451
+ });
452
+
453
+ it("builds query with aggregated facet", async () => {
454
+ mockRawQuery.mockResolvedValue({
455
+ funders: [{ id: "f1", name: "NIH", count: 50, rcr_avg: 2.5 }],
456
+ _stats: { total_count: 0 },
457
+ });
458
+
459
+ const result = await builder
460
+ .for("CRISPR")
461
+ .withAggregate("funders", ["rcr_avg"])
462
+ .executeWithFacets();
463
+
464
+ expect(mockRawQuery).toHaveBeenCalledWith(
465
+ 'search publications for "CRISPR" return funders aggregate rcr_avg',
466
+ );
467
+ expect(result.facets.funders.buckets[0]).toHaveProperty("rcr_avg", 2.5);
468
+ });
469
+
470
+ it("builds query with aggregated facet sort and limit", async () => {
471
+ mockRawQuery.mockResolvedValue({
472
+ funders: [],
473
+ _stats: { total_count: 0 },
474
+ });
475
+
476
+ await builder
477
+ .for("CRISPR")
478
+ .withAggregate("funders", ["rcr_avg", "altmetric_median"], {
479
+ sortBy: "rcr_avg",
480
+ sortOrder: "desc",
481
+ limit: 10,
482
+ })
483
+ .executeWithFacets();
484
+
485
+ expect(mockRawQuery).toHaveBeenCalledWith(
486
+ 'search publications for "CRISPR" return funders aggregate rcr_avg, altmetric_median sort by rcr_avg desc limit 10',
487
+ );
488
+ });
489
+
490
+ it("builds query with multiple facets", async () => {
491
+ mockRawQuery.mockResolvedValue({
492
+ year: [{ id: 2023, count: 100 }],
493
+ funders: [{ id: "f1", name: "NIH", count: 50 }],
494
+ _stats: { total_count: 0 },
495
+ });
496
+
497
+ const result = await builder
498
+ .for("CRISPR")
499
+ .withFacet("year")
500
+ .withFacet("funders", { limit: 10 })
501
+ .executeWithFacets();
502
+
503
+ expect(mockRawQuery).toHaveBeenCalledWith(
504
+ 'search publications for "CRISPR" return year return funders limit 10',
505
+ );
506
+ expect(result.facets.year).toBeDefined();
507
+ expect(result.facets.funders).toBeDefined();
508
+ });
509
+
510
+ it("combines entity return with facets", async () => {
511
+ mockRawQuery.mockResolvedValue({
512
+ publications: [{ id: "pub1", title: "Test" }],
513
+ year: [{ id: 2023, count: 100 }],
514
+ _stats: { total_count: 500 },
515
+ });
516
+
517
+ const result = await builder
518
+ .for("CRISPR")
519
+ .fields(["id", "title"])
520
+ .limit(100)
521
+ .withFacet("year")
522
+ .executeWithFacets();
523
+
524
+ expect(mockRawQuery).toHaveBeenCalledWith(
525
+ 'search publications for "CRISPR" return publications[id+title] limit 100 return year',
526
+ );
527
+ expect(result.entities).toBeDefined();
528
+ expect(result.entities?.data).toHaveLength(1);
529
+ expect(result.entities?.totalCount).toBe(500);
530
+ expect(result.facets.year).toBeDefined();
531
+ });
532
+
533
+ it("returns raw response in _raw field", async () => {
534
+ const rawResponse = {
535
+ publications: [{ id: "pub1" }],
536
+ year: [{ id: 2023, count: 100 }],
537
+ _stats: { total_count: 1 },
538
+ };
539
+ mockRawQuery.mockResolvedValue(rawResponse);
540
+
541
+ const result = await builder.for("CRISPR").withFacet("year").executeWithFacets();
542
+
543
+ expect(result._raw).toEqual(rawResponse);
544
+ });
545
+
546
+ it("handles missing entity data", async () => {
547
+ mockRawQuery.mockResolvedValue({
548
+ year: [{ id: 2023, count: 100 }],
549
+ _stats: { total_count: 0 },
550
+ });
551
+
552
+ const result = await builder.for("CRISPR").withFacet("year").executeWithFacets();
553
+
554
+ expect(result.entities).toBeUndefined();
555
+ expect(result.facets.year).toBeDefined();
556
+ });
557
+
558
+ it("getDsl returns query with facets without executing", () => {
559
+ const dsl = builder
560
+ .for("CRISPR")
561
+ .withFacet("year")
562
+ .withAggregate("funders", ["rcr_avg"], { limit: 10 })
563
+ .getDsl();
564
+
565
+ expect(dsl).toBe(
566
+ 'search publications for "CRISPR" return year return funders aggregate rcr_avg limit 10',
567
+ );
568
+ expect(mockRawQuery).not.toHaveBeenCalled();
569
+ });
570
+ });
571
+
572
+ describe("grant facets", () => {
573
+ let builder: FluentQueryBuilder<Grant, "grants">;
574
+
575
+ beforeEach(() => {
576
+ builder = new FluentQueryBuilder<Grant, "grants">(mockExecutor, "grants");
577
+ });
578
+
579
+ it("builds query with funding aggregation", async () => {
580
+ mockRawQuery.mockResolvedValue({
581
+ funders: [{ id: "f1", name: "NIH", count: 50, funding: 1000000 }],
582
+ _stats: { total_count: 0 },
583
+ });
584
+
585
+ await builder
586
+ .for("cancer")
587
+ .withAggregate("funders", ["funding"], {
588
+ sortBy: "funding",
589
+ sortOrder: "desc",
590
+ limit: 10,
591
+ })
592
+ .executeWithFacets();
593
+
594
+ expect(mockRawQuery).toHaveBeenCalledWith(
595
+ 'search grants for "cancer" return funders aggregate funding sort by funding desc limit 10',
596
+ );
597
+ });
598
+
599
+ it("builds query with start_year facet", async () => {
600
+ mockRawQuery.mockResolvedValue({
601
+ start_year: [{ id: 2023, count: 100 }],
602
+ _stats: { total_count: 0 },
603
+ });
604
+
605
+ await builder.for("cancer").withFacet("start_year").executeWithFacets();
606
+
607
+ expect(mockRawQuery).toHaveBeenCalledWith('search grants for "cancer" return start_year');
608
+ });
609
+ });
610
+
611
+ describe("time-series return clauses", () => {
612
+ let builder: FluentQueryBuilder<Publication, "publications">;
613
+
614
+ beforeEach(() => {
615
+ builder = new FluentQueryBuilder<Publication, "publications">(mockExecutor, "publications");
616
+ });
617
+
618
+ it("builds query with returnCitationsPerYear", async () => {
619
+ mockRawQuery.mockResolvedValue({
620
+ publications: [],
621
+ _stats: { total_count: 0 },
622
+ citations_per_year: [
623
+ { year: 2020, count: 10 },
624
+ { year: 2021, count: 15 },
625
+ ],
626
+ });
627
+
628
+ await builder.for("CRISPR").returnCitationsPerYear(2010, 2023).execute();
629
+
630
+ const actualCall = mockRawQuery.mock.calls[0][0] as string;
631
+ // Check contains the time-series function syntax
632
+ expect(actualCall).toContain('search publications for "CRISPR"');
633
+ expect(actualCall).toContain("citations_per_year(2010, 2023)");
634
+ });
635
+
636
+ it("builds query with returnFundingPerYear with default currency", async () => {
637
+ const grantBuilder = new FluentQueryBuilder<Grant, "grants">(mockExecutor, "grants");
638
+ mockRawQuery.mockResolvedValue({
639
+ grants: [],
640
+ _stats: { total_count: 0 },
641
+ });
642
+
643
+ await grantBuilder.for("cancer").returnFundingPerYear(2015, 2023).execute();
644
+
645
+ const actualCall = mockRawQuery.mock.calls[0][0] as string;
646
+ expect(actualCall).toContain('search grants for "cancer"');
647
+ expect(actualCall).toContain('funding_per_year(2015, 2023, "USD")');
648
+ });
649
+
650
+ it("builds query with returnFundingPerYear with EUR currency", async () => {
651
+ const grantBuilder = new FluentQueryBuilder<Grant, "grants">(mockExecutor, "grants");
652
+ mockRawQuery.mockResolvedValue({
653
+ grants: [],
654
+ _stats: { total_count: 0 },
655
+ });
656
+
657
+ await grantBuilder.for("cancer").returnFundingPerYear(2015, 2023, "EUR").execute();
658
+
659
+ const actualCall = mockRawQuery.mock.calls[0][0] as string;
660
+ expect(actualCall).toContain('search grants for "cancer"');
661
+ expect(actualCall).toContain('funding_per_year(2015, 2023, "EUR")');
662
+ });
663
+
664
+ it("builds query with returnFundingPerYear with GBP currency", async () => {
665
+ const grantBuilder = new FluentQueryBuilder<Grant, "grants">(mockExecutor, "grants");
666
+ mockRawQuery.mockResolvedValue({
667
+ grants: [],
668
+ _stats: { total_count: 0 },
669
+ });
670
+
671
+ await grantBuilder.for("cancer").returnFundingPerYear(2015, 2023, "GBP").execute();
672
+
673
+ const actualCall = mockRawQuery.mock.calls[0][0] as string;
674
+ expect(actualCall).toContain('search grants for "cancer"');
675
+ expect(actualCall).toContain('funding_per_year(2015, 2023, "GBP")');
676
+ });
677
+ });
678
+
679
+ describe("unnest operations", () => {
680
+ let builder: FluentQueryBuilder<Publication, "publications">;
681
+
682
+ beforeEach(() => {
683
+ builder = new FluentQueryBuilder<Publication, "publications">(mockExecutor, "publications");
684
+ });
685
+
686
+ it("builds query with fieldsWithUnnest", async () => {
687
+ mockRawQuery.mockResolvedValue({
688
+ publications: [],
689
+ _stats: { total_count: 0 },
690
+ });
691
+
692
+ await builder
693
+ .for("test")
694
+ .fieldsWithUnnest(["id", "title"], ["researchers", "category_for"])
695
+ .execute();
696
+
697
+ expect(mockRawQuery).toHaveBeenCalledWith(
698
+ 'search publications for "test" return publications[id+title+unnest(researchers)+unnest(category_for)]',
699
+ );
700
+ });
701
+
702
+ it("builds query with chained unnest method", async () => {
703
+ mockRawQuery.mockResolvedValue({
704
+ publications: [],
705
+ _stats: { total_count: 0 },
706
+ });
707
+
708
+ await builder
709
+ .for("test")
710
+ .fields(["id", "title"])
711
+ .unnest("researchers")
712
+ .unnest("category_for")
713
+ .execute();
714
+
715
+ expect(mockRawQuery).toHaveBeenCalledWith(
716
+ 'search publications for "test" return publications[id+title+unnest(researchers)+unnest(category_for)]',
717
+ );
718
+ });
719
+
720
+ it("builds query with single unnest field", async () => {
721
+ mockRawQuery.mockResolvedValue({
722
+ publications: [],
723
+ _stats: { total_count: 0 },
724
+ });
725
+
726
+ await builder.for("collaboration").fieldsWithUnnest(["id"], ["researchers"]).execute();
727
+
728
+ expect(mockRawQuery).toHaveBeenCalledWith(
729
+ 'search publications for "collaboration" return publications[id+unnest(researchers)]',
730
+ );
731
+ });
732
+ });
733
+
734
+ describe("builder branching isolation", () => {
735
+ it("branching withFacet from same base does not corrupt either query", () => {
736
+ const base = new FluentQueryBuilder<Publication, "publications">(
737
+ mockExecutor,
738
+ "publications",
739
+ );
740
+ base.for("test");
741
+
742
+ const a = base.withFacet("year");
743
+ const b = base.withFacet("funders");
744
+
745
+ expect(a.getDsl()).toBe('search publications for "test" return year');
746
+ expect(b.getDsl()).toBe('search publications for "test" return funders');
747
+ // base should not have any facets
748
+ expect(base.getDsl()).toBe('search publications for "test"');
749
+ });
750
+
751
+ it("branching withAggregate from same base does not corrupt either query", () => {
752
+ const base = new FluentQueryBuilder<Publication, "publications">(
753
+ mockExecutor,
754
+ "publications",
755
+ );
756
+ base.for("test");
757
+
758
+ const a = base.withAggregate("year", ["count"]);
759
+ const b = base.withAggregate("funders", ["rcr_avg"]);
760
+
761
+ expect(a.getDsl()).toBe('search publications for "test" return year aggregate count');
762
+ expect(b.getDsl()).toBe('search publications for "test" return funders aggregate rcr_avg');
763
+ });
764
+
765
+ it("chaining withFacet on FluentQueryBuilderWithFacets does not mutate parent", () => {
766
+ const base = new FluentQueryBuilder<Publication, "publications">(
767
+ mockExecutor,
768
+ "publications",
769
+ );
770
+ base.for("test");
771
+
772
+ const withYear = base.withFacet("year");
773
+ const withYearAndFunders = withYear.withFacet("funders");
774
+
775
+ expect(withYear.getDsl()).toBe('search publications for "test" return year');
776
+ expect(withYearAndFunders.getDsl()).toBe(
777
+ 'search publications for "test" return year return funders',
778
+ );
779
+ });
780
+ });
781
+
782
+ describe("sort, skip, limit chainability", () => {
783
+ let builder: FluentQueryBuilder<Publication, "publications">;
784
+
785
+ beforeEach(() => {
786
+ builder = new FluentQueryBuilder<Publication, "publications">(mockExecutor, "publications");
787
+ });
788
+
789
+ it("chains sort correctly", async () => {
790
+ mockRawQuery.mockResolvedValue({
791
+ publications: [],
792
+ _stats: { total_count: 0 },
793
+ });
794
+
795
+ await builder.for("test").sort("year", "asc").execute();
796
+
797
+ expect(mockRawQuery).toHaveBeenCalledWith(
798
+ 'search publications for "test" return publications sort by year asc',
799
+ );
800
+ });
801
+
802
+ it("chains skip correctly", async () => {
803
+ mockRawQuery.mockResolvedValue({
804
+ publications: [],
805
+ _stats: { total_count: 0 },
806
+ });
807
+
808
+ await builder.for("test").skip(50).execute();
809
+
810
+ expect(mockRawQuery).toHaveBeenCalledWith(
811
+ 'search publications for "test" return publications skip 50',
812
+ );
813
+ });
814
+
815
+ it("chains limit correctly", async () => {
816
+ mockRawQuery.mockResolvedValue({
817
+ publications: [],
818
+ _stats: { total_count: 0 },
819
+ });
820
+
821
+ await builder.for("test").limit(100).execute();
822
+
823
+ expect(mockRawQuery).toHaveBeenCalledWith(
824
+ 'search publications for "test" return publications limit 100',
825
+ );
826
+ });
827
+
828
+ it("builds query with forComplex for proximity search", async () => {
829
+ mockRawQuery.mockResolvedValue({
830
+ publications: [{ id: "pub1", title: "Quantum Networking" }],
831
+ _stats: { total_count: 3 },
832
+ });
833
+
834
+ const result = await builder
835
+ .forComplex("quantum networking", 3)
836
+ .where("year", ">", 2020)
837
+ .limit(10)
838
+ .execute();
839
+
840
+ expect(mockRawQuery).toHaveBeenCalledWith(
841
+ 'search publications for complex("quantum networking", 3) where year > 2020 return publications limit 10',
842
+ );
843
+ expect(result.data).toEqual([{ id: "pub1", title: "Quantum Networking" }]);
844
+ expect(result.totalCount).toBe(3);
845
+ });
846
+
847
+ it("builds query with forMinShouldMatch", async () => {
848
+ mockRawQuery.mockResolvedValue({
849
+ publications: [{ id: "pub1", title: "Optical Networks" }],
850
+ _stats: { total_count: 7 },
851
+ });
852
+
853
+ const result = await builder
854
+ .forMinShouldMatch("quantum OR optical networking", 2)
855
+ .limit(5)
856
+ .execute();
857
+
858
+ expect(mockRawQuery).toHaveBeenCalledWith(
859
+ 'search publications for min_should_match("quantum OR optical networking", 2) return publications limit 5',
860
+ );
861
+ expect(result.data).toEqual([{ id: "pub1", title: "Optical Networks" }]);
862
+ expect(result.totalCount).toBe(7);
863
+ });
864
+ });
865
+ });