@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.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/_virtual/___vite-browser-external.js +7 -0
- package/dist/_virtual/___vite-browser-external.js.map +1 -0
- package/dist/_virtual/__vite-browser-external.js +5 -0
- package/dist/_virtual/__vite-browser-external.js.map +1 -0
- package/dist/_virtual/_commonjsHelpers.js +28 -0
- package/dist/_virtual/_commonjsHelpers.js.map +1 -0
- package/dist/_virtual/cli-progress.js +6 -0
- package/dist/_virtual/cli-progress.js.map +1 -0
- package/dist/_virtual/index.js +5 -0
- package/dist/_virtual/index.js.map +1 -0
- package/dist/_virtual/index2.js +5 -0
- package/dist/_virtual/index2.js.map +1 -0
- package/dist/cli/commands/config.d.ts +37 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +117 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/export.d.ts +26 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +86 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/init.d.ts +45 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +134 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/query/translate.d.ts +32 -0
- package/dist/cli/commands/query/translate.d.ts.map +1 -0
- package/dist/cli/commands/query/translate.js +76 -0
- package/dist/cli/commands/query/translate.js.map +1 -0
- package/dist/cli/commands/query/validate.d.ts +25 -0
- package/dist/cli/commands/query/validate.d.ts.map +1 -0
- package/dist/cli/commands/query/validate.js +68 -0
- package/dist/cli/commands/query/validate.js.map +1 -0
- package/dist/cli/commands/register.d.ts +39 -0
- package/dist/cli/commands/register.d.ts.map +1 -0
- package/dist/cli/commands/register.js +78 -0
- package/dist/cli/commands/register.js.map +1 -0
- package/dist/cli/commands/resume-executor.d.ts +19 -0
- package/dist/cli/commands/resume-executor.d.ts.map +1 -0
- package/dist/cli/commands/resume-executor.js +170 -0
- package/dist/cli/commands/resume-executor.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +26 -0
- package/dist/cli/commands/resume.d.ts.map +1 -0
- package/dist/cli/commands/resume.js +51 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/search-executor.d.ts +26 -0
- package/dist/cli/commands/search-executor.d.ts.map +1 -0
- package/dist/cli/commands/search-executor.js +315 -0
- package/dist/cli/commands/search-executor.js.map +1 -0
- package/dist/cli/commands/search.d.ts +30 -0
- package/dist/cli/commands/search.d.ts.map +1 -0
- package/dist/cli/commands/search.js +67 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/status.d.ts +41 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +123 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/e2e-helpers.d.ts +165 -0
- package/dist/cli/e2e-helpers.d.ts.map +1 -0
- package/dist/cli/exit-codes.d.ts +25 -0
- package/dist/cli/exit-codes.d.ts.map +1 -0
- package/dist/cli/exit-codes.js +18 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/index.d.ts +25 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +638 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/progress.d.ts +24 -0
- package/dist/cli/utils/progress.d.ts.map +1 -0
- package/dist/cli/utils/progress.js +128 -0
- package/dist/cli/utils/progress.js.map +1 -0
- package/dist/cli/utils/sessions-dir.d.ts +14 -0
- package/dist/cli/utils/sessions-dir.d.ts.map +1 -0
- package/dist/cli/utils/sessions-dir.js +21 -0
- package/dist/cli/utils/sessions-dir.js.map +1 -0
- package/dist/cli/utils/validation.d.ts +18 -0
- package/dist/cli/utils/validation.d.ts.map +1 -0
- package/dist/cli/utils/validation.js +24 -0
- package/dist/cli/utils/validation.js.map +1 -0
- package/dist/config/defaults.d.ts +12 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +10 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/env.d.ts +12 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +36 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/loader.d.ts +49 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +72 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/paths.d.ts +23 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +22 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/schema.d.ts +109 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +74 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/ref-cli.d.ts +41 -0
- package/dist/integration/ref-cli.d.ts.map +1 -0
- package/dist/integration/ref-cli.js +132 -0
- package/dist/integration/ref-cli.js.map +1 -0
- package/dist/integration/register.d.ts +27 -0
- package/dist/integration/register.d.ts.map +1 -0
- package/dist/integration/register.js +108 -0
- package/dist/integration/register.js.map +1 -0
- package/dist/integration/types.d.ts +61 -0
- package/dist/integration/types.d.ts.map +1 -0
- package/dist/integration/types.js +61 -0
- package/dist/integration/types.js.map +1 -0
- package/dist/node_modules/cli-progress/cli-progress.js +37 -0
- package/dist/node_modules/cli-progress/cli-progress.js.map +1 -0
- package/dist/node_modules/cli-progress/lib/eta.js +52 -0
- package/dist/node_modules/cli-progress/lib/eta.js.map +1 -0
- package/dist/node_modules/cli-progress/lib/format-bar.js +16 -0
- package/dist/node_modules/cli-progress/lib/format-bar.js.map +1 -0
- package/dist/node_modules/cli-progress/lib/format-time.js +32 -0
- package/dist/node_modules/cli-progress/lib/format-time.js.map +1 -0
- package/dist/node_modules/cli-progress/lib/format-value.js +25 -0
- package/dist/node_modules/cli-progress/lib/format-value.js.map +1 -0
- package/dist/node_modules/cli-progress/lib/formatter.js +57 -0
- package/dist/node_modules/cli-progress/lib/formatter.js.map +1 -0
- package/dist/node_modules/cli-progress/lib/generic-bar.js +137 -0
- package/dist/node_modules/cli-progress/lib/generic-bar.js.map +1 -0
- package/dist/node_modules/cli-progress/lib/multi-bar.js +147 -0
- package/dist/node_modules/cli-progress/lib/multi-bar.js.map +1 -0
- package/dist/node_modules/cli-progress/lib/options.js +59 -0
- package/dist/node_modules/cli-progress/lib/options.js.map +1 -0
- package/dist/node_modules/cli-progress/lib/single-bar.js +95 -0
- package/dist/node_modules/cli-progress/lib/single-bar.js.map +1 -0
- package/dist/node_modules/cli-progress/lib/terminal.js +125 -0
- package/dist/node_modules/cli-progress/lib/terminal.js.map +1 -0
- package/dist/node_modules/cli-progress/node_modules/ansi-regex/index.js +18 -0
- package/dist/node_modules/cli-progress/node_modules/ansi-regex/index.js.map +1 -0
- package/dist/node_modules/cli-progress/node_modules/emoji-regex/index.js +14 -0
- package/dist/node_modules/cli-progress/node_modules/emoji-regex/index.js.map +1 -0
- package/dist/node_modules/cli-progress/node_modules/string-width/index.js +44 -0
- package/dist/node_modules/cli-progress/node_modules/string-width/index.js.map +1 -0
- package/dist/node_modules/cli-progress/node_modules/strip-ansi/index.js +14 -0
- package/dist/node_modules/cli-progress/node_modules/strip-ansi/index.js.map +1 -0
- package/dist/node_modules/cli-progress/presets/index.js +25 -0
- package/dist/node_modules/cli-progress/presets/index.js.map +1 -0
- package/dist/node_modules/cli-progress/presets/legacy.js +16 -0
- package/dist/node_modules/cli-progress/presets/legacy.js.map +1 -0
- package/dist/node_modules/cli-progress/presets/rect.js +16 -0
- package/dist/node_modules/cli-progress/presets/rect.js.map +1 -0
- package/dist/node_modules/cli-progress/presets/shades-classic.js +16 -0
- package/dist/node_modules/cli-progress/presets/shades-classic.js.map +1 -0
- package/dist/node_modules/cli-progress/presets/shades-grey.js +16 -0
- package/dist/node_modules/cli-progress/presets/shades-grey.js.map +1 -0
- package/dist/node_modules/env-paths/index.js +58 -0
- package/dist/node_modules/env-paths/index.js.map +1 -0
- package/dist/node_modules/fast-xml-parser/src/ignoreAttributes.js +22 -0
- package/dist/node_modules/fast-xml-parser/src/ignoreAttributes.js.map +1 -0
- package/dist/node_modules/fast-xml-parser/src/util.js +33 -0
- package/dist/node_modules/fast-xml-parser/src/util.js.map +1 -0
- package/dist/node_modules/fast-xml-parser/src/validator.js +309 -0
- package/dist/node_modules/fast-xml-parser/src/validator.js.map +1 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js +265 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js.map +1 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js +53 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js.map +1 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js +515 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js.map +1 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/XMLParser.js +68 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/XMLParser.js.map +1 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/node2json.js +88 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/node2json.js.map +1 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/xmlNode.js +36 -0
- package/dist/node_modules/fast-xml-parser/src/xmlparser/xmlNode.js.map +1 -0
- package/dist/node_modules/is-fullwidth-code-point/index.js +37 -0
- package/dist/node_modules/is-fullwidth-code-point/index.js.map +1 -0
- package/dist/node_modules/strnum/strnum.js +100 -0
- package/dist/node_modules/strnum/strnum.js.map +1 -0
- package/dist/providers/arxiv/client.d.ts +54 -0
- package/dist/providers/arxiv/client.d.ts.map +1 -0
- package/dist/providers/arxiv/client.js +105 -0
- package/dist/providers/arxiv/client.js.map +1 -0
- package/dist/providers/arxiv/index.d.ts +14 -0
- package/dist/providers/arxiv/index.d.ts.map +1 -0
- package/dist/providers/arxiv/parser.d.ts +16 -0
- package/dist/providers/arxiv/parser.d.ts.map +1 -0
- package/dist/providers/arxiv/parser.js +144 -0
- package/dist/providers/arxiv/parser.js.map +1 -0
- package/dist/providers/arxiv/provider.d.ts +39 -0
- package/dist/providers/arxiv/provider.d.ts.map +1 -0
- package/dist/providers/arxiv/provider.js +153 -0
- package/dist/providers/arxiv/provider.js.map +1 -0
- package/dist/providers/arxiv/translator.d.ts +17 -0
- package/dist/providers/arxiv/translator.d.ts.map +1 -0
- package/dist/providers/arxiv/translator.js +112 -0
- package/dist/providers/arxiv/translator.js.map +1 -0
- package/dist/providers/arxiv/types.d.ts +71 -0
- package/dist/providers/arxiv/types.d.ts.map +1 -0
- package/dist/providers/arxiv/types.js +17 -0
- package/dist/providers/arxiv/types.js.map +1 -0
- package/dist/providers/base/index.d.ts +16 -0
- package/dist/providers/base/index.d.ts.map +1 -0
- package/dist/providers/base/mock-provider.d.ts +76 -0
- package/dist/providers/base/mock-provider.d.ts.map +1 -0
- package/dist/providers/base/mock-provider.js +159 -0
- package/dist/providers/base/mock-provider.js.map +1 -0
- package/dist/providers/base/provider.d.ts +95 -0
- package/dist/providers/base/provider.d.ts.map +1 -0
- package/dist/providers/base/provider.js +117 -0
- package/dist/providers/base/provider.js.map +1 -0
- package/dist/providers/base/rate-limiter.d.ts +70 -0
- package/dist/providers/base/rate-limiter.d.ts.map +1 -0
- package/dist/providers/base/rate-limiter.js +111 -0
- package/dist/providers/base/rate-limiter.js.map +1 -0
- package/dist/providers/base/registry.d.ts +52 -0
- package/dist/providers/base/registry.d.ts.map +1 -0
- package/dist/providers/base/registry.js +55 -0
- package/dist/providers/base/registry.js.map +1 -0
- package/dist/providers/base/types.d.ts +163 -0
- package/dist/providers/base/types.d.ts.map +1 -0
- package/dist/providers/base/types.js +29 -0
- package/dist/providers/base/types.js.map +1 -0
- package/dist/providers/eric/client.d.ts +40 -0
- package/dist/providers/eric/client.d.ts.map +1 -0
- package/dist/providers/eric/client.js +93 -0
- package/dist/providers/eric/client.js.map +1 -0
- package/dist/providers/eric/index.d.ts +14 -0
- package/dist/providers/eric/index.d.ts.map +1 -0
- package/dist/providers/eric/parser.d.ts +21 -0
- package/dist/providers/eric/parser.d.ts.map +1 -0
- package/dist/providers/eric/parser.js +86 -0
- package/dist/providers/eric/parser.js.map +1 -0
- package/dist/providers/eric/provider.d.ts +59 -0
- package/dist/providers/eric/provider.d.ts.map +1 -0
- package/dist/providers/eric/provider.js +166 -0
- package/dist/providers/eric/provider.js.map +1 -0
- package/dist/providers/eric/translator.d.ts +12 -0
- package/dist/providers/eric/translator.d.ts.map +1 -0
- package/dist/providers/eric/translator.js +81 -0
- package/dist/providers/eric/translator.js.map +1 -0
- package/dist/providers/eric/types.d.ts +84 -0
- package/dist/providers/eric/types.d.ts.map +1 -0
- package/dist/providers/pubmed/client.d.ts +54 -0
- package/dist/providers/pubmed/client.d.ts.map +1 -0
- package/dist/providers/pubmed/client.js +141 -0
- package/dist/providers/pubmed/client.js.map +1 -0
- package/dist/providers/pubmed/index.d.ts +27 -0
- package/dist/providers/pubmed/index.d.ts.map +1 -0
- package/dist/providers/pubmed/parser.d.ts +22 -0
- package/dist/providers/pubmed/parser.d.ts.map +1 -0
- package/dist/providers/pubmed/parser.js +174 -0
- package/dist/providers/pubmed/parser.js.map +1 -0
- package/dist/providers/pubmed/provider.d.ts +44 -0
- package/dist/providers/pubmed/provider.d.ts.map +1 -0
- package/dist/providers/pubmed/provider.js +224 -0
- package/dist/providers/pubmed/provider.js.map +1 -0
- package/dist/providers/pubmed/translator.d.ts +7 -0
- package/dist/providers/pubmed/translator.d.ts.map +1 -0
- package/dist/providers/pubmed/translator.js +143 -0
- package/dist/providers/pubmed/translator.js.map +1 -0
- package/dist/providers/pubmed/types.d.ts +72 -0
- package/dist/providers/pubmed/types.d.ts.map +1 -0
- package/dist/providers/scopus/client.d.ts +64 -0
- package/dist/providers/scopus/client.d.ts.map +1 -0
- package/dist/providers/scopus/client.js +155 -0
- package/dist/providers/scopus/client.js.map +1 -0
- package/dist/providers/scopus/index.d.ts +21 -0
- package/dist/providers/scopus/index.d.ts.map +1 -0
- package/dist/providers/scopus/parser.d.ts +11 -0
- package/dist/providers/scopus/parser.d.ts.map +1 -0
- package/dist/providers/scopus/parser.js +128 -0
- package/dist/providers/scopus/parser.js.map +1 -0
- package/dist/providers/scopus/provider.d.ts +39 -0
- package/dist/providers/scopus/provider.d.ts.map +1 -0
- package/dist/providers/scopus/provider.js +175 -0
- package/dist/providers/scopus/provider.js.map +1 -0
- package/dist/providers/scopus/translator.d.ts +7 -0
- package/dist/providers/scopus/translator.d.ts.map +1 -0
- package/dist/providers/scopus/translator.js +83 -0
- package/dist/providers/scopus/translator.js.map +1 -0
- package/dist/providers/scopus/types.d.ts +86 -0
- package/dist/providers/scopus/types.d.ts.map +1 -0
- package/dist/query/index.d.ts +12 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/parser.d.ts +18 -0
- package/dist/query/parser.d.ts.map +1 -0
- package/dist/query/parser.js +16 -0
- package/dist/query/parser.js.map +1 -0
- package/dist/query/types.d.ts +85 -0
- package/dist/query/types.d.ts.map +1 -0
- package/dist/query/validator.d.ts +635 -0
- package/dist/query/validator.d.ts.map +1 -0
- package/dist/query/validator.js +117 -0
- package/dist/query/validator.js.map +1 -0
- package/dist/session/index.d.ts +14 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/logger.d.ts +15 -0
- package/dist/session/logger.d.ts.map +1 -0
- package/dist/session/logger.js +18 -0
- package/dist/session/logger.js.map +1 -0
- package/dist/session/manager.d.ts +63 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +193 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/types.d.ts +182 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/utils/deep-merge.d.ts +17 -0
- package/dist/utils/deep-merge.d.ts.map +1 -0
- package/dist/utils/deep-merge.js +23 -0
- package/dist/utils/deep-merge.js.map +1 -0
- package/dist/utils/path.d.ts +9 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +15 -0
- package/dist/utils/path.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Article } from '../base/types.js';
|
|
2
|
+
import { BaseProviderConfig } from '../base/provider.js';
|
|
3
|
+
/**
|
|
4
|
+
* arXiv category taxonomy.
|
|
5
|
+
* Format: archive.subject (e.g., cs.AI, physics.gen-ph)
|
|
6
|
+
*/
|
|
7
|
+
export type ArxivCategory = string;
|
|
8
|
+
/**
|
|
9
|
+
* Version information for an arXiv paper.
|
|
10
|
+
*/
|
|
11
|
+
export interface ArxivVersion {
|
|
12
|
+
version: string;
|
|
13
|
+
submitted: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* arXiv paper extending the base Article type.
|
|
17
|
+
* Includes arXiv-specific fields like categories and versions.
|
|
18
|
+
*/
|
|
19
|
+
export interface ArxivPaper extends Article {
|
|
20
|
+
/** arXiv identifier (e.g., 2401.12345) - required for arXiv papers */
|
|
21
|
+
arxivId: string;
|
|
22
|
+
/** arXiv source identifier */
|
|
23
|
+
source: 'arxiv';
|
|
24
|
+
/** List of arXiv categories */
|
|
25
|
+
categories: ArxivCategory[];
|
|
26
|
+
/** Primary arXiv category */
|
|
27
|
+
primaryCategory: ArxivCategory;
|
|
28
|
+
/** Version history (optional) */
|
|
29
|
+
versions?: ArxivVersion[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Response from arXiv API search.
|
|
33
|
+
* Parsed from Atom XML feed.
|
|
34
|
+
*/
|
|
35
|
+
export interface ArxivSearchResponse {
|
|
36
|
+
/** Total number of results matching the query */
|
|
37
|
+
totalResults: number;
|
|
38
|
+
/** Starting index of current page (0-based) */
|
|
39
|
+
startIndex: number;
|
|
40
|
+
/** Number of items per page */
|
|
41
|
+
itemsPerPage: number;
|
|
42
|
+
/** List of papers in current page */
|
|
43
|
+
entries: ArxivPaper[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Configuration for arXiv provider.
|
|
47
|
+
*/
|
|
48
|
+
export interface ArxivConfig extends BaseProviderConfig {
|
|
49
|
+
/** arXiv API base URL */
|
|
50
|
+
baseUrl?: string;
|
|
51
|
+
/** Maximum results to fetch (arXiv recommends max 2000 per request) */
|
|
52
|
+
maxResults?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* arXiv API requires a minimum of 3 seconds between requests.
|
|
56
|
+
* See: https://info.arxiv.org/help/api/user-manual.html#paging
|
|
57
|
+
*/
|
|
58
|
+
export declare const ARXIV_MIN_REQUEST_INTERVAL_MS = 3000;
|
|
59
|
+
/**
|
|
60
|
+
* Default arXiv configuration values.
|
|
61
|
+
*/
|
|
62
|
+
export declare const DEFAULT_ARXIV_CONFIG: Required<ArxivConfig>;
|
|
63
|
+
/**
|
|
64
|
+
* Provider-specific state for session resume.
|
|
65
|
+
* arXiv uses offset-based pagination, so we only need to track the offset.
|
|
66
|
+
*/
|
|
67
|
+
export interface ArxivProviderState {
|
|
68
|
+
/** Current pagination offset (0-based) */
|
|
69
|
+
offset: number;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/arxiv/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AAEnC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAW,SAAQ,OAAO;IACzC,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAEhB,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAC;IAEhB,+BAA+B;IAC/B,UAAU,EAAE,aAAa,EAAE,CAAC;IAE5B,6BAA6B;IAC7B,eAAe,EAAE,aAAa,CAAC;IAE/B,iCAAiC;IACjC,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAC;IAErB,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IAEnB,+BAA+B;IAC/B,YAAY,EAAE,MAAM,CAAC;IAErB,qCAAqC;IACrC,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,kBAAkB;IACrD,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,WAAW,CAStD,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const ARXIV_MIN_REQUEST_INTERVAL_MS = 3e3;
|
|
2
|
+
const DEFAULT_ARXIV_CONFIG = {
|
|
3
|
+
baseUrl: "https://export.arxiv.org/api/query",
|
|
4
|
+
// Convert interval to requests per second: 1000ms / 3000ms = 0.33 req/s
|
|
5
|
+
rateLimit: 1e3 / ARXIV_MIN_REQUEST_INTERVAL_MS,
|
|
6
|
+
timeout: 6e4,
|
|
7
|
+
// arXiv can be slow
|
|
8
|
+
retries: 3,
|
|
9
|
+
maxResults: 1e4,
|
|
10
|
+
initialBackoff: 1e3,
|
|
11
|
+
maxBackoff: 3e4
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
ARXIV_MIN_REQUEST_INTERVAL_MS,
|
|
15
|
+
DEFAULT_ARXIV_CONFIG
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../src/providers/arxiv/types.ts"],"sourcesContent":["/**\n * arXiv Provider Types\n *\n * Types for interacting with the arXiv API.\n * arXiv hosts preprints in physics, mathematics, computer science,\n * quantitative biology, and related fields.\n */\n\nimport type { Article } from '../base/types.js';\nimport type { BaseProviderConfig } from '../base/provider.js';\n\n/**\n * arXiv category taxonomy.\n * Format: archive.subject (e.g., cs.AI, physics.gen-ph)\n */\nexport type ArxivCategory = string;\n\n/**\n * Version information for an arXiv paper.\n */\nexport interface ArxivVersion {\n version: string;\n submitted: string;\n}\n\n/**\n * arXiv paper extending the base Article type.\n * Includes arXiv-specific fields like categories and versions.\n */\nexport interface ArxivPaper extends Article {\n /** arXiv identifier (e.g., 2401.12345) - required for arXiv papers */\n arxivId: string;\n\n /** arXiv source identifier */\n source: 'arxiv';\n\n /** List of arXiv categories */\n categories: ArxivCategory[];\n\n /** Primary arXiv category */\n primaryCategory: ArxivCategory;\n\n /** Version history (optional) */\n versions?: ArxivVersion[];\n}\n\n/**\n * Response from arXiv API search.\n * Parsed from Atom XML feed.\n */\nexport interface ArxivSearchResponse {\n /** Total number of results matching the query */\n totalResults: number;\n\n /** Starting index of current page (0-based) */\n startIndex: number;\n\n /** Number of items per page */\n itemsPerPage: number;\n\n /** List of papers in current page */\n entries: ArxivPaper[];\n}\n\n/**\n * Configuration for arXiv provider.\n */\nexport interface ArxivConfig extends BaseProviderConfig {\n /** arXiv API base URL */\n baseUrl?: string;\n\n /** Maximum results to fetch (arXiv recommends max 2000 per request) */\n maxResults?: number;\n}\n\n/**\n * arXiv API requires a minimum of 3 seconds between requests.\n * See: https://info.arxiv.org/help/api/user-manual.html#paging\n */\nexport const ARXIV_MIN_REQUEST_INTERVAL_MS = 3000;\n\n/**\n * Default arXiv configuration values.\n */\nexport const DEFAULT_ARXIV_CONFIG: Required<ArxivConfig> = {\n baseUrl: 'https://export.arxiv.org/api/query',\n // Convert interval to requests per second: 1000ms / 3000ms = 0.33 req/s\n rateLimit: 1000 / ARXIV_MIN_REQUEST_INTERVAL_MS,\n timeout: 60000, // arXiv can be slow\n retries: 3,\n maxResults: 10000,\n initialBackoff: 1000,\n maxBackoff: 30000,\n};\n\n/**\n * Provider-specific state for session resume.\n * arXiv uses offset-based pagination, so we only need to track the offset.\n */\nexport interface ArxivProviderState {\n /** Current pagination offset (0-based) */\n offset: number;\n}\n"],"names":[],"mappings":"AA+EO,MAAM,gCAAgC;AAKtC,MAAM,uBAA8C;AAAA,EACzD,SAAS;AAAA;AAAA,EAET,WAAW,MAAO;AAAA,EAClB,SAAS;AAAA;AAAA,EACT,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,YAAY;AACd;"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider base module.
|
|
3
|
+
*
|
|
4
|
+
* Provides the foundation for implementing database providers.
|
|
5
|
+
*/
|
|
6
|
+
export type { ProviderName, Author, Article, TranslatedQuery, SearchOptions, Provider, ProviderError, RateLimitError, AuthError, QueryAST, ProviderErrorCode, SearchState, SearchResumeResult, } from './types';
|
|
7
|
+
export { createProviderError, isProviderError, isRateLimitError, isAuthError, } from './types';
|
|
8
|
+
export { BaseProvider, serializeState, deserializeState } from './provider';
|
|
9
|
+
export type { BaseProviderConfig } from './provider';
|
|
10
|
+
export { RateLimiter } from './rate-limiter';
|
|
11
|
+
export type { RateLimiterOptions } from './rate-limiter';
|
|
12
|
+
export { ProviderRegistry, createProviderRegistry, globalRegistry } from './registry';
|
|
13
|
+
export type { ProviderFactory } from './registry';
|
|
14
|
+
export { MockProvider } from './mock-provider';
|
|
15
|
+
export type { MockProviderOptions } from './mock-provider';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/base/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EACV,YAAY,EACZ,MAAM,EACN,OAAO,EACP,eAAe,EACf,aAAa,EACb,QAAQ,EACR,aAAa,EACb,cAAc,EACd,SAAS,EACT,QAAQ,EACR,iBAAiB,EACjB,WAAW,EACX,kBAAkB,GACnB,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,WAAW,GACZ,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC5E,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtF,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { BaseProvider, BaseProviderConfig } from './provider';
|
|
2
|
+
import { ProviderName, Article, TranslatedQuery, SearchOptions, QueryAST, ProviderError, SearchState, SearchResumeResult } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for MockProvider.
|
|
5
|
+
*/
|
|
6
|
+
export interface MockProviderOptions extends BaseProviderConfig {
|
|
7
|
+
/** Provider name to use */
|
|
8
|
+
name?: ProviderName;
|
|
9
|
+
/** Articles to return from search */
|
|
10
|
+
articles?: Article[];
|
|
11
|
+
/** Pre-configured translated query response */
|
|
12
|
+
translatedQuery?: TranslatedQuery;
|
|
13
|
+
/** Connection status to return from testConnection */
|
|
14
|
+
connectionStatus?: boolean;
|
|
15
|
+
/** Delay in ms before returning search results */
|
|
16
|
+
searchDelay?: number;
|
|
17
|
+
/** Error to throw on search */
|
|
18
|
+
searchError?: ProviderError;
|
|
19
|
+
/** State validation result to return */
|
|
20
|
+
stateValidation?: SearchResumeResult;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Mock provider for testing.
|
|
24
|
+
*
|
|
25
|
+
* Allows configuring:
|
|
26
|
+
* - Articles to return from search
|
|
27
|
+
* - Translated query response
|
|
28
|
+
* - Connection status
|
|
29
|
+
* - Search delay (for simulating network latency)
|
|
30
|
+
* - Search errors (for testing error handling)
|
|
31
|
+
*/
|
|
32
|
+
export declare class MockProvider extends BaseProvider {
|
|
33
|
+
readonly name: ProviderName;
|
|
34
|
+
private mockArticles;
|
|
35
|
+
private translatedQueryResponse;
|
|
36
|
+
private connectionStatus;
|
|
37
|
+
private searchDelay;
|
|
38
|
+
private searchError;
|
|
39
|
+
private currentSearchState;
|
|
40
|
+
private stateValidationResult;
|
|
41
|
+
private currentRetrievedCount;
|
|
42
|
+
constructor(options?: MockProviderOptions);
|
|
43
|
+
search(_query: TranslatedQuery, options?: SearchOptions): AsyncIterable<Article>;
|
|
44
|
+
translateQuery(ast: QueryAST): TranslatedQuery;
|
|
45
|
+
testConnection(): Promise<boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* Update the articles to return.
|
|
48
|
+
*/
|
|
49
|
+
setArticles(articles: Article[]): void;
|
|
50
|
+
/**
|
|
51
|
+
* Set the error to throw on search.
|
|
52
|
+
*/
|
|
53
|
+
setSearchError(error: ProviderError | null): void;
|
|
54
|
+
/**
|
|
55
|
+
* Set the connection status.
|
|
56
|
+
*/
|
|
57
|
+
setConnectionStatus(status: boolean): void;
|
|
58
|
+
/**
|
|
59
|
+
* Get current search state for session persistence.
|
|
60
|
+
*/
|
|
61
|
+
getSearchState(): SearchState | null;
|
|
62
|
+
/**
|
|
63
|
+
* Resume search from saved state.
|
|
64
|
+
*/
|
|
65
|
+
resumeSearch(state: SearchState): AsyncIterable<Article>;
|
|
66
|
+
/**
|
|
67
|
+
* Validate if state is still valid for resuming.
|
|
68
|
+
*/
|
|
69
|
+
validateState(_state: SearchState): Promise<SearchResumeResult>;
|
|
70
|
+
/**
|
|
71
|
+
* Set the state validation result.
|
|
72
|
+
*/
|
|
73
|
+
setStateValidation(result: SearchResumeResult): void;
|
|
74
|
+
private delay;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=mock-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-provider.d.ts","sourceRoot":"","sources":["../../../src/providers/base/mock-provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EACV,YAAY,EACZ,OAAO,EACP,eAAe,EACf,aAAa,EACb,QAAQ,EACR,aAAa,EACb,WAAW,EACX,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC7D,2BAA2B;IAC3B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,+CAA+C;IAC/C,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,wCAAwC;IACxC,eAAe,CAAC,EAAE,kBAAkB,CAAC;CACtC;AAwBD;;;;;;;;;GASG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAC5C,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,OAAO,CAAC,YAAY,CAAY;IAChC,OAAO,CAAC,uBAAuB,CAAkB;IACjD,OAAO,CAAC,gBAAgB,CAAU;IAClC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,qBAAqB,CAAqB;IAClD,OAAO,CAAC,qBAAqB,CAAK;gBAEtB,OAAO,GAAE,mBAAwB;IA2BtC,MAAM,CACX,MAAM,EAAE,eAAe,EACvB,OAAO,CAAC,EAAE,aAAa,GACtB,aAAa,CAAC,OAAO,CAAC;IA8BzB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,eAAe;IAOxC,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAIxC;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI;IAOtC;;OAEG;IACH,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI;IAIjD;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAI1C;;OAEG;IACH,cAAc,IAAI,WAAW,GAAG,IAAI;IAIpC;;OAEG;IACI,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC;IAsC/D;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAIrE;;OAEG;IACH,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAIpD,OAAO,CAAC,KAAK;CAGd"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { BaseProvider } from "./provider.js";
|
|
2
|
+
const DEFAULT_ARTICLES = [
|
|
3
|
+
{
|
|
4
|
+
doi: "10.1234/mock-article-1",
|
|
5
|
+
title: "Mock Article 1",
|
|
6
|
+
authors: [{ family: "Mock", given: "Author" }],
|
|
7
|
+
source: "pubmed",
|
|
8
|
+
retrievedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9
|
+
abstract: "This is a mock article for testing purposes."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
doi: "10.1234/mock-article-2",
|
|
13
|
+
title: "Mock Article 2",
|
|
14
|
+
authors: [{ family: "Test", given: "Researcher" }],
|
|
15
|
+
source: "pubmed",
|
|
16
|
+
retrievedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17
|
+
abstract: "Another mock article for testing."
|
|
18
|
+
}
|
|
19
|
+
];
|
|
20
|
+
class MockProvider extends BaseProvider {
|
|
21
|
+
name;
|
|
22
|
+
mockArticles;
|
|
23
|
+
translatedQueryResponse;
|
|
24
|
+
connectionStatus;
|
|
25
|
+
searchDelay;
|
|
26
|
+
searchError;
|
|
27
|
+
currentSearchState = null;
|
|
28
|
+
stateValidationResult;
|
|
29
|
+
currentRetrievedCount = 0;
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
super(options);
|
|
32
|
+
this.name = options.name ?? "pubmed";
|
|
33
|
+
this.mockArticles = options.articles ?? [...DEFAULT_ARTICLES];
|
|
34
|
+
this.translatedQueryResponse = options.translatedQuery ?? {
|
|
35
|
+
native: "mock query",
|
|
36
|
+
originalAst: {
|
|
37
|
+
name: "mock-query",
|
|
38
|
+
blocks: [],
|
|
39
|
+
filters: {},
|
|
40
|
+
overrides: {}
|
|
41
|
+
},
|
|
42
|
+
provider: this.name
|
|
43
|
+
};
|
|
44
|
+
this.connectionStatus = options.connectionStatus ?? true;
|
|
45
|
+
this.searchDelay = options.searchDelay ?? 0;
|
|
46
|
+
this.searchError = options.searchError ?? null;
|
|
47
|
+
this.stateValidationResult = options.stateValidation ?? { valid: true };
|
|
48
|
+
this.mockArticles = this.mockArticles.map((article) => ({
|
|
49
|
+
...article,
|
|
50
|
+
source: this.name
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
async *search(_query, options) {
|
|
54
|
+
if (this.searchError !== null) {
|
|
55
|
+
throw this.searchError;
|
|
56
|
+
}
|
|
57
|
+
if (this.searchDelay > 0) {
|
|
58
|
+
await this.delay(this.searchDelay);
|
|
59
|
+
}
|
|
60
|
+
const maxResults = options?.maxResults ?? this.mockArticles.length;
|
|
61
|
+
let count = 0;
|
|
62
|
+
for (const article of this.mockArticles) {
|
|
63
|
+
if (count >= maxResults) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
if (options?.signal?.aborted) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
yield article;
|
|
70
|
+
count++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
translateQuery(ast) {
|
|
74
|
+
return {
|
|
75
|
+
...this.translatedQueryResponse,
|
|
76
|
+
originalAst: ast
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async testConnection() {
|
|
80
|
+
return this.connectionStatus;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Update the articles to return.
|
|
84
|
+
*/
|
|
85
|
+
setArticles(articles) {
|
|
86
|
+
this.mockArticles = articles.map((article) => ({
|
|
87
|
+
...article,
|
|
88
|
+
source: this.name
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Set the error to throw on search.
|
|
93
|
+
*/
|
|
94
|
+
setSearchError(error) {
|
|
95
|
+
this.searchError = error;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Set the connection status.
|
|
99
|
+
*/
|
|
100
|
+
setConnectionStatus(status) {
|
|
101
|
+
this.connectionStatus = status;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get current search state for session persistence.
|
|
105
|
+
*/
|
|
106
|
+
getSearchState() {
|
|
107
|
+
return this.currentSearchState;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resume search from saved state.
|
|
111
|
+
*/
|
|
112
|
+
async *resumeSearch(state) {
|
|
113
|
+
const providerState = state.providerState;
|
|
114
|
+
const offset = providerState?.offset ?? state.retrievedCount;
|
|
115
|
+
if (this.searchError !== null) {
|
|
116
|
+
throw this.searchError;
|
|
117
|
+
}
|
|
118
|
+
if (this.searchDelay > 0) {
|
|
119
|
+
await this.delay(this.searchDelay);
|
|
120
|
+
}
|
|
121
|
+
this.currentSearchState = {
|
|
122
|
+
...state,
|
|
123
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
124
|
+
};
|
|
125
|
+
this.currentRetrievedCount = offset;
|
|
126
|
+
for (let i = offset; i < this.mockArticles.length; i++) {
|
|
127
|
+
const article = this.mockArticles[i];
|
|
128
|
+
if (article) {
|
|
129
|
+
this.currentRetrievedCount++;
|
|
130
|
+
this.currentSearchState = {
|
|
131
|
+
...this.currentSearchState,
|
|
132
|
+
retrievedCount: this.currentRetrievedCount,
|
|
133
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
134
|
+
providerState: { offset: this.currentRetrievedCount }
|
|
135
|
+
};
|
|
136
|
+
yield article;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Validate if state is still valid for resuming.
|
|
142
|
+
*/
|
|
143
|
+
async validateState(_state) {
|
|
144
|
+
return this.stateValidationResult;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Set the state validation result.
|
|
148
|
+
*/
|
|
149
|
+
setStateValidation(result) {
|
|
150
|
+
this.stateValidationResult = result;
|
|
151
|
+
}
|
|
152
|
+
delay(ms) {
|
|
153
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
MockProvider
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=mock-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-provider.js","sources":["../../../src/providers/base/mock-provider.ts"],"sourcesContent":["/**\n * Mock provider for testing.\n *\n * Provides configurable responses for testing search orchestration\n * and other components that depend on providers.\n */\n\nimport { BaseProvider } from './provider';\nimport type { BaseProviderConfig } from './provider';\nimport type {\n ProviderName,\n Article,\n TranslatedQuery,\n SearchOptions,\n QueryAST,\n ProviderError,\n SearchState,\n SearchResumeResult,\n} from './types';\n\n/**\n * Configuration options for MockProvider.\n */\nexport interface MockProviderOptions extends BaseProviderConfig {\n /** Provider name to use */\n name?: ProviderName;\n /** Articles to return from search */\n articles?: Article[];\n /** Pre-configured translated query response */\n translatedQuery?: TranslatedQuery;\n /** Connection status to return from testConnection */\n connectionStatus?: boolean;\n /** Delay in ms before returning search results */\n searchDelay?: number;\n /** Error to throw on search */\n searchError?: ProviderError;\n /** State validation result to return */\n stateValidation?: SearchResumeResult;\n}\n\n/**\n * Default mock articles.\n */\nconst DEFAULT_ARTICLES: Article[] = [\n {\n doi: '10.1234/mock-article-1',\n title: 'Mock Article 1',\n authors: [{ family: 'Mock', given: 'Author' }],\n source: 'pubmed',\n retrievedAt: new Date().toISOString(),\n abstract: 'This is a mock article for testing purposes.',\n },\n {\n doi: '10.1234/mock-article-2',\n title: 'Mock Article 2',\n authors: [{ family: 'Test', given: 'Researcher' }],\n source: 'pubmed',\n retrievedAt: new Date().toISOString(),\n abstract: 'Another mock article for testing.',\n },\n];\n\n/**\n * Mock provider for testing.\n *\n * Allows configuring:\n * - Articles to return from search\n * - Translated query response\n * - Connection status\n * - Search delay (for simulating network latency)\n * - Search errors (for testing error handling)\n */\nexport class MockProvider extends BaseProvider {\n readonly name: ProviderName;\n private mockArticles: Article[];\n private translatedQueryResponse: TranslatedQuery;\n private connectionStatus: boolean;\n private searchDelay: number;\n private searchError: ProviderError | null;\n private currentSearchState: SearchState | null = null;\n private stateValidationResult: SearchResumeResult;\n private currentRetrievedCount = 0;\n\n constructor(options: MockProviderOptions = {}) {\n super(options);\n\n this.name = options.name ?? 'pubmed';\n this.mockArticles = options.articles ?? [...DEFAULT_ARTICLES];\n this.translatedQueryResponse = options.translatedQuery ?? {\n native: 'mock query',\n originalAst: {\n name: 'mock-query',\n blocks: [],\n filters: {},\n overrides: {},\n },\n provider: this.name,\n };\n this.connectionStatus = options.connectionStatus ?? true;\n this.searchDelay = options.searchDelay ?? 0;\n this.searchError = options.searchError ?? null;\n this.stateValidationResult = options.stateValidation ?? { valid: true };\n\n // Update articles source to match provider name\n this.mockArticles = this.mockArticles.map((article) => ({\n ...article,\n source: this.name,\n }));\n }\n\n async *search(\n _query: TranslatedQuery,\n options?: SearchOptions\n ): AsyncIterable<Article> {\n // Throw configured error if set\n if (this.searchError !== null) {\n throw this.searchError;\n }\n\n // Simulate network delay\n if (this.searchDelay > 0) {\n await this.delay(this.searchDelay);\n }\n\n // Determine how many articles to yield\n const maxResults = options?.maxResults ?? this.mockArticles.length;\n let count = 0;\n\n for (const article of this.mockArticles) {\n if (count >= maxResults) {\n break;\n }\n\n // Check for abort signal\n if (options?.signal?.aborted) {\n break;\n }\n\n yield article;\n count++;\n }\n }\n\n translateQuery(ast: QueryAST): TranslatedQuery {\n return {\n ...this.translatedQueryResponse,\n originalAst: ast,\n };\n }\n\n async testConnection(): Promise<boolean> {\n return this.connectionStatus;\n }\n\n /**\n * Update the articles to return.\n */\n setArticles(articles: Article[]): void {\n this.mockArticles = articles.map((article) => ({\n ...article,\n source: this.name,\n }));\n }\n\n /**\n * Set the error to throw on search.\n */\n setSearchError(error: ProviderError | null): void {\n this.searchError = error;\n }\n\n /**\n * Set the connection status.\n */\n setConnectionStatus(status: boolean): void {\n this.connectionStatus = status;\n }\n\n /**\n * Get current search state for session persistence.\n */\n getSearchState(): SearchState | null {\n return this.currentSearchState;\n }\n\n /**\n * Resume search from saved state.\n */\n async *resumeSearch(state: SearchState): AsyncIterable<Article> {\n // Extract offset from provider state\n const providerState = state.providerState as { offset?: number } | undefined;\n const offset = providerState?.offset ?? state.retrievedCount;\n\n // Throw configured error if set\n if (this.searchError !== null) {\n throw this.searchError;\n }\n\n // Simulate network delay\n if (this.searchDelay > 0) {\n await this.delay(this.searchDelay);\n }\n\n // Update current state\n this.currentSearchState = {\n ...state,\n lastUpdated: new Date(),\n };\n this.currentRetrievedCount = offset;\n\n // Yield remaining articles starting from offset\n for (let i = offset; i < this.mockArticles.length; i++) {\n const article = this.mockArticles[i];\n if (article) {\n this.currentRetrievedCount++;\n this.currentSearchState = {\n ...this.currentSearchState,\n retrievedCount: this.currentRetrievedCount,\n lastUpdated: new Date(),\n providerState: { offset: this.currentRetrievedCount },\n };\n yield article;\n }\n }\n }\n\n /**\n * Validate if state is still valid for resuming.\n */\n async validateState(_state: SearchState): Promise<SearchResumeResult> {\n return this.stateValidationResult;\n }\n\n /**\n * Set the state validation result.\n */\n setStateValidation(result: SearchResumeResult): void {\n this.stateValidationResult = result;\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { RateLimiter } from './rate-limiter';
|
|
2
|
+
import { Provider, ProviderName, Article, TranslatedQuery, SearchOptions, QueryAST, SearchState, SearchResumeResult } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for BaseProvider.
|
|
5
|
+
*/
|
|
6
|
+
export interface BaseProviderConfig {
|
|
7
|
+
/** Requests per second rate limit */
|
|
8
|
+
rateLimit?: number;
|
|
9
|
+
/** Request timeout in milliseconds */
|
|
10
|
+
timeout?: number;
|
|
11
|
+
/** Number of retry attempts */
|
|
12
|
+
retries?: number;
|
|
13
|
+
/** Initial backoff time in ms for exponential backoff */
|
|
14
|
+
initialBackoff?: number;
|
|
15
|
+
/** Maximum backoff time in ms */
|
|
16
|
+
maxBackoff?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Abstract base class for database providers.
|
|
20
|
+
*
|
|
21
|
+
* Provides common infrastructure:
|
|
22
|
+
* - Rate limiting
|
|
23
|
+
* - Configuration management
|
|
24
|
+
* - Retry logic (implemented in subclass)
|
|
25
|
+
*/
|
|
26
|
+
export declare abstract class BaseProvider implements Provider {
|
|
27
|
+
/** Provider name identifier */
|
|
28
|
+
abstract readonly name: ProviderName;
|
|
29
|
+
/** Rate limiter instance */
|
|
30
|
+
protected readonly rateLimiter: RateLimiter;
|
|
31
|
+
/** Merged configuration */
|
|
32
|
+
protected readonly config: Required<BaseProviderConfig>;
|
|
33
|
+
constructor(config?: BaseProviderConfig);
|
|
34
|
+
/**
|
|
35
|
+
* Execute search and return results as async iterable (streaming).
|
|
36
|
+
*/
|
|
37
|
+
abstract search(query: TranslatedQuery, options?: SearchOptions): AsyncIterable<Article>;
|
|
38
|
+
/**
|
|
39
|
+
* Convert QueryAST to database-native syntax.
|
|
40
|
+
*/
|
|
41
|
+
abstract translateQuery(ast: QueryAST): TranslatedQuery;
|
|
42
|
+
/**
|
|
43
|
+
* Verify API access and credentials.
|
|
44
|
+
* Returns false on failure (doesn't throw).
|
|
45
|
+
*/
|
|
46
|
+
abstract testConnection(): Promise<boolean>;
|
|
47
|
+
/**
|
|
48
|
+
* Get the current search state for session persistence.
|
|
49
|
+
* Returns null if no search is in progress.
|
|
50
|
+
*/
|
|
51
|
+
abstract getSearchState(): SearchState | null;
|
|
52
|
+
/**
|
|
53
|
+
* Resume a search from a saved state.
|
|
54
|
+
* Continues yielding articles from where the previous search left off.
|
|
55
|
+
*/
|
|
56
|
+
abstract resumeSearch(state: SearchState): AsyncIterable<Article>;
|
|
57
|
+
/**
|
|
58
|
+
* Validate if a saved state is still valid for resuming.
|
|
59
|
+
* Some providers (e.g., PubMed) have server-side state that can expire.
|
|
60
|
+
*/
|
|
61
|
+
abstract validateState(state: SearchState): Promise<SearchResumeResult>;
|
|
62
|
+
/**
|
|
63
|
+
* Create a base SearchState with common fields.
|
|
64
|
+
* Subclasses can extend this with provider-specific state.
|
|
65
|
+
*/
|
|
66
|
+
protected createBaseState(query: TranslatedQuery, totalResults: number, retrievedCount: number): SearchState;
|
|
67
|
+
/**
|
|
68
|
+
* Execute a function with retry logic.
|
|
69
|
+
*
|
|
70
|
+
* Retries on network errors and server errors.
|
|
71
|
+
* Does not retry on auth errors.
|
|
72
|
+
* Uses exponential backoff between retries.
|
|
73
|
+
* Respects rate limit error's retryAfter if provided.
|
|
74
|
+
*/
|
|
75
|
+
protected withRetry<T>(fn: () => Promise<T>): Promise<T>;
|
|
76
|
+
/**
|
|
77
|
+
* Determine if an error should trigger a retry.
|
|
78
|
+
*/
|
|
79
|
+
private shouldRetry;
|
|
80
|
+
/**
|
|
81
|
+
* Sleep for specified milliseconds.
|
|
82
|
+
*/
|
|
83
|
+
private sleep;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Serialize a SearchState to a JSON string.
|
|
87
|
+
* Handles Date conversion to ISO 8601 string.
|
|
88
|
+
*/
|
|
89
|
+
export declare function serializeState(state: SearchState): string;
|
|
90
|
+
/**
|
|
91
|
+
* Deserialize a JSON string to a SearchState.
|
|
92
|
+
* Handles Date conversion from ISO 8601 string.
|
|
93
|
+
*/
|
|
94
|
+
export declare function deserializeState(json: string): SearchState;
|
|
95
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/providers/base/provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,eAAe,EACf,aAAa,EACb,QAAQ,EAER,WAAW,EACX,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAGjB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD;;;;;;;GAOG;AACH,8BAAsB,YAAa,YAAW,QAAQ;IACpD,+BAA+B;IAC/B,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAErC,4BAA4B;IAC5B,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAE5C,2BAA2B;IAC3B,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;gBAE5C,MAAM,GAAE,kBAAuB;IAc3C;;OAEG;IACH,QAAQ,CAAC,MAAM,CACb,KAAK,EAAE,eAAe,EACtB,OAAO,CAAC,EAAE,aAAa,GACtB,aAAa,CAAC,OAAO,CAAC;IAEzB;;OAEG;IACH,QAAQ,CAAC,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,eAAe;IAEvD;;;OAGG;IACH,QAAQ,CAAC,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAE3C;;;OAGG;IACH,QAAQ,CAAC,cAAc,IAAI,WAAW,GAAG,IAAI;IAE7C;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC;IAEjE;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAEvE;;;OAGG;IACH,SAAS,CAAC,eAAe,CACvB,KAAK,EAAE,eAAe,EACtB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,GACrB,WAAW;IAUd;;;;;;;OAOG;cACa,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAqC9D;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;OAEG;IACH,OAAO,CAAC,KAAK;CAGd;AAcD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAUzD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAU1D"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { RateLimiter } from "./rate-limiter.js";
|
|
2
|
+
import { isRateLimitError, isProviderError } from "./types.js";
|
|
3
|
+
const DEFAULT_CONFIG = {
|
|
4
|
+
rateLimit: 3,
|
|
5
|
+
timeout: 3e4,
|
|
6
|
+
retries: 3,
|
|
7
|
+
initialBackoff: 1e3,
|
|
8
|
+
maxBackoff: 6e4
|
|
9
|
+
};
|
|
10
|
+
class BaseProvider {
|
|
11
|
+
/** Rate limiter instance */
|
|
12
|
+
rateLimiter;
|
|
13
|
+
/** Merged configuration */
|
|
14
|
+
config;
|
|
15
|
+
constructor(config = {}) {
|
|
16
|
+
this.config = {
|
|
17
|
+
...DEFAULT_CONFIG,
|
|
18
|
+
...config
|
|
19
|
+
};
|
|
20
|
+
this.rateLimiter = new RateLimiter({
|
|
21
|
+
tokensPerSecond: this.config.rateLimit,
|
|
22
|
+
burstSize: this.config.rateLimit,
|
|
23
|
+
initialBackoff: this.config.initialBackoff,
|
|
24
|
+
maxBackoff: this.config.maxBackoff
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a base SearchState with common fields.
|
|
29
|
+
* Subclasses can extend this with provider-specific state.
|
|
30
|
+
*/
|
|
31
|
+
createBaseState(query, totalResults, retrievedCount) {
|
|
32
|
+
return {
|
|
33
|
+
provider: this.name,
|
|
34
|
+
query,
|
|
35
|
+
totalResults,
|
|
36
|
+
retrievedCount,
|
|
37
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Execute a function with retry logic.
|
|
42
|
+
*
|
|
43
|
+
* Retries on network errors and server errors.
|
|
44
|
+
* Does not retry on auth errors.
|
|
45
|
+
* Uses exponential backoff between retries.
|
|
46
|
+
* Respects rate limit error's retryAfter if provided.
|
|
47
|
+
*/
|
|
48
|
+
async withRetry(fn) {
|
|
49
|
+
let lastError;
|
|
50
|
+
let currentBackoff = this.config.initialBackoff;
|
|
51
|
+
for (let attempt = 0; attempt <= this.config.retries; attempt++) {
|
|
52
|
+
try {
|
|
53
|
+
return await fn();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
lastError = error;
|
|
56
|
+
if (!this.shouldRetry(error)) {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
if (attempt >= this.config.retries) {
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
let waitTime;
|
|
63
|
+
if (isRateLimitError(error) && "retryAfter" in error && typeof error.retryAfter === "number") {
|
|
64
|
+
waitTime = error.retryAfter;
|
|
65
|
+
} else {
|
|
66
|
+
waitTime = currentBackoff;
|
|
67
|
+
currentBackoff = Math.min(currentBackoff * 2, this.config.maxBackoff);
|
|
68
|
+
}
|
|
69
|
+
await this.sleep(waitTime);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw lastError;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Determine if an error should trigger a retry.
|
|
76
|
+
*/
|
|
77
|
+
shouldRetry(error) {
|
|
78
|
+
if (isProviderError(error)) {
|
|
79
|
+
return error.retryable;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Sleep for specified milliseconds.
|
|
85
|
+
*/
|
|
86
|
+
sleep(ms) {
|
|
87
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function serializeState(state) {
|
|
91
|
+
const serialized = {
|
|
92
|
+
provider: state.provider,
|
|
93
|
+
query: state.query,
|
|
94
|
+
totalResults: state.totalResults,
|
|
95
|
+
retrievedCount: state.retrievedCount,
|
|
96
|
+
lastUpdated: state.lastUpdated.toISOString(),
|
|
97
|
+
providerState: state.providerState
|
|
98
|
+
};
|
|
99
|
+
return JSON.stringify(serialized);
|
|
100
|
+
}
|
|
101
|
+
function deserializeState(json) {
|
|
102
|
+
const parsed = JSON.parse(json);
|
|
103
|
+
return {
|
|
104
|
+
provider: parsed.provider,
|
|
105
|
+
query: parsed.query,
|
|
106
|
+
totalResults: parsed.totalResults,
|
|
107
|
+
retrievedCount: parsed.retrievedCount,
|
|
108
|
+
lastUpdated: new Date(parsed.lastUpdated),
|
|
109
|
+
providerState: parsed.providerState
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
BaseProvider,
|
|
114
|
+
deserializeState,
|
|
115
|
+
serializeState
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sources":["../../../src/providers/base/provider.ts"],"sourcesContent":["/**\n * Abstract base class for database providers.\n */\n\nimport { RateLimiter } from './rate-limiter';\nimport type {\n Provider,\n ProviderName,\n Article,\n TranslatedQuery,\n SearchOptions,\n QueryAST,\n ProviderError,\n SearchState,\n SearchResumeResult,\n} from './types';\nimport { isProviderError, isRateLimitError } from './types';\n\n/**\n * Configuration options for BaseProvider.\n */\nexport interface BaseProviderConfig {\n /** Requests per second rate limit */\n rateLimit?: number;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Number of retry attempts */\n retries?: number;\n /** Initial backoff time in ms for exponential backoff */\n initialBackoff?: number;\n /** Maximum backoff time in ms */\n maxBackoff?: number;\n}\n\nconst DEFAULT_CONFIG: Required<BaseProviderConfig> = {\n rateLimit: 3,\n timeout: 30000,\n retries: 3,\n initialBackoff: 1000,\n maxBackoff: 60000,\n};\n\n/**\n * Abstract base class for database providers.\n *\n * Provides common infrastructure:\n * - Rate limiting\n * - Configuration management\n * - Retry logic (implemented in subclass)\n */\nexport abstract class BaseProvider implements Provider {\n /** Provider name identifier */\n abstract readonly name: ProviderName;\n\n /** Rate limiter instance */\n protected readonly rateLimiter: RateLimiter;\n\n /** Merged configuration */\n protected readonly config: Required<BaseProviderConfig>;\n\n constructor(config: BaseProviderConfig = {}) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n\n this.rateLimiter = new RateLimiter({\n tokensPerSecond: this.config.rateLimit,\n burstSize: this.config.rateLimit,\n initialBackoff: this.config.initialBackoff,\n maxBackoff: this.config.maxBackoff,\n });\n }\n\n /**\n * Execute search and return results as async iterable (streaming).\n */\n abstract search(\n query: TranslatedQuery,\n options?: SearchOptions\n ): AsyncIterable<Article>;\n\n /**\n * Convert QueryAST to database-native syntax.\n */\n abstract translateQuery(ast: QueryAST): TranslatedQuery;\n\n /**\n * Verify API access and credentials.\n * Returns false on failure (doesn't throw).\n */\n abstract testConnection(): Promise<boolean>;\n\n /**\n * Get the current search state for session persistence.\n * Returns null if no search is in progress.\n */\n abstract getSearchState(): SearchState | null;\n\n /**\n * Resume a search from a saved state.\n * Continues yielding articles from where the previous search left off.\n */\n abstract resumeSearch(state: SearchState): AsyncIterable<Article>;\n\n /**\n * Validate if a saved state is still valid for resuming.\n * Some providers (e.g., PubMed) have server-side state that can expire.\n */\n abstract validateState(state: SearchState): Promise<SearchResumeResult>;\n\n /**\n * Create a base SearchState with common fields.\n * Subclasses can extend this with provider-specific state.\n */\n protected createBaseState(\n query: TranslatedQuery,\n totalResults: number,\n retrievedCount: number\n ): SearchState {\n return {\n provider: this.name,\n query,\n totalResults,\n retrievedCount,\n lastUpdated: new Date(),\n };\n }\n\n /**\n * Execute a function with retry logic.\n *\n * Retries on network errors and server errors.\n * Does not retry on auth errors.\n * Uses exponential backoff between retries.\n * Respects rate limit error's retryAfter if provided.\n */\n protected async withRetry<T>(fn: () => Promise<T>): Promise<T> {\n let lastError: ProviderError | Error | undefined;\n let currentBackoff = this.config.initialBackoff;\n\n for (let attempt = 0; attempt <= this.config.retries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error as ProviderError | Error;\n\n // Check if we should retry\n if (!this.shouldRetry(error)) {\n throw error;\n }\n\n // If we've exhausted retries, throw\n if (attempt >= this.config.retries) {\n throw error;\n }\n\n // Calculate wait time\n let waitTime: number;\n if (isRateLimitError(error) && 'retryAfter' in error && typeof error.retryAfter === 'number') {\n waitTime = error.retryAfter;\n } else {\n waitTime = currentBackoff;\n currentBackoff = Math.min(currentBackoff * 2, this.config.maxBackoff);\n }\n\n await this.sleep(waitTime);\n }\n }\n\n // This should never be reached, but TypeScript needs it\n throw lastError;\n }\n\n /**\n * Determine if an error should trigger a retry.\n */\n private shouldRetry(error: unknown): boolean {\n if (isProviderError(error)) {\n return error.retryable;\n }\n\n // Treat unknown errors as non-retryable\n return false;\n }\n\n /**\n * Sleep for specified milliseconds.\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n/**\n * Serialized search state for JSON storage.\n */\ninterface SerializedSearchState {\n provider: string;\n query: TranslatedQuery;\n totalResults: number;\n retrievedCount: number;\n lastUpdated: string; // ISO 8601 string\n providerState?: unknown;\n}\n\n/**\n * Serialize a SearchState to a JSON string.\n * Handles Date conversion to ISO 8601 string.\n */\nexport function serializeState(state: SearchState): string {\n const serialized: SerializedSearchState = {\n provider: state.provider,\n query: state.query,\n totalResults: state.totalResults,\n retrievedCount: state.retrievedCount,\n lastUpdated: state.lastUpdated.toISOString(),\n providerState: state.providerState,\n };\n return JSON.stringify(serialized);\n}\n\n/**\n * Deserialize a JSON string to a SearchState.\n * Handles Date conversion from ISO 8601 string.\n */\nexport function deserializeState(json: string): SearchState {\n const parsed = JSON.parse(json) as SerializedSearchState;\n return {\n provider: parsed.provider as SearchState['provider'],\n query: parsed.query,\n totalResults: parsed.totalResults,\n retrievedCount: parsed.retrievedCount,\n lastUpdated: new Date(parsed.lastUpdated),\n providerState: parsed.providerState,\n };\n}\n"],"names":[],"mappings":";;AAkCA,MAAM,iBAA+C;AAAA,EACnD,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,YAAY;AACd;AAUO,MAAe,aAAiC;AAAA;AAAA,EAKlC;AAAA;AAAA,EAGA;AAAA,EAEnB,YAAY,SAA6B,IAAI;AAC3C,SAAK,SAAS;AAAA,MACZ,GAAG;AAAA,MACH,GAAG;AAAA,IAAA;AAGL,SAAK,cAAc,IAAI,YAAY;AAAA,MACjC,iBAAiB,KAAK,OAAO;AAAA,MAC7B,WAAW,KAAK,OAAO;AAAA,MACvB,gBAAgB,KAAK,OAAO;AAAA,MAC5B,YAAY,KAAK,OAAO;AAAA,IAAA,CACzB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EA2CU,gBACR,OACA,cACA,gBACa;AACb,WAAO;AAAA,MACL,UAAU,KAAK;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA,iCAAiB,KAAA;AAAA,IAAK;AAAA,EAE1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAgB,UAAa,IAAkC;AAC7D,QAAI;AACJ,QAAI,iBAAiB,KAAK,OAAO;AAEjC,aAAS,UAAU,GAAG,WAAW,KAAK,OAAO,SAAS,WAAW;AAC/D,UAAI;AACF,eAAO,MAAM,GAAA;AAAA,MACf,SAAS,OAAO;AACd,oBAAY;AAGZ,YAAI,CAAC,KAAK,YAAY,KAAK,GAAG;AAC5B,gBAAM;AAAA,QACR;AAGA,YAAI,WAAW,KAAK,OAAO,SAAS;AAClC,gBAAM;AAAA,QACR;AAGA,YAAI;AACJ,YAAI,iBAAiB,KAAK,KAAK,gBAAgB,SAAS,OAAO,MAAM,eAAe,UAAU;AAC5F,qBAAW,MAAM;AAAA,QACnB,OAAO;AACL,qBAAW;AACX,2BAAiB,KAAK,IAAI,iBAAiB,GAAG,KAAK,OAAO,UAAU;AAAA,QACtE;AAEA,cAAM,KAAK,MAAM,QAAQ;AAAA,MAC3B;AAAA,IACF;AAGA,UAAM;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,OAAyB;AAC3C,QAAI,gBAAgB,KAAK,GAAG;AAC1B,aAAO,MAAM;AAAA,IACf;AAGA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,MAAM,IAA2B;AACvC,WAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,EACzD;AACF;AAkBO,SAAS,eAAe,OAA4B;AACzD,QAAM,aAAoC;AAAA,IACxC,UAAU,MAAM;AAAA,IAChB,OAAO,MAAM;AAAA,IACb,cAAc,MAAM;AAAA,IACpB,gBAAgB,MAAM;AAAA,IACtB,aAAa,MAAM,YAAY,YAAA;AAAA,IAC/B,eAAe,MAAM;AAAA,EAAA;AAEvB,SAAO,KAAK,UAAU,UAAU;AAClC;AAMO,SAAS,iBAAiB,MAA2B;AAC1D,QAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,SAAO;AAAA,IACL,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,cAAc,OAAO;AAAA,IACrB,gBAAgB,OAAO;AAAA,IACvB,aAAa,IAAI,KAAK,OAAO,WAAW;AAAA,IACxC,eAAe,OAAO;AAAA,EAAA;AAE1B;"}
|