@ncukondo/search-hub 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/dist/_virtual/___vite-browser-external.js +7 -0
  4. package/dist/_virtual/___vite-browser-external.js.map +1 -0
  5. package/dist/_virtual/__vite-browser-external.js +5 -0
  6. package/dist/_virtual/__vite-browser-external.js.map +1 -0
  7. package/dist/_virtual/_commonjsHelpers.js +28 -0
  8. package/dist/_virtual/_commonjsHelpers.js.map +1 -0
  9. package/dist/_virtual/cli-progress.js +6 -0
  10. package/dist/_virtual/cli-progress.js.map +1 -0
  11. package/dist/_virtual/index.js +5 -0
  12. package/dist/_virtual/index.js.map +1 -0
  13. package/dist/_virtual/index2.js +5 -0
  14. package/dist/_virtual/index2.js.map +1 -0
  15. package/dist/cli/commands/config.d.ts +37 -0
  16. package/dist/cli/commands/config.d.ts.map +1 -0
  17. package/dist/cli/commands/config.js +117 -0
  18. package/dist/cli/commands/config.js.map +1 -0
  19. package/dist/cli/commands/export.d.ts +26 -0
  20. package/dist/cli/commands/export.d.ts.map +1 -0
  21. package/dist/cli/commands/export.js +86 -0
  22. package/dist/cli/commands/export.js.map +1 -0
  23. package/dist/cli/commands/init.d.ts +45 -0
  24. package/dist/cli/commands/init.d.ts.map +1 -0
  25. package/dist/cli/commands/init.js +134 -0
  26. package/dist/cli/commands/init.js.map +1 -0
  27. package/dist/cli/commands/query/translate.d.ts +32 -0
  28. package/dist/cli/commands/query/translate.d.ts.map +1 -0
  29. package/dist/cli/commands/query/translate.js +76 -0
  30. package/dist/cli/commands/query/translate.js.map +1 -0
  31. package/dist/cli/commands/query/validate.d.ts +25 -0
  32. package/dist/cli/commands/query/validate.d.ts.map +1 -0
  33. package/dist/cli/commands/query/validate.js +68 -0
  34. package/dist/cli/commands/query/validate.js.map +1 -0
  35. package/dist/cli/commands/register.d.ts +39 -0
  36. package/dist/cli/commands/register.d.ts.map +1 -0
  37. package/dist/cli/commands/register.js +78 -0
  38. package/dist/cli/commands/register.js.map +1 -0
  39. package/dist/cli/commands/resume-executor.d.ts +19 -0
  40. package/dist/cli/commands/resume-executor.d.ts.map +1 -0
  41. package/dist/cli/commands/resume-executor.js +170 -0
  42. package/dist/cli/commands/resume-executor.js.map +1 -0
  43. package/dist/cli/commands/resume.d.ts +26 -0
  44. package/dist/cli/commands/resume.d.ts.map +1 -0
  45. package/dist/cli/commands/resume.js +51 -0
  46. package/dist/cli/commands/resume.js.map +1 -0
  47. package/dist/cli/commands/search-executor.d.ts +26 -0
  48. package/dist/cli/commands/search-executor.d.ts.map +1 -0
  49. package/dist/cli/commands/search-executor.js +315 -0
  50. package/dist/cli/commands/search-executor.js.map +1 -0
  51. package/dist/cli/commands/search.d.ts +30 -0
  52. package/dist/cli/commands/search.d.ts.map +1 -0
  53. package/dist/cli/commands/search.js +67 -0
  54. package/dist/cli/commands/search.js.map +1 -0
  55. package/dist/cli/commands/status.d.ts +41 -0
  56. package/dist/cli/commands/status.d.ts.map +1 -0
  57. package/dist/cli/commands/status.js +123 -0
  58. package/dist/cli/commands/status.js.map +1 -0
  59. package/dist/cli/e2e-helpers.d.ts +165 -0
  60. package/dist/cli/e2e-helpers.d.ts.map +1 -0
  61. package/dist/cli/exit-codes.d.ts +25 -0
  62. package/dist/cli/exit-codes.d.ts.map +1 -0
  63. package/dist/cli/exit-codes.js +18 -0
  64. package/dist/cli/exit-codes.js.map +1 -0
  65. package/dist/cli/index.d.ts +25 -0
  66. package/dist/cli/index.d.ts.map +1 -0
  67. package/dist/cli/index.js +638 -0
  68. package/dist/cli/index.js.map +1 -0
  69. package/dist/cli/utils/progress.d.ts +24 -0
  70. package/dist/cli/utils/progress.d.ts.map +1 -0
  71. package/dist/cli/utils/progress.js +128 -0
  72. package/dist/cli/utils/progress.js.map +1 -0
  73. package/dist/cli/utils/sessions-dir.d.ts +14 -0
  74. package/dist/cli/utils/sessions-dir.d.ts.map +1 -0
  75. package/dist/cli/utils/sessions-dir.js +21 -0
  76. package/dist/cli/utils/sessions-dir.js.map +1 -0
  77. package/dist/cli/utils/validation.d.ts +18 -0
  78. package/dist/cli/utils/validation.d.ts.map +1 -0
  79. package/dist/cli/utils/validation.js +24 -0
  80. package/dist/cli/utils/validation.js.map +1 -0
  81. package/dist/config/defaults.d.ts +12 -0
  82. package/dist/config/defaults.d.ts.map +1 -0
  83. package/dist/config/defaults.js +10 -0
  84. package/dist/config/defaults.js.map +1 -0
  85. package/dist/config/env.d.ts +12 -0
  86. package/dist/config/env.d.ts.map +1 -0
  87. package/dist/config/env.js +36 -0
  88. package/dist/config/env.js.map +1 -0
  89. package/dist/config/index.d.ts +9 -0
  90. package/dist/config/index.d.ts.map +1 -0
  91. package/dist/config/loader.d.ts +49 -0
  92. package/dist/config/loader.d.ts.map +1 -0
  93. package/dist/config/loader.js +72 -0
  94. package/dist/config/loader.js.map +1 -0
  95. package/dist/config/paths.d.ts +23 -0
  96. package/dist/config/paths.d.ts.map +1 -0
  97. package/dist/config/paths.js +22 -0
  98. package/dist/config/paths.js.map +1 -0
  99. package/dist/config/schema.d.ts +109 -0
  100. package/dist/config/schema.d.ts.map +1 -0
  101. package/dist/config/schema.js +74 -0
  102. package/dist/config/schema.js.map +1 -0
  103. package/dist/index.d.ts +11 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +54 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/integration/ref-cli.d.ts +41 -0
  108. package/dist/integration/ref-cli.d.ts.map +1 -0
  109. package/dist/integration/ref-cli.js +132 -0
  110. package/dist/integration/ref-cli.js.map +1 -0
  111. package/dist/integration/register.d.ts +27 -0
  112. package/dist/integration/register.d.ts.map +1 -0
  113. package/dist/integration/register.js +108 -0
  114. package/dist/integration/register.js.map +1 -0
  115. package/dist/integration/types.d.ts +61 -0
  116. package/dist/integration/types.d.ts.map +1 -0
  117. package/dist/integration/types.js +61 -0
  118. package/dist/integration/types.js.map +1 -0
  119. package/dist/node_modules/cli-progress/cli-progress.js +37 -0
  120. package/dist/node_modules/cli-progress/cli-progress.js.map +1 -0
  121. package/dist/node_modules/cli-progress/lib/eta.js +52 -0
  122. package/dist/node_modules/cli-progress/lib/eta.js.map +1 -0
  123. package/dist/node_modules/cli-progress/lib/format-bar.js +16 -0
  124. package/dist/node_modules/cli-progress/lib/format-bar.js.map +1 -0
  125. package/dist/node_modules/cli-progress/lib/format-time.js +32 -0
  126. package/dist/node_modules/cli-progress/lib/format-time.js.map +1 -0
  127. package/dist/node_modules/cli-progress/lib/format-value.js +25 -0
  128. package/dist/node_modules/cli-progress/lib/format-value.js.map +1 -0
  129. package/dist/node_modules/cli-progress/lib/formatter.js +57 -0
  130. package/dist/node_modules/cli-progress/lib/formatter.js.map +1 -0
  131. package/dist/node_modules/cli-progress/lib/generic-bar.js +137 -0
  132. package/dist/node_modules/cli-progress/lib/generic-bar.js.map +1 -0
  133. package/dist/node_modules/cli-progress/lib/multi-bar.js +147 -0
  134. package/dist/node_modules/cli-progress/lib/multi-bar.js.map +1 -0
  135. package/dist/node_modules/cli-progress/lib/options.js +59 -0
  136. package/dist/node_modules/cli-progress/lib/options.js.map +1 -0
  137. package/dist/node_modules/cli-progress/lib/single-bar.js +95 -0
  138. package/dist/node_modules/cli-progress/lib/single-bar.js.map +1 -0
  139. package/dist/node_modules/cli-progress/lib/terminal.js +125 -0
  140. package/dist/node_modules/cli-progress/lib/terminal.js.map +1 -0
  141. package/dist/node_modules/cli-progress/node_modules/ansi-regex/index.js +18 -0
  142. package/dist/node_modules/cli-progress/node_modules/ansi-regex/index.js.map +1 -0
  143. package/dist/node_modules/cli-progress/node_modules/emoji-regex/index.js +14 -0
  144. package/dist/node_modules/cli-progress/node_modules/emoji-regex/index.js.map +1 -0
  145. package/dist/node_modules/cli-progress/node_modules/string-width/index.js +44 -0
  146. package/dist/node_modules/cli-progress/node_modules/string-width/index.js.map +1 -0
  147. package/dist/node_modules/cli-progress/node_modules/strip-ansi/index.js +14 -0
  148. package/dist/node_modules/cli-progress/node_modules/strip-ansi/index.js.map +1 -0
  149. package/dist/node_modules/cli-progress/presets/index.js +25 -0
  150. package/dist/node_modules/cli-progress/presets/index.js.map +1 -0
  151. package/dist/node_modules/cli-progress/presets/legacy.js +16 -0
  152. package/dist/node_modules/cli-progress/presets/legacy.js.map +1 -0
  153. package/dist/node_modules/cli-progress/presets/rect.js +16 -0
  154. package/dist/node_modules/cli-progress/presets/rect.js.map +1 -0
  155. package/dist/node_modules/cli-progress/presets/shades-classic.js +16 -0
  156. package/dist/node_modules/cli-progress/presets/shades-classic.js.map +1 -0
  157. package/dist/node_modules/cli-progress/presets/shades-grey.js +16 -0
  158. package/dist/node_modules/cli-progress/presets/shades-grey.js.map +1 -0
  159. package/dist/node_modules/env-paths/index.js +58 -0
  160. package/dist/node_modules/env-paths/index.js.map +1 -0
  161. package/dist/node_modules/fast-xml-parser/src/ignoreAttributes.js +22 -0
  162. package/dist/node_modules/fast-xml-parser/src/ignoreAttributes.js.map +1 -0
  163. package/dist/node_modules/fast-xml-parser/src/util.js +33 -0
  164. package/dist/node_modules/fast-xml-parser/src/util.js.map +1 -0
  165. package/dist/node_modules/fast-xml-parser/src/validator.js +309 -0
  166. package/dist/node_modules/fast-xml-parser/src/validator.js.map +1 -0
  167. package/dist/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js +265 -0
  168. package/dist/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js.map +1 -0
  169. package/dist/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js +53 -0
  170. package/dist/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js.map +1 -0
  171. package/dist/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js +515 -0
  172. package/dist/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js.map +1 -0
  173. package/dist/node_modules/fast-xml-parser/src/xmlparser/XMLParser.js +68 -0
  174. package/dist/node_modules/fast-xml-parser/src/xmlparser/XMLParser.js.map +1 -0
  175. package/dist/node_modules/fast-xml-parser/src/xmlparser/node2json.js +88 -0
  176. package/dist/node_modules/fast-xml-parser/src/xmlparser/node2json.js.map +1 -0
  177. package/dist/node_modules/fast-xml-parser/src/xmlparser/xmlNode.js +36 -0
  178. package/dist/node_modules/fast-xml-parser/src/xmlparser/xmlNode.js.map +1 -0
  179. package/dist/node_modules/is-fullwidth-code-point/index.js +37 -0
  180. package/dist/node_modules/is-fullwidth-code-point/index.js.map +1 -0
  181. package/dist/node_modules/strnum/strnum.js +100 -0
  182. package/dist/node_modules/strnum/strnum.js.map +1 -0
  183. package/dist/providers/arxiv/client.d.ts +54 -0
  184. package/dist/providers/arxiv/client.d.ts.map +1 -0
  185. package/dist/providers/arxiv/client.js +105 -0
  186. package/dist/providers/arxiv/client.js.map +1 -0
  187. package/dist/providers/arxiv/index.d.ts +14 -0
  188. package/dist/providers/arxiv/index.d.ts.map +1 -0
  189. package/dist/providers/arxiv/parser.d.ts +16 -0
  190. package/dist/providers/arxiv/parser.d.ts.map +1 -0
  191. package/dist/providers/arxiv/parser.js +144 -0
  192. package/dist/providers/arxiv/parser.js.map +1 -0
  193. package/dist/providers/arxiv/provider.d.ts +39 -0
  194. package/dist/providers/arxiv/provider.d.ts.map +1 -0
  195. package/dist/providers/arxiv/provider.js +153 -0
  196. package/dist/providers/arxiv/provider.js.map +1 -0
  197. package/dist/providers/arxiv/translator.d.ts +17 -0
  198. package/dist/providers/arxiv/translator.d.ts.map +1 -0
  199. package/dist/providers/arxiv/translator.js +112 -0
  200. package/dist/providers/arxiv/translator.js.map +1 -0
  201. package/dist/providers/arxiv/types.d.ts +71 -0
  202. package/dist/providers/arxiv/types.d.ts.map +1 -0
  203. package/dist/providers/arxiv/types.js +17 -0
  204. package/dist/providers/arxiv/types.js.map +1 -0
  205. package/dist/providers/base/index.d.ts +16 -0
  206. package/dist/providers/base/index.d.ts.map +1 -0
  207. package/dist/providers/base/mock-provider.d.ts +76 -0
  208. package/dist/providers/base/mock-provider.d.ts.map +1 -0
  209. package/dist/providers/base/mock-provider.js +159 -0
  210. package/dist/providers/base/mock-provider.js.map +1 -0
  211. package/dist/providers/base/provider.d.ts +95 -0
  212. package/dist/providers/base/provider.d.ts.map +1 -0
  213. package/dist/providers/base/provider.js +117 -0
  214. package/dist/providers/base/provider.js.map +1 -0
  215. package/dist/providers/base/rate-limiter.d.ts +70 -0
  216. package/dist/providers/base/rate-limiter.d.ts.map +1 -0
  217. package/dist/providers/base/rate-limiter.js +111 -0
  218. package/dist/providers/base/rate-limiter.js.map +1 -0
  219. package/dist/providers/base/registry.d.ts +52 -0
  220. package/dist/providers/base/registry.d.ts.map +1 -0
  221. package/dist/providers/base/registry.js +55 -0
  222. package/dist/providers/base/registry.js.map +1 -0
  223. package/dist/providers/base/types.d.ts +163 -0
  224. package/dist/providers/base/types.d.ts.map +1 -0
  225. package/dist/providers/base/types.js +29 -0
  226. package/dist/providers/base/types.js.map +1 -0
  227. package/dist/providers/eric/client.d.ts +40 -0
  228. package/dist/providers/eric/client.d.ts.map +1 -0
  229. package/dist/providers/eric/client.js +93 -0
  230. package/dist/providers/eric/client.js.map +1 -0
  231. package/dist/providers/eric/index.d.ts +14 -0
  232. package/dist/providers/eric/index.d.ts.map +1 -0
  233. package/dist/providers/eric/parser.d.ts +21 -0
  234. package/dist/providers/eric/parser.d.ts.map +1 -0
  235. package/dist/providers/eric/parser.js +86 -0
  236. package/dist/providers/eric/parser.js.map +1 -0
  237. package/dist/providers/eric/provider.d.ts +59 -0
  238. package/dist/providers/eric/provider.d.ts.map +1 -0
  239. package/dist/providers/eric/provider.js +166 -0
  240. package/dist/providers/eric/provider.js.map +1 -0
  241. package/dist/providers/eric/translator.d.ts +12 -0
  242. package/dist/providers/eric/translator.d.ts.map +1 -0
  243. package/dist/providers/eric/translator.js +81 -0
  244. package/dist/providers/eric/translator.js.map +1 -0
  245. package/dist/providers/eric/types.d.ts +84 -0
  246. package/dist/providers/eric/types.d.ts.map +1 -0
  247. package/dist/providers/pubmed/client.d.ts +54 -0
  248. package/dist/providers/pubmed/client.d.ts.map +1 -0
  249. package/dist/providers/pubmed/client.js +141 -0
  250. package/dist/providers/pubmed/client.js.map +1 -0
  251. package/dist/providers/pubmed/index.d.ts +27 -0
  252. package/dist/providers/pubmed/index.d.ts.map +1 -0
  253. package/dist/providers/pubmed/parser.d.ts +22 -0
  254. package/dist/providers/pubmed/parser.d.ts.map +1 -0
  255. package/dist/providers/pubmed/parser.js +174 -0
  256. package/dist/providers/pubmed/parser.js.map +1 -0
  257. package/dist/providers/pubmed/provider.d.ts +44 -0
  258. package/dist/providers/pubmed/provider.d.ts.map +1 -0
  259. package/dist/providers/pubmed/provider.js +224 -0
  260. package/dist/providers/pubmed/provider.js.map +1 -0
  261. package/dist/providers/pubmed/translator.d.ts +7 -0
  262. package/dist/providers/pubmed/translator.d.ts.map +1 -0
  263. package/dist/providers/pubmed/translator.js +143 -0
  264. package/dist/providers/pubmed/translator.js.map +1 -0
  265. package/dist/providers/pubmed/types.d.ts +72 -0
  266. package/dist/providers/pubmed/types.d.ts.map +1 -0
  267. package/dist/providers/scopus/client.d.ts +64 -0
  268. package/dist/providers/scopus/client.d.ts.map +1 -0
  269. package/dist/providers/scopus/client.js +155 -0
  270. package/dist/providers/scopus/client.js.map +1 -0
  271. package/dist/providers/scopus/index.d.ts +21 -0
  272. package/dist/providers/scopus/index.d.ts.map +1 -0
  273. package/dist/providers/scopus/parser.d.ts +11 -0
  274. package/dist/providers/scopus/parser.d.ts.map +1 -0
  275. package/dist/providers/scopus/parser.js +128 -0
  276. package/dist/providers/scopus/parser.js.map +1 -0
  277. package/dist/providers/scopus/provider.d.ts +39 -0
  278. package/dist/providers/scopus/provider.d.ts.map +1 -0
  279. package/dist/providers/scopus/provider.js +175 -0
  280. package/dist/providers/scopus/provider.js.map +1 -0
  281. package/dist/providers/scopus/translator.d.ts +7 -0
  282. package/dist/providers/scopus/translator.d.ts.map +1 -0
  283. package/dist/providers/scopus/translator.js +83 -0
  284. package/dist/providers/scopus/translator.js.map +1 -0
  285. package/dist/providers/scopus/types.d.ts +86 -0
  286. package/dist/providers/scopus/types.d.ts.map +1 -0
  287. package/dist/query/index.d.ts +12 -0
  288. package/dist/query/index.d.ts.map +1 -0
  289. package/dist/query/parser.d.ts +18 -0
  290. package/dist/query/parser.d.ts.map +1 -0
  291. package/dist/query/parser.js +16 -0
  292. package/dist/query/parser.js.map +1 -0
  293. package/dist/query/types.d.ts +85 -0
  294. package/dist/query/types.d.ts.map +1 -0
  295. package/dist/query/validator.d.ts +635 -0
  296. package/dist/query/validator.d.ts.map +1 -0
  297. package/dist/query/validator.js +117 -0
  298. package/dist/query/validator.js.map +1 -0
  299. package/dist/session/index.d.ts +14 -0
  300. package/dist/session/index.d.ts.map +1 -0
  301. package/dist/session/logger.d.ts +15 -0
  302. package/dist/session/logger.d.ts.map +1 -0
  303. package/dist/session/logger.js +18 -0
  304. package/dist/session/logger.js.map +1 -0
  305. package/dist/session/manager.d.ts +63 -0
  306. package/dist/session/manager.d.ts.map +1 -0
  307. package/dist/session/manager.js +193 -0
  308. package/dist/session/manager.js.map +1 -0
  309. package/dist/session/types.d.ts +182 -0
  310. package/dist/session/types.d.ts.map +1 -0
  311. package/dist/utils/deep-merge.d.ts +17 -0
  312. package/dist/utils/deep-merge.d.ts.map +1 -0
  313. package/dist/utils/deep-merge.js +23 -0
  314. package/dist/utils/deep-merge.js.map +1 -0
  315. package/dist/utils/path.d.ts +9 -0
  316. package/dist/utils/path.d.ts.map +1 -0
  317. package/dist/utils/path.js +15 -0
  318. package/dist/utils/path.js.map +1 -0
  319. package/package.json +82 -0
@@ -0,0 +1,170 @@
1
+ import { readFile, appendFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { loadSession, getResumableProviders, updateDatabaseStatus, updateSessionStatus } from "../../session/manager.js";
4
+ import { MultiProviderProgress } from "../utils/progress.js";
5
+ import { createProviderInstance } from "./search-executor.js";
6
+ function isResumable(provider) {
7
+ return "resumeSearch" in provider && typeof provider.resumeSearch === "function";
8
+ }
9
+ async function executeResume(options, sessionsDir, config, showProgress = true) {
10
+ let session;
11
+ try {
12
+ session = await loadSession(options.sessionId, sessionsDir);
13
+ } catch (error) {
14
+ return {
15
+ success: false,
16
+ resumed: 0,
17
+ error: `Failed to load session: ${error instanceof Error ? error.message : error}`
18
+ };
19
+ }
20
+ let resumableProviders = getResumableProviders(session);
21
+ if (options.providers && options.providers.length > 0) {
22
+ resumableProviders = resumableProviders.filter(
23
+ (r) => options.providers.includes(r.provider)
24
+ );
25
+ }
26
+ if (options.retryFailed) {
27
+ resumableProviders = resumableProviders.filter((r) => r.strategy === "retry");
28
+ }
29
+ if (resumableProviders.length === 0) {
30
+ return {
31
+ success: true,
32
+ resumed: 0,
33
+ error: "No providers to resume"
34
+ };
35
+ }
36
+ const results = {};
37
+ let progress;
38
+ if (showProgress && process.stdout.isTTY) {
39
+ progress = new MultiProviderProgress(resumableProviders.map((p) => p.provider));
40
+ }
41
+ let successCount = 0;
42
+ for (const resumable of resumableProviders) {
43
+ const providerName = resumable.provider;
44
+ const dbStatus = session.databases[providerName];
45
+ if (!dbStatus) continue;
46
+ try {
47
+ const provider = createProviderInstance(providerName, config);
48
+ const queryPath = join(sessionsDir, options.sessionId, dbStatus.files.query);
49
+ let nativeQuery;
50
+ try {
51
+ nativeQuery = await readFile(queryPath, "utf-8");
52
+ } catch {
53
+ progress?.fail(providerName, "Query file not found");
54
+ results[providerName] = { hits: 0, retrieved: 0 };
55
+ continue;
56
+ }
57
+ const translatedQuery = {
58
+ native: nativeQuery.trim(),
59
+ provider: providerName
60
+ };
61
+ await updateDatabaseStatus(
62
+ options.sessionId,
63
+ providerName,
64
+ {
65
+ status: "in_progress",
66
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
67
+ },
68
+ sessionsDir
69
+ );
70
+ const resultsPath = join(sessionsDir, options.sessionId, dbStatus.files.results);
71
+ let retrievedCount = dbStatus.retrievedCount ?? 0;
72
+ const totalHits = dbStatus.totalHits ?? 0;
73
+ progress?.update(providerName, retrievedCount, totalHits || 100, "in_progress");
74
+ if (resumable.strategy === "continue" && isResumable(provider)) {
75
+ const searchState = {
76
+ provider: providerName,
77
+ query: translatedQuery,
78
+ totalResults: totalHits,
79
+ retrievedCount,
80
+ lastUpdated: /* @__PURE__ */ new Date(),
81
+ providerState: dbStatus.pagination ? {
82
+ cursor: dbStatus.pagination.cursor,
83
+ pageNumber: dbStatus.pagination.pageNumber
84
+ } : void 0
85
+ };
86
+ for await (const article of provider.resumeSearch(searchState)) {
87
+ retrievedCount++;
88
+ await appendFile(resultsPath, JSON.stringify(article) + "\n", "utf-8");
89
+ progress?.update(providerName, retrievedCount, totalHits || retrievedCount, "in_progress");
90
+ }
91
+ } else {
92
+ const searchOptions = {
93
+ maxResults: config.providers[providerName].max_results
94
+ };
95
+ for await (const article of provider.search(translatedQuery, searchOptions)) {
96
+ retrievedCount++;
97
+ await appendFile(resultsPath, JSON.stringify(article) + "\n", "utf-8");
98
+ progress?.update(providerName, retrievedCount, totalHits || retrievedCount * 2, "in_progress");
99
+ }
100
+ }
101
+ progress?.complete(providerName);
102
+ await updateDatabaseStatus(
103
+ options.sessionId,
104
+ providerName,
105
+ {
106
+ status: "completed",
107
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
108
+ totalHits: totalHits || retrievedCount,
109
+ retrievedCount
110
+ },
111
+ sessionsDir
112
+ );
113
+ results[providerName] = { hits: totalHits || retrievedCount, retrieved: retrievedCount };
114
+ successCount++;
115
+ } catch (error) {
116
+ const errorMessage = error instanceof Error ? error.message : String(error);
117
+ progress?.fail(providerName, errorMessage);
118
+ await updateDatabaseStatus(
119
+ options.sessionId,
120
+ providerName,
121
+ {
122
+ status: "failed",
123
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
124
+ error: {
125
+ code: "RESUME_ERROR",
126
+ message: errorMessage,
127
+ retryable: true
128
+ }
129
+ },
130
+ sessionsDir
131
+ );
132
+ results[providerName] = { hits: 0, retrieved: 0 };
133
+ }
134
+ }
135
+ progress?.stop();
136
+ const anyFailed = resumableProviders.some((p) => {
137
+ const r = results[p.provider];
138
+ return r && r.retrieved === 0 && r.hits === 0;
139
+ });
140
+ const anySucceeded = resumableProviders.some((p) => {
141
+ const r = results[p.provider];
142
+ return r && r.retrieved > 0;
143
+ });
144
+ let sessionStatus;
145
+ if (!anyFailed) {
146
+ sessionStatus = "completed";
147
+ } else if (anySucceeded) {
148
+ sessionStatus = "partial";
149
+ } else {
150
+ sessionStatus = "failed";
151
+ }
152
+ await updateSessionStatus(options.sessionId, sessionStatus, sessionsDir);
153
+ if (sessionStatus === "failed") {
154
+ return {
155
+ success: false,
156
+ resumed: successCount,
157
+ results,
158
+ error: "All providers failed"
159
+ };
160
+ }
161
+ return {
162
+ success: true,
163
+ resumed: successCount,
164
+ results
165
+ };
166
+ }
167
+ export {
168
+ executeResume
169
+ };
170
+ //# sourceMappingURL=resume-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-executor.js","sources":["../../../src/cli/commands/resume-executor.ts"],"sourcesContent":["/**\n * Resume executor for CLI resume command.\n *\n * Handles resuming interrupted search sessions by continuing\n * from where providers left off or retrying failed providers.\n */\nimport { readFile, appendFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { ResumeCommandOptions } from './resume.js';\nimport type { Config } from '../../config/index.js';\nimport type {\n Provider,\n TranslatedQuery,\n SearchState,\n} from '../../providers/base/types.js';\nimport {\n loadSession,\n updateDatabaseStatus,\n updateSessionStatus,\n getResumableProviders,\n} from '../../session/manager.js';\nimport { MultiProviderProgress } from '../utils/progress.js';\nimport { createProviderInstance } from './search-executor.js';\n\n/**\n * Result of a resume execution.\n */\nexport interface ResumeExecutionResult {\n success: boolean;\n resumed: number;\n results?: Record<string, { hits: number; retrieved: number }>;\n error?: string;\n}\n\n/**\n * Interface for providers that support resuming.\n */\ninterface ResumableProviderInstance extends Provider {\n resumeSearch(state: SearchState): AsyncIterable<import('../../providers/base/types.js').Article>;\n}\n\n/**\n * Check if a provider instance supports resuming.\n */\nfunction isResumable(provider: Provider): provider is ResumableProviderInstance {\n return 'resumeSearch' in provider && typeof (provider as any).resumeSearch === 'function';\n}\n\n/**\n * Execute resume for interrupted sessions.\n */\nexport async function executeResume(\n options: ResumeCommandOptions,\n sessionsDir: string,\n config: Config,\n showProgress = true\n): Promise<ResumeExecutionResult> {\n // Load session\n let session;\n try {\n session = await loadSession(options.sessionId, sessionsDir);\n } catch (error) {\n return {\n success: false,\n resumed: 0,\n error: `Failed to load session: ${error instanceof Error ? error.message : error}`,\n };\n }\n\n // Get resumable providers\n let resumableProviders = getResumableProviders(session);\n\n // Filter by specific providers if requested\n if (options.providers && options.providers.length > 0) {\n resumableProviders = resumableProviders.filter((r) =>\n options.providers!.includes(r.provider)\n );\n }\n\n // Filter to only retry strategies if retryFailed is true\n if (options.retryFailed) {\n resumableProviders = resumableProviders.filter((r) => r.strategy === 'retry');\n }\n\n if (resumableProviders.length === 0) {\n return {\n success: true,\n resumed: 0,\n error: 'No providers to resume',\n };\n }\n\n const results: Record<string, { hits: number; retrieved: number }> = {};\n\n // Create progress display if enabled\n let progress: MultiProviderProgress | undefined;\n if (showProgress && process.stdout.isTTY) {\n progress = new MultiProviderProgress(resumableProviders.map((p) => p.provider));\n }\n\n let successCount = 0;\n\n // Resume each provider\n for (const resumable of resumableProviders) {\n const providerName = resumable.provider;\n const dbStatus = session.databases[providerName];\n\n if (!dbStatus) continue;\n\n try {\n // Create provider instance\n const provider = createProviderInstance(providerName, config);\n\n // Build the translated query from stored query file\n const queryPath = join(sessionsDir, options.sessionId, dbStatus.files.query);\n let nativeQuery: string;\n try {\n nativeQuery = await readFile(queryPath, 'utf-8');\n } catch {\n // If query file doesn't exist, skip this provider\n progress?.fail(providerName, 'Query file not found');\n results[providerName] = { hits: 0, retrieved: 0 };\n continue;\n }\n\n const translatedQuery: TranslatedQuery = {\n native: nativeQuery.trim(),\n provider: providerName,\n };\n\n // Update database status to in_progress\n await updateDatabaseStatus(\n options.sessionId,\n providerName,\n {\n status: 'in_progress',\n startedAt: new Date().toISOString(),\n },\n sessionsDir\n );\n\n // Prepare results file path\n const resultsPath = join(sessionsDir, options.sessionId, dbStatus.files.results);\n\n let retrievedCount = dbStatus.retrievedCount ?? 0;\n const totalHits = dbStatus.totalHits ?? 0;\n\n progress?.update(providerName, retrievedCount, totalHits || 100, 'in_progress');\n\n // Determine how to resume\n if (resumable.strategy === 'continue' && isResumable(provider)) {\n // Build SearchState for continuing\n const searchState: SearchState = {\n provider: providerName,\n query: translatedQuery,\n totalResults: totalHits,\n retrievedCount,\n lastUpdated: new Date(),\n providerState: dbStatus.pagination\n ? {\n cursor: dbStatus.pagination.cursor,\n pageNumber: dbStatus.pagination.pageNumber,\n }\n : undefined,\n };\n\n // Resume search\n for await (const article of provider.resumeSearch(searchState)) {\n retrievedCount++;\n\n // Write article to JSONL file\n await appendFile(resultsPath, JSON.stringify(article) + '\\n', 'utf-8');\n\n progress?.update(providerName, retrievedCount, totalHits || retrievedCount, 'in_progress');\n }\n } else {\n // Fresh start or retry - use regular search\n const searchOptions = {\n maxResults: config.providers[providerName].max_results,\n };\n\n for await (const article of provider.search(translatedQuery, searchOptions)) {\n retrievedCount++;\n\n // Write article to JSONL file\n await appendFile(resultsPath, JSON.stringify(article) + '\\n', 'utf-8');\n\n progress?.update(providerName, retrievedCount, totalHits || retrievedCount * 2, 'in_progress');\n }\n }\n\n // Mark as completed\n progress?.complete(providerName);\n\n // Update database status\n await updateDatabaseStatus(\n options.sessionId,\n providerName,\n {\n status: 'completed',\n completedAt: new Date().toISOString(),\n totalHits: totalHits || retrievedCount,\n retrievedCount,\n },\n sessionsDir\n );\n\n results[providerName] = { hits: totalHits || retrievedCount, retrieved: retrievedCount };\n successCount++;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n progress?.fail(providerName, errorMessage);\n\n // Update database status with error\n await updateDatabaseStatus(\n options.sessionId,\n providerName,\n {\n status: 'failed',\n completedAt: new Date().toISOString(),\n error: {\n code: 'RESUME_ERROR',\n message: errorMessage,\n retryable: true,\n },\n },\n sessionsDir\n );\n\n results[providerName] = { hits: 0, retrieved: 0 };\n }\n }\n\n // Stop progress display\n progress?.stop();\n\n // Determine overall session status\n const anyFailed = resumableProviders.some((p) => {\n const r = results[p.provider];\n return r && r.retrieved === 0 && r.hits === 0;\n });\n const anySucceeded = resumableProviders.some((p) => {\n const r = results[p.provider];\n return r && r.retrieved > 0;\n });\n\n let sessionStatus: 'completed' | 'partial' | 'failed';\n if (!anyFailed) {\n sessionStatus = 'completed';\n } else if (anySucceeded) {\n sessionStatus = 'partial';\n } else {\n sessionStatus = 'failed';\n }\n\n // Update session status\n await updateSessionStatus(options.sessionId, sessionStatus, sessionsDir);\n\n if (sessionStatus === 'failed') {\n return {\n success: false,\n resumed: successCount,\n results,\n error: 'All providers failed',\n };\n }\n\n return {\n success: true,\n resumed: successCount,\n results,\n };\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { ProviderName, ResumableProvider } from '../../session/types.js';
2
+ export interface ResumeCommandOptions {
3
+ sessionId: string;
4
+ providers?: ProviderName[];
5
+ retryFailed?: boolean;
6
+ }
7
+ export interface CommandLineOptions {
8
+ db?: string | undefined;
9
+ retryFailed?: boolean | undefined;
10
+ }
11
+ export interface ValidationResult {
12
+ valid: boolean;
13
+ error?: string;
14
+ }
15
+ export interface ResumeResult {
16
+ success: boolean;
17
+ providers?: ResumableProvider[];
18
+ error?: string;
19
+ }
20
+ export declare function parseResumeOptions(sessionId: string, options: CommandLineOptions): ResumeCommandOptions;
21
+ export declare function validateResumeInput(options: ResumeCommandOptions): ValidationResult;
22
+ export declare function getResumableProvidersForCommand(sessionId: string, sessionsDir: string, options: {
23
+ providers?: ProviderName[] | undefined;
24
+ retryFailed?: boolean | undefined;
25
+ }): Promise<ResumeResult>;
26
+ //# sourceMappingURL=resume.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/resume.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG9E,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kBAAkB,GAC1B,oBAAoB,CActB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,gBAAgB,CASnF;AAED,wBAAsB,+BAA+B,CACnD,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IAAE,SAAS,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE,GACrF,OAAO,CAAC,YAAY,CAAC,CA0BvB"}
@@ -0,0 +1,51 @@
1
+ import { loadSession, getResumableProviders } from "../../session/manager.js";
2
+ import { parseProviderNames } from "../utils/validation.js";
3
+ function parseResumeOptions(sessionId, options) {
4
+ const result = {
5
+ sessionId
6
+ };
7
+ if (options.db) {
8
+ result.providers = parseProviderNames(options.db);
9
+ }
10
+ if (options.retryFailed) {
11
+ result.retryFailed = true;
12
+ }
13
+ return result;
14
+ }
15
+ function validateResumeInput(options) {
16
+ if (!options.sessionId || options.sessionId.trim() === "") {
17
+ return {
18
+ valid: false,
19
+ error: "A session ID is required"
20
+ };
21
+ }
22
+ return { valid: true };
23
+ }
24
+ async function getResumableProvidersForCommand(sessionId, sessionsDir, options) {
25
+ try {
26
+ const session = await loadSession(sessionId, sessionsDir);
27
+ let resumable = getResumableProviders(session);
28
+ if (options.providers && options.providers.length > 0) {
29
+ resumable = resumable.filter((r) => options.providers.includes(r.provider));
30
+ }
31
+ if (options.retryFailed) {
32
+ resumable = resumable.filter((r) => r.strategy === "retry");
33
+ }
34
+ return {
35
+ success: true,
36
+ providers: resumable
37
+ };
38
+ } catch (error) {
39
+ const message = error instanceof Error ? error.message : "Unknown error";
40
+ return {
41
+ success: false,
42
+ error: message
43
+ };
44
+ }
45
+ }
46
+ export {
47
+ getResumableProvidersForCommand,
48
+ parseResumeOptions,
49
+ validateResumeInput
50
+ };
51
+ //# sourceMappingURL=resume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.js","sources":["../../../src/cli/commands/resume.ts"],"sourcesContent":["import { loadSession, getResumableProviders } from '../../session/manager.js';\nimport type { ProviderName, ResumableProvider } from '../../session/types.js';\nimport { parseProviderNames } from '../utils/validation.js';\n\nexport interface ResumeCommandOptions {\n sessionId: string;\n providers?: ProviderName[];\n retryFailed?: boolean;\n}\n\nexport interface CommandLineOptions {\n db?: string | undefined;\n retryFailed?: boolean | undefined;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\nexport interface ResumeResult {\n success: boolean;\n providers?: ResumableProvider[];\n error?: string;\n}\n\nexport function parseResumeOptions(\n sessionId: string,\n options: CommandLineOptions\n): ResumeCommandOptions {\n const result: ResumeCommandOptions = {\n sessionId,\n };\n\n if (options.db) {\n result.providers = parseProviderNames(options.db);\n }\n\n if (options.retryFailed) {\n result.retryFailed = true;\n }\n\n return result;\n}\n\nexport function validateResumeInput(options: ResumeCommandOptions): ValidationResult {\n if (!options.sessionId || options.sessionId.trim() === '') {\n return {\n valid: false,\n error: 'A session ID is required',\n };\n }\n\n return { valid: true };\n}\n\nexport async function getResumableProvidersForCommand(\n sessionId: string,\n sessionsDir: string,\n options: { providers?: ProviderName[] | undefined; retryFailed?: boolean | undefined }\n): Promise<ResumeResult> {\n try {\n const session = await loadSession(sessionId, sessionsDir);\n let resumable = getResumableProviders(session);\n\n // Filter by specific providers if requested\n if (options.providers && options.providers.length > 0) {\n resumable = resumable.filter((r) => options.providers!.includes(r.provider));\n }\n\n // Filter to only retry strategies if retryFailed is true\n if (options.retryFailed) {\n resumable = resumable.filter((r) => r.strategy === 'retry');\n }\n\n return {\n success: true,\n providers: resumable,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n return {\n success: false,\n error: message,\n };\n }\n}\n"],"names":[],"mappings":";;AA0BO,SAAS,mBACd,WACA,SACsB;AACtB,QAAM,SAA+B;AAAA,IACnC;AAAA,EAAA;AAGF,MAAI,QAAQ,IAAI;AACd,WAAO,YAAY,mBAAmB,QAAQ,EAAE;AAAA,EAClD;AAEA,MAAI,QAAQ,aAAa;AACvB,WAAO,cAAc;AAAA,EACvB;AAEA,SAAO;AACT;AAEO,SAAS,oBAAoB,SAAiD;AACnF,MAAI,CAAC,QAAQ,aAAa,QAAQ,UAAU,KAAA,MAAW,IAAI;AACzD,WAAO;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IAAA;AAAA,EAEX;AAEA,SAAO,EAAE,OAAO,KAAA;AAClB;AAEA,eAAsB,gCACpB,WACA,aACA,SACuB;AACvB,MAAI;AACF,UAAM,UAAU,MAAM,YAAY,WAAW,WAAW;AACxD,QAAI,YAAY,sBAAsB,OAAO;AAG7C,QAAI,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;AACrD,kBAAY,UAAU,OAAO,CAAC,MAAM,QAAQ,UAAW,SAAS,EAAE,QAAQ,CAAC;AAAA,IAC7E;AAGA,QAAI,QAAQ,aAAa;AACvB,kBAAY,UAAU,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO;AAAA,IAC5D;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,WAAW;AAAA,IAAA;AAAA,EAEf,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IAAA;AAAA,EAEX;AACF;"}
@@ -0,0 +1,26 @@
1
+ import { SearchCommandOptions } from './search.js';
2
+ import { Config } from '../../config/index.js';
3
+ import { Provider, ProviderName } from '../../providers/base/types.js';
4
+ import { RegistrationRecord } from '../../integration/types.js';
5
+ /**
6
+ * Result of a search execution.
7
+ */
8
+ export interface SearchExecutionResult {
9
+ success: boolean;
10
+ sessionId?: string;
11
+ results?: Record<string, {
12
+ hits: number;
13
+ retrieved: number;
14
+ }>;
15
+ error?: string;
16
+ autoRegisterResult?: RegistrationRecord;
17
+ }
18
+ /**
19
+ * Create a provider instance for the given provider name.
20
+ */
21
+ export declare function createProviderInstance(name: ProviderName, config: Config): Provider;
22
+ /**
23
+ * Execute a search across multiple providers.
24
+ */
25
+ export declare function executeSearch(options: SearchCommandOptions, sessionsDir: string, config: Config, showProgress?: boolean): Promise<SearchExecutionResult>;
26
+ //# sourceMappingURL=search-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-executor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/search-executor.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAEV,QAAQ,EACR,YAAY,EAEb,MAAM,+BAA+B,CAAC;AAqBvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAGrE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAOD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,MAAM,GACb,QAAQ,CAgDV;AAyCD;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,YAAY,UAAO,GAClB,OAAO,CAAC,qBAAqB,CAAC,CA8QhC"}
@@ -0,0 +1,315 @@
1
+ import { readFile, writeFile, appendFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { parseQueryString } from "../../query/parser.js";
5
+ import "../../query/validator.js";
6
+ import { createSession, updateDatabaseStatus, updateSessionStatus } from "../../session/manager.js";
7
+ import { MultiProviderProgress } from "../utils/progress.js";
8
+ import { PubMedProvider } from "../../providers/pubmed/provider.js";
9
+ import { ERICProvider } from "../../providers/eric/provider.js";
10
+ import { ArxivProvider } from "../../providers/arxiv/provider.js";
11
+ import { ScopusProvider } from "../../providers/scopus/provider.js";
12
+ import { translateQuery as translateQuery$3 } from "../../providers/pubmed/translator.js";
13
+ import { translateQuery as translateQuery$2 } from "../../providers/eric/translator.js";
14
+ import { translateQuery as translateQuery$1 } from "../../providers/arxiv/translator.js";
15
+ import { translateQuery } from "../../providers/scopus/translator.js";
16
+ import { stringify } from "yaml";
17
+ import { registerArticles, saveRegistrationRecord } from "../../integration/register.js";
18
+ import { checkRefAvailable } from "../../integration/ref-cli.js";
19
+ const IMPLEMENTED_PROVIDERS = ["pubmed", "eric", "arxiv", "scopus"];
20
+ function createProviderInstance(name, config) {
21
+ const providerConfig = config.providers[name];
22
+ switch (name) {
23
+ case "pubmed": {
24
+ if (!providerConfig.email) {
25
+ console.warn(
26
+ "Warning: No email configured for PubMed. Set providers.pubmed.email in config."
27
+ );
28
+ }
29
+ const pubmedOpts = {
30
+ email: providerConfig.email ?? "search-hub@example.com",
31
+ rateLimit: providerConfig.rate_limit,
32
+ timeout: providerConfig.timeout,
33
+ retries: providerConfig.retries
34
+ };
35
+ if (providerConfig.api_key) {
36
+ pubmedOpts.apiKey = providerConfig.api_key;
37
+ }
38
+ return new PubMedProvider(pubmedOpts);
39
+ }
40
+ case "eric":
41
+ return new ERICProvider({
42
+ rateLimit: providerConfig.rate_limit,
43
+ timeout: providerConfig.timeout,
44
+ retries: providerConfig.retries
45
+ });
46
+ case "arxiv":
47
+ return new ArxivProvider({
48
+ rateLimit: providerConfig.rate_limit,
49
+ timeout: providerConfig.timeout,
50
+ retries: providerConfig.retries
51
+ });
52
+ case "scopus": {
53
+ const scopusOpts = {
54
+ apiKey: providerConfig.api_key ?? "",
55
+ rateLimit: providerConfig.rate_limit,
56
+ timeout: providerConfig.timeout,
57
+ retries: providerConfig.retries
58
+ };
59
+ if (providerConfig.inst_token) {
60
+ scopusOpts.instToken = providerConfig.inst_token;
61
+ }
62
+ return new ScopusProvider(scopusOpts);
63
+ }
64
+ default:
65
+ throw new Error(`Provider '${name}' is not implemented`);
66
+ }
67
+ }
68
+ function translateQueryForProvider(ast, provider) {
69
+ switch (provider) {
70
+ case "pubmed":
71
+ return translateQuery$3(ast);
72
+ case "eric":
73
+ return translateQuery$2(ast);
74
+ case "arxiv":
75
+ return translateQuery$1(ast);
76
+ case "scopus":
77
+ return translateQuery(ast);
78
+ default:
79
+ throw new Error(`No translator for provider '${provider}'`);
80
+ }
81
+ }
82
+ function getEnabledProviders(config, requestedProviders) {
83
+ const enabledInConfig = IMPLEMENTED_PROVIDERS.filter(
84
+ (name) => config.providers[name].enabled
85
+ );
86
+ if (requestedProviders && requestedProviders.length > 0) {
87
+ return requestedProviders.filter((p) => enabledInConfig.includes(p));
88
+ }
89
+ return enabledInConfig;
90
+ }
91
+ async function executeSearch(options, sessionsDir, config, showProgress = true) {
92
+ let ast;
93
+ let queryContent;
94
+ let queryFile;
95
+ if (options.directQuery && options.providers && options.providers.length === 1) {
96
+ queryFile = "direct-query";
97
+ ast = {
98
+ name: options.sessionName ?? "direct-query",
99
+ blocks: [
100
+ {
101
+ field: "all",
102
+ terms: { keywords: [options.directQuery] },
103
+ operator: "AND"
104
+ }
105
+ ],
106
+ filters: {},
107
+ overrides: {}
108
+ };
109
+ queryContent = stringify({
110
+ name: ast.name,
111
+ blocks: ast.blocks,
112
+ filters: ast.filters
113
+ });
114
+ } else if (options.queryFile) {
115
+ try {
116
+ queryContent = await readFile(options.queryFile, "utf-8");
117
+ ast = parseQueryString(queryContent);
118
+ queryFile = options.queryFile;
119
+ } catch (error) {
120
+ return {
121
+ success: false,
122
+ error: `Failed to parse query file: ${error instanceof Error ? error.message : error}`
123
+ };
124
+ }
125
+ } else {
126
+ return {
127
+ success: false,
128
+ error: "Either queryFile or directQuery with provider is required"
129
+ };
130
+ }
131
+ const providers = getEnabledProviders(config, options.providers);
132
+ if (providers.length === 0) {
133
+ return {
134
+ success: false,
135
+ error: "No providers enabled or selected"
136
+ };
137
+ }
138
+ const queryHash = createHash("sha256").update(queryContent).digest("hex").slice(0, 8);
139
+ let session;
140
+ try {
141
+ const sessionOpts = {
142
+ name: options.sessionName ?? ast.name,
143
+ queryFile,
144
+ queryContent,
145
+ queryHash,
146
+ targets: providers,
147
+ sessionsDir
148
+ };
149
+ if (ast.description) {
150
+ sessionOpts.description = ast.description;
151
+ }
152
+ session = await createSession(sessionOpts);
153
+ } catch (error) {
154
+ return {
155
+ success: false,
156
+ error: `Failed to create session: ${error instanceof Error ? error.message : error}`
157
+ };
158
+ }
159
+ const sessionId = session.id;
160
+ const results = {};
161
+ let progress;
162
+ if (showProgress && process.stdout.isTTY) {
163
+ progress = new MultiProviderProgress(providers);
164
+ }
165
+ for (const providerName of providers) {
166
+ try {
167
+ const provider = createProviderInstance(providerName, config);
168
+ let translatedQuery;
169
+ if (options.directQuery && options.providers?.length === 1) {
170
+ translatedQuery = {
171
+ native: options.directQuery,
172
+ provider: providerName
173
+ };
174
+ } else {
175
+ translatedQuery = translateQueryForProvider(ast, providerName);
176
+ }
177
+ const queryPath = join(sessionsDir, sessionId, `${providerName}_query.txt`);
178
+ await writeFile(queryPath, translatedQuery.native, "utf-8");
179
+ await updateDatabaseStatus(
180
+ sessionId,
181
+ providerName,
182
+ {
183
+ status: "in_progress",
184
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
185
+ },
186
+ sessionsDir
187
+ );
188
+ const resultsPath = join(sessionsDir, sessionId, `${providerName}_results.jsonl`);
189
+ let retrievedCount = 0;
190
+ let totalHits = 0;
191
+ progress?.update(providerName, 0, 0, "in_progress");
192
+ const searchOptions = {
193
+ maxResults: options.maxResults ?? config.providers[providerName].max_results
194
+ };
195
+ for await (const article of provider.search(translatedQuery, searchOptions)) {
196
+ retrievedCount++;
197
+ await appendFile(resultsPath, JSON.stringify(article) + "\n", "utf-8");
198
+ if (totalHits === 0) {
199
+ totalHits = Math.max(retrievedCount * 10, 100);
200
+ }
201
+ progress?.update(providerName, retrievedCount, totalHits, "in_progress");
202
+ }
203
+ totalHits = retrievedCount;
204
+ progress?.complete(providerName);
205
+ await updateDatabaseStatus(
206
+ sessionId,
207
+ providerName,
208
+ {
209
+ status: "completed",
210
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
211
+ totalHits,
212
+ retrievedCount,
213
+ files: {
214
+ query: `${providerName}_query.txt`,
215
+ results: `${providerName}_results.jsonl`
216
+ }
217
+ },
218
+ sessionsDir
219
+ );
220
+ results[providerName] = { hits: totalHits, retrieved: retrievedCount };
221
+ } catch (error) {
222
+ const errorMessage = error instanceof Error ? error.message : String(error);
223
+ progress?.fail(providerName, errorMessage);
224
+ await updateDatabaseStatus(
225
+ sessionId,
226
+ providerName,
227
+ {
228
+ status: "failed",
229
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
230
+ error: {
231
+ code: "SEARCH_ERROR",
232
+ message: errorMessage,
233
+ retryable: true
234
+ }
235
+ },
236
+ sessionsDir
237
+ );
238
+ results[providerName] = { hits: 0, retrieved: 0 };
239
+ }
240
+ }
241
+ progress?.stop();
242
+ const anyFailed = providers.some((p) => {
243
+ const r = results[p];
244
+ return r && r.retrieved === 0 && r.hits === 0;
245
+ });
246
+ const anySucceeded = providers.some((p) => {
247
+ const r = results[p];
248
+ return r && r.retrieved > 0;
249
+ });
250
+ let sessionStatus;
251
+ if (!anyFailed) {
252
+ sessionStatus = "completed";
253
+ } else if (anySucceeded) {
254
+ sessionStatus = "partial";
255
+ } else {
256
+ sessionStatus = "failed";
257
+ }
258
+ await updateSessionStatus(sessionId, sessionStatus, sessionsDir);
259
+ if (sessionStatus === "failed") {
260
+ return {
261
+ success: false,
262
+ sessionId,
263
+ results,
264
+ error: "All providers failed"
265
+ };
266
+ }
267
+ let autoRegisterResult;
268
+ if (config.integration.reference_manager.enabled && config.integration.reference_manager.auto_register) {
269
+ const refAvailable = await checkRefAvailable();
270
+ if (refAvailable) {
271
+ const allArticles = await loadArticlesFromSession(sessionsDir, sessionId, providers);
272
+ if (allArticles.length > 0) {
273
+ autoRegisterResult = await registerArticles(allArticles, {
274
+ sessionId,
275
+ sessionDir: join(sessionsDir, sessionId),
276
+ withAbstracts: config.integration.reference_manager.with_abstracts
277
+ });
278
+ await saveRegistrationRecord(join(sessionsDir, sessionId), autoRegisterResult);
279
+ }
280
+ }
281
+ }
282
+ const result = {
283
+ success: true,
284
+ sessionId,
285
+ results
286
+ };
287
+ if (autoRegisterResult) {
288
+ result.autoRegisterResult = autoRegisterResult;
289
+ }
290
+ return result;
291
+ }
292
+ async function loadArticlesFromSession(sessionsDir, sessionId, providers) {
293
+ const articles = [];
294
+ for (const provider of providers) {
295
+ const resultsPath = join(sessionsDir, sessionId, `${provider}_results.jsonl`);
296
+ try {
297
+ const content = await readFile(resultsPath, "utf-8");
298
+ const lines = content.trim().split("\n").filter((line) => line.length > 0);
299
+ for (const line of lines) {
300
+ try {
301
+ const article = JSON.parse(line);
302
+ articles.push(article);
303
+ } catch {
304
+ }
305
+ }
306
+ } catch {
307
+ }
308
+ }
309
+ return articles;
310
+ }
311
+ export {
312
+ createProviderInstance,
313
+ executeSearch
314
+ };
315
+ //# sourceMappingURL=search-executor.js.map